

















@@ -8,6 +8,48 @@ import path from "node:path";
88import { fileURLToPath } from "node:url";
991010const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11+const DEFAULT_PACKAGE_BUILD_TIMEOUT_MS = 45 * 60 * 1000;
12+const DEFAULT_PACKAGE_INVENTORY_TIMEOUT_MS = 5 * 60 * 1000;
13+const DEFAULT_PACKAGE_PACK_TIMEOUT_MS = 5 * 60 * 1000;
14+const DEFAULT_PACKAGE_TARBALL_CHECK_TIMEOUT_MS = 5 * 60 * 1000;
15+const DEFAULT_TIMEOUT_KILL_AFTER_MS = 5_000;
16+const ACTIVE_CHILD_KILLERS = new Set();
17+const SIGNAL_EXIT_CODES = {
18+SIGHUP: 129,
19+SIGINT: 130,
20+SIGTERM: 143,
21+};
22+let forwardedSignalExitCode;
23+24+for (const signal of Object.keys(SIGNAL_EXIT_CODES)) {
25+process.on(signal, () => {
26+forwardedSignalExitCode ??= SIGNAL_EXIT_CODES[signal];
27+if (ACTIVE_CHILD_KILLERS.size === 0) {
28+process.exit(forwardedSignalExitCode);
29+}
30+for (const killChild of ACTIVE_CHILD_KILLERS) {
31+killChild(signal);
32+}
33+setTimeout(() => {
34+for (const killChild of ACTIVE_CHILD_KILLERS) {
35+killChild("SIGKILL");
36+}
37+process.exit(forwardedSignalExitCode);
38+}, DEFAULT_TIMEOUT_KILL_AFTER_MS);
39+});
40+}
41+42+function resolveTimeoutMs(envName, defaultValue) {
43+const raw = process.env[envName];
44+if (raw === undefined || raw === "") {
45+return defaultValue;
46+}
47+const parsed = Number(raw);
48+if (!Number.isFinite(parsed) || parsed <= 0) {
49+throw new Error(`${envName} must be a positive timeout in milliseconds`);
50+}
51+return Math.trunc(parsed);
52+}
11531254function parseArgs(argv) {
1355const options = {
@@ -41,37 +83,78 @@ function parseArgs(argv) {
41834284function run(command, args, cwd, options = {}) {
4385return new Promise((resolve, reject) => {
86+const useProcessGroup = process.platform !== "win32";
4487const child = spawn(command, args, {
4588 cwd,
4689stdio: ["ignore", "pipe", "pipe"],
4790env: options.env ?? process.env,
91+detached: useProcessGroup,
4892});
4993let timedOut = false;
50-const timeout =
94+let stdout = "";
95+let settled = false;
96+let timeout;
97+const finish = (error, value = "") => {
98+if (settled) {
99+return;
100+}
101+settled = true;
102+if (timeout) {
103+clearTimeout(timeout);
104+}
105+ACTIVE_CHILD_KILLERS.delete(killChild);
106+if (forwardedSignalExitCode !== undefined && ACTIVE_CHILD_KILLERS.size === 0) {
107+process.exit(forwardedSignalExitCode);
108+}
109+if (error) {
110+reject(error);
111+return;
112+}
113+resolve(value);
114+};
115+const killChild = (signal) => {
116+if (useProcessGroup && child.pid) {
117+try {
118+process.kill(-child.pid, signal);
119+return;
120+} catch {
121+// The direct child may already have exited; fall back to child.kill.
122+}
123+}
124+child.kill(signal);
125+};
126+ACTIVE_CHILD_KILLERS.add(killChild);
127+timeout =
51128options.timeoutMs === undefined
52129 ? undefined
53130 : setTimeout(() => {
54131timedOut = true;
55-child.kill("SIGTERM");
56-setTimeout(() => child.kill("SIGKILL"), 5_000).unref?.();
132+killChild("SIGTERM");
133+setTimeout(
134+() => killChild("SIGKILL"),
135+options.killAfterMs ?? DEFAULT_TIMEOUT_KILL_AFTER_MS,
136+).unref?.();
57137}, options.timeoutMs);
58138timeout?.unref?.();
59-child.stdout.pipe(process.stderr, { end: false });
139+if (options.captureStdout) {
140+child.stdout.on("data", (chunk) => {
141+stdout += String(chunk);
142+});
143+} else {
144+child.stdout.pipe(process.stderr, { end: false });
145+}
60146child.stderr.pipe(process.stderr, { end: false });
61-child.on("error", reject);
147+child.on("error", (error) => finish(error));
62148child.on("close", (status, signal) => {
63-if (timeout) {
64-clearTimeout(timeout);
65-}
66149if (timedOut) {
67-reject(new Error(`${command} ${args.join(" ")} timed out after ${options.timeoutMs}ms`));
150+finish(new Error(`${command} ${args.join(" ")} timed out after ${options.timeoutMs}ms`));
68151return;
69152}
70153if (status === 0) {
71-resolve();
154+finish(undefined, stdout);
72155return;
73156}
74-reject(new Error(`${command} ${args.join(" ")} failed with ${status ?? signal}`));
157+finish(new Error(`${command} ${args.join(" ")} failed with ${status ?? signal}`));
75158});
76159});
77160}
@@ -90,30 +173,18 @@ export async function buildPackageArtifacts(sourceDir, options = {}) {
90173console.error(`==> ${step.label}`);
91174await runImpl(step.command, step.args, sourceDir, {
92175env: { ...process.env, OPENCLAW_BUILD_ALL_NO_PNPM: "1" },
176+timeoutMs: resolveTimeoutMs(
177+"OPENCLAW_DOCKER_PACKAGE_BUILD_TIMEOUT_MS",
178+DEFAULT_PACKAGE_BUILD_TIMEOUT_MS,
179+),
93180});
94181}
95182}
9618397-async function runCapture(command, args, cwd) {
98-return await new Promise((resolve, reject) => {
99-const child = spawn(command, args, {
100- cwd,
101-stdio: ["ignore", "pipe", "pipe"],
102-});
103-let stdout = "";
104-child.stdout.on("data", (chunk) => {
105-stdout += String(chunk);
106-});
107-child.stderr.pipe(process.stderr, { end: false });
108-child.on("error", reject);
109-child.on("close", (status, signal) => {
110-if (status === 0) {
111-resolve(stdout);
112-return;
113-}
114-reject(new Error(`${command} ${args.join(" ")} failed with ${status ?? signal}`));
115-});
116-});
184+export const runCommandForTest = run;
185+186+async function runCapture(command, args, cwd, options = {}) {
187+return await run(command, args, cwd, { ...options, captureStdout: true });
117188}
118189119190async function newestOpenClawTarball(outputDir, packOutput) {
@@ -163,13 +234,25 @@ async function main() {
163234"const { writePackageDistInventory } = await import('./src/infra/package-dist-inventory.ts'); await writePackageDistInventory(process.cwd());",
164235],
165236sourceDir,
237+{
238+timeoutMs: resolveTimeoutMs(
239+"OPENCLAW_DOCKER_PACKAGE_INVENTORY_TIMEOUT_MS",
240+DEFAULT_PACKAGE_INVENTORY_TIMEOUT_MS,
241+),
242+},
166243);
167244168245console.error("==> Packing OpenClaw package");
169246const packOutput = await runCapture(
170247"npm",
171248["pack", "--silent", "--ignore-scripts", "--pack-destination", outputDir],
172249sourceDir,
250+{
251+timeoutMs: resolveTimeoutMs(
252+"OPENCLAW_DOCKER_PACKAGE_PACK_TIMEOUT_MS",
253+DEFAULT_PACKAGE_PACK_TIMEOUT_MS,
254+),
255+},
173256);
174257let tarball = await newestOpenClawTarball(outputDir, packOutput);
175258@@ -188,7 +271,12 @@ async function main() {
188271"node",
189272[path.join(ROOT_DIR, "scripts/check-openclaw-package-tarball.mjs"), tarball],
190273sourceDir,
191-{ timeoutMs: 5 * 60 * 1000 },
274+{
275+timeoutMs: resolveTimeoutMs(
276+"OPENCLAW_DOCKER_PACKAGE_TARBALL_CHECK_TIMEOUT_MS",
277+DEFAULT_PACKAGE_TARBALL_CHECK_TIMEOUT_MS,
278+),
279+},
192280);
193281console.error(
194282`==> OpenClaw package tarball check finished in ${Math.round((Date.now() - checkStartedAt) / 1000)}s`,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。