























@@ -1,4 +1,6 @@
1-import { spawnSync } from "node:child_process";
1+import { spawn } from "node:child_process";
2+3+const DEFAULT_TIMEOUT_KILL_GRACE_MS = 30_000;
2435const usage = () => {
46console.error("Usage: assertions.mjs <run-with-timeout|assert-image-providers> [...]");
@@ -7,16 +9,72 @@ const usage = () => {
79810const [mode, ...args] = process.argv.slice(2);
91110-if (mode === "run-with-timeout") {
11-const [timeoutMs, command, ...commandArgs] = args;
12-const timeout = Number(timeoutMs);
13-if (!Number.isFinite(timeout) || timeout <= 0 || !command) {
14-usage();
12+const parsePositiveNumber = (value, label) => {
13+const parsed = Number(value);
14+if (!Number.isFinite(parsed) || parsed <= 0) {
15+throw new Error(`${label} must be a positive number`);
16+}
17+return parsed;
18+};
19+20+const signalChild = (child, signal) => {
21+if (!child.pid) {
22+return;
1523}
24+try {
25+if (process.platform === "win32") {
26+child.kill(signal);
27+return;
28+}
29+process.kill(-child.pid, signal);
30+} catch (error) {
31+if (error?.code !== "ESRCH") {
32+throw error;
33+}
34+}
35+};
36+37+const runWithTimeout = async (timeout, command, commandArgs) => {
38+const killGrace = parsePositiveNumber(
39+process.env.OPENCLAW_BUN_GLOBAL_SMOKE_TIMEOUT_KILL_GRACE_MS ??
40+String(DEFAULT_TIMEOUT_KILL_GRACE_MS),
41+"OPENCLAW_BUN_GLOBAL_SMOKE_TIMEOUT_KILL_GRACE_MS",
42+);
43+const child = spawn(command, commandArgs, {
44+detached: process.platform !== "win32",
45+env: process.env,
46+stdio: ["ignore", "pipe", "pipe"],
47+});
48+let timedOut = false;
49+let killTimer;
165017-const result = spawnSync(command, commandArgs, { encoding: "utf8", env: process.env, timeout });
18-process.stdout.write(result.stdout ?? "");
19-process.stderr.write(result.stderr ?? "");
51+child.stdout.setEncoding("utf8");
52+child.stderr.setEncoding("utf8");
53+child.stdout.on("data", (chunk) => process.stdout.write(chunk));
54+child.stderr.on("data", (chunk) => process.stderr.write(chunk));
55+56+const timeoutTimer = setTimeout(() => {
57+timedOut = true;
58+signalChild(child, "SIGTERM");
59+killTimer = setTimeout(() => signalChild(child, "SIGKILL"), killGrace);
60+killTimer.unref();
61+}, timeout);
62+timeoutTimer.unref();
63+64+let spawnError;
65+child.on("error", (error) => {
66+spawnError = error;
67+});
68+const result = await new Promise((resolve) => {
69+child.on("close", (status, signal) => resolve({ error: spawnError, signal, status }));
70+});
71+72+clearTimeout(timeoutTimer);
73+clearTimeout(killTimer);
74+if (timedOut) {
75+console.error(`command timed out after ${timeout}ms: ${command}`);
76+process.exit(1);
77+}
2078if (result.error) {
2179console.error(`command failed: ${command}: ${result.error.message}`);
2280process.exit(1);
@@ -26,6 +84,20 @@ if (mode === "run-with-timeout") {
2684process.exit(1);
2785}
2886process.exit(result.status ?? 0);
87+};
88+89+if (mode === "run-with-timeout") {
90+const [timeoutMs, command, ...commandArgs] = args;
91+if (!command) {
92+usage();
93+}
94+let timeout;
95+try {
96+timeout = parsePositiveNumber(timeoutMs, "timeoutMs");
97+} catch {
98+usage();
99+}
100+await runWithTimeout(timeout, command, commandArgs);
29101}
3010231103if (mode === "assert-image-providers") {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。