@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
@@ -1,368 +1,368 @@
1
- // ESM-to-CJS bulk transformer (esbuild-wasm). Used at install time to
2
- // pre-convert packages so synchronous require() works at runtime.
3
- // Transforms are offloaded to Web Workers when available.
4
-
5
- import type { MemoryVolume } from "./memory-volume";
6
- import { CDN_ESBUILD_ESM, CDN_ESBUILD_BINARY, cdnImport } from "./constants/cdn-urls";
7
- import { offload, taskId, TaskPriority } from "./threading/offload";
8
- import type { TransformResult } from "./threading/offload-types";
9
-
10
- const inBrowser = typeof window !== "undefined";
11
-
12
- // load and init esbuild-wasm from CDN (reuses existing instance)
13
- export async function prepareTransformer(): Promise<void> {
14
- if (!inBrowser) {
15
- return;
16
- }
17
-
18
- if (window.__esbuildEngine) {
19
- return;
20
- }
21
-
22
- if (window.__esbuildReady) {
23
- return window.__esbuildReady;
24
- }
25
-
26
- window.__esbuildReady = (async () => {
27
- try {
28
- const loaded = await cdnImport(CDN_ESBUILD_ESM);
29
- const engine = loaded.default || loaded;
30
-
31
- try {
32
- await engine.initialize({ wasmURL: CDN_ESBUILD_BINARY });
33
- } catch (initErr) {
34
- if (
35
- initErr instanceof Error &&
36
- initErr.message.includes('Cannot call "initialize" more than once')
37
- ) {
38
- /* already initialized, ignore */
39
- } else {
40
- throw initErr;
41
- }
42
- }
43
-
44
- window.__esbuildEngine = engine;
45
- } catch (err) {
46
- window.__esbuildReady = undefined;
47
- throw err;
48
- }
49
- })();
50
-
51
- return window.__esbuildReady;
52
- }
53
-
54
- export function isTransformerLoaded(): boolean {
55
- if (!inBrowser) return true;
56
- return window.__esbuildEngine !== undefined;
57
- }
58
-
59
- function containsJsx(source: string): boolean {
60
- if (/<[A-Z][a-zA-Z0-9.]*[\s/>]/.test(source)) return true;
61
- if (/<\/[a-zA-Z]/.test(source)) return true;
62
- if (/\/>/.test(source)) return true;
63
- if (/<>|<\/>/.test(source)) return true;
64
- if (/React\.createElement\b/.test(source)) return true;
65
- if (/jsx\(|jsxs\(|jsxDEV\(/.test(source)) return true;
66
- return false;
67
- }
68
-
69
- function detectLoader(
70
- filePath: string,
71
- source: string,
72
- ): "js" | "jsx" | "ts" | "tsx" {
73
- if (filePath.endsWith(".jsx")) return "jsx";
74
- if (filePath.endsWith(".ts")) return "ts";
75
- if (filePath.endsWith(".tsx")) return "tsx";
76
- if (filePath.endsWith(".mjs")) return "js";
77
- if (containsJsx(source)) return "jsx";
78
- return "js";
79
- }
80
-
81
- // convert a single file ESM->CJS via offloaded esbuild.transform()
82
- export async function convertFile(
83
- source: string,
84
- filePath: string,
85
- ): Promise<string> {
86
- if (!inBrowser) return source;
87
-
88
- const result: TransformResult = await offload({
89
- type: "transform",
90
- id: taskId(),
91
- source,
92
- filePath,
93
- options: {
94
- loader: detectLoader(filePath, source),
95
- format: "cjs",
96
- target: "esnext",
97
- platform: "neutral",
98
- define: {
99
- "import.meta.url": "import_meta.url",
100
- "import.meta.dirname": "import_meta.dirname",
101
- "import.meta.filename": "import_meta.filename",
102
- "import.meta": "import_meta",
103
- },
104
- },
105
- priority: TaskPriority.NORMAL,
106
- });
107
-
108
- // patch dynamic imports of node builtins to synchronous require()
109
- let code = result.code;
110
- code = code.replace(
111
- /\bimport\s*\(\s*["']node:([^"']+)["']\s*\)/g,
112
- 'Promise.resolve(require("node:$1"))',
113
- );
114
-
115
- const coreModules = [
116
- "assert", "buffer", "child_process", "cluster", "crypto",
117
- "dgram", "dns", "events", "fs", "http", "http2", "https",
118
- "net", "os", "path", "perf_hooks", "querystring", "readline",
119
- "stream", "string_decoder", "timers", "tls", "url", "util",
120
- "v8", "vm", "worker_threads", "zlib", "async_hooks",
121
- "inspector", "module",
122
- ];
123
- for (const mod of coreModules) {
124
- const re = new RegExp(`\\bimport\\s*\\(\\s*["']${mod}["']\\s*\\)`, "g");
125
- code = code.replace(re, `Promise.resolve(require("${mod}"))`);
126
- }
127
-
128
- return code;
129
- }
130
-
131
- // direct main-thread esbuild transform (no worker offloading).
132
- // used as the fallback path to avoid circular offload -> convertFile -> offload.
133
- export async function convertFileDirect(
134
- source: string,
135
- filePath: string,
136
- ): Promise<string> {
137
- if (!inBrowser) return source;
138
-
139
- if (!window.__esbuildEngine) await prepareTransformer();
140
- const engine = window.__esbuildEngine;
141
- if (!engine) throw new Error("esbuild engine not available");
142
-
143
- const loader = detectLoader(filePath, source);
144
-
145
- try {
146
- const output = await engine.transform(source, {
147
- loader,
148
- format: "cjs",
149
- target: "esnext",
150
- platform: "neutral",
151
- define: {
152
- "import.meta.url": "import_meta.url",
153
- "import.meta.dirname": "import_meta.dirname",
154
- "import.meta.filename": "import_meta.filename",
155
- "import.meta": "import_meta",
156
- },
157
- });
158
- return output.code;
159
- } catch (err: unknown) {
160
- const msg = err instanceof Error ? err.message : String(err);
161
-
162
- // retry with alternative loaders
163
- if (loader === "js" || loader === "jsx") {
164
- const fallbacks = loader === "js" ? ["jsx", "tsx", "ts"] : ["tsx"];
165
- for (const fallbackLoader of fallbacks) {
166
- try {
167
- const output = await engine.transform(source, {
168
- loader: fallbackLoader as "jsx" | "tsx" | "ts",
169
- format: "cjs",
170
- target: "esnext",
171
- platform: "neutral",
172
- define: {
173
- "import.meta.url": "import_meta.url",
174
- "import.meta.dirname": "import_meta.dirname",
175
- "import.meta.filename": "import_meta.filename",
176
- "import.meta": "import_meta",
177
- },
178
- });
179
- return output.code;
180
- } catch {
181
- }
182
- }
183
- }
184
-
185
- if (msg.includes("Top-level await")) {
186
- try {
187
- const output = await engine.transform(source, {
188
- loader,
189
- format: "esm",
190
- target: "esnext",
191
- platform: "neutral",
192
- define: {
193
- "import.meta.url": "import_meta.url",
194
- "import.meta.dirname": "import_meta.dirname",
195
- "import.meta.filename": "import_meta.filename",
196
- "import.meta": "import_meta",
197
- },
198
- });
199
- return output.code;
200
- } catch {
201
- return source;
202
- }
203
- }
204
- return source;
205
- }
206
- }
207
-
208
- function requiresConversion(filePath: string, source: string): boolean {
209
- // .mjs must stay ESM in the VFS -- tools read them directly. the runtime
210
- // handles ESM->CJS on-the-fly for require().
211
- if (filePath.endsWith(".mjs")) return false;
212
- if (filePath.endsWith(".cjs")) return false;
213
- return (
214
- /\bimport\s+[\w{*'"]/m.test(source) ||
215
- /\bexport\s+(?:default|const|let|var|function|class|{|\*)/m.test(source) ||
216
- /\bimport\.meta\b/.test(source)
217
- );
218
- }
219
-
220
- function needsBuiltinPatching(source: string): boolean {
221
- return (
222
- /\bimport\s*\(\s*["']node:/.test(source) ||
223
- /\bimport\s*\(\s*["'](fs|path|http|https|net|url|util|events|stream|os|crypto)["']/.test(
224
- source,
225
- )
226
- );
227
- }
228
-
229
- function patchBuiltinImports(source: string): string {
230
- let patched = source;
231
- patched = patched.replace(
232
- /\bimport\s*\(\s*["']node:([^"']+)["']\s*\)/g,
233
- 'Promise.resolve(require("node:$1"))',
234
- );
235
- const builtins = [
236
- "assert",
237
- "buffer",
238
- "child_process",
239
- "cluster",
240
- "crypto",
241
- "dgram",
242
- "dns",
243
- "events",
244
- "fs",
245
- "http",
246
- "http2",
247
- "https",
248
- "net",
249
- "os",
250
- "path",
251
- "perf_hooks",
252
- "querystring",
253
- "readline",
254
- "stream",
255
- "string_decoder",
256
- "timers",
257
- "tls",
258
- "url",
259
- "util",
260
- "v8",
261
- "vm",
262
- "worker_threads",
263
- "zlib",
264
- "async_hooks",
265
- "inspector",
266
- "module",
267
- ];
268
- for (const b of builtins) {
269
- patched = patched.replace(
270
- new RegExp(`\\bimport\\s*\\(\\s*["']${b}["']\\s*\\)`, "g"),
271
- `Promise.resolve(require("${b}"))`,
272
- );
273
- }
274
- return patched;
275
- }
276
-
277
- function listJsFiles(vol: MemoryVolume, dir: string): string[] {
278
- const result: string[] = [];
279
- try {
280
- for (const name of vol.readdirSync(dir)) {
281
- const full = dir + "/" + name;
282
- try {
283
- const info = vol.statSync(full);
284
- if (info.isDirectory()) {
285
- if (name !== "node_modules") result.push(...listJsFiles(vol, full));
286
- } else if (
287
- name.endsWith(".js") ||
288
- name.endsWith(".mjs") ||
289
- name.endsWith(".jsx")
290
- ) {
291
- result.push(full);
292
- }
293
- } catch {
294
- }
295
- }
296
- } catch {
297
- }
298
- return result;
299
- }
300
-
301
- function isEsmPackage(vol: MemoryVolume, packageDir: string): boolean {
302
- try {
303
- const pkgPath = packageDir + "/package.json";
304
- if (vol.existsSync(pkgPath)) {
305
- const raw = vol.readFileSync(pkgPath, "utf8");
306
- const pkg = JSON.parse(raw);
307
- return pkg.type === "module";
308
- }
309
- } catch {
310
- }
311
- return false;
312
- }
313
-
314
- // convert all ESM files in a package directory to CJS
315
- export async function convertPackage(
316
- vol: MemoryVolume,
317
- packageDir: string,
318
- onProgress?: (msg: string) => void,
319
- ): Promise<number> {
320
- let converted = 0;
321
- const files = listJsFiles(vol, packageDir);
322
- onProgress?.(` Converting ${files.length} files in ${packageDir}...`);
323
-
324
- // "type":"module" packages stay as ESM in VFS -- tools like Vite read them
325
- // directly and expect ESM syntax. require() handles conversion at runtime.
326
- const esmPackage = isEsmPackage(vol, packageDir);
327
-
328
- const BATCH = 50;
329
- for (let i = 0; i < files.length; i += BATCH) {
330
- const batch = files.slice(i, i + BATCH);
331
- await Promise.all(
332
- batch.map(async (fp) => {
333
- try {
334
- const src = vol.readFileSync(fp, "utf8");
335
-
336
- if (esmPackage) {
337
- if (needsBuiltinPatching(src)) {
338
- vol.writeFileSync(fp, patchBuiltinImports(src));
339
- converted++;
340
- }
341
- return;
342
- }
343
-
344
- // .mjs stays ESM even in non-ESM packages
345
- if (fp.endsWith(".mjs")) {
346
- if (needsBuiltinPatching(src)) {
347
- vol.writeFileSync(fp, patchBuiltinImports(src));
348
- converted++;
349
- }
350
- return;
351
- }
352
-
353
- if (requiresConversion(fp, src)) {
354
- const out = await convertFile(src, fp);
355
- vol.writeFileSync(fp, out);
356
- converted++;
357
- } else if (needsBuiltinPatching(src)) {
358
- vol.writeFileSync(fp, patchBuiltinImports(src));
359
- converted++;
360
- }
361
- } catch {
362
- }
363
- }),
364
- );
365
- }
366
-
367
- return converted;
368
- }
1
+ // ESM-to-CJS bulk transformer (esbuild-wasm). Used at install time to
2
+ // pre-convert packages so synchronous require() works at runtime.
3
+ // Transforms are offloaded to Web Workers when available.
4
+
5
+ import type { MemoryVolume } from "./memory-volume";
6
+ import { CDN_ESBUILD_ESM, CDN_ESBUILD_BINARY, cdnImport } from "./constants/cdn-urls";
7
+ import { offload, taskId, TaskPriority } from "./threading/offload";
8
+ import type { TransformResult } from "./threading/offload-types";
9
+
10
+ const inBrowser = typeof window !== "undefined";
11
+
12
+ // load and init esbuild-wasm from CDN (reuses existing instance)
13
+ export async function prepareTransformer(): Promise<void> {
14
+ if (!inBrowser) {
15
+ return;
16
+ }
17
+
18
+ if (window.__esbuildEngine) {
19
+ return;
20
+ }
21
+
22
+ if (window.__esbuildReady) {
23
+ return window.__esbuildReady;
24
+ }
25
+
26
+ window.__esbuildReady = (async () => {
27
+ try {
28
+ const loaded = await cdnImport(CDN_ESBUILD_ESM);
29
+ const engine = loaded.default || loaded;
30
+
31
+ try {
32
+ await engine.initialize({ wasmURL: CDN_ESBUILD_BINARY });
33
+ } catch (initErr) {
34
+ if (
35
+ initErr instanceof Error &&
36
+ initErr.message.includes('Cannot call "initialize" more than once')
37
+ ) {
38
+ /* already initialized, ignore */
39
+ } else {
40
+ throw initErr;
41
+ }
42
+ }
43
+
44
+ window.__esbuildEngine = engine;
45
+ } catch (err) {
46
+ window.__esbuildReady = undefined;
47
+ throw err;
48
+ }
49
+ })();
50
+
51
+ return window.__esbuildReady;
52
+ }
53
+
54
+ export function isTransformerLoaded(): boolean {
55
+ if (!inBrowser) return true;
56
+ return window.__esbuildEngine !== undefined;
57
+ }
58
+
59
+ function containsJsx(source: string): boolean {
60
+ if (/<[A-Z][a-zA-Z0-9.]*[\s/>]/.test(source)) return true;
61
+ if (/<\/[a-zA-Z]/.test(source)) return true;
62
+ if (/\/>/.test(source)) return true;
63
+ if (/<>|<\/>/.test(source)) return true;
64
+ if (/React\.createElement\b/.test(source)) return true;
65
+ if (/jsx\(|jsxs\(|jsxDEV\(/.test(source)) return true;
66
+ return false;
67
+ }
68
+
69
+ function detectLoader(
70
+ filePath: string,
71
+ source: string,
72
+ ): "js" | "jsx" | "ts" | "tsx" {
73
+ if (filePath.endsWith(".jsx")) return "jsx";
74
+ if (filePath.endsWith(".ts")) return "ts";
75
+ if (filePath.endsWith(".tsx")) return "tsx";
76
+ if (filePath.endsWith(".mjs")) return "js";
77
+ if (containsJsx(source)) return "jsx";
78
+ return "js";
79
+ }
80
+
81
+ // convert a single file ESM->CJS via offloaded esbuild.transform()
82
+ export async function convertFile(
83
+ source: string,
84
+ filePath: string,
85
+ ): Promise<string> {
86
+ if (!inBrowser) return source;
87
+
88
+ const result: TransformResult = await offload({
89
+ type: "transform",
90
+ id: taskId(),
91
+ source,
92
+ filePath,
93
+ options: {
94
+ loader: detectLoader(filePath, source),
95
+ format: "cjs",
96
+ target: "esnext",
97
+ platform: "neutral",
98
+ define: {
99
+ "import.meta.url": "import_meta.url",
100
+ "import.meta.dirname": "import_meta.dirname",
101
+ "import.meta.filename": "import_meta.filename",
102
+ "import.meta": "import_meta",
103
+ },
104
+ },
105
+ priority: TaskPriority.NORMAL,
106
+ });
107
+
108
+ // patch dynamic imports of node builtins to synchronous require()
109
+ let code = result.code;
110
+ code = code.replace(
111
+ /\bimport\s*\(\s*["']node:([^"']+)["']\s*\)/g,
112
+ 'Promise.resolve(require("node:$1"))',
113
+ );
114
+
115
+ const coreModules = [
116
+ "assert", "buffer", "child_process", "cluster", "crypto",
117
+ "dgram", "dns", "events", "fs", "http", "http2", "https",
118
+ "net", "os", "path", "perf_hooks", "querystring", "readline",
119
+ "stream", "string_decoder", "timers", "tls", "url", "util",
120
+ "v8", "vm", "worker_threads", "zlib", "async_hooks",
121
+ "inspector", "module",
122
+ ];
123
+ for (const mod of coreModules) {
124
+ const re = new RegExp(`\\bimport\\s*\\(\\s*["']${mod}["']\\s*\\)`, "g");
125
+ code = code.replace(re, `Promise.resolve(require("${mod}"))`);
126
+ }
127
+
128
+ return code;
129
+ }
130
+
131
+ // direct main-thread esbuild transform (no worker offloading).
132
+ // used as the fallback path to avoid circular offload -> convertFile -> offload.
133
+ export async function convertFileDirect(
134
+ source: string,
135
+ filePath: string,
136
+ ): Promise<string> {
137
+ if (!inBrowser) return source;
138
+
139
+ if (!window.__esbuildEngine) await prepareTransformer();
140
+ const engine = window.__esbuildEngine;
141
+ if (!engine) throw new Error("esbuild engine not available");
142
+
143
+ const loader = detectLoader(filePath, source);
144
+
145
+ try {
146
+ const output = await engine.transform(source, {
147
+ loader,
148
+ format: "cjs",
149
+ target: "esnext",
150
+ platform: "neutral",
151
+ define: {
152
+ "import.meta.url": "import_meta.url",
153
+ "import.meta.dirname": "import_meta.dirname",
154
+ "import.meta.filename": "import_meta.filename",
155
+ "import.meta": "import_meta",
156
+ },
157
+ });
158
+ return output.code;
159
+ } catch (err: unknown) {
160
+ const msg = err instanceof Error ? err.message : String(err);
161
+
162
+ // retry with alternative loaders
163
+ if (loader === "js" || loader === "jsx") {
164
+ const fallbacks = loader === "js" ? ["jsx", "tsx", "ts"] : ["tsx"];
165
+ for (const fallbackLoader of fallbacks) {
166
+ try {
167
+ const output = await engine.transform(source, {
168
+ loader: fallbackLoader as "jsx" | "tsx" | "ts",
169
+ format: "cjs",
170
+ target: "esnext",
171
+ platform: "neutral",
172
+ define: {
173
+ "import.meta.url": "import_meta.url",
174
+ "import.meta.dirname": "import_meta.dirname",
175
+ "import.meta.filename": "import_meta.filename",
176
+ "import.meta": "import_meta",
177
+ },
178
+ });
179
+ return output.code;
180
+ } catch {
181
+ }
182
+ }
183
+ }
184
+
185
+ if (msg.includes("Top-level await")) {
186
+ try {
187
+ const output = await engine.transform(source, {
188
+ loader,
189
+ format: "esm",
190
+ target: "esnext",
191
+ platform: "neutral",
192
+ define: {
193
+ "import.meta.url": "import_meta.url",
194
+ "import.meta.dirname": "import_meta.dirname",
195
+ "import.meta.filename": "import_meta.filename",
196
+ "import.meta": "import_meta",
197
+ },
198
+ });
199
+ return output.code;
200
+ } catch {
201
+ return source;
202
+ }
203
+ }
204
+ return source;
205
+ }
206
+ }
207
+
208
+ function requiresConversion(filePath: string, source: string): boolean {
209
+ // .mjs must stay ESM in the VFS -- tools read them directly. the runtime
210
+ // handles ESM->CJS on-the-fly for require().
211
+ if (filePath.endsWith(".mjs")) return false;
212
+ if (filePath.endsWith(".cjs")) return false;
213
+ return (
214
+ /\bimport\s+[\w{*'"]/m.test(source) ||
215
+ /\bexport\s+(?:default|const|let|var|function|class|{|\*)/m.test(source) ||
216
+ /\bimport\.meta\b/.test(source)
217
+ );
218
+ }
219
+
220
+ function needsBuiltinPatching(source: string): boolean {
221
+ return (
222
+ /\bimport\s*\(\s*["']node:/.test(source) ||
223
+ /\bimport\s*\(\s*["'](fs|path|http|https|net|url|util|events|stream|os|crypto)["']/.test(
224
+ source,
225
+ )
226
+ );
227
+ }
228
+
229
+ function patchBuiltinImports(source: string): string {
230
+ let patched = source;
231
+ patched = patched.replace(
232
+ /\bimport\s*\(\s*["']node:([^"']+)["']\s*\)/g,
233
+ 'Promise.resolve(require("node:$1"))',
234
+ );
235
+ const builtins = [
236
+ "assert",
237
+ "buffer",
238
+ "child_process",
239
+ "cluster",
240
+ "crypto",
241
+ "dgram",
242
+ "dns",
243
+ "events",
244
+ "fs",
245
+ "http",
246
+ "http2",
247
+ "https",
248
+ "net",
249
+ "os",
250
+ "path",
251
+ "perf_hooks",
252
+ "querystring",
253
+ "readline",
254
+ "stream",
255
+ "string_decoder",
256
+ "timers",
257
+ "tls",
258
+ "url",
259
+ "util",
260
+ "v8",
261
+ "vm",
262
+ "worker_threads",
263
+ "zlib",
264
+ "async_hooks",
265
+ "inspector",
266
+ "module",
267
+ ];
268
+ for (const b of builtins) {
269
+ patched = patched.replace(
270
+ new RegExp(`\\bimport\\s*\\(\\s*["']${b}["']\\s*\\)`, "g"),
271
+ `Promise.resolve(require("${b}"))`,
272
+ );
273
+ }
274
+ return patched;
275
+ }
276
+
277
+ function listJsFiles(vol: MemoryVolume, dir: string): string[] {
278
+ const result: string[] = [];
279
+ try {
280
+ for (const name of vol.readdirSync(dir)) {
281
+ const full = dir + "/" + name;
282
+ try {
283
+ const info = vol.statSync(full);
284
+ if (info.isDirectory()) {
285
+ if (name !== "node_modules") result.push(...listJsFiles(vol, full));
286
+ } else if (
287
+ name.endsWith(".js") ||
288
+ name.endsWith(".mjs") ||
289
+ name.endsWith(".jsx")
290
+ ) {
291
+ result.push(full);
292
+ }
293
+ } catch {
294
+ }
295
+ }
296
+ } catch {
297
+ }
298
+ return result;
299
+ }
300
+
301
+ function isEsmPackage(vol: MemoryVolume, packageDir: string): boolean {
302
+ try {
303
+ const pkgPath = packageDir + "/package.json";
304
+ if (vol.existsSync(pkgPath)) {
305
+ const raw = vol.readFileSync(pkgPath, "utf8");
306
+ const pkg = JSON.parse(raw);
307
+ return pkg.type === "module";
308
+ }
309
+ } catch {
310
+ }
311
+ return false;
312
+ }
313
+
314
+ // convert all ESM files in a package directory to CJS
315
+ export async function convertPackage(
316
+ vol: MemoryVolume,
317
+ packageDir: string,
318
+ onProgress?: (msg: string) => void,
319
+ ): Promise<number> {
320
+ let converted = 0;
321
+ const files = listJsFiles(vol, packageDir);
322
+ onProgress?.(` Converting ${files.length} files in ${packageDir}...`);
323
+
324
+ // "type":"module" packages stay as ESM in VFS -- tools like Vite read them
325
+ // directly and expect ESM syntax. require() handles conversion at runtime.
326
+ const esmPackage = isEsmPackage(vol, packageDir);
327
+
328
+ const BATCH = 50;
329
+ for (let i = 0; i < files.length; i += BATCH) {
330
+ const batch = files.slice(i, i + BATCH);
331
+ await Promise.all(
332
+ batch.map(async (fp) => {
333
+ try {
334
+ const src = vol.readFileSync(fp, "utf8");
335
+
336
+ if (esmPackage) {
337
+ if (needsBuiltinPatching(src)) {
338
+ vol.writeFileSync(fp, patchBuiltinImports(src));
339
+ converted++;
340
+ }
341
+ return;
342
+ }
343
+
344
+ // .mjs stays ESM even in non-ESM packages
345
+ if (fp.endsWith(".mjs")) {
346
+ if (needsBuiltinPatching(src)) {
347
+ vol.writeFileSync(fp, patchBuiltinImports(src));
348
+ converted++;
349
+ }
350
+ return;
351
+ }
352
+
353
+ if (requiresConversion(fp, src)) {
354
+ const out = await convertFile(src, fp);
355
+ vol.writeFileSync(fp, out);
356
+ converted++;
357
+ } else if (needsBuiltinPatching(src)) {
358
+ vol.writeFileSync(fp, patchBuiltinImports(src));
359
+ converted++;
360
+ }
361
+ } catch {
362
+ }
363
+ }),
364
+ );
365
+ }
366
+
367
+ return converted;
368
+ }