diff --git a/packages/cli/scripts/generate-release.js b/packages/cli/scripts/generate-release.js index f57ea26..47e7d7b 100644 --- a/packages/cli/scripts/generate-release.js +++ b/packages/cli/scripts/generate-release.js @@ -6,6 +6,7 @@ import { readFile, readdir, stat, writeFile } from 'node:fs/promises'; import { basename, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { assertReleaseMetadata, REPOSITORY, TARGETS } from './release-metadata.js'; +import { versionsAligned } from '../../../scripts/workspace-versions.mjs'; const root = fileURLToPath(new URL('../../../', import.meta.url)); const SHA256 = /^[a-f0-9]{64}$/; @@ -59,7 +60,7 @@ async function main() { if (!directory || process.argv.length > 4) throw new Error('Usage: node packages/cli/scripts/generate-release.js [output-file]'); const app = JSON.parse(await readFile(join(root, 'package.json'), 'utf8')); const cli = JSON.parse(await readFile(join(root, 'packages/cli/package.json'), 'utf8')); - if (app.version !== cli.version || app.dependencies?.[cli.name] !== cli.version) throw new Error('Application and CLI versions must match.'); + if (!versionsAligned(app, cli)) throw new Error('Application and CLI versions must match.'); const revision = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); const release = await generateRelease({ artifactDir: resolve(directory), version: cli.version, revision }); await writeFile(resolve(output), `${JSON.stringify(release, null, 2)}\n`); diff --git a/packages/cli/scripts/prepack.js b/packages/cli/scripts/prepack.js index b737353..fd9c864 100644 --- a/packages/cli/scripts/prepack.js +++ b/packages/cli/scripts/prepack.js @@ -2,12 +2,13 @@ import { execFileSync } from 'node:child_process'; import { copyFileSync, readFileSync, writeFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { assertReleaseMetadata } from './release-metadata.js'; +import { dependencyMatchesVersion } from '../../../scripts/workspace-versions.mjs'; const root = fileURLToPath(new URL('../../../', import.meta.url)); const git = (...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8' }).trim(); if (git('status', '--porcelain', '--untracked-files=normal')) throw new Error('Release packaging requires a clean, committed checkout.'); const app = JSON.parse(readFileSync(new URL('../../../package.json', import.meta.url), 'utf8')); const cli = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')); -if (app.dependencies['@earendil-works/pi-coding-agent'] !== cli.dependencies['@earendil-works/pi-coding-agent'] || app.dependencies[cli.name] !== cli.version) { +if (app.dependencies['@earendil-works/pi-coding-agent'] !== cli.dependencies['@earendil-works/pi-coding-agent'] || !dependencyMatchesVersion(app.dependencies[cli.name], cli.version)) { throw new Error('Align the CLI version and agent SDK versions in both workspace manifests before packing.'); } if (app.version !== cli.version) throw new Error('Align the application and CLI versions before packing.'); diff --git a/scripts/package-standalone.mjs b/scripts/package-standalone.mjs index 2131f62..3a941a8 100644 --- a/scripts/package-standalone.mjs +++ b/scripts/package-standalone.mjs @@ -6,6 +6,7 @@ import { tmpdir } from 'node:os'; import { basename, dirname, join, relative, resolve, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; import * as tar from 'tar'; +import { dependencyMatchesVersion } from './workspace-versions.mjs'; const root = fileURLToPath(new URL('../', import.meta.url)); const output = resolve(root, process.env.TRACESIFT_ARTIFACT_OUTPUT_DIR || 'dist'); @@ -53,12 +54,7 @@ async function main() { assert(['darwin', 'linux'].includes(platform) && ['arm64', 'x64'].includes(arch), `Unsupported artifact target: ${platform}-${arch}`); const app = JSON.parse(await readFile(join(root, 'package.json'), 'utf8')); const cli = JSON.parse(await readFile(join(root, 'packages/cli/package.json'), 'utf8')); - const dependency = app.dependencies?.[cli.name]; - assert(dependency, `The app must depend on ${cli.name} before packaging.`); - assert( - dependency === cli.version || /^workspace:(\*|\^|~)$/.test(dependency), - 'The app and CLI versions must match before packaging.', - ); + assert(dependencyMatchesVersion(app.dependencies?.[cli.name], cli.version), 'The app and CLI versions must match before packaging.'); assert(app.version === cli.version, 'The application and CLI package versions must match before packaging.'); assert(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(app.version), 'Invalid application version.'); const revision = process.env.TRACESIFT_RELEASE_REVISION || execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); diff --git a/scripts/workspace-versions.mjs b/scripts/workspace-versions.mjs new file mode 100644 index 0000000..ff9bfcf --- /dev/null +++ b/scripts/workspace-versions.mjs @@ -0,0 +1,12 @@ +/** + * The app depends on the CLI through the workspace protocol, so the manifest + * records `workspace:*` rather than a literal version. npm resolves that to the + * CLI's own version at install time, which `app.version === cli.version` pins. + */ +export function dependencyMatchesVersion(dependency, version) { + return dependency === version || /^workspace:(\*|\^|~)$/.test(dependency); +} + +export function versionsAligned(app, cli) { + return app.version === cli.version && dependencyMatchesVersion(app.dependencies?.[cli.name], cli.version); +}