fix(release): keep JSON output machine-readable

This commit is contained in:
Vincent Koc
2026-07-10 16:06:58 -07:00
committed by Vincent Koc
parent 38abdb64e8
commit a960b5f3aa
2 changed files with 63 additions and 5 deletions
+24 -4
View File
@@ -166,6 +166,7 @@ export function createReleasePrepareSteps(
export function runReleasePrepareSteps(params: {
cwd: string;
json?: boolean;
mode: ReleasePrepareMode;
runStep?: (step: ReleasePrepareStep, cwd: string) => number;
steps: ReleasePrepareStep[];
@@ -174,7 +175,10 @@ export function runReleasePrepareSteps(params: {
return params.steps.map((step) => ({ ...step, durationMs: 0, status: "planned" }));
}
const runStep = params.runStep ?? runReleasePrepareStep;
const runStep =
params.runStep ??
((step: ReleasePrepareStep, cwd: string) =>
runReleasePrepareStep(step, cwd, { json: params.json ?? false }));
const results: ReleasePrepareStepResult[] = [];
let blocked = false;
for (const step of params.steps) {
@@ -236,6 +240,7 @@ export function main(argv = process.argv.slice(2)): number {
const before = readWorktreeState(args.rootDir);
const results = runReleasePrepareSteps({
cwd: args.rootDir,
json: args.json,
mode: args.mode,
steps,
});
@@ -265,16 +270,31 @@ export function main(argv = process.argv.slice(2)): number {
return manifest.status === "failed" ? 1 : 0;
}
function runReleasePrepareStep(step: ReleasePrepareStep, cwd: string): number {
process.stdout.write(`\n[release-prepare] ${step.name}\n`);
export function runReleasePrepareStep(
step: ReleasePrepareStep,
cwd: string,
options: { json?: boolean } = {},
): number {
const json = options.json ?? false;
const progressStream = json ? process.stderr : process.stdout;
progressStream.write(`\n[release-prepare] ${step.name}\n`);
const result = spawnSync(step.command, step.args, {
cwd,
encoding: json ? "utf8" : undefined,
env: process.env,
stdio: "inherit",
stdio: json ? ["ignore", "pipe", "pipe"] : "inherit",
});
if (result.error) {
throw result.error;
}
if (json) {
if (typeof result.stdout === "string" && result.stdout) {
process.stderr.write(result.stdout);
}
if (typeof result.stderr === "string" && result.stderr) {
process.stderr.write(result.stderr);
}
}
return result.status ?? 1;
}
+39 -1
View File
@@ -1,9 +1,10 @@
// Release prepare tests cover shadow planning, cutover commands, and candidate manifests.
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import {
buildReleasePreparationManifest,
createReleasePrepareSteps,
parseReleasePrepareArgs,
runReleasePrepareStep,
runReleasePrepareSteps,
} from "../../scripts/release-prepare.ts";
@@ -117,6 +118,43 @@ describe("release preparation plan", () => {
expect(results.map((result) => result.status)).toEqual(["failed", "skipped"]);
});
it("keeps JSON mode child output off stdout", () => {
const stdout: string[] = [];
const stderr: string[] = [];
const stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation((chunk) => {
stdout.push(String(chunk));
return true;
});
const stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation((chunk) => {
stderr.push(String(chunk));
return true;
});
try {
const status = runReleasePrepareStep(
{
args: [
"-e",
'process.stdout.write("child stdout\\n"); process.stderr.write("child stderr\\n");',
],
command: process.execPath,
id: "release-version",
name: "JSON child",
},
process.cwd(),
{ json: true },
);
expect(status).toBe(0);
expect(stdout.join("")).toBe("");
expect(stderr.join("")).toContain("[release-prepare] JSON child");
expect(stderr.join("")).toContain("child stdout");
expect(stderr.join("")).toContain("child stderr");
} finally {
stdoutSpy.mockRestore();
stderrSpy.mockRestore();
}
});
});
describe("release preparation manifest", () => {