diff --git a/.github/workflows/verify-publish.yml b/.github/workflows/verify-publish.yml index 8d2a460..1fda586 100644 --- a/.github/workflows/verify-publish.yml +++ b/.github/workflows/verify-publish.yml @@ -15,12 +15,18 @@ on: type: choice options: - '@agentworkforce/events' + - '@agentworkforce/persona-kit' + - '@agentworkforce/runtime' - '@agentworkforce/compose' - - '@agentworkforce/cli' - - 'agentworkforce' + - '@agentworkforce/delivery' - '@agentworkforce/workload-router' - - '@agentworkforce/persona-kit' + - '@agentworkforce/deploy' - '@agentworkforce/review-kit' + - '@agentworkforce/mcp-workforce' + - '@agentworkforce/daytona-runner' + - '@agentworkforce/local-surface' + - '@agentworkforce/cli' + - 'agentworkforce' version: description: 'Version to verify (defaults to the "latest" dist-tag)' required: false @@ -104,7 +110,6 @@ jobs: const mod = await import('./node_modules/@agentworkforce/cli/dist/cli.js'); assert.equal(typeof mod.main, 'function'); - assert.equal(typeof mod.CLI_VERSION, 'string'); console.log(`scoped CLI package smoke passed for ${pkg.name}@${pkg.version}`); EOF diff --git a/package.json b/package.json index 1f7e37b..3771cff 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "dev:cli": "node packages/cli/dist/cli.js", "typecheck": "pnpm -r typecheck && pnpm run typecheck:examples", "typecheck:examples": "tsc -p examples/tsconfig.json --noEmit", - "test": "pnpm -r test", + "test": "node --test scripts/release-workflows.test.mjs && pnpm -r test", "lint": "pnpm -r lint", "check": "pnpm run lint && pnpm run typecheck && pnpm run test" } diff --git a/scripts/release-workflows.test.mjs b/scripts/release-workflows.test.mjs new file mode 100644 index 0000000..6969c11 --- /dev/null +++ b/scripts/release-workflows.test.mjs @@ -0,0 +1,74 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const publishWorkflow = readFileSync('.github/workflows/publish.yml', 'utf8'); +const verifyWorkflow = readFileSync('.github/workflows/verify-publish.yml', 'utf8'); + +function publishTargetDirectories(workflow) { + const match = workflow.match(/echo "packages=([^"]+)"/); + assert.ok(match, 'publish workflow must declare its package targets'); + + return match[1].trim().split(/\s+/); +} + +function publishPackageNames(workflow = publishWorkflow) { + return publishTargetDirectories(workflow).map((directory) => { + const packageJson = JSON.parse(readFileSync(`packages/${directory}/package.json`, 'utf8')); + return packageJson.name; + }); +} + +function verifyPackageChoices(workflow = verifyWorkflow) { + const lines = workflow.replaceAll('\r\n', '\n').split('\n'); + const packageInput = lines.findIndex((line) => line === ' package:'); + assert.notEqual(packageInput, -1, 'verify workflow must declare the package input'); + + const options = lines.findIndex( + (line, index) => index > packageInput && line === ' options:' + ); + assert.notEqual(options, -1, 'verify package input must declare choices'); + + const choices = []; + for (const line of lines.slice(options + 1)) { + const match = line.match(/^ - '([^']+)'$/); + if (!match) break; + choices.push(match[1]); + } + return choices; +} + +test('Verify Publish exposes every lockstep package exactly once', () => { + const published = publishPackageNames(); + const verified = verifyPackageChoices(); + + assert.equal(new Set(published).size, published.length, 'publish targets must be unique'); + assert.equal(new Set(verified).size, verified.length, 'verify choices must be unique'); + assert.deepEqual(verified, published); +}); + +test('release workflow parsing tolerates CRLF and target whitespace', () => { + assert.deepEqual(publishTargetDirectories('echo "packages= events persona-kit "'), [ + 'events', + 'persona-kit', + ]); + assert.deepEqual( + verifyPackageChoices(verifyWorkflow.replaceAll('\n', '\r\n')), + verifyPackageChoices() + ); +}); + +test('scoped CLI verification checks only the supported thin-entry contract', () => { + const match = verifyWorkflow.match( + /- name: Scoped CLI package smoke test([\s\S]*?)\n - name: Library smoke test/ + ); + assert.ok(match, 'scoped CLI smoke step must exist'); + + const step = match[1]; + assert.match(step, /assert\.equal\(pkg\.name, '@agentworkforce\/cli'\)/); + assert.match(step, /assert\.equal\(pkg\.version, '\$\{\{ steps\.resolve\.outputs\.version \}\}'\)/); + assert.match(step, /assert\.equal\(pkg\.bin, undefined\)/); + assert.match(step, /@agentworkforce\/cli\/dist\/cli\.js/); + assert.match(step, /assert\.equal\(typeof mod\.main, 'function'\)/); + assert.doesNotMatch(step, /CLI_VERSION|cli-impl/); +});