diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..dfb503e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,15 @@ +name: test +on: + push: + pull_request: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: node --test test/*.test.js + - run: node scripts/check-versions.js ${{ startsWith(github.ref, 'refs/tags/v') && github.ref_name || '' }} + - run: pip install pytest && python3 -m pytest test/test_compress.py -q diff --git a/scripts/check-versions.js b/scripts/check-versions.js new file mode 100644 index 0000000..e62ad40 --- /dev/null +++ b/scripts/check-versions.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node +'use strict'; +// Fails if comb's four manifests disagree on version (the ponytail v4.8.0 bug: +// manifests shipped stale, so plugins reported the old version after update). +// Optional: pass a release tag (v1.2.3) as argv[2] to also match it. +const fs = require('fs'); +const path = require('path'); + +function readVersions(root) { + const read = (f) => fs.readFileSync(path.join(root, f), 'utf8'); + const yaml = /^version:\s*["']?([^"'\s#]+)/m.exec(read('plugin.yaml')); + const market = JSON.parse(read('.claude-plugin/marketplace.json')); + return { + 'package.json': JSON.parse(read('package.json')).version, + 'plugin.yaml': yaml && yaml[1], + '.claude-plugin/plugin.json': JSON.parse(read('.claude-plugin/plugin.json')).version, + '.claude-plugin/marketplace.json': market.plugins && market.plugins[0] && market.plugins[0].version, + }; +} + +function checkVersions(root, tag) { + const v = readVersions(root); + const want = tag ? tag.replace(/^v/, '') : Object.values(v)[0]; + const bad = Object.entries(v).filter(([, x]) => x !== want); + return { versions: v, expected: want, mismatches: bad.map(([f, x]) => `${f}: ${x === undefined || x === null ? 'missing' : x} (expected ${want})`) }; +} + +if (require.main === module) { + const r = checkVersions(path.join(__dirname, '..'), process.argv[2]); + if (r.mismatches.length) { console.error('version mismatch:\n ' + r.mismatches.join('\n ')); process.exit(1); } + console.log(`versions consistent: ${r.expected}`); +} +module.exports = { readVersions, checkVersions }; diff --git a/test/versions.test.js b/test/versions.test.js new file mode 100644 index 0000000..bdf9182 --- /dev/null +++ b/test/versions.test.js @@ -0,0 +1,43 @@ +'use strict'; +const test = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { checkVersions } = require('../scripts/check-versions.js'); + +const repo = path.join(__dirname, '..'); + +function fixture(v) { + const d = fs.mkdtempSync(path.join(os.tmpdir(), 'comb-ver-')); + fs.mkdirSync(path.join(d, '.claude-plugin')); + const w = (f, s) => fs.writeFileSync(path.join(d, f), s); + w('package.json', JSON.stringify({ version: v.pkg })); + w('plugin.yaml', `name: comb\nversion: "${v.yaml}"\n`); + w('.claude-plugin/plugin.json', JSON.stringify({ version: v.plugin })); + w('.claude-plugin/marketplace.json', JSON.stringify({ plugins: [{ version: v.market }] })); + return d; +} + +test('repo manifests agree on one version', () => { + const r = checkVersions(repo); + assert.deepStrictEqual(r.mismatches, []); +}); + +test('detects a stale manifest', () => { + const r = checkVersions(fixture({ pkg: '0.2.0', yaml: '0.2.0', plugin: '0.1.0', market: '0.2.0' })); + assert.strictEqual(r.mismatches.length, 1); + assert.match(r.mismatches[0], /plugin\.json/); +}); + +test('release tag must match manifests', () => { + const d = fixture({ pkg: '1.0.0', yaml: '1.0.0', plugin: '1.0.0', market: '1.0.0' }); + assert.deepStrictEqual(checkVersions(d, 'v1.0.0').mismatches, []); + assert.strictEqual(checkVersions(d, 'v1.0.1').mismatches.length, 4); +}); + +test('missing marketplace entry is reported', () => { + const d = fixture({ pkg: '1.0.0', yaml: '1.0.0', plugin: '1.0.0', market: '1.0.0' }); + fs.writeFileSync(path.join(d, '.claude-plugin/marketplace.json'), JSON.stringify({ plugins: [] })); + assert.match(checkVersions(d).mismatches[0], /marketplace\.json: missing/); +});