feat(changelog): one source file per release, generated outputs

Release notes now live in changelog/<version>.md (front matter with
version and date, then ## App and ## VS Code sections grouped into
New, Improvements, Fixes, Misc) plus changelog/unreleased.md for what
has not shipped. `bun run changelog:build` renders CHANGELOG.md,
packages/vscode/CHANGELOG.md, and changelog/index.json from them;
`changelog:check` fails when the outputs are behind and runs in CI and
in release:prepare. `oc-dev create-release` promotes unreleased.md to
the versioned file dated today and rebuilds.

The existing history was split mechanically: every bullet kept, sorted
into groups by keyword, five hand-typed headers with one-digit days
normalised to YYYY-MM-DD (the update dialog matched none of them). The
generated files keep today's release headers, which the update dialog,
the release workflow, and the website match by regex.

Claude-Session: https://claude.ai/code/session_01VqV56Hez25hTxXH4ipJfzH
This commit is contained in:
Bohdan Triapitsyn
2026-09-05 15:58:23 +03:00
parent bc2513f410
commit d9b9c8edab
153 changed files with 11977 additions and 700 deletions
+5 -15
View File
@@ -174,20 +174,8 @@ 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.`);
}
}
// The release notes source plus the files generated from it; all go into the release commit.
const RELEASE_CHANGELOG_FILES = ['changelog', 'CHANGELOG.md', 'packages/vscode/CHANGELOG.md'];
function printReleaseNextSteps(version) {
log.success(`Release v${version} prepared locally`);
@@ -613,7 +601,9 @@ 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));
// Turns changelog/unreleased.md into changelog/<version>.md dated today and
// regenerates CHANGELOG.md; fails when nothing was written for the release.
step('Promoting the changelog', () => run('node', ['scripts/changelog/build.mjs', '--release', version]));
step('Validating codebase', () => run('bun', ['run', 'release:prepare']));
step(`Bumping version to ${version}`, () => run('node', ['scripts/bump-version.mjs', version]));
printReleaseNextSteps(version);