fix(plugins): abort sibling boundary prep steps

This commit is contained in:
Vincent Koc
2026-04-07 11:42:38 +01:00
parent aa61b508d1
commit f54188f600
2 changed files with 63 additions and 13 deletions

View File

@@ -33,11 +33,19 @@ export function createPrefixedOutputWriter(label, target) {
};
}
function runNodeStep(label, args, timeoutMs) {
function abortSiblingSteps(abortController) {
if (abortController && !abortController.signal.aborted) {
abortController.abort();
}
}
export function runNodeStep(label, args, timeoutMs, params = {}) {
const abortController = params.abortController;
return new Promise((resolvePromise, rejectPromise) => {
const child = spawn(process.execPath, args, {
cwd: repoRoot,
env: process.env,
signal: abortController?.signal,
stdio: ["ignore", "pipe", "pipe"],
});
@@ -52,6 +60,7 @@ function runNodeStep(label, args, timeoutMs) {
settled = true;
stdoutWriter.flush();
stderrWriter.flush();
abortSiblingSteps(abortController);
rejectPromise(new Error(`${label} timed out after ${timeoutMs}ms`));
}, timeoutMs);
@@ -71,6 +80,11 @@ function runNodeStep(label, args, timeoutMs) {
settled = true;
stdoutWriter.flush();
stderrWriter.flush();
if (error.name === "AbortError" && abortController?.signal.aborted) {
rejectPromise(new Error(`${label} canceled after sibling failure`));
return;
}
abortSiblingSteps(abortController);
rejectPromise(new Error(`${label} failed to start: ${error.message}`));
});
child.on("close", (code) => {
@@ -85,24 +99,36 @@ function runNodeStep(label, args, timeoutMs) {
resolvePromise();
return;
}
abortSiblingSteps(abortController);
rejectPromise(new Error(`${label} failed with exit code ${code ?? 1}`));
});
});
}
export async function runNodeStepsInParallel(steps) {
const abortController = new AbortController();
const results = await Promise.allSettled(
steps.map((step) => runNodeStep(step.label, step.args, step.timeoutMs, { abortController })),
);
const firstFailure = results.find((result) => result.status === "rejected");
if (firstFailure) {
throw firstFailure.reason;
}
}
export async function main() {
try {
await Promise.all([
runNodeStep(
"plugin-sdk boundary dts",
[tscBin, "-p", "tsconfig.plugin-sdk.dts.json"],
300_000,
),
runNodeStep(
"plugin-sdk package boundary dts",
[tscBin, "-p", "packages/plugin-sdk/tsconfig.json"],
300_000,
),
await runNodeStepsInParallel([
{
label: "plugin-sdk boundary dts",
args: [tscBin, "-p", "tsconfig.plugin-sdk.dts.json"],
timeoutMs: 300_000,
},
{
label: "plugin-sdk package boundary dts",
args: [tscBin, "-p", "packages/plugin-sdk/tsconfig.json"],
timeoutMs: 300_000,
},
]);
await runNodeStep(
"plugin-sdk boundary root shims",

View File

@@ -1,5 +1,8 @@
import { describe, expect, it } from "vitest";
import { createPrefixedOutputWriter } from "../../scripts/prepare-extension-package-boundary-artifacts.mjs";
import {
createPrefixedOutputWriter,
runNodeStepsInParallel,
} from "../../scripts/prepare-extension-package-boundary-artifacts.mjs";
describe("prepare-extension-package-boundary-artifacts", () => {
it("prefixes each completed line and flushes the trailing partial line", () => {
@@ -16,4 +19,25 @@ describe("prepare-extension-package-boundary-artifacts", () => {
expect(output).toBe("[boundary] first line\n[boundary] second line\n[boundary] third");
});
it("aborts sibling steps after the first failure", async () => {
const startedAt = Date.now();
await expect(
runNodeStepsInParallel([
{
label: "fail-fast",
args: ["--eval", "setTimeout(() => process.exit(2), 10)"],
timeoutMs: 5_000,
},
{
label: "slow-step",
args: ["--eval", "setTimeout(() => {}, 10_000)"],
timeoutMs: 5_000,
},
]),
).rejects.toThrow("fail-fast failed with exit code 2");
expect(Date.now() - startedAt).toBeLessThan(2_000);
});
});