import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { createPrefixedOutputWriter, isArtifactSetFresh, parseMode, runNodeStepsInParallel, } from "../../scripts/prepare-extension-package-boundary-artifacts.mjs"; const tempRoots = new Set(); afterEach(() => { for (const rootDir of tempRoots) { fs.rmSync(rootDir, { force: true, recursive: true }); } tempRoots.clear(); }); describe("prepare-extension-package-boundary-artifacts", () => { it("prefixes each completed line and flushes the trailing partial line", () => { let output = ""; const writer = createPrefixedOutputWriter("boundary", { write(chunk: string) { output += chunk; }, }); writer.write("first line\nsecond"); writer.write(" line\nthird"); writer.flush(); 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); }); it("treats artifacts as fresh only when outputs are newer than inputs", () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-boundary-prep-")); tempRoots.add(rootDir); const inputPath = path.join(rootDir, "src", "demo.ts"); const outputPath = path.join(rootDir, "dist", "demo.tsbuildinfo"); fs.mkdirSync(path.dirname(inputPath), { recursive: true }); fs.mkdirSync(path.dirname(outputPath), { recursive: true }); fs.writeFileSync(inputPath, "export const demo = 1;\n", "utf8"); fs.writeFileSync(outputPath, "ok\n", "utf8"); fs.utimesSync(inputPath, new Date(1_000), new Date(1_000)); fs.utimesSync(outputPath, new Date(2_000), new Date(2_000)); expect( isArtifactSetFresh({ rootDir, inputPaths: ["src"], outputPaths: ["dist/demo.tsbuildinfo"], }), ).toBe(true); fs.utimesSync(inputPath, new Date(3_000), new Date(3_000)); expect( isArtifactSetFresh({ rootDir, inputPaths: ["src"], outputPaths: ["dist/demo.tsbuildinfo"], }), ).toBe(false); }); it("parses prep mode and rejects unknown values", () => { expect(parseMode([])).toBe("all"); expect(parseMode(["--mode=package-boundary"])).toBe("package-boundary"); expect(() => parseMode(["--mode=nope"])).toThrow("Unknown mode: nope"); }); });