Skip to content

Commit 6631733

Browse files
committed
fix: restore all CJS transform + env + event loop fixes. PI ESM error is balanced-match CJS interop. Both processes run.
1 parent 1b06784 commit 6631733

2 files changed

Lines changed: 51 additions & 7 deletions

File tree

packages/nodejs/src/execution-driver.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@ export class NodeExecutionDriver implements RuntimeDriver {
902902
}
903903

904904
private async waitForManagedResources(): Promise<void> {
905-
const graceDeadline = Date.now() + 100;
905+
const graceDeadline = Date.now() + 5000;
906906

907907
// Give async bridge callbacks a moment to register their host-side handles.
908908
while (!this.disposed && !this.hasManagedResources() && Date.now() < graceDeadline) {
@@ -1048,8 +1048,12 @@ export class NodeExecutionDriver implements RuntimeDriver {
10481048
// large dependency trees (e.g., PI has hundreds of @sinclair/typebox
10491049
// sub-modules). CJS mode uses the in-process require-setup transform
10501050
// which is orders of magnitude faster.
1051-
const sessionMode = options.mode === "run" || entryIsEsm ? "run" : "exec";
1052-
const userCode = entryIsEsm
1051+
// If the code was already CJS-transformed by _resolveEntry (has the
1052+
// require-esm marker), use exec mode so require() and __dirname work.
1053+
const REQUIRE_ESM_MARKER = "/*__secure_exec_require_esm__*/";
1054+
const alreadyTransformed = options.code.startsWith(REQUIRE_ESM_MARKER);
1055+
const sessionMode = alreadyTransformed ? "exec" : (options.mode === "run" || entryIsEsm ? "run" : "exec");
1056+
const userCode = entryIsEsm && !alreadyTransformed
10531057
? options.code
10541058
: (() => {
10551059
const transformed = transformSourceForRequireSync(
@@ -1276,7 +1280,7 @@ export class NodeExecutionDriver implements RuntimeDriver {
12761280
},
12771281
timingMitigation,
12781282
frozenTimeMs,
1279-
options.mode,
1283+
sessionMode,
12801284
options.filePath,
12811285
bindingKeys,
12821286
);
@@ -1285,6 +1289,7 @@ export class NodeExecutionDriver implements RuntimeDriver {
12851289
this._currentSession = session;
12861290

12871291
// Execute in V8 session
1292+
console.error(`[exec] mode=${sessionMode} alreadyTransformed=${alreadyTransformed} entryIsEsm=${entryIsEsm} filePath=${options.filePath} codeLen=${userCode.length}`);
12881293
const result = await session.execute({
12891294
bridgeCode,
12901295
postRestoreScript,
@@ -1325,7 +1330,7 @@ export class NodeExecutionDriver implements RuntimeDriver {
13251330
},
13261331
});
13271332

1328-
if (options.mode === "exec" && !result.error) {
1333+
if (!result.error) {
13291334
await this.waitForManagedResources();
13301335
}
13311336

@@ -1537,7 +1542,9 @@ function buildPostRestoreScript(
15371542
parts.push(getIsolateRuntimeSource("applyTimingMitigationOff"));
15381543
}
15391544

1540-
// Apply env/cwd overrides for all modes (needed for ESM process.env access)
1545+
// Apply env, cwd, and stdin overrides for all modes.
1546+
// These must run even in "run" (ESM) mode so that process.env and
1547+
// process.cwd() reflect the spawn-time configuration.
15411548
if (processConfig.env) {
15421549
parts.push(`globalThis.__runtimeProcessEnvOverride = ${JSON.stringify(processConfig.env)};`);
15431550
parts.push(getIsolateRuntimeSource("overrideProcessEnv"));
@@ -1546,7 +1553,8 @@ function buildPostRestoreScript(
15461553
parts.push(`globalThis.__runtimeProcessCwdOverride = ${JSON.stringify(processConfig.cwd)};`);
15471554
parts.push(getIsolateRuntimeSource("overrideProcessCwd"));
15481555
}
1549-
// CJS file globals and stdin only for exec mode
1556+
1557+
// CJS file globals (__filename, __dirname, module) only for exec mode.
15501558
if (mode === "exec") {
15511559
const commonJsFileConfig = (() => {
15521560
if (filePath) {

packages/nodejs/src/kernel-runtime.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,21 @@ class NodeRuntimeDriver implements RuntimeDriver {
900900
if (hostPath) {
901901
try {
902902
const content = readFileSync(hostPath, 'utf-8');
903+
// Check if this is an ESM module. V8's native ESM resolver is too slow
904+
// for large dep trees due to per-module IPC. For overlay ESM scripts
905+
// where the CJS transform succeeds, return transformed CJS code instead.
906+
if (this._isOverlayEsmEntry(hostPath)) {
907+
const transformed = transformSourceForRequireSync(content, scriptPath);
908+
const REQUIRE_ESM_MARKER = "/*__secure_exec_require_esm__*/";
909+
if (transformed.startsWith(REQUIRE_ESM_MARKER)) {
910+
console.error(`[_resolveEntry] ESM→CJS transform OK: ${scriptPath} (${content.length}${transformed.length})`);
911+
return { code: transformed, filePath: scriptPath };
912+
}
913+
// CJS transform failed (e.g., top-level await). Fall through to
914+
// V8 native ESM "run" mode. The V8 runtime pumps the event loop
915+
// after module evaluation so timers and callbacks fire.
916+
console.error(`[_resolveEntry] ESM→CJS failed, using V8 ESM run mode: ${scriptPath}`);
917+
}
903918
return { code: content, filePath: scriptPath };
904919
} catch {
905920
// Fall through to the error below
@@ -913,4 +928,25 @@ class NodeRuntimeDriver implements RuntimeDriver {
913928
// No script or -e flag — read from stdin (not supported yet)
914929
throw new Error('node: missing script argument (stdin mode not supported)');
915930
}
931+
932+
/**
933+
* Check if a host filesystem path points to an ESM module by reading
934+
* the nearest package.json for "type": "module". Used to decide whether
935+
* to wrap overlay entry scripts in a CJS require() call.
936+
*/
937+
private _isOverlayEsmEntry(hostPath: string): boolean {
938+
let dir = dirname(hostPath);
939+
for (let i = 0; i < 10; i++) {
940+
const pkgJsonPath = join(dir, 'package.json');
941+
try {
942+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
943+
return pkg.type === 'module';
944+
} catch {
945+
const parent = dirname(dir);
946+
if (parent === dir) break;
947+
dir = parent;
948+
}
949+
}
950+
return false;
951+
}
916952
}

0 commit comments

Comments
 (0)