138 lines
4.0 KiB
TypeScript
138 lines
4.0 KiB
TypeScript
/**
|
|
* Prepare a release: bump versions, generate raw release notes.
|
|
* Runs inside the "create release PR" workflow.
|
|
*
|
|
* Usage: tsx scripts/release/prepare-release.ts --bump <patch|minor|major> --scope <scope from release.config.json> [--dry-run]
|
|
*/
|
|
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import {
|
|
getCurrentVersion,
|
|
computeNextStableVersion,
|
|
bumpPackages,
|
|
getPackagesForScope,
|
|
type BumpLevel,
|
|
} from "./lib/versions.js";
|
|
import {
|
|
getChangesSummary,
|
|
type ChangesSummary,
|
|
type Commit,
|
|
} from "./lib/changes.js";
|
|
import { ROOT, loadConfig, type ReleaseScope } from "./lib/config.js";
|
|
|
|
function generateRawReleaseNotes(
|
|
version: string,
|
|
scope: ReleaseScope,
|
|
summary: ChangesSummary,
|
|
): string {
|
|
const lines: string[] = [];
|
|
const label = scope === "monorepo" ? "" : ` (${scope})`;
|
|
lines.push(`## v${version}${label}`, "");
|
|
|
|
if (summary.commits.length === 0) {
|
|
lines.push("No changes since last release.");
|
|
return lines.join("\n");
|
|
}
|
|
|
|
const features: Commit[] = [];
|
|
const fixes: Commit[] = [];
|
|
const other: Commit[] = [];
|
|
|
|
for (const c of summary.commits) {
|
|
if (/^feat[:(]/.test(c.subject)) features.push(c);
|
|
else if (/^fix[:(]/.test(c.subject)) fixes.push(c);
|
|
else other.push(c);
|
|
}
|
|
|
|
if (features.length > 0) {
|
|
lines.push("### Features", "");
|
|
for (const c of features)
|
|
lines.push(`- ${c.subject} (${c.hash.slice(0, 7)})`);
|
|
lines.push("");
|
|
}
|
|
|
|
if (fixes.length > 0) {
|
|
lines.push("### Fixes", "");
|
|
for (const c of fixes) lines.push(`- ${c.subject} (${c.hash.slice(0, 7)})`);
|
|
lines.push("");
|
|
}
|
|
|
|
if (other.length > 0) {
|
|
lines.push("### Other Changes", "");
|
|
for (const c of other) lines.push(`- ${c.subject} (${c.hash.slice(0, 7)})`);
|
|
lines.push("");
|
|
}
|
|
|
|
return lines.join("\n");
|
|
}
|
|
|
|
// Valid scopes come from release.config.json — the single source of truth.
|
|
const VALID_SCOPES = Object.keys(loadConfig().scopes);
|
|
|
|
function main() {
|
|
const argv = process.argv.slice(2);
|
|
const dryRun = argv.includes("--dry-run");
|
|
const bumpIdx = argv.indexOf("--bump");
|
|
const bumpLevel = (
|
|
bumpIdx !== -1 ? argv[bumpIdx + 1] : null
|
|
) as BumpLevel | null;
|
|
const scopeIdx = argv.indexOf("--scope");
|
|
const scope = (
|
|
scopeIdx !== -1 ? argv[scopeIdx + 1] : null
|
|
) as ReleaseScope | null;
|
|
|
|
if (!bumpLevel || !["patch", "minor", "major"].includes(bumpLevel)) {
|
|
console.error(
|
|
"Usage: prepare-release.ts --bump <patch|minor|major> --scope <scope from release.config.json>",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!scope || !VALID_SCOPES.includes(scope)) {
|
|
console.error(
|
|
`Invalid scope: ${scope}. Valid scopes: ${VALID_SCOPES.join(", ")}`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const currentVersion = getCurrentVersion(scope);
|
|
const nextVersion = computeNextStableVersion(currentVersion, bumpLevel);
|
|
console.log(`Scope: ${scope}`);
|
|
console.log(`Current version: ${currentVersion}`);
|
|
console.log(`Bump level: ${bumpLevel}`);
|
|
console.log(`Next version: ${nextVersion}`);
|
|
|
|
const summary = getChangesSummary();
|
|
console.log(
|
|
`\nCommits since ${summary.lastTag || "beginning"}: ${summary.commitCount}`,
|
|
);
|
|
|
|
if (dryRun) {
|
|
console.log("\n[DRY RUN] Would bump these packages:");
|
|
for (const p of getPackagesForScope(scope)) {
|
|
console.log(` ${p.name}: ${p.pkg.version} -> ${nextVersion}`);
|
|
}
|
|
console.log("\n[DRY RUN] Exiting.");
|
|
return;
|
|
}
|
|
|
|
const updated = bumpPackages(scope, nextVersion);
|
|
console.log(`\nBumped ${updated.length} packages to ${nextVersion}`);
|
|
|
|
const rawNotes = generateRawReleaseNotes(nextVersion, scope, summary);
|
|
const releaseNotesPath = path.join(ROOT, "release-notes.md");
|
|
fs.writeFileSync(releaseNotesPath, rawNotes);
|
|
console.log("Raw release notes written to release-notes.md");
|
|
|
|
const outputPath = process.env.GITHUB_OUTPUT;
|
|
if (outputPath) {
|
|
fs.appendFileSync(outputPath, `version=${nextVersion}\n`);
|
|
fs.appendFileSync(outputPath, `scope=${scope}\n`);
|
|
}
|
|
|
|
console.log(`\nRelease prepared: v${nextVersion} (${scope})`);
|
|
}
|
|
|
|
main();
|