diff --git a/release.config.js b/release.config.js index 7b83438f..cdd722cd 100644 --- a/release.config.js +++ b/release.config.js @@ -32,12 +32,18 @@ export default { }, ], '@semantic-release/changelog', - // Note: the previous @semantic-release/exec step ran a custom - // scripts/bump-version.js to sync per-package versions. That script was - // deleted in the knip cleanup (#279) and the desktop release only needs - // the root + apps/desktop package.json versions bumped — which the - // @semantic-release/git plugin below does via the `assets` list. So we - // drop the exec step entirely. + // The @semantic-release/git plugin below only COMMITS files; it does + // not mutate them. Without this exec step, package.json and + // apps/desktop/package.json stay at the previous version even after + // tag/release (the v0.15.0 bug). bump-version.mjs is a pure-ESM, + // zero-dependency script that only touches the `version` field of + // exactly the two files in scope. + [ + '@semantic-release/exec', + { + prepareCmd: 'node scripts/bump-version.mjs ${nextRelease.version}', + }, + ], [ '@semantic-release/git', { diff --git a/scripts/bump-version.mjs b/scripts/bump-version.mjs new file mode 100644 index 00000000..ada877a8 --- /dev/null +++ b/scripts/bump-version.mjs @@ -0,0 +1,91 @@ +#!/usr/bin/env node +/** + * Sync the `version` field across the monorepo's release-relevant + * package.json files. Called by @semantic-release/exec at the + * `prepareCmd` step so the @semantic-release/git plugin actually has + * a diff to commit. + * + * Why this script exists (history): + * + * 1. semantic-release/git's `assets` list is COMMIT-only — it does + * not mutate files. Something else has to bump the versions first. + * 2. The original scripts/bump-version.js was deleted in the knip + * cleanup (#279) under the false assumption nothing referenced it. + * release.config.js still had a prepareCmd pointing at it, + * so v0.15.0 shipped with both package.json files reading 0.14.0 + * (visible mismatch in the v0.15.0 tag). + * 3. PR #289 patched release.config.js by removing the prepareCmd, + * which made semantic-release runnable but kept versions stale. + * 4. This script restores step (1) properly. It's pure ESM, has no + * dependencies, and updates ONLY the version field — no other + * package.json keys are touched. + * + * Usage: + * + * node scripts/bump-version.mjs 0.15.1 + * + * Fails non-zero if: + * - the version arg is missing or empty + * - any target file can't be parsed as JSON + * - a target file has no `version` field to update + */ + +import { readFile, writeFile } from 'node:fs/promises'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(__dirname, '..'); + +/** + * Files whose `version` should track the desktop release. + * + * Keep this list explicit (not a glob) so we never accidentally bump + * a workspace package that's intentionally on its own version line + * (e.g. packages/api which deploys on its own cycle to Cloudflare). + */ +const targets = ['package.json', 'apps/desktop/package.json']; + +const newVersion = process.argv[2]; +if (!newVersion || newVersion.trim().length === 0) { + console.error('bump-version: missing version argument'); + console.error('usage: node scripts/bump-version.mjs '); + process.exit(1); +} + +let bumped = 0; +for (const rel of targets) { + const abs = resolve(repoRoot, rel); + const raw = await readFile(abs, 'utf-8'); + + let parsed; + try { + parsed = JSON.parse(raw); + } catch (err) { + console.error(`bump-version: cannot parse ${rel} as JSON: ${err.message}`); + process.exit(1); + } + + if (typeof parsed.version !== 'string') { + console.error(`bump-version: ${rel} has no version field to update`); + process.exit(1); + } + + const old = parsed.version; + if (old === newVersion) { + console.log(`bump-version: ${rel} already at ${newVersion}, skipped`); + continue; + } + + parsed.version = newVersion; + + // Preserve a trailing newline if the original file had one — Prettier and + // most editors expect it. Detecting from `raw` keeps the diff minimal. + const trailingNewline = raw.endsWith('\n') ? '\n' : ''; + await writeFile(abs, JSON.stringify(parsed, null, 2) + trailingNewline, 'utf-8'); + + console.log(`bump-version: ${rel} ${old} -> ${newVersion}`); + bumped++; +} + +console.log(`bump-version: updated ${bumped} of ${targets.length} target(s)`);