diff --git a/scripts/ensure-cli-startup-build.mjs b/scripts/ensure-cli-startup-build.mjs index 4c4060a998d..19b0e046ea1 100644 --- a/scripts/ensure-cli-startup-build.mjs +++ b/scripts/ensure-cli-startup-build.mjs @@ -8,6 +8,20 @@ import { fileURLToPath, pathToFileURL } from "node:url"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const entryCandidates = ["dist/entry.js", "dist/entry.mjs"]; const startupMetadataPath = "dist/cli-startup-metadata.json"; +const DEFAULT_BUILD_TIMEOUT_MS = 10 * 60 * 1000; + +function positiveEnvInt(name, env, fallback) { + const raw = env[name]?.trim(); + if (raw === undefined || raw === "" || !/^[0-9]+$/.test(raw)) { + return fallback; + } + const value = Number.parseInt(raw, 10); + return Number.isSafeInteger(value) && value > 0 ? value : fallback; +} + +export function resolveCliStartupBuildTimeoutMs(env = process.env) { + return positiveEnvInt("OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS", env, DEFAULT_BUILD_TIMEOUT_MS); +} export function hasCliStartupBuild(params = {}) { const rootDir = params.rootDir ?? repoRoot; @@ -32,7 +46,9 @@ export function ensureCliStartupBuild(params = {}) { const result = spawn(nodeExecPath, [buildScript, "cliStartup"], { cwd: rootDir, env: params.env ?? process.env, + killSignal: params.killSignal ?? "SIGKILL", stdio: params.stdio ?? "inherit", + timeout: params.timeoutMs ?? resolveCliStartupBuildTimeoutMs(params.env ?? process.env), }); if (result.error) { throw result.error; diff --git a/scripts/ensure-extension-memory-build.mjs b/scripts/ensure-extension-memory-build.mjs index 733bd7493ce..47ec8ef9071 100644 --- a/scripts/ensure-extension-memory-build.mjs +++ b/scripts/ensure-extension-memory-build.mjs @@ -10,6 +10,24 @@ import { } from "./lib/bundled-plugin-build-entries.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const DEFAULT_BUILD_TIMEOUT_MS = 10 * 60 * 1000; + +function positiveEnvInt(name, env, fallback) { + const raw = env[name]?.trim(); + if (raw === undefined || raw === "" || !/^[0-9]+$/.test(raw)) { + return fallback; + } + const value = Number.parseInt(raw, 10); + return Number.isSafeInteger(value) && value > 0 ? value : fallback; +} + +export function resolveExtensionMemoryBuildTimeoutMs(env = process.env) { + return positiveEnvInt( + "OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS", + env, + DEFAULT_BUILD_TIMEOUT_MS, + ); +} function collectExpectedExtensionMemoryEntryIds(rootDir, env) { try { @@ -80,7 +98,9 @@ export function ensureExtensionMemoryBuild(params = {}) { const result = spawn(nodeExecPath, [buildScript, "cliStartup"], { cwd: rootDir, env: params.env ?? process.env, + killSignal: params.killSignal ?? "SIGKILL", stdio: params.stdio ?? "inherit", + timeout: params.timeoutMs ?? resolveExtensionMemoryBuildTimeoutMs(params.env ?? process.env), }); if (result.error) { throw result.error; diff --git a/scripts/openclaw-npm-release-check.ts b/scripts/openclaw-npm-release-check.ts index fb8a0318fd2..0c93d73e248 100644 --- a/scripts/openclaw-npm-release-check.ts +++ b/scripts/openclaw-npm-release-check.ts @@ -302,8 +302,8 @@ export function utcCalendarDayDistance(left: Date, right: Date): number { } function positiveEnvInt(name: string, env: NodeJS.ProcessEnv, fallback: number): number { - const raw = env[name]; - if (raw === undefined || raw === "") { + const raw = env[name]?.trim(); + if (raw === undefined || raw === "" || !/^[0-9]+$/u.test(raw)) { return fallback; } const value = Number.parseInt(raw, 10); diff --git a/scripts/openclaw-prepack.ts b/scripts/openclaw-prepack.ts index e91bc43d01a..8da9f8d7249 100644 --- a/scripts/openclaw-prepack.ts +++ b/scripts/openclaw-prepack.ts @@ -93,8 +93,8 @@ function ensurePreparedArtifacts(): void { } function positiveEnvInt(name: string, env: NodeJS.ProcessEnv, fallback: number): number { - const raw = env[name]; - if (raw === undefined || raw === "") { + const raw = env[name]?.trim(); + if (raw === undefined || raw === "" || !/^[0-9]+$/u.test(raw)) { return fallback; } const value = Number.parseInt(raw, 10); diff --git a/scripts/release-check.ts b/scripts/release-check.ts index a649fe97559..93abaacdfc6 100755 --- a/scripts/release-check.ts +++ b/scripts/release-check.ts @@ -175,8 +175,8 @@ const PACKED_PLUGIN_SDK_TYPESCRIPT_SMOKE_FIXTURE = resolve( ); function positiveEnvInt(name: string, fallback: number): number { - const raw = process.env[name]; - if (raw === undefined || raw === "") { + const raw = process.env[name]?.trim(); + if (raw === undefined || raw === "" || !/^[0-9]+$/u.test(raw)) { return fallback; } const value = Number.parseInt(raw, 10); diff --git a/test/openclaw-npm-release-check.test.ts b/test/openclaw-npm-release-check.test.ts index 18f9da06448..1a737026ed5 100644 --- a/test/openclaw-npm-release-check.test.ts +++ b/test/openclaw-npm-release-check.test.ts @@ -471,6 +471,14 @@ describe("resolveNpmReleaseCheckCommandTimeoutMs", () => { }), ).toBe(10 * 60 * 1000); }); + + it("falls back when the environment timeout has a numeric prefix", () => { + expect( + resolveNpmReleaseCheckCommandTimeoutMs({ + OPENCLAW_NPM_RELEASE_CHECK_COMMAND_TIMEOUT_MS: "10m", + }), + ).toBe(10 * 60 * 1000); + }); }); describe("parseNpmPackJsonOutput", () => { diff --git a/test/openclaw-prepack.test.ts b/test/openclaw-prepack.test.ts index c651830d624..8ca79dc6cae 100644 --- a/test/openclaw-prepack.test.ts +++ b/test/openclaw-prepack.test.ts @@ -64,4 +64,10 @@ describe("resolvePrepackCommandTimeoutMs", () => { 30 * 60 * 1000, ); }); + + it("falls back when the environment timeout has a numeric prefix", () => { + expect(resolvePrepackCommandTimeoutMs({ OPENCLAW_PREPACK_COMMAND_TIMEOUT_MS: "10m" })).toBe( + 30 * 60 * 1000, + ); + }); }); diff --git a/test/scripts/ensure-cli-startup-build.test.ts b/test/scripts/ensure-cli-startup-build.test.ts index 95bc853a63c..40166061a89 100644 --- a/test/scripts/ensure-cli-startup-build.test.ts +++ b/test/scripts/ensure-cli-startup-build.test.ts @@ -5,6 +5,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { ensureCliStartupBuild, hasCliStartupBuild, + resolveCliStartupBuildTimeoutMs, } from "../../scripts/ensure-cli-startup-build.mjs"; const tempRoots: string[] = []; @@ -80,7 +81,9 @@ describe("ensure-cli-startup-build", () => { args: [path.join(root, "scripts", "build-all.mjs"), "cliStartup"], options: expect.objectContaining({ cwd: root, + killSignal: "SIGKILL", stdio: "pipe", + timeout: 10 * 60 * 1000, }), }, ]); @@ -107,12 +110,37 @@ describe("ensure-cli-startup-build", () => { 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(); @@ -137,3 +165,23 @@ describe("ensure-cli-startup-build", () => { ).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, + ); + }); +}); diff --git a/test/scripts/ensure-extension-memory-build.test.ts b/test/scripts/ensure-extension-memory-build.test.ts index dd4e42b3f3f..136c5e56524 100644 --- a/test/scripts/ensure-extension-memory-build.test.ts +++ b/test/scripts/ensure-extension-memory-build.test.ts @@ -5,6 +5,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { ensureExtensionMemoryBuild, hasBuiltExtensionMemoryEntries, + resolveExtensionMemoryBuildTimeoutMs, } from "../../scripts/ensure-extension-memory-build.mjs"; const tempRoots: string[] = []; @@ -97,12 +98,38 @@ describe("ensure-extension-memory-build", () => { 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 extension memory build timeout", () => { + const root = makeTempRoot(); + const calls: unknown[] = []; + + ensureExtensionMemoryBuild({ + rootDir: root, + env: { OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS: "1234" }, + requiredExtensionIds: ["discord"], + 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(); @@ -115,3 +142,29 @@ describe("ensure-extension-memory-build", () => { ).toThrow("cliStartup build profile failed with exit code 1"); }); }); + +describe("resolveExtensionMemoryBuildTimeoutMs", () => { + it("uses a positive environment timeout", () => { + expect( + resolveExtensionMemoryBuildTimeoutMs({ + OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS: "4321", + }), + ).toBe(4321); + }); + + it("falls back when the environment timeout is invalid", () => { + expect( + resolveExtensionMemoryBuildTimeoutMs({ + OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS: "nope", + }), + ).toBe(10 * 60 * 1000); + }); + + it("falls back when the environment timeout has a numeric prefix", () => { + expect( + resolveExtensionMemoryBuildTimeoutMs({ + OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS: "10m", + }), + ).toBe(10 * 60 * 1000); + }); +});