import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { ensureCliStartupBuild, hasCliStartupBuild, resolveCliStartupBuildTimeoutMs, } from "../../scripts/ensure-cli-startup-build.mjs"; const tempRoots: string[] = []; function makeTempRoot(): string { const root = mkdtempSync(path.join(tmpdir(), "openclaw-cli-startup-build-")); tempRoots.push(root); mkdirSync(path.join(root, "scripts"), { recursive: true }); writeFileSync(path.join(root, "scripts", "build-all.mjs"), "", "utf8"); return root; } afterEach(() => { for (const root of tempRoots.splice(0)) { rmSync(root, { recursive: true, force: true }); } }); describe("ensure-cli-startup-build", () => { it("detects an existing CLI startup build", () => { const root = makeTempRoot(); mkdirSync(path.join(root, "dist"), { recursive: true }); writeFileSync(path.join(root, "dist", "entry.js"), "export {};\n", "utf8"); writeFileSync( path.join(root, "dist", "cli-startup-metadata.json"), '{"rootHelpText":"help\\n"}\n', "utf8", ); expect(hasCliStartupBuild({ rootDir: root })).toBe(true); }); it("skips the build profile when dist entry output and startup metadata already exist", () => { const root = makeTempRoot(); mkdirSync(path.join(root, "dist"), { recursive: true }); writeFileSync(path.join(root, "dist", "entry.mjs"), "export {};\n", "utf8"); writeFileSync( path.join(root, "dist", "cli-startup-metadata.json"), '{"rootHelpText":"help\\n"}\n', "utf8", ); const result = ensureCliStartupBuild({ rootDir: root, spawnSync: () => { throw new Error("unexpected build"); }, }); expect(result).toEqual({ built: false }); }); it("runs the cliStartup build profile when startup metadata is missing", () => { const root = makeTempRoot(); mkdirSync(path.join(root, "dist"), { recursive: true }); writeFileSync(path.join(root, "dist", "entry.js"), "export {};\n", "utf8"); const calls: unknown[] = []; const result = ensureCliStartupBuild({ rootDir: root, nodeExecPath: "/node", spawnSync: (command, args, options) => { calls.push({ command, args, options }); return { status: 0 }; }, stdio: "pipe", }); expect(result).toEqual({ built: true }); expect(calls).toEqual([ { command: "/node", args: [path.join(root, "scripts", "build-all.mjs"), "cliStartup"], options: expect.objectContaining({ cwd: root, killSignal: "SIGKILL", stdio: "pipe", timeout: 10 * 60 * 1000, }), }, ]); }); it("runs the cliStartup build profile when dist entry output is missing", () => { const root = makeTempRoot(); const calls: unknown[] = []; const result = ensureCliStartupBuild({ rootDir: root, nodeExecPath: "/node", spawnSync: (command, args, options) => { calls.push({ command, args, options }); return { status: 0 }; }, stdio: "pipe", }); expect(result).toEqual({ built: true }); expect(calls).toEqual([ { command: "/node", args: [path.join(root, "scripts", "build-all.mjs"), "cliStartup"], options: expect.objectContaining({ cwd: root, killSignal: "SIGKILL", stdio: "pipe", timeout: 10 * 60 * 1000, }), }, ]); }); it("uses the configured cliStartup build timeout", () => { const root = makeTempRoot(); const calls: unknown[] = []; ensureCliStartupBuild({ rootDir: root, env: { OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS: "1234" }, spawnSync: (command, args, options) => { calls.push({ command, args, options }); return { status: 0 }; }, stdio: "pipe", }); expect(calls).toEqual([ expect.objectContaining({ options: expect.objectContaining({ timeout: 1234, }), }), ]); }); it("fails when the cliStartup build profile fails", () => { const root = makeTempRoot(); expect(() => ensureCliStartupBuild({ rootDir: root, spawnSync: () => ({ status: 1 }), stdio: "pipe", }), ).toThrow("cliStartup build profile failed with exit code 1"); }); it("fails when spawning the cliStartup build profile fails", () => { const root = makeTempRoot(); expect(() => ensureCliStartupBuild({ rootDir: root, spawnSync: () => ({ error: new Error("spawn denied") }), stdio: "pipe", }), ).toThrow("spawn denied"); }); }); describe("resolveCliStartupBuildTimeoutMs", () => { it("uses a positive environment timeout", () => { expect(resolveCliStartupBuildTimeoutMs({ OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS: "4321" })).toBe( 4321, ); }); it("falls back when the environment timeout is invalid", () => { expect(resolveCliStartupBuildTimeoutMs({ OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS: "nope" })).toBe( 10 * 60 * 1000, ); }); it("falls back when the environment timeout has a numeric prefix", () => { expect(resolveCliStartupBuildTimeoutMs({ OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS: "10m" })).toBe( 10 * 60 * 1000, ); }); });