diff --git a/scripts/bump-version.mjs b/scripts/bump-version.mjs index ff621862..f98c1d85 100755 --- a/scripts/bump-version.mjs +++ b/scripts/bump-version.mjs @@ -6,7 +6,9 @@ import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, '..'); -const PACKAGES = [ +// Every package.json a release bumps. `oc-dev create-release` imports this so +// the commit it suggests stages exactly these files. +export const RELEASE_PACKAGE_FILES = [ 'package.json', 'packages/ui/package.json', 'packages/web/package.json', @@ -14,23 +16,26 @@ const PACKAGES = [ 'packages/vscode/package.json', ]; -const newVersion = process.argv[2]; -if (!newVersion || !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(newVersion)) { - console.error('Usage: node scripts/bump-version.mjs '); - console.error('Example: node scripts/bump-version.mjs 0.2.0'); - console.error('Example: node scripts/bump-version.mjs 0.2.0-beta.1'); - process.exit(1); +const isDirectRun = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url); +if (isDirectRun) { + const newVersion = process.argv[2]; + if (!newVersion || !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(newVersion)) { + console.error('Usage: node scripts/bump-version.mjs '); + console.error('Example: node scripts/bump-version.mjs 0.2.0'); + console.error('Example: node scripts/bump-version.mjs 0.2.0-beta.1'); + process.exit(1); + } + + console.log(`Bumping version to ${newVersion}\n`); + + for (const pkgPath of RELEASE_PACKAGE_FILES) { + const fullPath = path.join(ROOT, pkgPath); + const pkg = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + const oldVersion = pkg.version; + pkg.version = newVersion; + fs.writeFileSync(fullPath, JSON.stringify(pkg, null, 2) + '\n'); + console.log(` ${pkgPath}: ${oldVersion} -> ${newVersion}`); + } + + console.log('\nVersion bump complete. Review changes, then commit and tag.'); } - -console.log(`Bumping version to ${newVersion}\n`); - -for (const pkgPath of PACKAGES) { - const fullPath = path.join(ROOT, pkgPath); - const pkg = JSON.parse(fs.readFileSync(fullPath, 'utf8')); - const oldVersion = pkg.version; - pkg.version = newVersion; - fs.writeFileSync(fullPath, JSON.stringify(pkg, null, 2) + '\n'); - console.log(` ${pkgPath}: ${oldVersion} -> ${newVersion}`); -} - -console.log('\nVersion bump complete. Review changes, then commit and tag.'); diff --git a/scripts/oc-dev.mjs b/scripts/oc-dev.mjs index a4346d4a..7062cb34 100755 --- a/scripts/oc-dev.mjs +++ b/scripts/oc-dev.mjs @@ -27,6 +27,7 @@ import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { cancel, intro, isCancel, log, outro, select, text } from '@clack/prompts'; +import { RELEASE_PACKAGE_FILES } from './bump-version.mjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -173,16 +174,30 @@ function step(label, fn) { return result; } +const RELEASE_CHANGELOG_FILES = ['CHANGELOG.md', 'packages/vscode/CHANGELOG.md']; + +// Same check the release workflow runs before it publishes, so a missing +// section fails here in seconds instead of after the tag is pushed. +function assertChangelogSection(version) { + const changelogPath = path.join(repoRoot, 'CHANGELOG.md'); + if (!existsSync(changelogPath)) { + throw new Error('CHANGELOG.md not found; add it before releasing.'); + } + const sections = readFileSync(changelogPath, 'utf8').split(/^## /m); + if (!sections.some((section) => section.startsWith(`[${version}]`))) { + throw new Error(`CHANGELOG.md has no "## [${version}] - YYYY-MM-DD" section. Add it before releasing.`); + } +} + function printReleaseNextSteps(version) { log.success(`Release v${version} prepared locally`); - log.info('Next steps:'); - console.log(` git add -A`); + log.info('Next steps (only the release files are staged, unrelated changes stay out):'); + console.log(` git add ${[...RELEASE_PACKAGE_FILES, ...RELEASE_CHANGELOG_FILES].join(' ')}`); console.log(` git commit -m "release v${version}"`); console.log(` git tag v${version}`); - console.log(` git push origin main --tags`); + console.log(` git push origin main v${version}`); console.log(''); - console.log('This will trigger the GitHub Actions release workflow.'); - console.log(`Make sure CHANGELOG.md contains a section like "## [${version}] - YYYY-MM-DD" before pushing.`); + console.log('Pushing the tag is what starts the GitHub Actions release; pushing main alone does not.'); } function normalizeAction(action = '') { @@ -598,6 +613,7 @@ async function createRelease(options) { } } if (!/^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/.test(version)) throw new Error('Invalid version format. Use semver, e.g. 1.4.7 or 1.4.7-beta.1'); + step('Checking changelog', () => assertChangelogSection(version)); step('Validating codebase', () => run('bun', ['run', 'release:prepare'])); step(`Bumping version to ${version}`, () => run('node', ['scripts/bump-version.mjs', version])); printReleaseNextSteps(version);