@scelar/nodepod 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/__sw__.js +642 -642
  2. package/dist/__tests__/bench/integration.bench.d.ts +1 -0
  3. package/dist/__tests__/bench/memory-volume.bench.d.ts +1 -0
  4. package/dist/__tests__/bench/polyfills.bench.d.ts +1 -0
  5. package/dist/__tests__/bench/script-engine.bench.d.ts +1 -0
  6. package/dist/__tests__/bench/shell.bench.d.ts +1 -0
  7. package/dist/__tests__/bench/syntax-transforms.bench.d.ts +1 -0
  8. package/dist/__tests__/bench/version-resolver.bench.d.ts +1 -0
  9. package/dist/__tests__/buffer.test.d.ts +1 -0
  10. package/dist/__tests__/byte-encoding.test.d.ts +1 -0
  11. package/dist/__tests__/digest.test.d.ts +1 -0
  12. package/dist/__tests__/events.test.d.ts +1 -0
  13. package/dist/__tests__/memory-volume.test.d.ts +1 -0
  14. package/dist/__tests__/path.test.d.ts +1 -0
  15. package/dist/__tests__/process.test.d.ts +1 -0
  16. package/dist/__tests__/script-engine.test.d.ts +1 -0
  17. package/dist/__tests__/shell-builtins.test.d.ts +1 -0
  18. package/dist/__tests__/shell-interpreter.test.d.ts +1 -0
  19. package/dist/__tests__/shell-parser.test.d.ts +1 -0
  20. package/dist/__tests__/stream.test.d.ts +1 -0
  21. package/dist/__tests__/syntax-transforms.test.d.ts +1 -0
  22. package/dist/__tests__/version-resolver.test.d.ts +1 -0
  23. package/dist/{child_process-Dopvyd-E.js → child_process-53fMkug_.js} +4 -4
  24. package/dist/child_process-53fMkug_.js.map +1 -0
  25. package/dist/{child_process-B38qoN6R.cjs → child_process-lxSKECHq.cjs} +5 -5
  26. package/dist/child_process-lxSKECHq.cjs.map +1 -0
  27. package/dist/{index--Qr8LVpQ.js → index-B8lyh_ti.js} +1316 -559
  28. package/dist/index-B8lyh_ti.js.map +1 -0
  29. package/dist/{index-cnitc68U.cjs → index-C-TQIrdG.cjs} +1422 -612
  30. package/dist/index-C-TQIrdG.cjs.map +1 -0
  31. package/dist/index.cjs +1 -1
  32. package/dist/index.mjs +1 -1
  33. package/dist/memory-volume.d.ts +1 -1
  34. package/dist/polyfills/wasi.d.ts +45 -4
  35. package/dist/script-engine.d.ts +2 -0
  36. package/dist/sdk/nodepod.d.ts +4 -3
  37. package/dist/sdk/types.d.ts +6 -0
  38. package/dist/syntax-transforms.d.ts +1 -0
  39. package/dist/threading/process-manager.d.ts +1 -1
  40. package/dist/threading/worker-protocol.d.ts +1 -1
  41. package/package.json +5 -3
  42. package/src/__tests__/bench/integration.bench.ts +117 -0
  43. package/src/__tests__/bench/memory-volume.bench.ts +115 -0
  44. package/src/__tests__/bench/polyfills.bench.ts +147 -0
  45. package/src/__tests__/bench/script-engine.bench.ts +104 -0
  46. package/src/__tests__/bench/shell.bench.ts +101 -0
  47. package/src/__tests__/bench/syntax-transforms.bench.ts +82 -0
  48. package/src/__tests__/bench/version-resolver.bench.ts +95 -0
  49. package/src/__tests__/buffer.test.ts +273 -0
  50. package/src/__tests__/byte-encoding.test.ts +98 -0
  51. package/src/__tests__/digest.test.ts +44 -0
  52. package/src/__tests__/events.test.ts +245 -0
  53. package/src/__tests__/memory-volume.test.ts +443 -0
  54. package/src/__tests__/path.test.ts +181 -0
  55. package/src/__tests__/process.test.ts +129 -0
  56. package/src/__tests__/script-engine.test.ts +229 -0
  57. package/src/__tests__/shell-builtins.test.ts +357 -0
  58. package/src/__tests__/shell-interpreter.test.ts +157 -0
  59. package/src/__tests__/shell-parser.test.ts +204 -0
  60. package/src/__tests__/stream.test.ts +142 -0
  61. package/src/__tests__/syntax-transforms.test.ts +158 -0
  62. package/src/__tests__/version-resolver.test.ts +184 -0
  63. package/src/constants/cdn-urls.ts +18 -18
  64. package/src/helpers/byte-encoding.ts +51 -39
  65. package/src/index.ts +192 -192
  66. package/src/memory-volume.ts +968 -941
  67. package/src/module-transformer.ts +368 -368
  68. package/src/packages/installer.ts +396 -396
  69. package/src/packages/version-resolver.ts +12 -2
  70. package/src/polyfills/buffer.ts +633 -628
  71. package/src/polyfills/child_process.ts +2288 -2288
  72. package/src/polyfills/esbuild.ts +854 -854
  73. package/src/polyfills/events.ts +282 -276
  74. package/src/polyfills/fs.ts +2888 -2888
  75. package/src/polyfills/http.ts +1450 -1449
  76. package/src/polyfills/process.ts +721 -690
  77. package/src/polyfills/readline.ts +692 -692
  78. package/src/polyfills/stream.ts +1620 -1620
  79. package/src/polyfills/tty.ts +71 -71
  80. package/src/polyfills/wasi.ts +1284 -22
  81. package/src/request-proxy.ts +716 -716
  82. package/src/script-engine.ts +465 -146
  83. package/src/sdk/nodepod.ts +525 -509
  84. package/src/sdk/types.ts +7 -0
  85. package/src/syntax-transforms.ts +543 -561
  86. package/src/threading/offload-worker.ts +383 -383
  87. package/src/threading/offload.ts +271 -271
  88. package/src/threading/process-manager.ts +956 -956
  89. package/src/threading/process-worker-entry.ts +858 -854
  90. package/src/threading/worker-protocol.ts +1 -1
  91. package/dist/child_process-B38qoN6R.cjs.map +0 -1
  92. package/dist/child_process-Dopvyd-E.js.map +0 -1
  93. package/dist/index--Qr8LVpQ.js.map +0 -1
  94. package/dist/index-cnitc68U.cjs.map +0 -1
@@ -117,10 +117,15 @@ import {
117
117
  } from "resolve.exports";
118
118
  import {
119
119
  esmToCjs,
120
- stripTopLevelAwait,
120
+ collectEsmCjsPatches,
121
121
  hasTopLevelAwait,
122
+ stripTopLevelAwait,
122
123
  } from "./syntax-transforms";
123
- import { getCachedModule, precompileWasm, compileWasmInWorker } from "./helpers/wasm-cache";
124
+ import {
125
+ getCachedModule,
126
+ precompileWasm,
127
+ compileWasmInWorker,
128
+ } from "./helpers/wasm-cache";
124
129
  import * as acorn from "acorn";
125
130
 
126
131
  // ── TypeScript type stripper ──
@@ -223,11 +228,8 @@ function stripTypeScript(source: string): string {
223
228
 
224
229
  function isTypeScriptFile(filename: string): boolean {
225
230
  const clean = filename.split("?")[0];
226
- if (
227
- clean.endsWith(".ts") ||
228
- clean.endsWith(".tsx") ||
229
- clean.endsWith(".mts")
230
- ) return true;
231
+ if (clean.endsWith(".ts") || clean.endsWith(".tsx") || clean.endsWith(".mts"))
232
+ return true;
231
233
  // Vite SFC query params like ?type=script&lang.ts
232
234
  if (filename.includes("lang.ts") || filename.includes("lang=ts")) return true;
233
235
  return false;
@@ -244,9 +246,11 @@ function isCSSFile(filename: string): boolean {
244
246
  clean.endsWith(".styl") ||
245
247
  clean.endsWith(".stylus") ||
246
248
  clean.endsWith(".postcss")
247
- ) return true;
249
+ )
250
+ return true;
248
251
  if (filename.includes("type=style")) return true;
249
- if (/lang[.=](?:css|scss|sass|less|styl|stylus|postcss)/.test(filename)) return true;
252
+ if (/lang[.=](?:css|scss|sass|less|styl|stylus|postcss)/.test(filename))
253
+ return true;
250
254
  return false;
251
255
  }
252
256
 
@@ -254,7 +258,9 @@ function isCSSFile(filename: string): boolean {
254
258
  function looksLikeTypeScript(source: string): boolean {
255
259
  return (
256
260
  /\b(?:interface|type)\s+\w+/.test(source) ||
257
- /:\s*(?:string|number|boolean|void|any|never|unknown|Record|Array|Promise)\b/.test(source) ||
261
+ /:\s*(?:string|number|boolean|void|any|never|unknown|Record|Array|Promise)\b/.test(
262
+ source,
263
+ ) ||
258
264
  /(?:as\s+(?:string|number|boolean|any|const)\b)/.test(source)
259
265
  );
260
266
  }
@@ -263,7 +269,7 @@ function looksLikeTypeScript(source: string): boolean {
263
269
  function traverseAst(node: any, visitor: (n: any) => void): void {
264
270
  if (!node || typeof node !== "object") return;
265
271
  if (typeof node.type === "string") visitor(node);
266
- for (const key of Object.keys(node)) {
272
+ for (const key in node) {
267
273
  if (
268
274
  key === "type" ||
269
275
  key === "start" ||
@@ -275,7 +281,8 @@ function traverseAst(node: any, visitor: (n: any) => void): void {
275
281
  const val = node[key];
276
282
  if (val && typeof val === "object") {
277
283
  if (Array.isArray(val)) {
278
- for (const item of val) {
284
+ for (let i = 0; i < val.length; i++) {
285
+ const item = val[i];
279
286
  if (item && typeof item === "object" && typeof item.type === "string")
280
287
  traverseAst(item, visitor);
281
288
  }
@@ -297,7 +304,12 @@ function convertModuleSyntax(source: string, filePath: string): string {
297
304
  try {
298
305
  return convertViaAst(source, filePath);
299
306
  } catch (astErr) {
300
- _nativeConsole.warn("[convertModuleSyntax] AST parse failed for", filePath, "falling back to regex:", astErr instanceof Error ? astErr.message : String(astErr));
307
+ _nativeConsole.warn(
308
+ "[convertModuleSyntax] AST parse failed for",
309
+ filePath,
310
+ "falling back to regex:",
311
+ astErr instanceof Error ? astErr.message : String(astErr),
312
+ );
301
313
  return convertViaRegex(source, filePath);
302
314
  }
303
315
  }
@@ -309,6 +321,7 @@ function convertViaAst(source: string, filePath: string): string {
309
321
  }) as any;
310
322
  const patches: Array<[number, number, string]> = [];
311
323
 
324
+ // collect import.meta and import() patches
312
325
  traverseAst(ast, (node: any) => {
313
326
  if (
314
327
  node.type === "MetaProperty" &&
@@ -327,18 +340,21 @@ function convertViaAst(source: string, filePath: string): string {
327
340
  );
328
341
  const hasExportDecl = ast.body.some((n: any) => n.type?.startsWith("Export"));
329
342
 
343
+ // collect ESM→CJS patches from the same AST (no second parse)
344
+ if (hasImportDecl || hasExportDecl) {
345
+ collectEsmCjsPatches(ast, source, patches);
346
+ }
347
+
348
+ // apply all patches in one pass
330
349
  let output = source;
331
- patches.sort((a, b) => b[0] - a[0]);
350
+ patches.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
332
351
  for (const [s, e, r] of patches)
333
352
  output = output.slice(0, s) + r + output.slice(e);
334
353
 
335
- if (hasImportDecl || hasExportDecl) {
336
- output = esmToCjs(output);
337
- if (hasExportDecl) {
338
- output =
339
- 'Object.defineProperty(exports, "__esModule", { value: true });\n' +
340
- output;
341
- }
354
+ if (hasExportDecl) {
355
+ output =
356
+ 'Object.defineProperty(exports, "__esModule", { value: true });\n' +
357
+ output;
342
358
  }
343
359
 
344
360
  // .mjs files with `const require = createRequire(...)` hit TDZ after esmToCjs
@@ -350,10 +366,7 @@ function convertViaAst(source: string, filePath: string): string {
350
366
  // Demote `const/let require =` to plain assignment to avoid TDZ with esmToCjs-generated require() calls
351
367
  function demoteLexicalRequire(code: string): string {
352
368
  if (!/\b(?:const|let)\s+require\s*=/.test(code)) return code;
353
- return code.replace(
354
- /\b(const|let)\s+(require)\s*=/g,
355
- "require =",
356
- );
369
+ return code.replace(/\b(const|let)\s+(require)\s*=/g, "require =");
357
370
  }
358
371
 
359
372
  // Builds the IIFE wrapper that sandboxes user code with shimmed globals
@@ -626,7 +639,9 @@ function createSyncPromise(): typeof Promise {
626
639
  res(onRejected(innerErr) as TResult2),
627
640
  ) as any;
628
641
  }
629
- return new SyncPromise<TResult2>((_, rej) => rej(innerErr)) as any;
642
+ return new SyncPromise<TResult2>((_, rej) =>
643
+ rej(innerErr),
644
+ ) as any;
630
645
  }
631
646
  return NativePromise.resolve(result).then(null, onRejected) as any;
632
647
  }
@@ -664,8 +679,7 @@ function createSyncPromise(): typeof Promise {
664
679
 
665
680
  // instanceof must work for native Promises too since we inject SyncPromise as `Promise`
666
681
  Object.defineProperty(SyncPromise, Symbol.hasInstance, {
667
- value: (instance: any) =>
668
- instance instanceof NativePromise,
682
+ value: (instance: any) => instance instanceof NativePromise,
669
683
  configurable: true,
670
684
  });
671
685
 
@@ -698,11 +712,13 @@ function createSyncPromise(): typeof Promise {
698
712
  allSync = false;
699
713
  break;
700
714
  }
701
- } else if (
702
- v && typeof v === "object" && typeof v.then === "function"
703
- ) {
704
- let probed = false, pVal: any;
705
- v.then((x: any) => { pVal = x; probed = true; });
715
+ } else if (v && typeof v === "object" && typeof v.then === "function") {
716
+ let probed = false,
717
+ pVal: any;
718
+ v.then((x: any) => {
719
+ pVal = x;
720
+ probed = true;
721
+ });
706
722
  if (probed) {
707
723
  results[i] = pVal;
708
724
  } else {
@@ -736,9 +752,7 @@ function createSyncPromise(): typeof Promise {
736
752
  allSync = false;
737
753
  break;
738
754
  }
739
- } else if (
740
- v && typeof v === "object" && typeof v.then === "function"
741
- ) {
755
+ } else if (v && typeof v === "object" && typeof v.then === "function") {
742
756
  allSync = false;
743
757
  break;
744
758
  } else {
@@ -780,9 +794,7 @@ function createSyncPromise(): typeof Promise {
780
794
  if (v instanceof SyncPromise && (v as any)._syncResolved) {
781
795
  return new SyncPromise((res: any) => res((v as any)._syncValue));
782
796
  }
783
- if (
784
- !(v && typeof v === "object" && typeof v.then === "function")
785
- ) {
797
+ if (!(v && typeof v === "object" && typeof v.then === "function")) {
786
798
  return new SyncPromise((res: any) => res(v));
787
799
  }
788
800
  }
@@ -824,12 +836,16 @@ function makeDynamicLoader(
824
836
  ) {
825
837
  return new SyncThenable(loaded);
826
838
  }
827
- const spread = loaded && (typeof loaded === "object" || typeof loaded === "function")
828
- ? Object.getOwnPropertyNames(loaded as object).reduce((acc, key) => {
829
- acc[key] = (loaded as any)[key];
830
- return acc;
831
- }, {} as Record<string, unknown>)
832
- : {};
839
+ const spread =
840
+ loaded && (typeof loaded === "object" || typeof loaded === "function")
841
+ ? Object.getOwnPropertyNames(loaded as object).reduce(
842
+ (acc, key) => {
843
+ acc[key] = (loaded as any)[key];
844
+ return acc;
845
+ },
846
+ {} as Record<string, unknown>,
847
+ )
848
+ : {};
833
849
  return new SyncThenable({
834
850
  default: loaded,
835
851
  ...spread,
@@ -845,6 +861,7 @@ export interface ModuleRecord {
845
861
  loaded: boolean;
846
862
  children: ModuleRecord[];
847
863
  paths: string[];
864
+ parent: ModuleRecord | null;
848
865
  }
849
866
 
850
867
  export interface EngineOptions {
@@ -867,6 +884,7 @@ export interface ResolverFn {
867
884
  cache: Record<string, ModuleRecord>;
868
885
  extensions: Record<string, unknown>;
869
886
  main: ModuleRecord | null;
887
+ _ownerRecord?: ModuleRecord;
870
888
  }
871
889
 
872
890
  // Mutable copy so packages can monkey-patch frozen polyfill namespaces
@@ -931,8 +949,8 @@ const CORE_MODULES: Record<string, unknown> = {
931
949
  sea: seaPolyfill,
932
950
  sqlite: sqlitePolyfill,
933
951
  quic: quicPolyfill,
934
- lightningcss: lightningcssPolyfill,
935
- "@tailwindcss/oxide": tailwindOxidePolyfill,
952
+ // lightningcss: lightningcssPolyfill,
953
+ // "@tailwindcss/oxide": tailwindOxidePolyfill,
936
954
  sys: helpersPolyfill,
937
955
  "util/types": helpersPolyfill.types,
938
956
  "path/posix": pathPolyfill,
@@ -1100,8 +1118,13 @@ function buildResolver(
1100
1118
  codeCache?: Map<string, string>,
1101
1119
  deAsyncImports = false,
1102
1120
  ): ResolverFn {
1103
- const resolveCache = new Map<string, string | null>();
1104
- const manifestCache = new Map<string, PackageManifest | null>();
1121
+ // Shared across all resolvers avoids re-resolving the same paths/manifests per module
1122
+ const resolveCache: Map<string, string | null> =
1123
+ (cache as any).__resolveCache ??
1124
+ ((cache as any).__resolveCache = new Map());
1125
+ const manifestCache: Map<string, PackageManifest | null> =
1126
+ (cache as any).__manifestCache ??
1127
+ ((cache as any).__manifestCache = new Map());
1105
1128
  // Shared across all resolvers — deduplicates same-version packages from nested node_modules
1106
1129
  const _pkgIdentityMap: Record<string, string> =
1107
1130
  (cache as any).__pkgIdentityMap ?? ((cache as any).__pkgIdentityMap = {});
@@ -1223,13 +1246,19 @@ function buildResolver(
1223
1246
  sync: noop,
1224
1247
  transform: async (filename: string, code: string, opts?: any) => {
1225
1248
  code = applyDefineReplacements(code, opts?.define);
1226
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1249
+ if (
1250
+ !isCSSFile(filename) &&
1251
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1252
+ )
1227
1253
  code = stripTypeScript(code);
1228
1254
  return { code, map: null, errors: [], warnings: [] };
1229
1255
  },
1230
1256
  transformSync: (filename: string, code: string, opts?: any) => {
1231
1257
  code = applyDefineReplacements(code, opts?.define);
1232
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1258
+ if (
1259
+ !isCSSFile(filename) &&
1260
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1261
+ )
1233
1262
  code = stripTypeScript(code);
1234
1263
  return { code, map: null, errors: [], warnings: [] };
1235
1264
  },
@@ -1239,7 +1268,10 @@ function buildResolver(
1239
1268
  opts?: any,
1240
1269
  ) => {
1241
1270
  code = applyDefineReplacements(code, opts?.define);
1242
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1271
+ if (
1272
+ !isCSSFile(filename) &&
1273
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1274
+ )
1243
1275
  code = stripTypeScript(code);
1244
1276
  return {
1245
1277
  code,
@@ -1256,7 +1288,10 @@ function buildResolver(
1256
1288
  opts?: any,
1257
1289
  ) => {
1258
1290
  code = applyDefineReplacements(code, opts?.define);
1259
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1291
+ if (
1292
+ !isCSSFile(filename) &&
1293
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1294
+ )
1260
1295
  code = stripTypeScript(code);
1261
1296
  return {
1262
1297
  code,
@@ -1292,12 +1327,18 @@ function buildResolver(
1292
1327
  warnings: [],
1293
1328
  }),
1294
1329
  moduleRunnerTransform: async (filename: string, code: string) => {
1295
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1330
+ if (
1331
+ !isCSSFile(filename) &&
1332
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1333
+ )
1296
1334
  code = stripTypeScript(code);
1297
1335
  return { code, map: null, deps: [], dynamicDeps: [], errors: [] };
1298
1336
  },
1299
1337
  moduleRunnerTransformSync: (filename: string, code: string) => {
1300
- if (!isCSSFile(filename) && (isTypeScriptFile(filename) || looksLikeTypeScript(code)))
1338
+ if (
1339
+ !isCSSFile(filename) &&
1340
+ (isTypeScriptFile(filename) || looksLikeTypeScript(code))
1341
+ )
1301
1342
  code = stripTypeScript(code);
1302
1343
  return { code, map: null, deps: [], dynamicDeps: [], errors: [] };
1303
1344
  },
@@ -1393,7 +1434,9 @@ function buildResolver(
1393
1434
  )
1394
1435
  specs.push(node.source.value);
1395
1436
  }
1396
- } catch { /* fallback to regex */ }
1437
+ } catch {
1438
+ /* fallback to regex */
1439
+ }
1397
1440
  const dynRe = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1398
1441
  let m;
1399
1442
  while ((m = dynRe.exec(code)) !== null) specs.push(m[1]);
@@ -1405,8 +1448,14 @@ function buildResolver(
1405
1448
  try {
1406
1449
  await p.buildStart(mockCtx, {});
1407
1450
  } catch (hookErr) {
1408
- const msg = hookErr instanceof Error ? hookErr.message : String(hookErr);
1409
- _nativeConsole.warn("[rolldown scan] buildStart hook error:", msg);
1451
+ const msg =
1452
+ hookErr instanceof Error
1453
+ ? hookErr.message
1454
+ : String(hookErr);
1455
+ _nativeConsole.warn(
1456
+ "[rolldown scan] buildStart hook error:",
1457
+ msg,
1458
+ );
1410
1459
  }
1411
1460
  }
1412
1461
  }
@@ -1435,8 +1484,15 @@ function buildResolver(
1435
1484
  if (r && typeof r === "object" && r.code)
1436
1485
  return r.code;
1437
1486
  } catch (loadErr) {
1438
- const msg = loadErr instanceof Error ? loadErr.message : String(loadErr);
1439
- _nativeConsole.warn("[rolldown scan] load hook error:", filePath, msg);
1487
+ const msg =
1488
+ loadErr instanceof Error
1489
+ ? loadErr.message
1490
+ : String(loadErr);
1491
+ _nativeConsole.warn(
1492
+ "[rolldown scan] load hook error:",
1493
+ filePath,
1494
+ msg,
1495
+ );
1440
1496
  }
1441
1497
  }
1442
1498
  }
@@ -1476,8 +1532,15 @@ function buildResolver(
1476
1532
  custom: undefined,
1477
1533
  });
1478
1534
  } catch (resolveErr) {
1479
- const msg = resolveErr instanceof Error ? resolveErr.message : String(resolveErr);
1480
- _nativeConsole.warn("[rolldown scan] resolveId hook error:", spec, msg);
1535
+ const msg =
1536
+ resolveErr instanceof Error
1537
+ ? resolveErr.message
1538
+ : String(resolveErr);
1539
+ _nativeConsole.warn(
1540
+ "[rolldown scan] resolveId hook error:",
1541
+ spec,
1542
+ msg,
1543
+ );
1481
1544
  }
1482
1545
  }
1483
1546
  }
@@ -1516,14 +1579,24 @@ function buildResolver(
1516
1579
  try {
1517
1580
  await p.buildEnd(mockCtx);
1518
1581
  } catch (endErr) {
1519
- const msg = endErr instanceof Error ? endErr.message : String(endErr);
1520
- _nativeConsole.warn("[rolldown scan] buildEnd hook error:", msg);
1582
+ const msg =
1583
+ endErr instanceof Error
1584
+ ? endErr.message
1585
+ : String(endErr);
1586
+ _nativeConsole.warn(
1587
+ "[rolldown scan] buildEnd hook error:",
1588
+ msg,
1589
+ );
1521
1590
  }
1522
1591
  }
1523
1592
  }
1524
1593
  } catch (scanErr) {
1525
- const msg = scanErr instanceof Error ? scanErr.message : String(scanErr);
1526
- const detail = scanErr instanceof Error && scanErr.stack ? `\n${scanErr.stack}` : "";
1594
+ const msg =
1595
+ scanErr instanceof Error ? scanErr.message : String(scanErr);
1596
+ const detail =
1597
+ scanErr instanceof Error && scanErr.stack
1598
+ ? `\n${scanErr.stack}`
1599
+ : "";
1527
1600
  _nativeConsole.warn("[rolldown scan]", msg);
1528
1601
  }
1529
1602
  }
@@ -1592,11 +1665,14 @@ function buildResolver(
1592
1665
  const parts = afterNm.split("/");
1593
1666
  const scoped = parts[0].startsWith("@");
1594
1667
  const pkgName = scoped ? parts.slice(0, 2).join("/") : parts[0];
1595
- const pkgRoot = origPath.slice(0, nmIdx) + "/node_modules/" + pkgName;
1668
+ const pkgRoot =
1669
+ origPath.slice(0, nmIdx) + "/node_modules/" + pkgName;
1596
1670
  const pkgJsonPath = pkgRoot + "/package.json";
1597
1671
  try {
1598
1672
  if (!vol.existsSync(pkgJsonPath)) return null;
1599
- const manifest = JSON.parse(vol.readFileSync(pkgJsonPath, "utf8"));
1673
+ const manifest = JSON.parse(
1674
+ vol.readFileSync(pkgJsonPath, "utf8"),
1675
+ );
1600
1676
  for (const conds of [
1601
1677
  { browser: true, import: true },
1602
1678
  { import: true },
@@ -1605,11 +1681,16 @@ function buildResolver(
1605
1681
  const resolved = resolveExports(manifest, ".", conds);
1606
1682
  if (resolved?.length) {
1607
1683
  const full = pathPolyfill.join(pkgRoot, resolved[0]);
1608
- if (vol.existsSync(full) && full !== origPath) return full;
1684
+ if (vol.existsSync(full) && full !== origPath)
1685
+ return full;
1609
1686
  }
1610
- } catch { /* try next */ }
1687
+ } catch {
1688
+ /* try next */
1689
+ }
1611
1690
  }
1612
- } catch { /* can't re-resolve */ }
1691
+ } catch {
1692
+ /* can't re-resolve */
1693
+ }
1613
1694
  return null;
1614
1695
  };
1615
1696
 
@@ -1635,7 +1716,10 @@ function buildResolver(
1635
1716
  target: "esnext",
1636
1717
  logLevel: "warning",
1637
1718
  });
1638
- return result.outputFiles?.[0]?.text ?? vol.readFileSync(ep, "utf8");
1719
+ return (
1720
+ result.outputFiles?.[0]?.text ??
1721
+ vol.readFileSync(ep, "utf8")
1722
+ );
1639
1723
  };
1640
1724
 
1641
1725
  try {
@@ -1658,8 +1742,15 @@ function buildResolver(
1658
1742
  }
1659
1743
  }
1660
1744
  } catch (buildErr) {
1661
- const buildMsg = buildErr instanceof Error ? buildErr.message : String(buildErr);
1662
- _nativeConsole.warn("[rolldown bundle] esbuild failed for", entryPath, buildMsg);
1745
+ const buildMsg =
1746
+ buildErr instanceof Error
1747
+ ? buildErr.message
1748
+ : String(buildErr);
1749
+ _nativeConsole.warn(
1750
+ "[rolldown bundle] esbuild failed for",
1751
+ entryPath,
1752
+ buildMsg,
1753
+ );
1663
1754
  try {
1664
1755
  code = vol.readFileSync(entryPath, "utf8");
1665
1756
  } catch {
@@ -1759,10 +1850,13 @@ function buildResolver(
1759
1850
  if (resolved) {
1760
1851
  return { id: resolved + querySuffix };
1761
1852
  }
1762
- } catch (_e) { /* not found */ }
1853
+ } catch (_e) {
1854
+ /* not found */
1855
+ }
1763
1856
 
1764
1857
  if (candidate.startsWith("/")) {
1765
- if (vol.existsSync(candidate)) return { id: candidate + querySuffix };
1858
+ if (vol.existsSync(candidate))
1859
+ return { id: candidate + querySuffix };
1766
1860
  const cwd =
1767
1861
  (typeof globalThis !== "undefined" &&
1768
1862
  (globalThis as any).process?.cwd?.()) ||
@@ -1771,7 +1865,8 @@ function buildResolver(
1771
1865
  const abs = cwd + candidate;
1772
1866
  if (vol.existsSync(abs)) return { id: abs + querySuffix };
1773
1867
  for (const ext of RESOLVE_EXTENSIONS) {
1774
- if (vol.existsSync(abs + ext)) return { id: abs + ext + querySuffix };
1868
+ if (vol.existsSync(abs + ext))
1869
+ return { id: abs + ext + querySuffix };
1775
1870
  }
1776
1871
  }
1777
1872
  }
@@ -2068,7 +2163,9 @@ function buildResolver(
2068
2163
  const cached = resolveCache.get(cacheKey);
2069
2164
  if (cached !== undefined) {
2070
2165
  if (cached === null) {
2071
- const e = new Error(`Cannot find module '${id}'`) as Error & { code: string };
2166
+ const e = new Error(`Cannot find module '${id}'`) as Error & {
2167
+ code: string;
2168
+ };
2072
2169
  e.code = "MODULE_NOT_FOUND";
2073
2170
  throw e;
2074
2171
  }
@@ -2113,7 +2210,9 @@ function buildResolver(
2113
2210
  }
2114
2211
 
2115
2212
  resolveCache.set(cacheKey, null);
2116
- const e = new Error(`Cannot find module '${id}' from '${fromDir}'`) as Error & { code: string };
2213
+ const e = new Error(
2214
+ `Cannot find module '${id}' from '${fromDir}'`,
2215
+ ) as Error & { code: string };
2117
2216
  e.code = "MODULE_NOT_FOUND";
2118
2217
  throw e;
2119
2218
  }
@@ -2162,7 +2261,8 @@ function buildResolver(
2162
2261
  for (let ai = 0; ai < execArgv.length; ai++) {
2163
2262
  const arg = execArgv[ai];
2164
2263
  if (arg === "--conditions" || arg === "-C") {
2165
- if (ai + 1 < execArgv.length) extraConditions.push(execArgv[++ai]);
2264
+ if (ai + 1 < execArgv.length)
2265
+ extraConditions.push(execArgv[++ai]);
2166
2266
  } else if (arg.startsWith("--conditions=")) {
2167
2267
  extraConditions.push(arg.slice("--conditions=".length));
2168
2268
  }
@@ -2170,9 +2270,8 @@ function buildResolver(
2170
2270
  const nodeOpts = proc.env?.NODE_OPTIONS || "";
2171
2271
  const condMatch = nodeOpts.matchAll(/(?:--conditions[= ]|-C )(\S+)/g);
2172
2272
  for (const m of condMatch) extraConditions.push(m[1]);
2173
- const condExtra = extraConditions.length > 0
2174
- ? { conditions: extraConditions }
2175
- : {};
2273
+ const condExtra =
2274
+ extraConditions.length > 0 ? { conditions: extraConditions } : {};
2176
2275
 
2177
2276
  const baseSets: Record<string, unknown>[] = preferEsm
2178
2277
  ? [
@@ -2264,12 +2363,17 @@ function buildResolver(
2264
2363
  }
2265
2364
 
2266
2365
  resolveCache.set(cacheKey, null);
2267
- const e = new Error(`Cannot find module '${id}' from '${fromDir}'`) as Error & { code: string };
2366
+ const e = new Error(
2367
+ `Cannot find module '${id}' from '${fromDir}'`,
2368
+ ) as Error & { code: string };
2268
2369
  e.code = "MODULE_NOT_FOUND";
2269
2370
  throw e;
2270
2371
  };
2271
2372
 
2272
- const loadModule = (resolved: string): ModuleRecord => {
2373
+ const loadModule = (
2374
+ resolved: string,
2375
+ parentRecord?: ModuleRecord,
2376
+ ): ModuleRecord => {
2273
2377
  if (cache[resolved]) return cache[resolved];
2274
2378
 
2275
2379
  // Package dedup: reuse first instance of name@version:path to prevent
@@ -2278,12 +2382,17 @@ function buildResolver(
2278
2382
  if (nmIdx !== -1) {
2279
2383
  const afterNm = resolved.slice(nmIdx + "/node_modules/".length);
2280
2384
  const parts = afterNm.split("/");
2281
- const pkgName = parts[0].startsWith("@") ? parts[0] + "/" + parts[1] : parts[0];
2385
+ const pkgName = parts[0].startsWith("@")
2386
+ ? parts[0] + "/" + parts[1]
2387
+ : parts[0];
2282
2388
  const pkgDir = resolved.slice(0, nmIdx) + "/node_modules/" + pkgName;
2283
2389
  try {
2284
- const pkgJson = JSON.parse(vol.readFileSync(pkgDir + "/package.json", "utf8"));
2390
+ const pkgJson = JSON.parse(
2391
+ vol.readFileSync(pkgDir + "/package.json", "utf8"),
2392
+ );
2285
2393
  // Include file path so different subpath exports (svelte vs svelte/compiler) aren't deduped
2286
- const identity = pkgName + "@" + (pkgJson.version || "0.0.0") + ":" + afterNm;
2394
+ const identity =
2395
+ pkgName + "@" + (pkgJson.version || "0.0.0") + ":" + afterNm;
2287
2396
  if (!_pkgIdentityMap[identity]) {
2288
2397
  _pkgIdentityMap[identity] = resolved;
2289
2398
  } else if (_pkgIdentityMap[identity] !== resolved) {
@@ -2294,7 +2403,9 @@ function buildResolver(
2294
2403
  return cache[canonical];
2295
2404
  }
2296
2405
  }
2297
- } catch { /* no package.json */ }
2406
+ } catch {
2407
+ /* no package.json */
2408
+ }
2298
2409
  }
2299
2410
 
2300
2411
  const _loadDepth = ((globalThis as any).__loadModuleDepth ?? 0) + 1;
@@ -2307,7 +2418,9 @@ function buildResolver(
2307
2418
  loaded: false,
2308
2419
  children: [],
2309
2420
  paths: [],
2421
+ parent: parentRecord ?? null,
2310
2422
  };
2423
+ if (parentRecord) parentRecord.children.push(record);
2311
2424
 
2312
2425
  cache[resolved] = record;
2313
2426
 
@@ -2321,11 +2434,15 @@ function buildResolver(
2321
2434
  return record;
2322
2435
  }
2323
2436
 
2324
- // Native addons can't run in browser — return empty module for fallback
2437
+ // Native .node addons cannot run in the browser — throw so that
2438
+ // callers (e.g. napi-rs packages) fall back to their WASM build.
2325
2439
  if (resolved.endsWith(".node")) {
2326
- record.exports = {};
2327
- record.loaded = true;
2328
- return record;
2440
+ delete cache[resolved];
2441
+ const e = new Error(
2442
+ `Cannot load native addon '${resolved}' — native .node binaries are not supported in the browser`,
2443
+ ) as Error & { code: string };
2444
+ e.code = "ERR_DLOPEN_FAILED";
2445
+ throw e;
2329
2446
  }
2330
2447
 
2331
2448
  const rawSource = vol.readFileSync(resolved, "utf8");
@@ -2353,12 +2470,19 @@ function buildResolver(
2353
2470
  const cjsPatches: Array<[number, number, string]> = [];
2354
2471
  const walkCjs = (node: any) => {
2355
2472
  if (!node || typeof node !== "object") return;
2356
- if (Array.isArray(node)) { for (const c of node) walkCjs(c); return; }
2473
+ if (Array.isArray(node)) {
2474
+ for (const c of node) walkCjs(c);
2475
+ return;
2476
+ }
2357
2477
  if (typeof node.type !== "string") return;
2358
2478
  if (node.type === "ImportExpression") {
2359
2479
  cjsPatches.push([node.start, node.start + 6, "__asyncLoad"]);
2360
2480
  }
2361
- if (node.type === "MetaProperty" && node.meta?.name === "import" && node.property?.name === "meta") {
2481
+ if (
2482
+ node.type === "MetaProperty" &&
2483
+ node.meta?.name === "import" &&
2484
+ node.property?.name === "meta"
2485
+ ) {
2362
2486
  cjsPatches.push([
2363
2487
  node.start,
2364
2488
  node.end,
@@ -2373,22 +2497,31 @@ function buildResolver(
2373
2497
  };
2374
2498
  walkCjs(cjsAst);
2375
2499
  if (cjsPatches.length > 0) {
2376
- cjsPatches.sort((a, b) => b[0] - a[0]);
2500
+ cjsPatches.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
2377
2501
  for (const [start, end, replacement] of cjsPatches) {
2378
- processedCode = processedCode.slice(0, start) + replacement + processedCode.slice(end);
2502
+ processedCode =
2503
+ processedCode.slice(0, start) +
2504
+ replacement +
2505
+ processedCode.slice(end);
2379
2506
  }
2380
2507
  }
2381
- } catch { /* can't parse — leave untransformed */ }
2508
+ } catch {
2509
+ /* can't parse — leave untransformed */
2510
+ }
2382
2511
  } else {
2383
2512
  processedCode = convertModuleSyntax(processedCode, resolved);
2384
2513
  }
2385
2514
  codeCache?.set(codeCacheKey, processedCode);
2386
2515
  }
2387
2516
 
2388
- // "full" de-async strips async from all functions so cross-module calls work with __syncAwait
2389
- // "topLevelOnly" preserves genuine async behavior for non-TLA modules
2390
- const moduleHasTLA = resolved.endsWith(".cjs") ? false : hasTopLevelAwait(processedCode);
2517
+ const isCjs = resolved.endsWith(".cjs");
2518
+ const moduleHasTLA = !isCjs && hasTopLevelAwait(processedCode);
2391
2519
  const useFullDeAsync = deAsyncImports || moduleHasTLA;
2520
+ if (!isCjs)
2521
+ processedCode = stripTopLevelAwait(
2522
+ processedCode,
2523
+ deAsyncImports ? "full" : "topLevelOnly",
2524
+ );
2392
2525
 
2393
2526
  const childResolver = buildResolver(
2394
2527
  vol,
@@ -2401,18 +2534,12 @@ function buildResolver(
2401
2534
  useFullDeAsync,
2402
2535
  );
2403
2536
  childResolver.cache = cache;
2537
+ childResolver._ownerRecord = record;
2404
2538
 
2405
2539
  const wrappedConsole = wrapConsole(opts.onConsole);
2406
2540
 
2407
2541
  try {
2408
2542
  const metaUrl = "file://" + resolved;
2409
- // CJS files: skip — would corrupt await inside async functions
2410
- if (!resolved.endsWith(".cjs")) {
2411
- processedCode = stripTopLevelAwait(
2412
- processedCode,
2413
- useFullDeAsync ? "full" : "topLevelOnly",
2414
- );
2415
- }
2416
2543
  const wrapper = buildModuleWrapper(processedCode);
2417
2544
 
2418
2545
  let fn;
@@ -2425,7 +2552,9 @@ function buildResolver(
2425
2552
  }
2426
2553
 
2427
2554
  {
2428
- const srcMap = (globalThis as any).__dbgSrcMap || ((globalThis as any).__dbgSrcMap = new Map());
2555
+ const srcMap =
2556
+ (globalThis as any).__dbgSrcMap ||
2557
+ ((globalThis as any).__dbgSrcMap = new Map());
2429
2558
  srcMap.set(resolved, wrapper);
2430
2559
  }
2431
2560
 
@@ -2514,7 +2643,7 @@ function buildResolver(
2514
2643
  if (typeof id !== "string") {
2515
2644
  // Match real Node.js error: TypeError with ERR_INVALID_ARG_TYPE code
2516
2645
  const err: any = new TypeError(
2517
- `The "id" argument must be of type string. Received ${id === null ? "null" : typeof id}`
2646
+ `The "id" argument must be of type string. Received ${id === null ? "null" : typeof id}`,
2518
2647
  );
2519
2648
  err.code = "ERR_INVALID_ARG_TYPE";
2520
2649
  throw err;
@@ -2528,7 +2657,11 @@ function buildResolver(
2528
2657
  if (id === "worker_threads") {
2529
2658
  if (opts.workerThreadsOverride) {
2530
2659
  const base = CORE_MODULES["worker_threads"];
2531
- const override = Object.assign(Object.create(null), base, opts.workerThreadsOverride);
2660
+ const override = Object.assign(
2661
+ Object.create(null),
2662
+ base,
2663
+ opts.workerThreadsOverride,
2664
+ );
2532
2665
  override.default = override;
2533
2666
  return override;
2534
2667
  }
@@ -2551,7 +2684,16 @@ function buildResolver(
2551
2684
  fromPath = fromPath.slice(1);
2552
2685
  }
2553
2686
  const childDir = pathPolyfill.dirname(fromPath);
2554
- const child = buildResolver(vol, fsBridge, proc, childDir, cache, opts, codeCache, deAsyncImports);
2687
+ const child = buildResolver(
2688
+ vol,
2689
+ fsBridge,
2690
+ proc,
2691
+ childDir,
2692
+ cache,
2693
+ opts,
2694
+ codeCache,
2695
+ deAsyncImports,
2696
+ );
2555
2697
  child.cache = cache;
2556
2698
  return child;
2557
2699
  };
@@ -2565,9 +2707,7 @@ function buildResolver(
2565
2707
  options?: any,
2566
2708
  ) => {
2567
2709
  if (typeof request !== "string") {
2568
- const err: any = new Error(
2569
- `Cannot find module '${request}'`,
2570
- );
2710
+ const err: any = new Error(`Cannot find module '${request}'`);
2571
2711
  err.code = "MODULE_NOT_FOUND";
2572
2712
  throw err;
2573
2713
  }
@@ -2598,14 +2738,16 @@ function buildResolver(
2598
2738
  try {
2599
2739
  return resolveId(request, fromDir);
2600
2740
  } catch {
2601
- const err: any = new Error(
2602
- `Cannot find module '${request}'`,
2603
- );
2741
+ const err: any = new Error(`Cannot find module '${request}'`);
2604
2742
  err.code = "MODULE_NOT_FOUND";
2605
2743
  throw err;
2606
2744
  }
2607
2745
  };
2608
- PerEngineModule._load = (request: string, parent?: any, isMain?: boolean) => {
2746
+ PerEngineModule._load = (
2747
+ request: string,
2748
+ parent?: any,
2749
+ isMain?: boolean,
2750
+ ) => {
2609
2751
  try {
2610
2752
  return resolver(request);
2611
2753
  } catch {
@@ -2633,10 +2775,81 @@ function buildResolver(
2633
2775
  }
2634
2776
  if (CORE_MODULES[id]) return CORE_MODULES[id];
2635
2777
 
2636
- const resolved = resolveId(id, baseDir);
2778
+ let resolved: string;
2779
+ try {
2780
+ resolved = resolveId(id, baseDir);
2781
+ } catch (resolveErr: any) {
2782
+ if (
2783
+ resolveErr?.code === "MODULE_NOT_FOUND" &&
2784
+ !id.startsWith("./") &&
2785
+ !id.startsWith("../")
2786
+ ) {
2787
+ // --- Determine WASM alternative package name(s) to try ---
2788
+ const wasmAlts: string[] = [];
2789
+
2790
+ if (id.includes("wasm32-wasi")) {
2791
+ // Explicit wasm32-wasi package — auto-install as-is
2792
+ wasmAlts.push(id);
2793
+ } else {
2794
+ // Platform-specific native package pattern:
2795
+ // {name}-{platform}-{arch}[-{abi}]
2796
+ // e.g. lightningcss-linux-x64-gnu, @pkg/core-darwin-arm64
2797
+ const platformRe =
2798
+ /^(.+)-(darwin|linux|win32|freebsd|android|sunos)-(x64|x86|arm64|arm|ia32|s390x|ppc64|mips64el|riscv64)(-[a-z]+)?$/;
2799
+ const m = id.match(platformRe);
2800
+ if (m) {
2801
+ const baseName = m[1]; // e.g. "lightningcss" or "@scope/pkg"
2802
+ wasmAlts.push(baseName + "-wasm32-wasi");
2803
+ wasmAlts.push(baseName + "-wasm");
2804
+ }
2805
+ }
2806
+
2807
+ // Try resolving any of the WASM alternatives (already installed)
2808
+ for (const alt of wasmAlts) {
2809
+ resolveCache.delete(`${baseDir}|${alt}`);
2810
+ try {
2811
+ const altResolved = resolveId(alt, baseDir);
2812
+ const altRec = loadModule(altResolved, resolver._ownerRecord);
2813
+ return altRec.exports;
2814
+ } catch {
2815
+ // not installed yet
2816
+ }
2817
+ }
2818
+
2819
+ }
2820
+ throw resolveErr;
2821
+ }
2637
2822
  if (CORE_MODULES[resolved]) return CORE_MODULES[resolved];
2638
2823
 
2639
- const rec = loadModule(resolved);
2824
+ let rec: ModuleRecord;
2825
+ try {
2826
+ rec = loadModule(resolved, resolver._ownerRecord);
2827
+ } catch (loadErr: any) {
2828
+ // When a bare module fails to load (e.g. native binding not found inside
2829
+ // the module), try a WASM drop-in replacement: {name}-wasm or {name}-wasm32-wasi
2830
+ if (
2831
+ !id.startsWith("./") &&
2832
+ !id.startsWith("../") &&
2833
+ !id.startsWith("/") &&
2834
+ (loadErr?.code === "MODULE_NOT_FOUND" ||
2835
+ loadErr?.code === "ERR_DLOPEN_FAILED" ||
2836
+ (loadErr?.message &&
2837
+ /cannot\s+(find|load)\s+(module|native)/i.test(loadErr.message)))
2838
+ ) {
2839
+ const wasmAlts = [id + "-wasm32-wasi", id + "-wasm"];
2840
+ for (const alt of wasmAlts) {
2841
+ try {
2842
+ resolveCache.delete(`${baseDir}|${alt}`);
2843
+ const altResolved = resolveId(alt, baseDir);
2844
+ const altRec = loadModule(altResolved, resolver._ownerRecord);
2845
+ return altRec.exports;
2846
+ } catch {
2847
+ // not available
2848
+ }
2849
+ }
2850
+ }
2851
+ throw loadErr;
2852
+ }
2640
2853
  // Proxy for async WASM — reads from rec.exports at access time so
2641
2854
  // reassigned module.exports is picked up after compilation finishes
2642
2855
  if ((rec as any).__wasmReady) {
@@ -2728,6 +2941,62 @@ export class ScriptEngine {
2728
2941
 
2729
2942
  (globalThis as any).__nodepodVolume = vol;
2730
2943
 
2944
+ // Intercept fetch() for file:// URLs — serve from VFS instead of network.
2945
+ // napi-rs wasm32-wasi packages use fetch(new URL('file.wasm', import.meta.url))
2946
+ // which browsers block. This patches fetch to read from the in-memory filesystem.
2947
+ if (!(globalThis.fetch as any).__nodepodPatched) {
2948
+ const origFetch = globalThis.fetch.bind(globalThis);
2949
+ const patchedFetch = (
2950
+ input: RequestInfo | URL,
2951
+ init?: RequestInit,
2952
+ ): Promise<Response> => {
2953
+ let url: string | undefined;
2954
+ if (typeof input === "string") url = input;
2955
+ else if (input instanceof URL) url = input.href;
2956
+ else if (input instanceof Request) url = input.url;
2957
+
2958
+ if (url?.startsWith("file://")) {
2959
+ // Convert file:// URL to VFS path
2960
+ let vfsPath: string;
2961
+ try {
2962
+ vfsPath = decodeURIComponent(new URL(url).pathname);
2963
+ } catch {
2964
+ vfsPath = decodeURIComponent(url.slice(7));
2965
+ }
2966
+ const v = (globalThis as any).__nodepodVolume as MemoryVolume | undefined;
2967
+ if (v) {
2968
+ try {
2969
+ const data = v.readFileSync(vfsPath);
2970
+ const bytes =
2971
+ data instanceof Uint8Array
2972
+ ? data
2973
+ : new TextEncoder().encode(String(data));
2974
+ const contentType = vfsPath.endsWith(".wasm")
2975
+ ? "application/wasm"
2976
+ : "application/octet-stream";
2977
+ return Promise.resolve(
2978
+ new Response(bytes.buffer.slice(
2979
+ bytes.byteOffset,
2980
+ bytes.byteOffset + bytes.byteLength,
2981
+ ) as ArrayBuffer, {
2982
+ status: 200,
2983
+ headers: { "Content-Type": contentType },
2984
+ }),
2985
+ );
2986
+ } catch {
2987
+ return Promise.resolve(
2988
+ new Response("Not found", { status: 404 }),
2989
+ );
2990
+ }
2991
+ }
2992
+ }
2993
+ return origFetch(input, init);
2994
+ };
2995
+ (globalThis as any).fetch = Object.assign(patchedFetch, {
2996
+ __nodepodPatched: true,
2997
+ });
2998
+ }
2999
+
2731
3000
  if (typeof globalThis.setImmediate === "undefined") {
2732
3001
  (globalThis as any).setImmediate = (
2733
3002
  fn: (...a: unknown[]) => void,
@@ -2759,7 +3028,9 @@ export class ScriptEngine {
2759
3028
  const cached2 = getCachedModule(bytes);
2760
3029
  if (cached2) return cached2;
2761
3030
  const compilePromise = compileWasmInWorker(
2762
- bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes as Uint8Array,
3031
+ bytes instanceof ArrayBuffer
3032
+ ? new Uint8Array(bytes)
3033
+ : (bytes as Uint8Array),
2763
3034
  );
2764
3035
  (globalThis as any).__wasmCompilePromise = compilePromise;
2765
3036
  }
@@ -2786,11 +3057,23 @@ export class ScriptEngine {
2786
3057
  const obj = {
2787
3058
  _id: id,
2788
3059
  _ref: true,
2789
- ref() { obj._ref = true; return obj; },
2790
- unref() { obj._ref = false; return obj; },
2791
- hasRef() { return obj._ref; },
2792
- refresh() { return obj; },
2793
- [Symbol.toPrimitive]() { return id; },
3060
+ ref() {
3061
+ obj._ref = true;
3062
+ return obj;
3063
+ },
3064
+ unref() {
3065
+ obj._ref = false;
3066
+ return obj;
3067
+ },
3068
+ hasRef() {
3069
+ return obj._ref;
3070
+ },
3071
+ refresh() {
3072
+ return obj;
3073
+ },
3074
+ [Symbol.toPrimitive]() {
3075
+ return id;
3076
+ },
2794
3077
  };
2795
3078
  return obj;
2796
3079
  };
@@ -2799,11 +3082,23 @@ export class ScriptEngine {
2799
3082
  const obj = {
2800
3083
  _id: id,
2801
3084
  _ref: true,
2802
- ref() { obj._ref = true; return obj; },
2803
- unref() { obj._ref = false; return obj; },
2804
- hasRef() { return obj._ref; },
2805
- refresh() { return obj; },
2806
- [Symbol.toPrimitive]() { return id; },
3085
+ ref() {
3086
+ obj._ref = true;
3087
+ return obj;
3088
+ },
3089
+ unref() {
3090
+ obj._ref = false;
3091
+ return obj;
3092
+ },
3093
+ hasRef() {
3094
+ return obj._ref;
3095
+ },
3096
+ refresh() {
3097
+ return obj;
3098
+ },
3099
+ [Symbol.toPrimitive]() {
3100
+ return id;
3101
+ },
2807
3102
  };
2808
3103
  return obj;
2809
3104
  };
@@ -3045,7 +3340,14 @@ export class ScriptEngine {
3045
3340
  filename: string = "/index.js",
3046
3341
  ): { exports: unknown; module: ModuleRecord } {
3047
3342
  const dir = pathPolyfill.dirname(filename);
3048
- this.vol.writeFileSync(filename, code);
3343
+ // Only write when the content differs to avoid triggering file watchers
3344
+ // (e.g. nodemon/chokidar) with a no-op write that causes restart loops.
3345
+ try {
3346
+ const existing = this.vol.readFileSync(filename, "utf8");
3347
+ if (existing !== code) this.vol.writeFileSync(filename, code);
3348
+ } catch {
3349
+ this.vol.writeFileSync(filename, code);
3350
+ }
3049
3351
 
3050
3352
  const mod: ModuleRecord = {
3051
3353
  id: filename,
@@ -3054,6 +3356,7 @@ export class ScriptEngine {
3054
3356
  loaded: false,
3055
3357
  children: [],
3056
3358
  paths: [],
3359
+ parent: null,
3057
3360
  };
3058
3361
  this.moduleRegistry[filename] = mod;
3059
3362
 
@@ -3075,12 +3378,19 @@ export class ScriptEngine {
3075
3378
  const cjsPatches: Array<[number, number, string]> = [];
3076
3379
  const walkCjs = (node: any) => {
3077
3380
  if (!node || typeof node !== "object") return;
3078
- if (Array.isArray(node)) { for (const c of node) walkCjs(c); return; }
3381
+ if (Array.isArray(node)) {
3382
+ for (const c of node) walkCjs(c);
3383
+ return;
3384
+ }
3079
3385
  if (typeof node.type !== "string") return;
3080
3386
  if (node.type === "ImportExpression") {
3081
3387
  cjsPatches.push([node.start, node.start + 6, "__asyncLoad"]);
3082
3388
  }
3083
- if (node.type === "MetaProperty" && node.meta?.name === "import" && node.property?.name === "meta") {
3389
+ if (
3390
+ node.type === "MetaProperty" &&
3391
+ node.meta?.name === "import" &&
3392
+ node.property?.name === "meta"
3393
+ ) {
3084
3394
  cjsPatches.push([
3085
3395
  node.start,
3086
3396
  node.end,
@@ -3097,15 +3407,21 @@ export class ScriptEngine {
3097
3407
  if (cjsPatches.length > 0) {
3098
3408
  cjsPatches.sort((a, b) => b[0] - a[0]);
3099
3409
  for (const [start, end, replacement] of cjsPatches) {
3100
- processed = processed.slice(0, start) + replacement + processed.slice(end);
3410
+ processed =
3411
+ processed.slice(0, start) + replacement + processed.slice(end);
3101
3412
  }
3102
3413
  }
3103
- } catch { /* can't parse */ }
3414
+ } catch {
3415
+ /* can't parse */
3416
+ }
3104
3417
  } else {
3105
3418
  processed = convertModuleSyntax(processed, filename);
3106
3419
  }
3107
3420
 
3108
- const fileHasTLA = filename.endsWith(".cjs") ? false : hasTopLevelAwait(processed);
3421
+ const isCjs = filename.endsWith(".cjs");
3422
+ const fileHasTLA = !isCjs && hasTopLevelAwait(processed);
3423
+ if (!isCjs) processed = stripTopLevelAwait(processed);
3424
+
3109
3425
  const resolver = buildResolver(
3110
3426
  this.vol,
3111
3427
  this.fsBridge,
@@ -3116,15 +3432,10 @@ export class ScriptEngine {
3116
3432
  this.transformCache,
3117
3433
  fileHasTLA,
3118
3434
  );
3435
+ resolver._ownerRecord = mod;
3119
3436
 
3120
3437
  try {
3121
3438
  const metaUrl = "file://" + filename;
3122
- if (!filename.endsWith(".cjs")) {
3123
- processed = stripTopLevelAwait(
3124
- processed,
3125
- fileHasTLA ? "full" : "topLevelOnly",
3126
- );
3127
- }
3128
3439
  const wrapper = buildModuleWrapper(processed);
3129
3440
 
3130
3441
  const asyncLoader = makeDynamicLoader(resolver);
@@ -3180,7 +3491,8 @@ export class ScriptEngine {
3180
3491
  ): Promise<{ exports: unknown; module: ModuleRecord }> {
3181
3492
  const source = this.vol.readFileSync(filename, "utf8");
3182
3493
  const dir = pathPolyfill.dirname(filename);
3183
- this.vol.writeFileSync(filename, source);
3494
+ // No need to write — source was just read from the same volume.
3495
+ // Writing it back triggers file watchers (nodemon restart loops).
3184
3496
 
3185
3497
  const mod: ModuleRecord = {
3186
3498
  id: filename,
@@ -3189,6 +3501,7 @@ export class ScriptEngine {
3189
3501
  loaded: false,
3190
3502
  children: [],
3191
3503
  paths: [],
3504
+ parent: null,
3192
3505
  };
3193
3506
  this.moduleRegistry[filename] = mod;
3194
3507
 
@@ -3214,6 +3527,7 @@ export class ScriptEngine {
3214
3527
  processed = convertModuleSyntax(processed, filename);
3215
3528
  }
3216
3529
  const tla = hasTopLevelAwait(processed);
3530
+ const tlaStripped = stripTopLevelAwait(processed);
3217
3531
 
3218
3532
  // Don't propagate deAsyncImports from entry — it uses native await (async IIFE),
3219
3533
  // so deps don't need de-async. loadModule handles individual TLA modules.
@@ -3227,10 +3541,11 @@ export class ScriptEngine {
3227
3541
  this.transformCache,
3228
3542
  false,
3229
3543
  );
3544
+ resolver._ownerRecord = mod;
3230
3545
 
3231
3546
  if (!tla) {
3232
3547
  try {
3233
- processed = stripTopLevelAwait(processed);
3548
+ processed = tlaStripped;
3234
3549
  const wrapper = buildModuleWrapper(processed);
3235
3550
  const asyncLoader = makeDynamicLoader(resolver);
3236
3551
  const fn = (0, eval)(wrapper);
@@ -3289,6 +3604,10 @@ export class ScriptEngine {
3289
3604
  clearCache(): void {
3290
3605
  for (const k of Object.keys(this.moduleRegistry))
3291
3606
  delete this.moduleRegistry[k];
3607
+ // Also clear shared resolver/manifest caches
3608
+ (this.moduleRegistry as any).__resolveCache?.clear();
3609
+ (this.moduleRegistry as any).__manifestCache?.clear();
3610
+ delete (this.moduleRegistry as any).__pkgIdentityMap;
3292
3611
  }
3293
3612
 
3294
3613
  getVolume(): MemoryVolume {