singularity-forge/scripts/generate-changelog.mjs

160 lines
4.9 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* Parse conventional commits since the last stable tag.
* Outputs JSON: { bumpType, newVersion, changelogEntry, releaseNotes }
*/
import { execSync } from "child_process";
import { readFileSync } from "fs";
import { resolve } from "path";
const __dirname = import.meta.dirname;
const root = resolve(__dirname, "..");
// ---------------------------------------------------------------------------
// 1. Find last stable tag (skip -next, -dev prereleases)
// ---------------------------------------------------------------------------
2026-05-05 14:31:16 +02:00
const allTags = execSync("git tag --sort=-v:refname", {
cwd: root,
encoding: "utf-8",
})
.trim()
.split("\n")
.filter(Boolean);
const stableTag = allTags.find((t) => /^v\d+\.\d+\.\d+$/.test(t));
if (!stableTag) {
2026-05-05 14:31:16 +02:00
console.error("No stable vX.Y.Z tag found");
process.exit(1);
}
// ---------------------------------------------------------------------------
// 2. Collect commits since that tag
// ---------------------------------------------------------------------------
const range = `${stableTag}..HEAD`;
const rawLog = execSync(
2026-05-05 14:31:16 +02:00
`git log ${range} --pretty=format:"%H %s" --no-merges`,
{ cwd: root, encoding: "utf-8" },
).trim();
if (!rawLog) {
2026-05-05 14:31:16 +02:00
console.error(`No commits since ${stableTag}`);
process.exit(1);
}
// ---------------------------------------------------------------------------
// 3. Parse conventional commits
// ---------------------------------------------------------------------------
2026-05-05 14:31:16 +02:00
const CONVENTIONAL_RE =
/^(?<type>\w+)(?:\((?<scope>[^)]*)\))?!?:\s*(?<desc>.+)$/;
const DISPLAY_FILTER = new Set(["ci", "docs", "test", "tests", "style"]);
const groups = { Added: [], Fixed: [], Changed: [], Removed: [] };
const TYPE_MAP = {
2026-05-05 14:31:16 +02:00
feat: "Added",
fix: "Fixed",
refactor: "Changed",
perf: "Changed",
chore: "Changed",
revert: "Removed",
};
let hasBreaking = false;
let hasFeat = false;
let userFacingCount = 0;
for (const line of rawLog.split("\n")) {
2026-05-05 14:31:16 +02:00
const spaceIdx = line.indexOf(" ");
const subject = line.slice(spaceIdx + 1);
2026-05-05 14:31:16 +02:00
if (subject.includes("BREAKING CHANGE") || subject.includes("!:")) {
hasBreaking = true;
}
2026-05-05 14:31:16 +02:00
const match = CONVENTIONAL_RE.exec(subject);
if (!match) continue;
2026-05-05 14:31:16 +02:00
const { type, scope, desc } = match.groups;
2026-05-05 14:31:16 +02:00
if (type === "feat") hasFeat = true;
2026-05-05 14:31:16 +02:00
// Skip display-only types but still count them for bump logic
if (DISPLAY_FILTER.has(type)) continue;
2026-05-05 14:31:16 +02:00
const group = TYPE_MAP[type];
if (!group) continue;
2026-05-05 14:31:16 +02:00
userFacingCount++;
const scopePrefix = scope ? `**${scope}**: ` : "";
groups[group].push(`- ${scopePrefix}${desc}`);
}
if (userFacingCount === 0) {
2026-05-05 14:31:16 +02:00
console.error(`No user-facing commits since ${stableTag}`);
process.exit(1);
}
// ---------------------------------------------------------------------------
// 4. Determine bump type and new version
// ---------------------------------------------------------------------------
const bumpType = hasBreaking ? "major" : hasFeat ? "minor" : "patch";
fix(release): sync all workspace versions and harden release scripts Two bugs were causing version drift across the repo: 1. Root package.json was silently reverted from 2.74.0 → 2.73.1 during commit b03c9401c (a CI optimization rebase). Tag v2.74.0 is already published on npm, so the next release would have computed 2.73.2 — lower than what's already out — and shipped a broken version. 2. scripts/bump-version.mjs only touches pi-coding-agent + pkg + native platform shims. Other workspace packages drift independently: - @gsd-build/mcp-server: stuck at 2.52.0 (22 minor versions behind) - @gsd-build/rpc-client: stuck at 2.52.0 - @gsd/pi-ai, pi-tui, pi-agent-core: stuck at 0.57.1 - @gsd/native, @gsd-build/daemon: stuck at 0.1.0 Changes: - Bump all non-private workspace packages to 2.74.0 to match the latest release tag. Update daemon + mcp-server's internal rpc-client dep from ^2.52.0 → ^2.74.0. Regenerate root lockfile. - scripts/generate-changelog.mjs: compute newVersion from max(latest stable tag, package.json) instead of package.json alone. Prevents version regressions when package.json is accidentally clobbered by rebases or merges. - scripts/bump-version.mjs: extend to sync all eight non-private workspace packages (daemon, mcp-server, native, pi-agent-core, pi-ai, pi-coding-agent, pi-tui, rpc-client) including their internal deps on each other. Private packages (studio, web) are left alone. Studio and web remain on their own versioning (private: true, never published). The native platform shims under native/npm/* are still synced via native/scripts/sync-platform-versions.cjs from the root version as before.
2026-04-14 19:35:28 -05:00
// Use the higher of (latest stable tag, package.json version) as the baseline.
// Tag is the authoritative record of what's already published; package.json can
// be clobbered by rebases. Taking the max prevents version regressions if the
// source version is accidentally reverted.
const tagVersion = stableTag.replace(/^v/, "");
2026-05-05 14:31:16 +02:00
const currentPkg = JSON.parse(
readFileSync(resolve(root, "package.json"), "utf-8"),
);
fix(release): sync all workspace versions and harden release scripts Two bugs were causing version drift across the repo: 1. Root package.json was silently reverted from 2.74.0 → 2.73.1 during commit b03c9401c (a CI optimization rebase). Tag v2.74.0 is already published on npm, so the next release would have computed 2.73.2 — lower than what's already out — and shipped a broken version. 2. scripts/bump-version.mjs only touches pi-coding-agent + pkg + native platform shims. Other workspace packages drift independently: - @gsd-build/mcp-server: stuck at 2.52.0 (22 minor versions behind) - @gsd-build/rpc-client: stuck at 2.52.0 - @gsd/pi-ai, pi-tui, pi-agent-core: stuck at 0.57.1 - @gsd/native, @gsd-build/daemon: stuck at 0.1.0 Changes: - Bump all non-private workspace packages to 2.74.0 to match the latest release tag. Update daemon + mcp-server's internal rpc-client dep from ^2.52.0 → ^2.74.0. Regenerate root lockfile. - scripts/generate-changelog.mjs: compute newVersion from max(latest stable tag, package.json) instead of package.json alone. Prevents version regressions when package.json is accidentally clobbered by rebases or merges. - scripts/bump-version.mjs: extend to sync all eight non-private workspace packages (daemon, mcp-server, native, pi-agent-core, pi-ai, pi-coding-agent, pi-tui, rpc-client) including their internal deps on each other. Private packages (studio, web) are left alone. Studio and web remain on their own versioning (private: true, never published). The native platform shims under native/npm/* are still synced via native/scripts/sync-platform-versions.cjs from the root version as before.
2026-04-14 19:35:28 -05:00
const pkgVersion = currentPkg.version.replace(/-.*$/, "");
const cmp = (a, b) => {
2026-05-05 14:31:16 +02:00
const [aMaj, aMin, aPat] = a.split(".").map(Number);
const [bMaj, bMin, bPat] = b.split(".").map(Number);
return aMaj - bMaj || aMin - bMin || aPat - bPat;
fix(release): sync all workspace versions and harden release scripts Two bugs were causing version drift across the repo: 1. Root package.json was silently reverted from 2.74.0 → 2.73.1 during commit b03c9401c (a CI optimization rebase). Tag v2.74.0 is already published on npm, so the next release would have computed 2.73.2 — lower than what's already out — and shipped a broken version. 2. scripts/bump-version.mjs only touches pi-coding-agent + pkg + native platform shims. Other workspace packages drift independently: - @gsd-build/mcp-server: stuck at 2.52.0 (22 minor versions behind) - @gsd-build/rpc-client: stuck at 2.52.0 - @gsd/pi-ai, pi-tui, pi-agent-core: stuck at 0.57.1 - @gsd/native, @gsd-build/daemon: stuck at 0.1.0 Changes: - Bump all non-private workspace packages to 2.74.0 to match the latest release tag. Update daemon + mcp-server's internal rpc-client dep from ^2.52.0 → ^2.74.0. Regenerate root lockfile. - scripts/generate-changelog.mjs: compute newVersion from max(latest stable tag, package.json) instead of package.json alone. Prevents version regressions when package.json is accidentally clobbered by rebases or merges. - scripts/bump-version.mjs: extend to sync all eight non-private workspace packages (daemon, mcp-server, native, pi-agent-core, pi-ai, pi-coding-agent, pi-tui, rpc-client) including their internal deps on each other. Private packages (studio, web) are left alone. Studio and web remain on their own versioning (private: true, never published). The native platform shims under native/npm/* are still synced via native/scripts/sync-platform-versions.cjs from the root version as before.
2026-04-14 19:35:28 -05:00
};
const baseline = cmp(pkgVersion, tagVersion) >= 0 ? pkgVersion : tagVersion;
if (baseline !== pkgVersion) {
2026-05-05 14:31:16 +02:00
console.error(
`[generate-changelog] package.json (${pkgVersion}) is behind latest tag (${tagVersion}); using tag as baseline.`,
);
fix(release): sync all workspace versions and harden release scripts Two bugs were causing version drift across the repo: 1. Root package.json was silently reverted from 2.74.0 → 2.73.1 during commit b03c9401c (a CI optimization rebase). Tag v2.74.0 is already published on npm, so the next release would have computed 2.73.2 — lower than what's already out — and shipped a broken version. 2. scripts/bump-version.mjs only touches pi-coding-agent + pkg + native platform shims. Other workspace packages drift independently: - @gsd-build/mcp-server: stuck at 2.52.0 (22 minor versions behind) - @gsd-build/rpc-client: stuck at 2.52.0 - @gsd/pi-ai, pi-tui, pi-agent-core: stuck at 0.57.1 - @gsd/native, @gsd-build/daemon: stuck at 0.1.0 Changes: - Bump all non-private workspace packages to 2.74.0 to match the latest release tag. Update daemon + mcp-server's internal rpc-client dep from ^2.52.0 → ^2.74.0. Regenerate root lockfile. - scripts/generate-changelog.mjs: compute newVersion from max(latest stable tag, package.json) instead of package.json alone. Prevents version regressions when package.json is accidentally clobbered by rebases or merges. - scripts/bump-version.mjs: extend to sync all eight non-private workspace packages (daemon, mcp-server, native, pi-agent-core, pi-ai, pi-coding-agent, pi-tui, rpc-client) including their internal deps on each other. Private packages (studio, web) are left alone. Studio and web remain on their own versioning (private: true, never published). The native platform shims under native/npm/* are still synced via native/scripts/sync-platform-versions.cjs from the root version as before.
2026-04-14 19:35:28 -05:00
}
const [major, minor, patch] = baseline.split(".").map(Number);
let newVersion;
switch (bumpType) {
2026-05-05 14:31:16 +02:00
case "major":
newVersion = `${major + 1}.0.0`;
break;
case "minor":
newVersion = `${major}.${minor + 1}.0`;
break;
case "patch":
newVersion = `${major}.${minor}.${patch + 1}`;
break;
}
// ---------------------------------------------------------------------------
// 5. Build changelog entry
// ---------------------------------------------------------------------------
const today = new Date().toISOString().slice(0, 10);
const sections = [];
for (const [heading, items] of Object.entries(groups)) {
2026-05-05 14:31:16 +02:00
if (items.length > 0) {
sections.push(`### ${heading}\n${items.join("\n")}`);
}
}
const releaseNotes = sections.join("\n\n");
const changelogEntry = `## [${newVersion}] - ${today}\n\n${releaseNotes}`;
// ---------------------------------------------------------------------------
// 6. Output JSON
// ---------------------------------------------------------------------------
const output = JSON.stringify(
2026-05-05 14:31:16 +02:00
{ bumpType, newVersion, changelogEntry, releaseNotes },
null,
2,
);
process.stdout.write(output);