mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
fix(release): scope the AI notes prompt to the lane being released
The generator fed the model a repo-wide `git log -50` as context and told it it was writing notes for "CopilotKit vX.Y.Z, an open-source AI agent framework for React applications" — wrong on three counts for an angular or channels release: the wrong commits, the wrong framing, and the wrong release title (only the monorepo lane is titled `vX.Y.Z`). Pass the scope through, build context from that lane's own commits, name the packages actually being published, and tell the model to write about nothing else. Also fix the API call itself: the pinned model string was a dated snapshot, max_tokens 2048 could truncate a large release, and the response reader took content[0].text — which is not the text block on models that return thinking blocks first.
This commit is contained in:
@@ -11,26 +11,28 @@
|
||||
* Env vars:
|
||||
* ANTHROPIC_API_KEY — for AI generation (falls back to raw if missing)
|
||||
*
|
||||
* Usage: tsx scripts/release/generate-ai-release-notes.ts <version>
|
||||
* Usage: tsx scripts/release/generate-ai-release-notes.ts <version> <scope>
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import https from "https";
|
||||
import { spawnSync } from "child_process";
|
||||
import { ROOT } from "./lib/config.js";
|
||||
import { GIT_LOG_FORMAT, parseCommitLog } from "./lib/changes.js";
|
||||
import { ROOT, loadConfig } from "./lib/config.js";
|
||||
import type { ReleaseScope } from "./lib/config.js";
|
||||
import { getCommitsSinceLastRelease } from "./lib/changes.js";
|
||||
|
||||
function getRecentCommits(count = 50): string {
|
||||
const result = spawnSync(
|
||||
"git",
|
||||
["log", `-${count}`, "--no-merges", `--format=${GIT_LOG_FORMAT}`],
|
||||
{ cwd: ROOT, encoding: "utf8" },
|
||||
);
|
||||
return parseCommitLog(result.stdout)
|
||||
/**
|
||||
* Context for the model: the commits of THIS release lane, with bodies.
|
||||
*
|
||||
* This used to be a repo-wide `git log -50`, which fed an angular release the
|
||||
* last fifty monorepo commits — showcase renames, other packages' work — as
|
||||
* "context" for notes it had no business mentioning.
|
||||
*/
|
||||
function getScopeCommitContext(scope: ReleaseScope): string {
|
||||
return getCommitsSinceLastRelease(scope)
|
||||
.map((commit) =>
|
||||
[
|
||||
`commit ${commit.hash.slice(0, 7)}`,
|
||||
`commit ${commit.hash.slice(0, 7)}${commit.pr ? ` (PR #${commit.pr})` : ""}`,
|
||||
`subject: ${commit.subject}`,
|
||||
commit.body ? `body:\n${commit.body}` : "",
|
||||
]
|
||||
@@ -65,8 +67,16 @@ function callAnthropic(apiKey: string, prompt: string): Promise<string> {
|
||||
res.on("end", () => {
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.content?.[0]) {
|
||||
resolve(parsed.content[0].text);
|
||||
|
||||
// Thinking is on by default on current models, so the first content
|
||||
// block is not necessarily the text — select by type rather than
|
||||
// position.
|
||||
const text = (parsed.content ?? []).find(
|
||||
(block: { type?: string }) => block.type === "text",
|
||||
)?.text;
|
||||
|
||||
if (typeof text === "string" && text.trim()) {
|
||||
resolve(text);
|
||||
} else {
|
||||
reject(new Error(`Unexpected API response: ${data}`));
|
||||
}
|
||||
@@ -84,11 +94,23 @@ function callAnthropic(apiKey: string, prompt: string): Promise<string> {
|
||||
|
||||
async function main() {
|
||||
const version = process.argv[2];
|
||||
if (!version) {
|
||||
console.error("Usage: generate-ai-release-notes.ts <version>");
|
||||
const scope = process.argv[3] as ReleaseScope | undefined;
|
||||
const validScopes = Object.keys(loadConfig().scopes);
|
||||
|
||||
if (!version || !scope || !validScopes.includes(scope)) {
|
||||
console.error(
|
||||
`Usage: generate-ai-release-notes.ts <version> <scope>\n` +
|
||||
`Valid scopes: ${validScopes.join(", ")}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const packages = loadConfig().scopes[scope].packages;
|
||||
// Only the monorepo scope is titled `vX.Y.Z`; every other lane is
|
||||
// `<scope>/vX.Y.Z`, and the notes must not claim otherwise.
|
||||
const releaseTitle =
|
||||
scope === "monorepo" ? `v${version}` : `${scope}/v${version}`;
|
||||
|
||||
const releaseNotesPath = path.join(ROOT, "release-notes.md");
|
||||
if (!fs.existsSync(releaseNotesPath)) {
|
||||
console.error("release-notes.md not found. Run prepare-release.ts first.");
|
||||
@@ -102,17 +124,20 @@ async function main() {
|
||||
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
||||
if (anthropicKey) {
|
||||
console.log("Generating AI-enhanced release notes...");
|
||||
const recentCommits = getRecentCommits();
|
||||
const scopeCommits = getScopeCommitContext(scope);
|
||||
|
||||
const prompt = `You are writing release notes for CopilotKit v${version}, an open-source AI agent framework for React applications.
|
||||
const prompt = `You are writing release notes for the \`${scope}\` release lane of CopilotKit, an open-source framework for building AI agent experiences.
|
||||
|
||||
Here is the raw changelog extracted from git history:
|
||||
This release publishes exactly these npm packages at version ${version}:
|
||||
${packages.map((name) => `- ${name}`).join("\n")}
|
||||
|
||||
Here is the raw changelog, already filtered to the commits that touched those packages:
|
||||
|
||||
${rawChangelog}
|
||||
|
||||
Here are the recent git commits, including their bodies, for additional context:
|
||||
Here are those same commits with their full bodies, for additional context:
|
||||
|
||||
${recentCommits}
|
||||
${scopeCommits}
|
||||
|
||||
Write polished, user-facing release notes for a GitHub Release. Guidelines:
|
||||
- Start with a brief (1-2 sentence) summary of the release
|
||||
@@ -122,7 +147,11 @@ Write polished, user-facing release notes for a GitHub Release. Guidelines:
|
||||
- Include any migration notes for breaking changes
|
||||
- Keep it concise — no filler, no marketing speak
|
||||
- Use markdown formatting
|
||||
- Do NOT include a title/header — the GitHub Release title will be "v${version}"
|
||||
- Write only about the packages listed above. Do not describe changes to other
|
||||
CopilotKit packages, the docs site, or the examples, even if a commit body
|
||||
mentions them.
|
||||
- Where a change has a PR number, reference it as (#1234) so it links on GitHub
|
||||
- Do NOT include a title/header — the GitHub Release title will be "${releaseTitle}"
|
||||
|
||||
Output ONLY the release notes content, nothing else.`;
|
||||
|
||||
@@ -139,7 +168,6 @@ Output ONLY the release notes content, nothing else.`;
|
||||
"No ANTHROPIC_API_KEY found. Using raw changelog as release notes.",
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
|
||||
+199
-179
@@ -79,56 +79,66 @@ describe("Channels release history", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves multiline commit bodies and trailers from real git history", { timeout: 30_000 }, async () => {
|
||||
const actualChildProcess = await vi.importActual("child_process");
|
||||
const spawnSync = actualChildProcess.spawnSync as typeof spawnSyncMock;
|
||||
const repository = mkdtempSync(join(tmpdir(), "copilotkit-release-"));
|
||||
it(
|
||||
"preserves multiline commit bodies and trailers from real git history",
|
||||
{ timeout: 30_000 },
|
||||
async () => {
|
||||
const actualChildProcess = await vi.importActual("child_process");
|
||||
const spawnSync = actualChildProcess.spawnSync as typeof spawnSyncMock;
|
||||
const repository = mkdtempSync(join(tmpdir(), "copilotkit-release-"));
|
||||
|
||||
const git = (args: string[]) => {
|
||||
const result = spawnSync("git", args, {
|
||||
cwd: repository,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
return result.stdout;
|
||||
};
|
||||
const git = (args: string[]) => {
|
||||
const result = spawnSync("git", args, {
|
||||
cwd: repository,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
return result.stdout;
|
||||
};
|
||||
|
||||
try {
|
||||
git(["init", "--quiet"]);
|
||||
git(["config", "user.name", "Release Test"]);
|
||||
git(["config", "user.email", "release-test@example.com"]);
|
||||
git(["commit", "--quiet", "--allow-empty", "-m", "fix(core): baseline"]);
|
||||
git([
|
||||
"commit",
|
||||
"--quiet",
|
||||
"--allow-empty",
|
||||
"-m",
|
||||
"feat(runtime)!: replace the transport",
|
||||
"-m",
|
||||
"The transport now streams every response.\n\nBREAKING CHANGE: configure a streaming adapter before upgrading.\nKeep existing adapters until migration is complete.\n\nCo-authored-by: Release Test <release-test@example.com>",
|
||||
]);
|
||||
try {
|
||||
git(["init", "--quiet"]);
|
||||
git(["config", "user.name", "Release Test"]);
|
||||
git(["config", "user.email", "release-test@example.com"]);
|
||||
git([
|
||||
"commit",
|
||||
"--quiet",
|
||||
"--allow-empty",
|
||||
"-m",
|
||||
"fix(core): baseline",
|
||||
]);
|
||||
git([
|
||||
"commit",
|
||||
"--quiet",
|
||||
"--allow-empty",
|
||||
"-m",
|
||||
"feat(runtime)!: replace the transport",
|
||||
"-m",
|
||||
"The transport now streams every response.\n\nBREAKING CHANGE: configure a streaming adapter before upgrading.\nKeep existing adapters until migration is complete.\n\nCo-authored-by: Release Test <release-test@example.com>",
|
||||
]);
|
||||
|
||||
const output = git([
|
||||
"log",
|
||||
"HEAD",
|
||||
"--no-merges",
|
||||
`--format=${GIT_LOG_FORMAT}`,
|
||||
]);
|
||||
const output = git([
|
||||
"log",
|
||||
"HEAD",
|
||||
"--no-merges",
|
||||
`--format=${GIT_LOG_FORMAT}`,
|
||||
]);
|
||||
|
||||
expect(parseCommitLog(output)).toEqual([
|
||||
expect.objectContaining({
|
||||
subject: "feat(runtime)!: replace the transport",
|
||||
body: "The transport now streams every response.\n\nBREAKING CHANGE: configure a streaming adapter before upgrading.\nKeep existing adapters until migration is complete.\n\nCo-authored-by: Release Test <release-test@example.com>",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
subject: "fix(core): baseline",
|
||||
body: "",
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
rmSync(repository, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
expect(parseCommitLog(output)).toEqual([
|
||||
expect.objectContaining({
|
||||
subject: "feat(runtime)!: replace the transport",
|
||||
body: "The transport now streams every response.\n\nBREAKING CHANGE: configure a streaming adapter before upgrading.\nKeep existing adapters until migration is complete.\n\nCo-authored-by: Release Test <release-test@example.com>",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
subject: "fix(core): baseline",
|
||||
body: "",
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
rmSync(repository, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("Release-note commit selection", () => {
|
||||
@@ -172,152 +182,162 @@ describe("Release-note commit selection", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("collapses a merged PR to one entry and drops the other scope's work", { timeout: 30_000 }, async () => {
|
||||
const actualChildProcess = await vi.importActual("child_process");
|
||||
const spawnSync = actualChildProcess.spawnSync as typeof spawnSyncMock;
|
||||
const repository = mkdtempSync(join(tmpdir(), "copilotkit-firstparent-"));
|
||||
it(
|
||||
"collapses a merged PR to one entry and drops the other scope's work",
|
||||
{ timeout: 30_000 },
|
||||
async () => {
|
||||
const actualChildProcess = await vi.importActual("child_process");
|
||||
const spawnSync = actualChildProcess.spawnSync as typeof spawnSyncMock;
|
||||
const repository = mkdtempSync(join(tmpdir(), "copilotkit-firstparent-"));
|
||||
|
||||
const git = (args: string[]) => {
|
||||
const result = spawnSync("git", args, {
|
||||
cwd: repository,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
return result.stdout;
|
||||
};
|
||||
const write = (file: string, contents: string) => {
|
||||
mkdirSync(join(repository, dirname(file)), { recursive: true });
|
||||
writeFileSync(join(repository, file), contents);
|
||||
};
|
||||
const git = (args: string[]) => {
|
||||
const result = spawnSync("git", args, {
|
||||
cwd: repository,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
return result.stdout;
|
||||
};
|
||||
const write = (file: string, contents: string) => {
|
||||
mkdirSync(join(repository, dirname(file)), { recursive: true });
|
||||
writeFileSync(join(repository, file), contents);
|
||||
};
|
||||
|
||||
try {
|
||||
git(["init", "--quiet", "--initial-branch=main"]);
|
||||
git(["config", "user.name", "Release Test"]);
|
||||
git(["config", "user.email", "release-test@example.com"]);
|
||||
try {
|
||||
git(["init", "--quiet", "--initial-branch=main"]);
|
||||
git(["config", "user.name", "Release Test"]);
|
||||
git(["config", "user.email", "release-test@example.com"]);
|
||||
|
||||
write("packages/angular/a.ts", "base");
|
||||
git(["add", "-A"]);
|
||||
git(["commit", "--quiet", "-m", "chore: baseline"]);
|
||||
write("packages/angular/a.ts", "base");
|
||||
git(["add", "-A"]);
|
||||
git(["commit", "--quiet", "-m", "chore: baseline"]);
|
||||
|
||||
// A feature branch with two noisy intermediate commits, merged as a PR.
|
||||
git(["checkout", "--quiet", "-b", "feature"]);
|
||||
write("packages/angular/a.ts", "one");
|
||||
git(["add", "-A"]);
|
||||
git(["commit", "--quiet", "-m", "feat(angular): half of the feature"]);
|
||||
write("packages/angular/a.ts", "two");
|
||||
git(["add", "-A"]);
|
||||
git(["commit", "--quiet", "-m", "test(angular): cover the feature"]);
|
||||
git(["checkout", "--quiet", "main"]);
|
||||
git([
|
||||
"merge",
|
||||
"--quiet",
|
||||
"--no-ff",
|
||||
"feature",
|
||||
"-m",
|
||||
"feat(angular): add registerComponent (#6773)",
|
||||
]);
|
||||
// A feature branch with two noisy intermediate commits, merged as a PR.
|
||||
git(["checkout", "--quiet", "-b", "feature"]);
|
||||
write("packages/angular/a.ts", "one");
|
||||
git(["add", "-A"]);
|
||||
git(["commit", "--quiet", "-m", "feat(angular): half of the feature"]);
|
||||
write("packages/angular/a.ts", "two");
|
||||
git(["add", "-A"]);
|
||||
git(["commit", "--quiet", "-m", "test(angular): cover the feature"]);
|
||||
git(["checkout", "--quiet", "main"]);
|
||||
git([
|
||||
"merge",
|
||||
"--quiet",
|
||||
"--no-ff",
|
||||
"feature",
|
||||
"-m",
|
||||
"feat(angular): add registerComponent (#6773)",
|
||||
]);
|
||||
|
||||
// Work in a package that belongs to a DIFFERENT release scope.
|
||||
write("packages/channels-core/b.ts", "other");
|
||||
git(["add", "-A"]);
|
||||
git(["commit", "--quiet", "-m", "feat(channels): unrelated lane"]);
|
||||
// Work in a package that belongs to a DIFFERENT release scope.
|
||||
write("packages/channels-core/b.ts", "other");
|
||||
git(["add", "-A"]);
|
||||
git(["commit", "--quiet", "-m", "feat(channels): unrelated lane"]);
|
||||
|
||||
const output = git([
|
||||
"log",
|
||||
"HEAD",
|
||||
"--first-parent",
|
||||
`--format=${GIT_LOG_FORMAT}`,
|
||||
"--",
|
||||
"packages/angular",
|
||||
]);
|
||||
const output = git([
|
||||
"log",
|
||||
"HEAD",
|
||||
"--first-parent",
|
||||
`--format=${GIT_LOG_FORMAT}`,
|
||||
"--",
|
||||
"packages/angular",
|
||||
]);
|
||||
|
||||
const subjects = parseCommitLog(output)
|
||||
.filter((c) => !isNoiseCommit(c.subject))
|
||||
.map((c) => c.subject);
|
||||
const subjects = parseCommitLog(output)
|
||||
.filter((c) => !isNoiseCommit(c.subject))
|
||||
.map((c) => c.subject);
|
||||
|
||||
// One entry for the whole PR — not its two branch commits — and nothing
|
||||
// from the channels scope.
|
||||
expect(subjects).toEqual(["feat(angular): add registerComponent (#6773)"]);
|
||||
expect(parseCommitLog(output)[0].pr).toBe(6773);
|
||||
} finally {
|
||||
rmSync(repository, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
// One entry for the whole PR — not its two branch commits — and nothing
|
||||
// from the channels scope.
|
||||
expect(subjects).toEqual([
|
||||
"feat(angular): add registerComponent (#6773)",
|
||||
]);
|
||||
expect(parseCommitLog(output)[0].pr).toBe(6773);
|
||||
} finally {
|
||||
rmSync(repository, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("Breaking-change footers on branch commits", () => {
|
||||
it("folds a merged PR's branch messages into the merge commit body", { timeout: 30_000 }, async () => {
|
||||
const actualChildProcess = await vi.importActual("child_process");
|
||||
const spawnSync = actualChildProcess.spawnSync as typeof spawnSyncMock;
|
||||
const repository = mkdtempSync(join(tmpdir(), "copilotkit-branchbody-"));
|
||||
it(
|
||||
"folds a merged PR's branch messages into the merge commit body",
|
||||
{ timeout: 30_000 },
|
||||
async () => {
|
||||
const actualChildProcess = await vi.importActual("child_process");
|
||||
const spawnSync = actualChildProcess.spawnSync as typeof spawnSyncMock;
|
||||
const repository = mkdtempSync(join(tmpdir(), "copilotkit-branchbody-"));
|
||||
|
||||
const git = (args: string[]) => {
|
||||
const result = spawnSync("git", args, {
|
||||
cwd: repository,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
return result.stdout;
|
||||
};
|
||||
|
||||
try {
|
||||
git(["init", "--quiet", "--initial-branch=main"]);
|
||||
git(["config", "user.name", "Release Test"]);
|
||||
git(["config", "user.email", "release-test@example.com"]);
|
||||
git(["commit", "--quiet", "--allow-empty", "-m", "chore: baseline"]);
|
||||
|
||||
git(["checkout", "--quiet", "-b", "feature"]);
|
||||
git([
|
||||
"commit",
|
||||
"--quiet",
|
||||
"--allow-empty",
|
||||
"-m",
|
||||
"refactor(core)!: drop the legacy registry",
|
||||
"-m",
|
||||
"BREAKING CHANGE: useLegacyRegistry is removed; use the shared one.",
|
||||
]);
|
||||
git(["checkout", "--quiet", "main"]);
|
||||
// A merge message that says nothing about the break — the footer exists
|
||||
// only on the branch commit.
|
||||
git([
|
||||
"merge",
|
||||
"--quiet",
|
||||
"--no-ff",
|
||||
"feature",
|
||||
"-m",
|
||||
"refactor(core)!: converge the registry (#1234)",
|
||||
]);
|
||||
|
||||
const mergeSha = git(["rev-parse", "HEAD"]).trim();
|
||||
|
||||
// withBranchMessages shells out with cwd: ROOT, so run it against a real
|
||||
// clone of this history rather than mocking the boundary away.
|
||||
spawnSyncMock.mockImplementation((command: string, args: string[]) =>
|
||||
spawnSync(command, args, { cwd: repository, encoding: "utf8" }),
|
||||
);
|
||||
|
||||
const merge = {
|
||||
hash: mergeSha,
|
||||
subject: "refactor(core)!: converge the registry (#1234)",
|
||||
body: "",
|
||||
pr: 1234,
|
||||
const git = (args: string[]) => {
|
||||
const result = spawnSync("git", args, {
|
||||
cwd: repository,
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
return result.stdout;
|
||||
};
|
||||
|
||||
expect(withBranchMessages(merge).body).toContain(
|
||||
"BREAKING CHANGE: useLegacyRegistry is removed; use the shared one.",
|
||||
);
|
||||
try {
|
||||
git(["init", "--quiet", "--initial-branch=main"]);
|
||||
git(["config", "user.name", "Release Test"]);
|
||||
git(["config", "user.email", "release-test@example.com"]);
|
||||
git(["commit", "--quiet", "--allow-empty", "-m", "chore: baseline"]);
|
||||
|
||||
// A non-merge commit makes `sha^1..sha^2` invalid; that must be a no-op,
|
||||
// not a throw.
|
||||
const baseline = {
|
||||
hash: git(["rev-parse", "HEAD^1"]).trim(),
|
||||
subject: "chore: baseline",
|
||||
body: "",
|
||||
pr: null,
|
||||
};
|
||||
expect(withBranchMessages(baseline)).toEqual(baseline);
|
||||
} finally {
|
||||
rmSync(repository, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
git(["checkout", "--quiet", "-b", "feature"]);
|
||||
git([
|
||||
"commit",
|
||||
"--quiet",
|
||||
"--allow-empty",
|
||||
"-m",
|
||||
"refactor(core)!: drop the legacy registry",
|
||||
"-m",
|
||||
"BREAKING CHANGE: useLegacyRegistry is removed; use the shared one.",
|
||||
]);
|
||||
git(["checkout", "--quiet", "main"]);
|
||||
// A merge message that says nothing about the break — the footer exists
|
||||
// only on the branch commit.
|
||||
git([
|
||||
"merge",
|
||||
"--quiet",
|
||||
"--no-ff",
|
||||
"feature",
|
||||
"-m",
|
||||
"refactor(core)!: converge the registry (#1234)",
|
||||
]);
|
||||
|
||||
const mergeSha = git(["rev-parse", "HEAD"]).trim();
|
||||
|
||||
// withBranchMessages shells out with cwd: ROOT, so run it against a real
|
||||
// clone of this history rather than mocking the boundary away.
|
||||
spawnSyncMock.mockImplementation((command: string, args: string[]) =>
|
||||
spawnSync(command, args, { cwd: repository, encoding: "utf8" }),
|
||||
);
|
||||
|
||||
const merge = {
|
||||
hash: mergeSha,
|
||||
subject: "refactor(core)!: converge the registry (#1234)",
|
||||
body: "",
|
||||
pr: 1234,
|
||||
};
|
||||
|
||||
expect(withBranchMessages(merge).body).toContain(
|
||||
"BREAKING CHANGE: useLegacyRegistry is removed; use the shared one.",
|
||||
);
|
||||
|
||||
// A non-merge commit makes `sha^1..sha^2` invalid; that must be a no-op,
|
||||
// not a throw.
|
||||
const baseline = {
|
||||
hash: git(["rev-parse", "HEAD^1"]).trim(),
|
||||
subject: "chore: baseline",
|
||||
body: "",
|
||||
pr: null,
|
||||
};
|
||||
expect(withBranchMessages(baseline)).toEqual(baseline);
|
||||
} finally {
|
||||
rmSync(repository, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -166,7 +166,6 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// NOTE: Build is handled by the CI build job (no secrets).
|
||||
// The publish job receives pre-built artifacts via download-artifact.
|
||||
// We intentionally do NOT rebuild here to keep NPM_TOKEN out of the
|
||||
|
||||
Reference in New Issue
Block a user