murashit_codex-panel/scripts/release/notes.mjs

74 lines
2.3 KiB
JavaScript
Raw Permalink Normal View History

import { spawnSync } from "node:child_process";
import { pathToFileURL } from "node:url";
import conventionalCommits from "conventional-changelog-conventionalcommits";
import { CommitParser } from "conventional-commits-parser";
const releaseNoteTypes = new Set(["feat", "fix", "perf"]);
const parser = new CommitParser(conventionalCommits().parser);
function fail(message) {
throw new Error(`release notes generation failed: ${message}`);
}
function runGit(args, cwd) {
const result = spawnSync("git", args, {
cwd,
encoding: "utf8",
shell: false,
});
if (result.error) fail(result.error.message);
if (result.status !== 0) {
fail(`${args.join(" ")} exited with ${result.status}: ${result.stderr.trim()}`);
}
return result.stdout;
}
function formatSubject(subject) {
const sentence = subject.replace(/^([a-z])/, (letter) => letter.toUpperCase());
return /[.!?]$/.test(sentence) ? sentence : `${sentence}.`;
}
export function releaseNoteForCommit(message) {
const commit = parser.parse(message);
if (!commit.subject) return null;
const isBreaking = commit.notes.length > 0;
if (!isBreaking && !releaseNoteTypes.has(commit.type ?? "")) return null;
return formatSubject(commit.subject);
}
export function renderReleaseNotes(messages) {
const entries = messages.map(releaseNoteForCommit).filter((entry) => entry !== null);
const bullets = entries.length > 0 ? entries.map((entry) => `- ${entry}`).join("\n") : "- ";
return `## Changes\n\n${bullets}\n`;
}
export function readCommitMessagesSince(tag, cwd = process.cwd()) {
runGit(["rev-parse", "--verify", `refs/tags/${tag}`], cwd);
const output = runGit(["log", "--format=%B%x00", `${tag}..HEAD`], cwd);
return output
.split("\0")
.map((message) => message.trim())
.filter(Boolean);
}
export function generateReleaseNotes(tag, cwd = process.cwd()) {
return renderReleaseNotes(readCommitMessagesSince(tag, cwd));
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const previousTag = process.argv[2];
if (!previousTag) {
console.error("usage: npm run release:notes -- X.Y.Z");
process.exit(1);
}
try {
process.stdout.write(generateReleaseNotes(previousTag));
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exit(1);
}
}