From f742775be433f363d5e86e5524b7d3a2a07d2709 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sat, 5 Sep 2026 01:26:01 +0300 Subject: [PATCH] feat: streamline release preparation and changelog checks Exports the release package list so release prep stages the same versioned files as the bump script. Validates that CHANGELOG.md contains the target release section before preparing a release. Updates the suggested release steps to commit only release files and push the tag explicitly. --- scripts/bump-version.mjs | 45 ++++++++++++++++++++++------------------ scripts/oc-dev.mjs | 26 ++++++++++++++++++----- 2 files changed, 46 insertions(+), 25 deletions(-) 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);