Files
James Russo 17b0db1d3e chore: add release prepare command (#1165)
## What

- Add `bun run release:prepare <version>` as the maintainer-facing stable release entrypoint.
- Make the first run draft missing changelog artifacts and intentionally exit before tagging; rerunning after manual review delegates to `set-version`.
- Tighten the direct `set-version` guard so stable releases also fail when generated TODO changelog copy is still present.
- Update maintainer docs to recommend `release:prepare` while keeping `changelog:draft` as the lower-level regeneration tool.

## Why

Stable releases should be hard to run without reviewed GitHub release notes and Mintlify changelog copy. This keeps the existing manual rewrite step, but makes the expected path one command that engineers can rerun after review.

## How

- Added `scripts/release-prepare.ts` with parsing, draft/review/set-version action selection, and command forwarding.
- Added focused script tests for parser behavior, action selection, command forwarding, and TODO detection.
- Extracted shared script CLI parsing helpers so `changelog:draft` and `release:prepare` use the same option handling.
- Adjusted `changelog:draft --write` so an existing release file is left unchanged unless `--force` is passed, while still allowing a missing docs entry to be added.

## Test plan

- [x] Unit tests added/updated: `bun run test:scripts`
- [x] Format check: `bun run format:check`
- [x] Lint: `bun run lint`
- [x] Typecheck: `bun run --filter '*' typecheck`
- [x] Fallow audit: `bunx fallow audit --base origin/main --fail-on-issues`
- [x] Manual CLI checks: `bun run release:prepare --help`; `bun run set-version 9.9.9` fails before mutation when changelog artifacts are missing
- [x] Documentation updated
2026-06-02 20:34:29 -04:00

139 lines
3.3 KiB
TypeScript

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
buildDraftCommandArgs,
buildSetVersionCommandArgs,
parsePrepareOptions,
resolveStableReleaseAction,
} from "./release-prepare.ts";
import {
CHANGELOG_REVIEW_TODO,
docsChangelogEntryHasGeneratedTodo,
hasGeneratedChangelogTodo,
} from "./set-version.ts";
describe("release prepare arguments", () => {
it("parses changelog draft and set-version options", () => {
assert.deepEqual(
parsePrepareOptions([
"v1.2.3",
"--from",
"v1.2.2",
"--to=HEAD",
"--date",
"2026-06-02",
"--force",
"--no-tag",
"--skip-changelog-check",
]),
{
version: "1.2.3",
from: "v1.2.2",
to: "HEAD",
date: "2026-06-02",
force: true,
skipTag: true,
skipChangelogCheck: true,
},
);
});
});
describe("release prepare actions", () => {
it("drafts when reviewed changelog artifacts are missing", () => {
assert.equal(
resolveStableReleaseAction({
missingArtifacts: ["releases/v1.2.3.md"],
unreviewedArtifacts: [],
}),
"draft",
);
});
it("blocks on review when generated TODOs are still present", () => {
assert.equal(
resolveStableReleaseAction({
missingArtifacts: [],
unreviewedArtifacts: ["docs/changelog.mdx#HyperFrames v1.2.3"],
}),
"review",
);
});
it("delegates to set-version after artifacts are reviewed", () => {
assert.equal(
resolveStableReleaseAction({
missingArtifacts: [],
unreviewedArtifacts: [],
}),
"set-version",
);
});
});
describe("release prepare command builders", () => {
it("passes only changelog options to changelog:draft", () => {
assert.deepEqual(
buildDraftCommandArgs({
version: "1.2.3",
from: "v1.2.2",
to: "HEAD",
date: "2026-06-02",
force: true,
skipTag: true,
skipChangelogCheck: true,
}),
[
"run",
"changelog:draft",
"1.2.3",
"--write",
"--force",
"--from",
"v1.2.2",
"--to",
"HEAD",
"--date",
"2026-06-02",
],
);
});
it("passes only release options to set-version", () => {
assert.deepEqual(
buildSetVersionCommandArgs({
version: "1.2.3",
from: "v1.2.2",
to: "HEAD",
date: "2026-06-02",
force: true,
skipTag: true,
skipChangelogCheck: true,
}),
["run", "set-version", "1.2.3", "--no-tag", "--skip-changelog-check"],
);
});
});
describe("reviewed changelog detection", () => {
it("detects the generated TODO marker", () => {
assert.equal(hasGeneratedChangelogTodo(CHANGELOG_REVIEW_TODO), true);
assert.equal(hasGeneratedChangelogTodo("Polished user-facing summary."), false);
});
it("checks only the matching docs changelog entry", () => {
const docs = `
<Update label="HyperFrames v1.2.4">
Reviewed summary.
</Update>
<Update label="HyperFrames v1.2.3">
${CHANGELOG_REVIEW_TODO}
</Update>
`;
assert.equal(docsChangelogEntryHasGeneratedTodo(docs, "HyperFrames v1.2.3"), true);
assert.equal(docsChangelogEntryHasGeneratedTodo(docs, "HyperFrames v1.2.4"), false);
});
});