From bd5cd5adb9d182c40079e8f0176f6880ed4a9095 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2026 23:34:14 +0000 Subject: [PATCH] meta(cli): Use Craft's versioning.files instead of the bump script Craft 3.8.0 can write the release version into extra files during its automatic bump and postReleaseBump, so the CLI workspace no longer needs a custom preReleaseCommand. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com> --- .craft.yml | 16 +- docs/releases/stdio.md | 7 +- packages/cli/script/bump-version.ts | 145 ------------------ packages/cli/test/script/bump-version.test.ts | 127 --------------- packages/mcp-server/script/bundle.ts | 2 +- 5 files changed, 19 insertions(+), 278 deletions(-) delete mode 100644 packages/cli/script/bump-version.ts delete mode 100644 packages/cli/test/script/bump-version.test.ts diff --git a/.craft.yml b/.craft.yml index a9d077321..f1f8f3f38 100644 --- a/.craft.yml +++ b/.craft.yml @@ -1,4 +1,4 @@ -minVersion: "3.7.0" +minVersion: "3.8.0" workspaces: packages/cli: changelog: @@ -16,7 +16,19 @@ workspaces: style: Keep it concise and friendly for developers; avoid hype. versioning: policy: auto - preReleaseCommand: bash -c 'cd packages/cli && node --experimental-strip-types script/bump-version.ts --pre' + # The npm targets bump only packages/cli/package.json. These files ship + # with every CLI release and carry its version too. Craft writes them for + # the release and again for the next -dev version (postReleaseBump). + files: + # Published at the CLI's version; its bundle script fails on a mismatch. + - path: packages/mcp-server/package.json + # The plugin's version stays a plain release version, even on -dev builds. + - path: packages/cli/plugins/sentry-cli/.claude-plugin/plugin.json + stripPrerelease: true + # Skill frontmatter. The skill generator embeds the version too, but it + # needs a gitignored API schema that Craft's checkout doesn't have. + - path: packages/cli/plugins/sentry-cli/skills/sentry-cli/SKILL.md + - path: packages/cli/plugins/sentry-cli/skills/sentry-cli/references/*.md postReleaseBump: true releaseBranchPrefix: release/cli artifactProvider: diff --git a/docs/releases/stdio.md b/docs/releases/stdio.md index e744172e0..111f12f3d 100644 --- a/docs/releases/stdio.md +++ b/docs/releases/stdio.md @@ -66,9 +66,10 @@ smoke-tests the result. The `packages/cli` Craft workspace releases both packages: run the **Release** workflow with `product: cli`. -- `packages/cli/script/bump-version.ts` sets `packages/mcp-server/package.json` - to the CLI's version, both for the release and for the next `-dev` version, - which Craft's `postReleaseBump` writes and pushes after publishing. +- Craft sets `packages/mcp-server/package.json` to the CLI's version through + `versioning.files` in `.craft.yml` (Craft 3.8.0+), both for the release and + for the next `-dev` version, which `postReleaseBump` writes and pushes after + publishing. The bundle script fails if the two versions differ. - `.github/workflows/cli-build.yml` bundles, tests, and packs the package next to the CLI's npm package and uploads it as `mcp-server-npm-package`. diff --git a/packages/cli/script/bump-version.ts b/packages/cli/script/bump-version.ts deleted file mode 100644 index b44f9d021..000000000 --- a/packages/cli/script/bump-version.ts +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env node -/** - * Version Bump Script - * - * Craft's `preReleaseCommand` for the CLI workspace. It runs for the release - * version in `craft prepare`, and again for the next `-dev` version after - * `craft publish` (`postReleaseBump` in .craft.yml), which Craft commits and - * pushes. `@sentry/mcp-server` ships with every CLI release, so it takes the - * same version. - * - * Usage: - * CRAFT_NEW_VERSION=1.0.0 node --experimental-strip-types script/bump-version.ts --pre - * - * Sets CRAFT_NEW_VERSION as: - * - the CLI and @sentry/mcp-server package.json versions - * - the plugin.json version, without the prerelease suffix - * - the tracked CLI skill frontmatter versions - */ - -import { execSync } from "node:child_process"; -import { readdirSync, readFileSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; - -const PLUGIN_PATH = "plugins/sentry-cli/.claude-plugin/plugin.json"; -const MCP_SERVER_PATH = "../mcp-server"; -const SKILL_PATH = "plugins/sentry-cli/skills/sentry-cli"; -const SKILL_VERSION_LINE = /^version: [^\r\n]+$/gm; - -/** Regex to match prerelease suffix (e.g., -dev.0, -alpha.1) */ -const PRERELEASE_SUFFIX_REGEX = /-.*$/; - -type PluginJson = { - version: string; - [key: string]: unknown; -}; - -/** - * Read and parse plugin.json - */ -function readPluginJson(): PluginJson { - return JSON.parse(readFileSync(PLUGIN_PATH, "utf-8")) as PluginJson; -} - -/** - * Write plugin.json with consistent formatting - */ -function writePluginJson(plugin: PluginJson): void { - writeFileSync(PLUGIN_PATH, `${JSON.stringify(plugin, null, 2)}\n`); -} - -/** - * Strip prerelease suffix from version string. - * "1.0.0-dev.0" → "1.0.0" - * "1.0.0" → "1.0.0" - */ -function stripPrerelease(version: string): string { - return version.replace(PRERELEASE_SUFFIX_REGEX, ""); -} - -/** Update the tracked skill frontmatter without requiring a network-backed schema build. */ -function syncSkillVersions(version: string): void { - const references = readdirSync(join(SKILL_PATH, "references")) - .filter((name) => name.endsWith(".md")) - .map((name) => join(SKILL_PATH, "references", name)); - if (references.length === 0) { - throw new Error("Generated CLI skill references are missing"); - } - - const files = [join(SKILL_PATH, "SKILL.md"), ...references].map((path) => { - const content = readFileSync(path, "utf-8"); - const end = content.indexOf("\n---\n", 4); - if (!content.startsWith("---\n") || end === -1) { - throw new Error(`Invalid CLI skill frontmatter: ${path}`); - } - const frontmatter = content.slice(0, end); - const versions = frontmatter.match(SKILL_VERSION_LINE); - if (versions?.length !== 1) { - throw new Error(`Expected one CLI skill version: ${path}`); - } - return { - path, - content: - frontmatter.replace(SKILL_VERSION_LINE, `version: ${version}`) + - content.slice(end), - }; - }); - - for (const { path, content } of files) { - writeFileSync(path, content); - } -} - -/** - * Execute a shell command with output inherited to stdout/stderr - */ -function exec(command: string, cwd?: string): void { - execSync(command, { stdio: "inherit", cwd }); -} - -/** Give `@sentry/mcp-server` the CLI's version. */ -function setMcpServerVersion(version: string): void { - exec( - `npm --no-git-tag-version --allow-same-version version ${version}`, - MCP_SERVER_PATH, - ); - console.log(`Updated @sentry/mcp-server to ${version}`); -} - -/** - * Pre-release: Set version from CRAFT_NEW_VERSION environment variable - */ -function preRelease(): void { - const version = process.env.CRAFT_NEW_VERSION; - if (!version) { - console.error("Error: CRAFT_NEW_VERSION environment variable is required"); - process.exit(1); - } - - console.log(`Setting version to ${version}`); - - // Update package.json via npm (handles formatting consistently) - exec(`npm --no-git-tag-version version ${version}`); - setMcpServerVersion(version); - - // Update plugin.json (strip prerelease suffix for cleaner version) - const plugin = readPluginJson(); - plugin.version = stripPrerelease(version); - writePluginJson(plugin); - - console.log(`Updated plugin.json to ${plugin.version}`); - - // The generator embeds package.json's version in every tracked skill file. - // Sync only its frontmatter here: the full generator needs an API schema that - // is gitignored and may not exist in Craft's checkout. - syncSkillVersions(version); -} - -if (process.argv.includes("--pre")) { - preRelease(); -} else { - console.log(` -Usage: CRAFT_NEW_VERSION= node --experimental-strip-types script/bump-version.ts --pre -`); - process.exit(1); -} diff --git a/packages/cli/test/script/bump-version.test.ts b/packages/cli/test/script/bump-version.test.ts deleted file mode 100644 index 14ceb48cd..000000000 --- a/packages/cli/test/script/bump-version.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { spawnSync } from "node:child_process"; -import { - chmodSync, - mkdirSync, - mkdtempSync, - readFileSync, - rmSync, - writeFileSync, -} from "node:fs"; -import { tmpdir } from "node:os"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import { afterEach, expect, test } from "vitest"; - -const script = resolve( - dirname(fileURLToPath(import.meta.url)), - "../../script/bump-version.ts", -); -const skill = "plugins/sentry-cli/skills/sentry-cli"; -const fixtures: string[] = []; - -afterEach(() => { - for (const fixture of fixtures.splice(0)) - rmSync(fixture, { recursive: true, force: true }); -}); - -/** - * Lay out `cli/` and its sibling `mcp-server/` like `packages/`, with a fake - * `npm` binary whose `npm version` writes the requested version. - */ -function createFixture(): { root: string; cli: string; bin: string } { - const root = mkdtempSync(join(tmpdir(), "cli-bump-version-")); - fixtures.push(root); - const cli = join(root, "cli"); - const bin = join(root, "bin"); - mkdirSync(bin); - mkdirSync(join(cli, skill, "references"), { recursive: true }); - mkdirSync(join(cli, "plugins/sentry-cli/.claude-plugin"), { - recursive: true, - }); - mkdirSync(join(root, "mcp-server")); - writeFileSync(join(cli, "package.json"), '{"version":"0.47.0"}\n'); - writeFileSync( - join(root, "mcp-server/package.json"), - '{"version":"0.42.0"}\n', - ); - writeFileSync( - join(cli, "plugins/sentry-cli/.claude-plugin/plugin.json"), - '{"version":"0.47.0"}\n', - ); - for (const path of ["SKILL.md", "references/auth.md"]) { - writeFileSync( - join(cli, skill, path), - "---\nname: sentry-cli\nversion: 0.47.0-dev.0\n---\n\nContent\n", - ); - } - - writeFileSync( - join(bin, "npm"), - `#!/usr/bin/env node -require("node:fs").writeFileSync("package.json", JSON.stringify({ version: process.argv.at(-1) }) + "\\n"); -`, - ); - chmodSync(join(bin, "npm"), 0o755); - return { root, cli, bin }; -} - -function runScript( - { cli, bin }: { cli: string; bin: string }, - env: Record, -) { - return spawnSync( - process.execPath, - ["--experimental-strip-types", script, "--pre"], - { - cwd: cli, - encoding: "utf8", - env: { - HOME: cli, - PATH: `${bin}:${process.env.PATH ?? ""}`, - XDG_CONFIG_HOME: cli, - ...env, - }, - timeout: 15_000, - }, - ); -} - -function readVersion(path: string): string { - return (JSON.parse(readFileSync(path, "utf8")) as { version: string }) - .version; -} - -test("pre-release bump gives @sentry/mcp-server the release version", () => { - const fixture = createFixture(); - - const result = runScript(fixture, { CRAFT_NEW_VERSION: "0.48.0" }); - - expect(result.status, result.stderr).toBe(0); - expect(readVersion(join(fixture.cli, "package.json"))).toBe("0.48.0"); - expect(readVersion(join(fixture.root, "mcp-server/package.json"))).toBe( - "0.48.0", - ); -}); - -// Craft's post-release bump reruns the pre-release command with the next -// development version. -test("dev version bump syncs every tracked version", () => { - const fixture = createFixture(); - - const result = runScript(fixture, { CRAFT_NEW_VERSION: "0.49.0-dev.0" }); - - expect(result.status, result.stderr).toBe(0); - for (const path of ["package.json", "../mcp-server/package.json"]) { - expect(readVersion(join(fixture.cli, path))).toBe("0.49.0-dev.0"); - } - expect( - readVersion( - join(fixture.cli, "plugins/sentry-cli/.claude-plugin/plugin.json"), - ), - ).toBe("0.49.0"); - for (const path of ["SKILL.md", "references/auth.md"]) { - expect(readFileSync(join(fixture.cli, skill, path), "utf8")).toContain( - "version: 0.49.0-dev.0", - ); - } -}); diff --git a/packages/mcp-server/script/bundle.ts b/packages/mcp-server/script/bundle.ts index 522874301..5a0e8343f 100644 --- a/packages/mcp-server/script/bundle.ts +++ b/packages/mcp-server/script/bundle.ts @@ -95,7 +95,7 @@ const cliPkg = JSON.parse( ) as { version: string }; // Each CLI release publishes this package at the CLI's version, set by -// `packages/cli/script/bump-version.ts`. +// `versioning.files` in `.craft.yml`. if (pkg.version !== cliPkg.version) { console.error( `\nError: @sentry/mcp-server is at ${pkg.version} but the CLI is at ${cliPkg.version}. Both must share one version.\n`,