diff --git a/extensions/ext-bundler-esbuild/src/esbuild-bundler.test.ts b/extensions/ext-bundler-esbuild/src/esbuild-bundler.test.ts index f97f01256a..d827d9e52b 100644 --- a/extensions/ext-bundler-esbuild/src/esbuild-bundler.test.ts +++ b/extensions/ext-bundler-esbuild/src/esbuild-bundler.test.ts @@ -9,7 +9,7 @@ import { assertEquals, assertExists, assertRejects, assertStringIncludes } from import { describe, it } from "@std/testing/bdd"; import { createRequire } from "node:module"; -import { EsbuildBundler } from "./esbuild-bundler.ts"; +import { EsbuildBundler, isLiveEsbuildServiceProcess } from "./esbuild-bundler.ts"; import { rebuildContextWithSignal } from "./context-build-lifecycle.ts"; const childProcess = createRequire(import.meta.url)("node:child_process") as { @@ -90,6 +90,19 @@ describe("EsbuildBundler.transform", () => { }); }); +describe("esbuild service lifecycle", () => { + it("treats a Node-compatible child with unset exit fields as live", () => { + assertEquals( + isLiveEsbuildServiceProcess({ + killed: false, + exitCode: undefined, + signalCode: undefined, + } as Pick, "killed" | "exitCode" | "signalCode">), + true, + ); + }); +}); + describe("abortable esbuild context lifecycle", () => { it("cancels active work and preserves the primary abort over cleanup failures", async () => { const controller = new AbortController(); diff --git a/extensions/ext-bundler-esbuild/src/esbuild-bundler.ts b/extensions/ext-bundler-esbuild/src/esbuild-bundler.ts index e4f56e90ea..1092a02dca 100644 --- a/extensions/ext-bundler-esbuild/src/esbuild-bundler.ts +++ b/extensions/ext-bundler-esbuild/src/esbuild-bundler.ts @@ -214,10 +214,22 @@ function isEsbuildServiceSpawn(spawnArgs: unknown[]): boolean { args.includes("--ping"); } +/** + * Keep lifecycle tracking tolerant of Node-compatible child-process shims. + * + * Native Node represents an active child with `null` exit fields. Some + * compatible runtimes leave those fields undefined until the child exits. + */ +export function isLiveEsbuildServiceProcess( + child: Pick, +): boolean { + return !child.killed && + (child.exitCode === null || child.exitCode === undefined) && + (child.signalCode === null || child.signalCode === undefined); +} + function isLiveService(service: EsbuildService): boolean { - return !service.child.killed && - service.child.exitCode === null && - service.child.signalCode === null; + return isLiveEsbuildServiceProcess(service.child); } function invokeEsbuild>(operation: () => T): T {