Files
callstack__agent-device/scripts/release-mark-dev.mjs
Claude e8a9abdd8d chore(release): keep the version on main distinct from every published version
Registry scanners diff the repository's tool surface per version string, so a
released number left on main while main keeps changing is indistinguishable
from a republished ("rug-pull") version — two scans of the same version see
two different tool sets (AS-012).

- release:publish now runs release:mark-dev after npm publish, moving
  package.json and the synchronized server.json to the next patch with a
  -dev prerelease marker.
- release:prepare refuses to publish while the -dev marker is in place, so
  a forgotten version bump cannot ship a prerelease as latest.
- Mark the current tree 0.20.11-dev: main had been sitting on the published
  0.20.10 while the tool surface kept changing, which is the live finding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wb8EuoySL26UsdtCtRj5X8
2026-08-25 09:04:50 +00:00

68 lines
2.4 KiB
JavaScript

import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
// The version on main must never equal a published version: registry scanners
// diff the repository's tool surface per version string, so a released number
// left in place while main keeps moving reads as a rug-pull republish
// (AS-012). `release:publish` runs this right after `npm publish` to move
// main to the next patch with a `-dev` prerelease marker; `--check-release-version`
// is the inverse guard in `release:prepare`, refusing to publish a `-dev`
// version because the maintainer has not set the release version yet.
const root = process.cwd();
const checkReleaseVersion = process.argv.includes('--check-release-version');
const packagePath = path.join(root, 'package.json');
const raw = fs.readFileSync(packagePath, 'utf8');
const pkg = JSON.parse(raw);
const version = pkg.version;
if (typeof version !== 'string' || version.length === 0) {
fail('package.json must define version.');
}
if (checkReleaseVersion) {
if (version.includes('-')) {
fail(
`package.json version ${version} is a prerelease marker. Set the release version first ` +
'(e.g. `npm version patch`), commit, then publish.',
);
}
process.exit(0);
}
if (version.includes('-')) {
process.stdout.write(`Version ${version} already carries a prerelease marker; nothing to do.\n`);
process.exit(0);
}
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
if (!match) {
fail(`Unsupported version format: ${version}`);
}
const nextVersion = `${match[1]}.${match[2]}.${Number(match[3]) + 1}-dev`;
const versionField = `"version": "${version}"`;
if (raw.split(versionField).length !== 2) {
fail(`Expected exactly one ${versionField} in package.json.`);
}
fs.writeFileSync(packagePath, raw.replace(versionField, `"version": "${nextVersion}"`));
const sync = spawnSync(process.execPath, [path.join(root, 'scripts', 'sync-mcp-metadata.mjs')], {
stdio: 'inherit',
});
if (sync.status !== 0) {
fail('sync-mcp-metadata failed after the version bump.');
}
process.stdout.write(
`Marked main as unreleased: ${version} -> ${nextVersion} (package.json + server.json).\n` +
`Commit and push this so the version on main never equals the published ${version}.\n`,
);
function fail(message) {
process.stderr.write(`${message}\n`);
process.exit(1);
}