






















11#!/usr/bin/env -S pnpm tsx
22import { spawn } from "node:child_process";
3-import { readFileSync } from "node:fs";
3+import { appendFileSync, readFileSync, writeFileSync } from "node:fs";
44import { readFile, rm, writeFile } from "node:fs/promises";
55import path from "node:path";
66import { pathToFileURL } from "node:url";
@@ -64,6 +64,7 @@ interface Job {
6464interface UpdateJobContext {
6565append(chunk: string | Uint8Array): void;
6666logPath: string;
67+signal: AbortSignal;
6768}
68696970interface NpmUpdateSummary {
@@ -575,19 +576,19 @@ class NpmUpdateSmoke {
575576 startedAt,
576577};
577578job.promise = (async () => {
578-let log = "";
579+writeFileSync(logPath, "", "utf8");
579580const append = (chunk: string | Uint8Array): void => {
580581const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
581-log += text;
582+appendFileSync(logPath, text, "utf8");
582583this.noteJobOutput(job, text);
583584};
584585return await runTimedUpdateJob({
585586 append,
586587 label,
587-run: () => fn({ append, logPath }),
588+run: ({ signal }) => fn({ append, logPath, signal }),
588589timeoutDescription: `${updateTimeoutSeconds}s plus cleanup backstop`,
589590timeoutMs: updateTimeoutSeconds * 1000 + updateCleanupBackstopMs,
590-writeLog: () => writeFile(logPath, log, "utf8"),
591+writeLog: async () => undefined,
591592});
592593})().finally(() => {
593594job.durationMs = Date.now() - job.startedAt;
@@ -898,6 +899,7 @@ class NpmUpdateSmoke {
898899return await new Promise((resolve, reject) => {
899900const child = spawn(command, args, {
900901cwd: repoRoot,
902+detached: process.platform !== "win32",
901903env: process.env,
902904stdio: ["ignore", "pipe", "pipe"],
903905});
@@ -906,16 +908,54 @@ class NpmUpdateSmoke {
906908child.stderr.on("data", (chunk: Buffer) => ctx.append(chunk));
907909908910let timedOut = false;
909-const timer = setTimeout(() => {
911+let killTimer: NodeJS.Timeout | undefined;
912+const signalChild = (signal: NodeJS.Signals): void => {
913+if (!child.pid) {
914+return;
915+}
916+try {
917+if (process.platform === "win32") {
918+child.kill(signal);
919+} else {
920+process.kill(-child.pid, signal);
921+}
922+} catch {
923+child.kill(signal);
924+}
925+};
926+const abort = (): void => {
927+if (timedOut) {
928+return;
929+}
910930timedOut = true;
911-child.kill("SIGTERM");
912-setTimeout(() => child.kill("SIGKILL"), 2_000).unref();
931+signalChild("SIGTERM");
932+killTimer = setTimeout(() => signalChild("SIGKILL"), 2_000);
933+killTimer.unref();
934+};
935+if (ctx.signal.aborted) {
936+abort();
937+} else {
938+ctx.signal.addEventListener("abort", abort, { once: true });
939+}
940+const timer = setTimeout(() => {
941+abort();
913942}, timeoutMs);
914943915-child.on("error", reject);
944+child.on("error", (error) => {
945+ctx.signal.removeEventListener("abort", abort);
946+if (killTimer) {
947+clearTimeout(killTimer);
948+}
949+reject(error);
950+});
916951child.on("close", (code, signal) => {
952+ctx.signal.removeEventListener("abort", abort);
917953clearTimeout(timer);
954+if (killTimer) {
955+clearTimeout(killTimer);
956+}
918957if (timedOut) {
958+signalChild("SIGKILL");
919959resolve(124);
920960return;
921961}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。