mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
141 lines
4.6 KiB
JavaScript
141 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
|
import { execFile } from "node:child_process";
|
|
import { readFile, writeFile } from "node:fs/promises";
|
|
import { parseArgs, promisify } from "node:util";
|
|
import { collectGitHubCredits } from "./lib/release-notes-github.mjs";
|
|
import { verifyNotesPublication } from "./lib/release-notes-publication.mjs";
|
|
import {
|
|
contributionRecord,
|
|
contributionSummary,
|
|
readReleaseHistory,
|
|
releaseInventorySchema,
|
|
selectedReleaseNotes,
|
|
verifyInventoryHistory,
|
|
verifyReleaseNotes,
|
|
} from "./lib/release-notes.mjs";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
async function run(command, args, input) {
|
|
const pending = execFileAsync(command, args, {
|
|
encoding: "utf8",
|
|
maxBuffer: 64 * 1024 * 1024,
|
|
});
|
|
pending.child.stdin.end(input);
|
|
return (await pending).stdout;
|
|
}
|
|
|
|
async function createInventory(values) {
|
|
if (!values.repository || !values.base || !values.target || !values.version)
|
|
throw new Error(
|
|
"inventory requires --repository, --base, --target and --version",
|
|
);
|
|
// Parse caller-selected boundaries before making network requests.
|
|
const history = await readReleaseHistory(run, values.base, values.target);
|
|
releaseInventorySchema.omit({ commits: true, pullRequests: true }).parse({
|
|
schemaVersion: 1,
|
|
repository: values.repository,
|
|
version: values.version,
|
|
base: history.base,
|
|
target: history.target,
|
|
});
|
|
const credits = await collectGitHubCredits(
|
|
run,
|
|
values.repository,
|
|
history.commits,
|
|
);
|
|
const inventory = releaseInventorySchema.parse({
|
|
schemaVersion: 1,
|
|
repository: values.repository,
|
|
version: values.version,
|
|
base: history.base,
|
|
target: history.target,
|
|
...credits,
|
|
});
|
|
await verifyInventoryHistory(run, inventory);
|
|
await writeFile(values.manifest, `${JSON.stringify(inventory, null, 2)}\n`, {
|
|
flag: "wx",
|
|
});
|
|
process.stdout.write(
|
|
`Inventoried ${inventory.commits.length} commits and ${inventory.pullRequests.length} merged PRs. Saved ${values.manifest}\n`,
|
|
);
|
|
}
|
|
|
|
async function main() {
|
|
const { values, positionals } = parseArgs({
|
|
allowPositionals: true,
|
|
options: {
|
|
repository: { type: "string" },
|
|
pr: { type: "string" },
|
|
base: { type: "string" },
|
|
target: { type: "string" },
|
|
version: { type: "string" },
|
|
manifest: { type: "string" },
|
|
notes: { type: "string", default: "CHANGELOG.md" },
|
|
"pr-body": { type: "string" },
|
|
source: { type: "string" },
|
|
output: { type: "string" },
|
|
summary: { type: "boolean", default: false },
|
|
},
|
|
});
|
|
const [command] = positionals;
|
|
if (command === "publication" && positionals.length === 1) {
|
|
if (!values.repository || !values.source)
|
|
throw new Error(
|
|
"publication requires --repository and --source; --pr selects the merged release PR",
|
|
);
|
|
process.stdout.write(
|
|
`${JSON.stringify(await verifyNotesPublication(run, values), null, 2)}\n`,
|
|
);
|
|
return;
|
|
}
|
|
if (
|
|
positionals.length !== 1 ||
|
|
!["inventory", "record", "check", "render"].includes(command) ||
|
|
!values.manifest
|
|
)
|
|
throw new Error(
|
|
"Usage: release-notes inventory|record|check|render --manifest FILE [--repository OWNER/REPO --base TAG --target SHA --version VERSION] [--notes FILE --pr-body FILE --source SHA --output FILE] [--summary for record]",
|
|
);
|
|
if (command === "inventory") {
|
|
await createInventory(values);
|
|
return;
|
|
}
|
|
const inventory = releaseInventorySchema.parse(
|
|
JSON.parse(await readFile(values.manifest, "utf8")),
|
|
);
|
|
await verifyInventoryHistory(run, inventory, values.source);
|
|
if (command === "record") {
|
|
const record = values.summary
|
|
? contributionSummary(inventory)
|
|
: contributionRecord(inventory);
|
|
if (values.output)
|
|
await writeFile(values.output, record, {
|
|
flag: "wx",
|
|
});
|
|
else process.stdout.write(record);
|
|
return;
|
|
}
|
|
const notes = await readFile(values.notes, "utf8");
|
|
const body = values["pr-body"]
|
|
? await readFile(values["pr-body"], "utf8")
|
|
: undefined;
|
|
const report = verifyReleaseNotes(inventory, notes, body);
|
|
if (command === "check") {
|
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
return;
|
|
}
|
|
if (!values.output)
|
|
throw new Error(
|
|
"render requires --output (an existing file is never overwritten)",
|
|
);
|
|
const rendered = selectedReleaseNotes(notes, inventory.version);
|
|
await writeFile(values.output, `${rendered}\n`, { flag: "wx" });
|
|
}
|
|
|
|
await main().catch((error) => {
|
|
process.stderr.write(
|
|
`Release notes failed: ${error instanceof Error ? error.message : String(error)}\n`,
|
|
);
|
|
process.exitCode = 1;
|
|
});
|