// Applies pending changesets, then checks that the version actually moved the // way the changesets asked. // // This exists because it did not. A release carrying one minor and one patch // changeset — no `major` anywhere, and no "### Major Changes" in any changelog — // published as 2.0.0. The cause was `@pdsjs/spaces` sitting in the fixed group: // with it there, every bump escalated to major, and the only visible sign was // the version number in the success output, after publishing. // // Config alone would not have caught that, because the failure was silent. This // makes it loud and puts it before `npm run release` rather than after. import { execFileSync } from 'node:child_process'; import { readdirSync, readFileSync } from 'node:fs'; const RANK = { patch: 1, minor: 2, major: 3 }; const CORE = new URL('../packages/core/package.json', import.meta.url); /** @param {URL} url */ const versionOf = (url) => JSON.parse(readFileSync(url, 'utf8')).version; /** * Highest bump any pending changeset asks for. * @returns {'patch'|'minor'|'major'|null} Null when nothing is pending. */ function requestedBump() { const dir = new URL('../.changeset/', import.meta.url); let highest = null; for (const file of readdirSync(dir)) { if (!file.endsWith('.md') || file === 'README.md') continue; const text = readFileSync(new URL(file, dir), 'utf8'); const frontmatter = text.split('---')[1] ?? ''; for (const match of frontmatter.matchAll(/:\s*(patch|minor|major)\s*$/gm)) { const bump = /** @type {'patch'|'minor'|'major'} */ (match[1]); if (!highest || RANK[bump] > RANK[highest]) highest = bump; } } return highest; } /** * How a version actually moved. * @param {string} before * @param {string} after */ function actualBump(before, after) { const [bMaj, bMin] = before.split('.').map(Number); const [aMaj, aMin] = after.split('.').map(Number); if (aMaj > bMaj) return 'major'; if (aMin > bMin) return 'minor'; return 'patch'; } const wanted = requestedBump(); if (!wanted) { console.error('No pending changesets — nothing to version.'); process.exit(1); } const before = versionOf(CORE); execFileSync('pnpm', ['exec', 'changeset', 'version'], { stdio: 'inherit' }); const after = versionOf(CORE); const got = actualBump(before, after); if (RANK[got] > RANK[wanted]) { console.error( `\nRefusing to continue: @pdsjs/core went ${before} -> ${after}, a ${got} ` + `bump, but the highest any changeset asked for was ${wanted}.\n\n` + 'Something in .changeset/config.json is escalating the bump — a package ' + 'in the fixed group that drags the rest is the usual cause. The working ' + 'tree now holds the wrong versions: run `git checkout -- . && git clean ' + '-fd packages` before trying again.\n', ); process.exit(1); } console.log(`\n@pdsjs/core ${before} -> ${after} (${got}, as requested)`); execFileSync('node', ['scripts/sync-version.js'], { stdio: 'inherit' }); execFileSync('pnpm', ['install', '--lockfile-only'], { stdio: 'inherit' });