| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * derive-changelog.mjs — emit `web/lib/changelog.generated.ts` from the |
| 4 | * repository CHANGELOG.md so the /changelog route and the docs release-truth |
| 5 | * band render the real release record, never a hand-typed copy. |
| 6 | * |
| 7 | * Runs at `npm run prebuild`, `npm run dev` and in the vitest global setup. |
| 8 | * The output is NOT tracked (web/.gitignore): a committed copy raced between |
| 9 | * merges — two PRs that each added a CHANGELOG line each regenerated it |
| 10 | * against their own base, git merged both to a stale item count, and main |
| 11 | * went red (twice on 2026-09-26). Deriving at build time removes the race. |
| 12 | */ |
| 13 | import { readFileSync, writeFileSync, existsSync } from "node:fs"; |
| 14 | import { resolve, dirname } from "node:path"; |
| 15 | import { fileURLToPath } from "node:url"; |
| 16 | import { parseChangelog, renderChangelogModule } from "./changelog-lib.mjs"; |
| 17 | |
| 18 | const __dirname = dirname(fileURLToPath(import.meta.url)); |
| 19 | const REPO_ROOT = resolve(__dirname, "..", ".."); |
| 20 | const SOURCE = resolve(REPO_ROOT, "CHANGELOG.md"); |
| 21 | const TARGET = resolve(__dirname, "..", "lib", "changelog.generated.ts"); |
| 22 | |
| 23 | if (!existsSync(SOURCE)) { |
| 24 | console.error(`[derive-changelog] CHANGELOG.md not found at ${SOURCE}`); |
| 25 | process.exit(1); |
| 26 | } |
| 27 | |
| 28 | const parsed = parseChangelog(readFileSync(SOURCE, "utf-8")); |
| 29 | const next = renderChangelogModule(parsed); |
| 30 | const current = existsSync(TARGET) ? readFileSync(TARGET, "utf-8") : null; |
| 31 | if (current !== next) { |
| 32 | writeFileSync(TARGET, next); |
| 33 | console.log(`[derive-changelog] wrote ${parsed.releases.length} releases → lib/changelog.generated.ts`); |
| 34 | } else { |
| 35 | console.log(`[derive-changelog] lib/changelog.generated.ts is current (${parsed.releases.length} releases)`); |
| 36 | } |
| 37 |