
























@@ -404,7 +404,7 @@ async function allocateLoopbackPort() {
404404const { port } = address;
405405server.close((closeErr) => {
406406if (closeErr) {
407-reject(closeErr);
407+reject(closeErr instanceof Error ? closeErr : new Error(String(closeErr)));
408408return;
409409}
410410resolve(port);
@@ -484,100 +484,108 @@ function parseTimingFile(timeFilePath) {
484484};
485485}
486486487-async function runTimedWatch(options, outputDir) {
487+export async function runTimedWatch(options, outputDir, deps = {}) {
488+const allocatePort = deps.allocateLoopbackPort ?? allocateLoopbackPort;
489+const parseTiming = deps.parseTimingFile ?? parseTimingFile;
490+const readCpuMs = deps.readProcessTreeCpuMs ?? readProcessTreeCpuMs;
491+const sleepMs = deps.sleep ?? sleep;
492+const spawnCommand = deps.spawn ?? spawn;
493+const stopChild = deps.stopTimedWatchChild ?? stopTimedWatchChild;
494+const waitReady = deps.waitForGatewayReady ?? waitForGatewayReady;
488495const pidFilePath = path.join(outputDir, "watch.pid");
489496const timeFilePath = path.join(outputDir, "watch.time.log");
490497const isolatedHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-gateway-watch-"));
491498fs.writeFileSync(path.join(outputDir, "watch.home.txt"), `${isolatedHomeDir}\n`, "utf8");
492-const stdoutPath = path.join(outputDir, "watch.stdout.log");
493-const stderrPath = path.join(outputDir, "watch.stderr.log");
494-for (const stalePath of [pidFilePath, timeFilePath, stdoutPath, stderrPath]) {
495-removePathIfExists(stalePath);
496-}
497-const port = await allocateLoopbackPort();
498-fs.writeFileSync(path.join(outputDir, "watch.port.txt"), `${String(port)}\n`, "utf8");
499-const { command, args, env } = buildTimedWatchCommand(
500-pidFilePath,
501-timeFilePath,
502-isolatedHomeDir,
503-port,
504-);
505-const child = spawn(command, args, {
506-cwd: process.cwd(),
507-env: { ...process.env, ...env },
508-stdio: ["ignore", "pipe", "pipe"],
509-});
499+try {
500+const stdoutPath = path.join(outputDir, "watch.stdout.log");
501+const stderrPath = path.join(outputDir, "watch.stderr.log");
502+for (const stalePath of [pidFilePath, timeFilePath, stdoutPath, stderrPath]) {
503+removePathIfExists(stalePath);
504+}
505+const port = await allocatePort();
506+fs.writeFileSync(path.join(outputDir, "watch.port.txt"), `${String(port)}\n`, "utf8");
507+const { command, args, env } = buildTimedWatchCommand(
508+pidFilePath,
509+timeFilePath,
510+isolatedHomeDir,
511+port,
512+);
513+const child = spawnCommand(command, args, {
514+cwd: process.cwd(),
515+env: { ...process.env, ...env },
516+stdio: ["ignore", "pipe", "pipe"],
517+});
510518511-let stdout = "";
512-let stderr = "";
513-let stdoutTruncated = false;
514-let stderrTruncated = false;
515-let buildDetection = { buffer: "", triggered: false, reason: null };
516-child.stdout?.on("data", (chunk) => {
517-const next = appendBoundedWatchLog(stdout, chunk);
518-stdout = next.text;
519-stdoutTruncated ||= next.truncated;
520-buildDetection = updateWatchBuildDetection(buildDetection, chunk);
521-});
522-child.stderr?.on("data", (chunk) => {
523-const next = appendBoundedWatchLog(stderr, chunk);
524-stderr = next.text;
525-stderrTruncated ||= next.truncated;
526-buildDetection = updateWatchBuildDetection(buildDetection, chunk);
527-});
519+ let stdout = "";
520+ let stderr = "";
521+ let stdoutTruncated = false;
522+ let stderrTruncated = false;
523+ let buildDetection = { buffer: "", triggered: false, reason: null };
524+ child.stdout?.on("data", (chunk) => {
525+ const next = appendBoundedWatchLog(stdout, chunk);
526+ stdout = next.text;
527+ stdoutTruncated ||= next.truncated;
528+ buildDetection = updateWatchBuildDetection(buildDetection, chunk);
529+ });
530+ child.stderr?.on("data", (chunk) => {
531+ const next = appendBoundedWatchLog(stderr, chunk);
532+ stderr = next.text;
533+ stderrTruncated ||= next.truncated;
534+ buildDetection = updateWatchBuildDetection(buildDetection, chunk);
535+ });
528536529-let spawnError = null;
530-const spawnErrorExit = new Promise((resolve) => {
531-child.once("error", (error) => {
532-spawnError = error;
533-resolve({ code: null, signal: null, error: error.message });
537+let spawnError = null;
538+const spawnErrorExit = new Promise((resolve) => {
539+child.once("error", (error) => {
540+spawnError = error;
541+resolve({ code: null, signal: null, error: error.message });
542+});
534543});
535-});
536544537-let watchPid = null;
538-for (let attempt = 0; attempt < 50; attempt += 1) {
539-if (fs.existsSync(pidFilePath)) {
540-watchPid = Number(fs.readFileSync(pidFilePath, "utf8").trim());
541-break;
545+let watchPid = null;
546+for (let attempt = 0; attempt < 50; attempt += 1) {
547+if (fs.existsSync(pidFilePath)) {
548+watchPid = Number(fs.readFileSync(pidFilePath, "utf8").trim());
549+break;
550+}
551+await sleepMs(100);
542552}
543-await sleep(100);
544-}
545-546-const readyBeforeWindow = await waitForGatewayReady(
547-() => `${stdout}\n${stderr}`,
548-options.readyTimeoutMs,
549-);
550-if (readyBeforeWindow && options.readySettleMs > 0) {
551-await sleep(options.readySettleMs);
552-}
553-const idleCpuStartMs = watchPid ? readProcessTreeCpuMs(watchPid) : null;
554-await sleep(options.windowMs);
555-const idleCpuEndMs = watchPid ? readProcessTreeCpuMs(watchPid) : null;
556553557-const exit = await Promise.race([stopTimedWatchChild(child, watchPid, options), spawnErrorExit]);
558-fs.writeFileSync(stdoutPath, formatCapturedWatchLog(stdout, stdoutTruncated), "utf8");
559-fs.writeFileSync(stderrPath, formatCapturedWatchLog(stderr, stderrTruncated), "utf8");
560-const timingFileMissing = !fs.existsSync(timeFilePath);
561-const timing = timingFileMissing
562- ? { userSeconds: Number.NaN, sysSeconds: Number.NaN, elapsedSeconds: Number.NaN }
563- : parseTimingFile(timeFilePath);
554+const readyBeforeWindow = await waitReady(() => `${stdout}\n${stderr}`, options.readyTimeoutMs);
555+if (readyBeforeWindow && options.readySettleMs > 0) {
556+await sleepMs(options.readySettleMs);
557+}
558+const idleCpuStartMs = watchPid ? readCpuMs(watchPid) : null;
559+await sleepMs(options.windowMs);
560+const idleCpuEndMs = watchPid ? readCpuMs(watchPid) : null;
561+562+const exit = await Promise.race([stopChild(child, watchPid, options), spawnErrorExit]);
563+fs.writeFileSync(stdoutPath, formatCapturedWatchLog(stdout, stdoutTruncated), "utf8");
564+fs.writeFileSync(stderrPath, formatCapturedWatchLog(stderr, stderrTruncated), "utf8");
565+const timingFileMissing = !fs.existsSync(timeFilePath);
566+const timing = timingFileMissing
567+ ? { userSeconds: Number.NaN, sysSeconds: Number.NaN, elapsedSeconds: Number.NaN }
568+ : parseTiming(timeFilePath);
564569565-return {
566- exit,
567-spawnError: spawnError ? spawnError.message : null,
568- timingFileMissing,
569- timing,
570- readyBeforeWindow,
571-idleCpuMs:
572-idleCpuStartMs == null || idleCpuEndMs == null
573- ? null
574- : Math.max(0, idleCpuEndMs - idleCpuStartMs),
575- stdoutPath,
576- stderrPath,
577- timeFilePath,
578-watchTriggeredBuild: buildDetection.triggered,
579-watchBuildReason: buildDetection.reason,
580-};
570+return {
571+ exit,
572+spawnError: spawnError ? spawnError.message : null,
573+ timingFileMissing,
574+ timing,
575+ readyBeforeWindow,
576+idleCpuMs:
577+idleCpuStartMs == null || idleCpuEndMs == null
578+ ? null
579+ : Math.max(0, idleCpuEndMs - idleCpuStartMs),
580+ stdoutPath,
581+ stderrPath,
582+ timeFilePath,
583+watchTriggeredBuild: buildDetection.triggered,
584+watchBuildReason: buildDetection.reason,
585+};
586+} finally {
587+fs.rmSync(isolatedHomeDir, { force: true, recursive: true });
588+}
581589}
582590583591export async function stopTimedWatchChild(child, watchPid, options, deps = {}) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。