




















@@ -1,6 +1,6 @@
11#!/usr/bin/env node
223-import { spawnSync } from "node:child_process";
3+import { spawn, spawnSync } from "node:child_process";
44import fs from "node:fs";
55import os from "node:os";
66import path from "node:path";
@@ -25,6 +25,7 @@ const DEFAULT_CPU_CORE_WARN = 0.9;
2525const DEFAULT_HOT_WALL_WARN_MS = 30_000;
2626const DEFAULT_MAX_RSS_WARN_MB = 1536;
2727const DEFAULT_QA_PLUGIN_CHUNK_SIZE = 12;
28+const COMMAND_OUTPUT_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
2829const ANSI_PATTERN = new RegExp(String.raw`\u001B\[[0-9;]*m`, "gu");
29303031function parseArgs(argv) {
@@ -396,7 +397,7 @@ export function runMeasuredCommand(params) {
396397env: params.env,
397398encoding: "utf8",
398399timeout: params.timeoutMs,
399-maxBuffer: 16 * 1024 * 1024,
400+maxBuffer: params.maxBufferBytes ?? COMMAND_OUTPUT_MAX_BUFFER_BYTES,
400401 ...(mode === "none" ? (params.spawnOptions ?? {}) : {}),
401402});
402403const wallMs = performance.now() - started;
@@ -436,6 +437,134 @@ export function runMeasuredCommand(params) {
436437};
437438}
438439440+export function runMeasuredCommandLive(params) {
441+const { command, args, mode } =
442+params.timeMode === "none"
443+ ? { command: params.command, args: params.args, mode: "none" }
444+ : timeWrapperArgs(params.command, params.args);
445+const started = performance.now();
446+return new Promise((resolve) => {
447+let stdout = "";
448+let stderr = "";
449+let stdoutBytes = 0;
450+let stderrBytes = 0;
451+let stdoutTruncated = false;
452+let stderrTruncated = false;
453+let spawnError = null;
454+let timedOut = false;
455+let settled = false;
456+const maxBufferBytes = params.maxBufferBytes ?? COMMAND_OUTPUT_MAX_BUFFER_BYTES;
457+const child = spawn(command, args, {
458+cwd: params.cwd,
459+env: params.env,
460+ ...(mode === "none" ? (params.spawnOptions ?? {}) : {}),
461+});
462+const appendCapturedOutput = (streamName, chunk) => {
463+const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
464+const currentBytes = streamName === "stdout" ? stdoutBytes : stderrBytes;
465+const alreadyTruncated = streamName === "stdout" ? stdoutTruncated : stderrTruncated;
466+if (alreadyTruncated) {
467+return;
468+}
469+const remainingBytes = maxBufferBytes - currentBytes;
470+const appendTruncation = () => {
471+const message = `\n[${streamName} truncated after ${maxBufferBytes} bytes]\n`;
472+if (streamName === "stdout") {
473+stdout += message;
474+stdoutTruncated = true;
475+} else {
476+stderr += message;
477+stderrTruncated = true;
478+}
479+};
480+if (remainingBytes <= 0) {
481+appendTruncation();
482+return;
483+}
484+const capturedBuffer =
485+buffer.length > remainingBytes ? buffer.subarray(0, remainingBytes) : buffer;
486+if (streamName === "stdout") {
487+stdout += capturedBuffer.toString("utf8");
488+stdoutBytes += capturedBuffer.length;
489+} else {
490+stderr += capturedBuffer.toString("utf8");
491+stderrBytes += capturedBuffer.length;
492+}
493+if (buffer.length > remainingBytes) {
494+appendTruncation();
495+}
496+};
497+const appendOutput = (streamName, chunk) => {
498+const text = chunk.toString("utf8");
499+if (streamName === "stdout") {
500+process.stdout.write(text);
501+} else {
502+process.stderr.write(text);
503+}
504+appendCapturedOutput(streamName, chunk);
505+};
506+child.stdout?.on("data", (chunk) => appendOutput("stdout", chunk));
507+child.stderr?.on("data", (chunk) => appendOutput("stderr", chunk));
508+const timeout =
509+params.timeoutMs > 0
510+ ? setTimeout(() => {
511+timedOut = true;
512+spawnError = {
513+code: "ETIMEDOUT",
514+message: `Command timed out after ${params.timeoutMs}ms`,
515+};
516+child.kill();
517+}, params.timeoutMs)
518+ : null;
519+timeout?.unref?.();
520+const finish = (status, signal) => {
521+if (settled) {
522+return;
523+}
524+settled = true;
525+if (timeout) {
526+clearTimeout(timeout);
527+}
528+const wallMs = performance.now() - started;
529+const finalStatus = status ?? (signal || spawnError ? 1 : 0);
530+const finalStderr = [
531+stderr,
532+spawnError ? `[spawn error] ${spawnError.code ?? "unknown"} ${spawnError.message}` : "",
533+]
534+.filter(Boolean)
535+.join("\n");
536+const diagnosticFailure = detectCommandDiagnosticFailure(stdout, finalStderr);
537+const logPath = writeCommandLog({
538+logDir: params.logDir,
539+label: params.label,
540+command: [params.command, ...params.args],
541+ stdout,
542+stderr: finalStderr,
543+});
544+resolve({
545+label: params.label,
546+phase: params.phase,
547+pluginId: params.pluginId ?? null,
548+status: finalStatus,
549+ diagnosticFailure,
550+signal: signal ?? null,
551+ timedOut,
552+ spawnError,
553+ logPath,
554+ ...parseTimedMetrics(finalStderr, wallMs, mode),
555+});
556+};
557+child.on("error", (error) => {
558+spawnError = {
559+code: typeof error.code === "string" ? error.code : null,
560+message: error.message,
561+};
562+finish(null, null);
563+});
564+child.on("close", (status, signal) => finish(status, signal));
565+});
566+}
567+439568function runPluginLifecycle(params) {
440569for (const plugin of params.plugins) {
441570const commands = [
@@ -564,7 +693,7 @@ async function main() {
564693const prebuildEnv = buildGauntletPrebuildEnv(env, { includePrivateQa: !options.skipQa });
565694const prebuildCommand = createGauntletPrebuildCommand(repoRoot);
566695rows.push(
567-runMeasuredCommand({
696+await runMeasuredCommandLive({
568697cwd: repoRoot,
569698env: prebuildEnv,
570699logDir: path.join(options.outputDir, "logs", "prebuild"),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。