@scelar/nodepod 1.0.0
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.
- package/LICENSE +43 -0
- package/README.md +240 -0
- package/dist/child_process-BJOMsZje.js +8233 -0
- package/dist/child_process-BJOMsZje.js.map +1 -0
- package/dist/child_process-Cj8vOcuc.cjs +7434 -0
- package/dist/child_process-Cj8vOcuc.cjs.map +1 -0
- package/dist/index-Cb1Cgdnd.js +35308 -0
- package/dist/index-Cb1Cgdnd.js.map +1 -0
- package/dist/index-DsMGS-xc.cjs +37195 -0
- package/dist/index-DsMGS-xc.cjs.map +1 -0
- package/dist/index.cjs +65 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +59 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +95 -0
- package/src/__tests__/smoke.test.ts +11 -0
- package/src/constants/cdn-urls.ts +18 -0
- package/src/constants/config.ts +236 -0
- package/src/cross-origin.ts +26 -0
- package/src/engine-factory.ts +176 -0
- package/src/engine-types.ts +56 -0
- package/src/helpers/byte-encoding.ts +39 -0
- package/src/helpers/digest.ts +9 -0
- package/src/helpers/event-loop.ts +96 -0
- package/src/helpers/wasm-cache.ts +133 -0
- package/src/iframe-sandbox.ts +141 -0
- package/src/index.ts +192 -0
- package/src/isolation-helpers.ts +148 -0
- package/src/memory-volume.ts +941 -0
- package/src/module-transformer.ts +368 -0
- package/src/packages/archive-extractor.ts +248 -0
- package/src/packages/browser-bundler.ts +284 -0
- package/src/packages/installer.ts +396 -0
- package/src/packages/registry-client.ts +131 -0
- package/src/packages/version-resolver.ts +411 -0
- package/src/polyfills/assert.ts +384 -0
- package/src/polyfills/async_hooks.ts +144 -0
- package/src/polyfills/buffer.ts +628 -0
- package/src/polyfills/child_process.ts +2288 -0
- package/src/polyfills/chokidar.ts +336 -0
- package/src/polyfills/cluster.ts +106 -0
- package/src/polyfills/console.ts +136 -0
- package/src/polyfills/constants.ts +123 -0
- package/src/polyfills/crypto.ts +885 -0
- package/src/polyfills/dgram.ts +87 -0
- package/src/polyfills/diagnostics_channel.ts +76 -0
- package/src/polyfills/dns.ts +134 -0
- package/src/polyfills/domain.ts +68 -0
- package/src/polyfills/esbuild.ts +854 -0
- package/src/polyfills/events.ts +276 -0
- package/src/polyfills/fs.ts +2888 -0
- package/src/polyfills/fsevents.ts +79 -0
- package/src/polyfills/http.ts +1449 -0
- package/src/polyfills/http2.ts +199 -0
- package/src/polyfills/https.ts +76 -0
- package/src/polyfills/inspector.ts +62 -0
- package/src/polyfills/lightningcss.ts +105 -0
- package/src/polyfills/module.ts +191 -0
- package/src/polyfills/net.ts +353 -0
- package/src/polyfills/os.ts +238 -0
- package/src/polyfills/path.ts +206 -0
- package/src/polyfills/perf_hooks.ts +102 -0
- package/src/polyfills/process.ts +690 -0
- package/src/polyfills/punycode.ts +159 -0
- package/src/polyfills/querystring.ts +93 -0
- package/src/polyfills/quic.ts +118 -0
- package/src/polyfills/readdirp.ts +229 -0
- package/src/polyfills/readline.ts +692 -0
- package/src/polyfills/repl.ts +134 -0
- package/src/polyfills/rollup.ts +119 -0
- package/src/polyfills/sea.ts +33 -0
- package/src/polyfills/sqlite.ts +78 -0
- package/src/polyfills/stream.ts +1620 -0
- package/src/polyfills/string_decoder.ts +25 -0
- package/src/polyfills/tailwindcss-oxide.ts +309 -0
- package/src/polyfills/test.ts +197 -0
- package/src/polyfills/timers.ts +32 -0
- package/src/polyfills/tls.ts +105 -0
- package/src/polyfills/trace_events.ts +50 -0
- package/src/polyfills/tty.ts +71 -0
- package/src/polyfills/url.ts +174 -0
- package/src/polyfills/util.ts +559 -0
- package/src/polyfills/v8.ts +126 -0
- package/src/polyfills/vm.ts +132 -0
- package/src/polyfills/volume-registry.ts +15 -0
- package/src/polyfills/wasi.ts +44 -0
- package/src/polyfills/worker_threads.ts +326 -0
- package/src/polyfills/ws.ts +595 -0
- package/src/polyfills/zlib.ts +881 -0
- package/src/request-proxy.ts +716 -0
- package/src/script-engine.ts +3375 -0
- package/src/sdk/nodepod-fs.ts +93 -0
- package/src/sdk/nodepod-process.ts +86 -0
- package/src/sdk/nodepod-terminal.ts +350 -0
- package/src/sdk/nodepod.ts +509 -0
- package/src/sdk/types.ts +70 -0
- package/src/shell/commands/bun.ts +121 -0
- package/src/shell/commands/directory.ts +297 -0
- package/src/shell/commands/file-ops.ts +525 -0
- package/src/shell/commands/git.ts +2142 -0
- package/src/shell/commands/node.ts +80 -0
- package/src/shell/commands/npm.ts +198 -0
- package/src/shell/commands/pm-types.ts +45 -0
- package/src/shell/commands/pnpm.ts +82 -0
- package/src/shell/commands/search.ts +264 -0
- package/src/shell/commands/shell-env.ts +352 -0
- package/src/shell/commands/text-processing.ts +1152 -0
- package/src/shell/commands/yarn.ts +84 -0
- package/src/shell/shell-builtins.ts +19 -0
- package/src/shell/shell-helpers.ts +250 -0
- package/src/shell/shell-interpreter.ts +514 -0
- package/src/shell/shell-parser.ts +429 -0
- package/src/shell/shell-types.ts +85 -0
- package/src/syntax-transforms.ts +561 -0
- package/src/threading/engine-worker.ts +64 -0
- package/src/threading/inline-worker.ts +372 -0
- package/src/threading/offload-types.ts +112 -0
- package/src/threading/offload-worker.ts +383 -0
- package/src/threading/offload.ts +271 -0
- package/src/threading/process-context.ts +92 -0
- package/src/threading/process-handle.ts +275 -0
- package/src/threading/process-manager.ts +956 -0
- package/src/threading/process-worker-entry.ts +854 -0
- package/src/threading/shared-vfs.ts +352 -0
- package/src/threading/sync-channel.ts +135 -0
- package/src/threading/task-queue.ts +177 -0
- package/src/threading/vfs-bridge.ts +231 -0
- package/src/threading/worker-pool.ts +233 -0
- package/src/threading/worker-protocol.ts +358 -0
- package/src/threading/worker-vfs.ts +218 -0
- package/src/types/externals.d.ts +38 -0
- package/src/types/fs-streams.ts +142 -0
- package/src/types/manifest.ts +17 -0
- package/src/worker-sandbox.ts +90 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
// ESM-to-CJS conversion via acorn AST, with regex fallback
|
|
2
|
+
|
|
3
|
+
import * as acorn from "acorn";
|
|
4
|
+
|
|
5
|
+
export function esmToCjs(code: string): string {
|
|
6
|
+
try {
|
|
7
|
+
return esmToCjsViaAst(code);
|
|
8
|
+
} catch {
|
|
9
|
+
return esmToCjsViaRegex(code);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function esmToCjsViaAst(code: string): string {
|
|
14
|
+
const ast = acorn.parse(code, {
|
|
15
|
+
ecmaVersion: "latest",
|
|
16
|
+
sourceType: "module",
|
|
17
|
+
});
|
|
18
|
+
const patches: Array<[number, number, string]> = [];
|
|
19
|
+
|
|
20
|
+
const hasDefaultExport = (ast as any).body.some(
|
|
21
|
+
(n: any) => n.type === "ExportDefaultDeclaration",
|
|
22
|
+
);
|
|
23
|
+
const hasNamedExport = (ast as any).body.some(
|
|
24
|
+
(n: any) => n.type === "ExportNamedDeclaration",
|
|
25
|
+
);
|
|
26
|
+
const mixedExports = hasDefaultExport && hasNamedExport;
|
|
27
|
+
|
|
28
|
+
for (const node of (ast as any).body) {
|
|
29
|
+
if (node.type === "ImportDeclaration") {
|
|
30
|
+
const src = node.source.value;
|
|
31
|
+
const specs = node.specifiers;
|
|
32
|
+
|
|
33
|
+
if (specs.length === 0) {
|
|
34
|
+
patches.push([
|
|
35
|
+
node.start,
|
|
36
|
+
node.end,
|
|
37
|
+
`require(${JSON.stringify(src)});`,
|
|
38
|
+
]);
|
|
39
|
+
} else {
|
|
40
|
+
const defSpec = specs.find(
|
|
41
|
+
(s: any) => s.type === "ImportDefaultSpecifier",
|
|
42
|
+
);
|
|
43
|
+
const nsSpec = specs.find(
|
|
44
|
+
(s: any) => s.type === "ImportNamespaceSpecifier",
|
|
45
|
+
);
|
|
46
|
+
const namedSpecs = specs.filter(
|
|
47
|
+
(s: any) => s.type === "ImportSpecifier",
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const lines: string[] = [];
|
|
51
|
+
const tmpVar = `__import_${node.start}`;
|
|
52
|
+
const needsTmp = defSpec && (namedSpecs.length > 0 || nsSpec);
|
|
53
|
+
|
|
54
|
+
if (needsTmp) {
|
|
55
|
+
lines.push(`const ${tmpVar} = require(${JSON.stringify(src)})`);
|
|
56
|
+
lines.push(
|
|
57
|
+
`const ${defSpec.local.name} = ${tmpVar}.__esModule ? ${tmpVar}.default : ${tmpVar}`,
|
|
58
|
+
);
|
|
59
|
+
} else if (defSpec) {
|
|
60
|
+
lines.push(
|
|
61
|
+
`const ${defSpec.local.name} = (function(m) { return m.__esModule ? m.default : m; })(require(${JSON.stringify(src)}))`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (nsSpec) {
|
|
66
|
+
if (!needsTmp) {
|
|
67
|
+
lines.push(
|
|
68
|
+
`const ${nsSpec.local.name} = require(${JSON.stringify(src)})`,
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
lines.push(`const ${nsSpec.local.name} = ${tmpVar}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (namedSpecs.length > 0) {
|
|
76
|
+
const binds = namedSpecs
|
|
77
|
+
.map((s: any) =>
|
|
78
|
+
s.imported.name === s.local.name
|
|
79
|
+
? s.local.name
|
|
80
|
+
: `${s.imported.name}: ${s.local.name}`,
|
|
81
|
+
)
|
|
82
|
+
.join(", ");
|
|
83
|
+
if (needsTmp) {
|
|
84
|
+
lines.push(`const { ${binds} } = ${tmpVar}`);
|
|
85
|
+
} else {
|
|
86
|
+
lines.push(`const { ${binds} } = require(${JSON.stringify(src)})`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
patches.push([node.start, node.end, lines.join(";\n") + ";"]);
|
|
90
|
+
}
|
|
91
|
+
} else if (node.type === "ExportDefaultDeclaration") {
|
|
92
|
+
const decl = node.declaration;
|
|
93
|
+
const bodyCode = code.slice(decl.start, node.end);
|
|
94
|
+
const exportTarget = mixedExports ? "exports.default" : "module.exports";
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
(decl.type === "FunctionDeclaration" ||
|
|
98
|
+
decl.type === "ClassDeclaration") &&
|
|
99
|
+
decl.id?.name
|
|
100
|
+
) {
|
|
101
|
+
// preserve the declaration so the name is bound in local scope
|
|
102
|
+
patches.push([
|
|
103
|
+
node.start,
|
|
104
|
+
node.end,
|
|
105
|
+
`${bodyCode};\n${exportTarget} = ${decl.id.name};`,
|
|
106
|
+
]);
|
|
107
|
+
} else {
|
|
108
|
+
patches.push([node.start, node.end, `${exportTarget} = ${bodyCode}`]);
|
|
109
|
+
}
|
|
110
|
+
} else if (node.type === "ExportNamedDeclaration") {
|
|
111
|
+
if (node.declaration) {
|
|
112
|
+
const decl = node.declaration;
|
|
113
|
+
if (
|
|
114
|
+
decl.type === "FunctionDeclaration" ||
|
|
115
|
+
decl.type === "ClassDeclaration"
|
|
116
|
+
) {
|
|
117
|
+
const name = decl.id.name;
|
|
118
|
+
const bodyCode = code.slice(decl.start, node.end);
|
|
119
|
+
// emit declaration so name is bound locally, then export it
|
|
120
|
+
patches.push([
|
|
121
|
+
node.start,
|
|
122
|
+
node.end,
|
|
123
|
+
`${bodyCode};\nexports.${name} = ${name};`,
|
|
124
|
+
]);
|
|
125
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
126
|
+
const lines: string[] = [];
|
|
127
|
+
const needsLiveBinding = decl.kind === "let" || decl.kind === "var";
|
|
128
|
+
const hasDestructuring = decl.declarations.some(
|
|
129
|
+
(d: any) =>
|
|
130
|
+
d.id.type === "ObjectPattern" || d.id.type === "ArrayPattern",
|
|
131
|
+
);
|
|
132
|
+
if (hasDestructuring) {
|
|
133
|
+
const declCode = code.slice(decl.start, decl.end);
|
|
134
|
+
lines.push(declCode);
|
|
135
|
+
for (const d of decl.declarations) {
|
|
136
|
+
for (const name of extractBindingNames(d.id)) {
|
|
137
|
+
if (needsLiveBinding) {
|
|
138
|
+
lines.push(
|
|
139
|
+
`Object.defineProperty(exports, ${JSON.stringify(name)}, { get() { return ${name}; }, enumerable: true })`,
|
|
140
|
+
);
|
|
141
|
+
} else {
|
|
142
|
+
lines.push(`exports.${name} = ${name}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
const declCode = code.slice(decl.start, decl.end);
|
|
148
|
+
lines.push(declCode);
|
|
149
|
+
for (const d of decl.declarations) {
|
|
150
|
+
if (needsLiveBinding) {
|
|
151
|
+
// ESM live binding: getter so reassignments are visible to importers
|
|
152
|
+
lines.push(
|
|
153
|
+
`Object.defineProperty(exports, ${JSON.stringify(d.id.name)}, { get() { return ${d.id.name}; }, enumerable: true })`,
|
|
154
|
+
);
|
|
155
|
+
} else {
|
|
156
|
+
lines.push(`exports.${d.id.name} = ${d.id.name}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
patches.push([node.start, node.end, lines.join(";\n") + ";"]);
|
|
161
|
+
}
|
|
162
|
+
} else if (node.source) {
|
|
163
|
+
const src = node.source.value;
|
|
164
|
+
const tmp = `__reexport_${node.start}`;
|
|
165
|
+
const lines = [`const ${tmp} = require(${JSON.stringify(src)})`];
|
|
166
|
+
for (const spec of node.specifiers) {
|
|
167
|
+
if (spec.local.name === "default") {
|
|
168
|
+
// handle both module.exports=X and exports.default=X conventions
|
|
169
|
+
lines.push(
|
|
170
|
+
`exports.${spec.exported.name} = ${tmp}.__esModule ? ${tmp}.default : ${tmp}`,
|
|
171
|
+
);
|
|
172
|
+
} else {
|
|
173
|
+
lines.push(
|
|
174
|
+
`exports.${spec.exported.name} = ${tmp}.${spec.local.name}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
patches.push([node.start, node.end, lines.join(";\n") + ";"]);
|
|
179
|
+
} else {
|
|
180
|
+
const lines = node.specifiers.map(
|
|
181
|
+
(s: any) => `exports.${s.exported.name} = ${s.local.name}`,
|
|
182
|
+
);
|
|
183
|
+
patches.push([node.start, node.end, lines.join(";\n") + ";"]);
|
|
184
|
+
}
|
|
185
|
+
} else if (node.type === "ExportAllDeclaration") {
|
|
186
|
+
const src = node.source.value;
|
|
187
|
+
patches.push([
|
|
188
|
+
node.start,
|
|
189
|
+
node.end,
|
|
190
|
+
`Object.assign(exports, require(${JSON.stringify(src)}))`,
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let output = code;
|
|
196
|
+
patches.sort((a, b) => b[0] - a[0]);
|
|
197
|
+
for (const [s, e, r] of patches)
|
|
198
|
+
output = output.slice(0, s) + r + output.slice(e);
|
|
199
|
+
return output;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// extract all bound names from a destructuring pattern or identifier
|
|
203
|
+
function extractBindingNames(pattern: any): string[] {
|
|
204
|
+
if (pattern.type === "Identifier") {
|
|
205
|
+
return [pattern.name];
|
|
206
|
+
}
|
|
207
|
+
if (pattern.type === "ObjectPattern") {
|
|
208
|
+
const names: string[] = [];
|
|
209
|
+
for (const prop of pattern.properties) {
|
|
210
|
+
if (prop.type === "RestElement") {
|
|
211
|
+
names.push(...extractBindingNames(prop.argument));
|
|
212
|
+
} else {
|
|
213
|
+
names.push(...extractBindingNames(prop.value));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return names;
|
|
217
|
+
}
|
|
218
|
+
if (pattern.type === "ArrayPattern") {
|
|
219
|
+
const names: string[] = [];
|
|
220
|
+
for (const elem of pattern.elements) {
|
|
221
|
+
if (elem) {
|
|
222
|
+
if (elem.type === "RestElement") {
|
|
223
|
+
names.push(...extractBindingNames(elem.argument));
|
|
224
|
+
} else {
|
|
225
|
+
names.push(...extractBindingNames(elem));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return names;
|
|
230
|
+
}
|
|
231
|
+
if (pattern.type === "AssignmentPattern") {
|
|
232
|
+
return extractBindingNames(pattern.left);
|
|
233
|
+
}
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// detect whether code contains any top-level await (outside async functions)
|
|
238
|
+
export function hasTopLevelAwait(code: string): boolean {
|
|
239
|
+
if (!/\bawait\b/.test(code)) return false;
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
let ast: any;
|
|
243
|
+
try {
|
|
244
|
+
ast = acorn.parse(code, {
|
|
245
|
+
ecmaVersion: "latest",
|
|
246
|
+
sourceType: "script",
|
|
247
|
+
allowAwaitOutsideFunction: true,
|
|
248
|
+
});
|
|
249
|
+
} catch {
|
|
250
|
+
ast = acorn.parse(code, {
|
|
251
|
+
ecmaVersion: "latest",
|
|
252
|
+
sourceType: "module",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let found = false;
|
|
257
|
+
let insideAsync = 0;
|
|
258
|
+
|
|
259
|
+
function walk(node: any): void {
|
|
260
|
+
if (found || !node || typeof node !== "object") return;
|
|
261
|
+
if (Array.isArray(node)) {
|
|
262
|
+
for (const child of node) walk(child);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (typeof node.type !== "string") return;
|
|
266
|
+
|
|
267
|
+
const isAsyncFn =
|
|
268
|
+
(node.type === "FunctionDeclaration" ||
|
|
269
|
+
node.type === "FunctionExpression" ||
|
|
270
|
+
node.type === "ArrowFunctionExpression") &&
|
|
271
|
+
node.async;
|
|
272
|
+
|
|
273
|
+
if (isAsyncFn) insideAsync++;
|
|
274
|
+
if (node.type === "AwaitExpression" && insideAsync === 0) {
|
|
275
|
+
found = true;
|
|
276
|
+
}
|
|
277
|
+
if (node.type === "ForOfStatement" && node.await && insideAsync === 0) {
|
|
278
|
+
found = true;
|
|
279
|
+
}
|
|
280
|
+
if (!found) {
|
|
281
|
+
for (const key of Object.keys(node)) {
|
|
282
|
+
if (key === "type" || key === "start" || key === "end") continue;
|
|
283
|
+
const val = node[key];
|
|
284
|
+
if (val && typeof val === "object") walk(val);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (isAsyncFn) insideAsync--;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
walk(ast);
|
|
291
|
+
return found;
|
|
292
|
+
} catch {
|
|
293
|
+
// fallback: conservative regex
|
|
294
|
+
return /(?<![.\w])await\s+(?=[\w$("'\[`!~+\-/])/.test(code);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// strip top-level await and optionally de-async inner functions.
|
|
299
|
+
// "topLevelOnly": only replace top-level await with __syncAwait(), leave inner async intact.
|
|
300
|
+
// "full": strip async from all functions, replace ALL awaits with __syncAwait().
|
|
301
|
+
// full mode is needed for synchronous require() chains where async would wrap
|
|
302
|
+
// returns in native Promises that syncAwait() can't unwrap.
|
|
303
|
+
export function stripTopLevelAwait(
|
|
304
|
+
code: string,
|
|
305
|
+
mode: "topLevelOnly" | "full" = "topLevelOnly",
|
|
306
|
+
): string {
|
|
307
|
+
const full = mode === "full";
|
|
308
|
+
if (!full && !/\bawait\b/.test(code)) return code;
|
|
309
|
+
if (full && !/\bawait\b/.test(code) && !/\basync\b/.test(code)) return code;
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
let ast: any;
|
|
313
|
+
try {
|
|
314
|
+
ast = acorn.parse(code, {
|
|
315
|
+
ecmaVersion: "latest",
|
|
316
|
+
sourceType: "script",
|
|
317
|
+
allowAwaitOutsideFunction: true,
|
|
318
|
+
});
|
|
319
|
+
} catch {
|
|
320
|
+
ast = acorn.parse(code, {
|
|
321
|
+
ecmaVersion: "latest",
|
|
322
|
+
sourceType: "module",
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const patches: Array<[number, number, string]> = [];
|
|
327
|
+
let insideAsync = 0;
|
|
328
|
+
|
|
329
|
+
function walk(node: any) {
|
|
330
|
+
if (!node || typeof node !== "object") return;
|
|
331
|
+
|
|
332
|
+
if (Array.isArray(node)) {
|
|
333
|
+
for (const child of node) walk(child);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (typeof node.type !== "string") return;
|
|
338
|
+
|
|
339
|
+
const isAsyncFn =
|
|
340
|
+
(node.type === "FunctionDeclaration" ||
|
|
341
|
+
node.type === "FunctionExpression" ||
|
|
342
|
+
node.type === "ArrowFunctionExpression") &&
|
|
343
|
+
node.async;
|
|
344
|
+
|
|
345
|
+
if (isAsyncFn) insideAsync++;
|
|
346
|
+
|
|
347
|
+
// in full mode, strip async so functions return plain values (not native Promises).
|
|
348
|
+
// skip async generators -- yield semantics can't be replaced.
|
|
349
|
+
if (full && isAsyncFn && !node.generator) {
|
|
350
|
+
if (code.slice(node.start, node.start + 5) === "async") {
|
|
351
|
+
let end = node.start + 5;
|
|
352
|
+
while (end < code.length && (code[end] === " " || code[end] === "\t")) end++;
|
|
353
|
+
patches.push([node.start, end, ""]);
|
|
354
|
+
} else {
|
|
355
|
+
// method syntax: { async foo() {} }
|
|
356
|
+
const searchStart = Math.max(0, node.start - 30);
|
|
357
|
+
const region = code.slice(searchStart, node.start);
|
|
358
|
+
const asyncIdx = region.lastIndexOf("async");
|
|
359
|
+
if (asyncIdx >= 0) {
|
|
360
|
+
const absStart = searchStart + asyncIdx;
|
|
361
|
+
let absEnd = absStart + 5;
|
|
362
|
+
while (absEnd < code.length && (code[absEnd] === " " || code[absEnd] === "\t")) absEnd++;
|
|
363
|
+
patches.push([absStart, absEnd, ""]);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Replace only the `await` keyword + trailing whitespace. We can't use
|
|
369
|
+
// node.argument.start because acorn doesn't produce ParenthesizedExpression
|
|
370
|
+
// nodes, so `await (expr)` would swallow the opening paren.
|
|
371
|
+
if (node.type === "AwaitExpression") {
|
|
372
|
+
if (full || insideAsync === 0) {
|
|
373
|
+
let awaitEnd = node.start + 5; // "await" is 5 chars
|
|
374
|
+
while (
|
|
375
|
+
awaitEnd < node.argument.start &&
|
|
376
|
+
(code[awaitEnd] === " " || code[awaitEnd] === "\t" || code[awaitEnd] === "\n" || code[awaitEnd] === "\r")
|
|
377
|
+
) {
|
|
378
|
+
awaitEnd++;
|
|
379
|
+
}
|
|
380
|
+
patches.push([node.start, awaitEnd, "__syncAwait("]);
|
|
381
|
+
patches.push([node.end, node.end, ")"]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// strip await from `for await (...of ...)`
|
|
386
|
+
if (node.type === "ForOfStatement" && node.await) {
|
|
387
|
+
if (full || insideAsync === 0) {
|
|
388
|
+
const forEnd = node.start + 3;
|
|
389
|
+
const snippet = code.slice(forEnd, node.left.start);
|
|
390
|
+
const awIdx = snippet.indexOf("await");
|
|
391
|
+
if (awIdx >= 0) {
|
|
392
|
+
const absStart = forEnd + awIdx;
|
|
393
|
+
let absEnd = absStart + 5;
|
|
394
|
+
while (absEnd < code.length && code[absEnd] === " ") absEnd++;
|
|
395
|
+
patches.push([absStart, absEnd, ""]);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
for (const key of Object.keys(node)) {
|
|
401
|
+
if (key === "type" || key === "start" || key === "end") continue;
|
|
402
|
+
const val = node[key];
|
|
403
|
+
if (val && typeof val === "object") {
|
|
404
|
+
walk(val);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (isAsyncFn) insideAsync--;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
walk(ast);
|
|
412
|
+
|
|
413
|
+
if (patches.length === 0) return code;
|
|
414
|
+
|
|
415
|
+
let output = code;
|
|
416
|
+
patches.sort((a, b) => b[0] - a[0]);
|
|
417
|
+
for (const [start, end, replacement] of patches) {
|
|
418
|
+
output = output.slice(0, start) + replacement + output.slice(end);
|
|
419
|
+
}
|
|
420
|
+
return output;
|
|
421
|
+
} catch {
|
|
422
|
+
// regex fallback -- can't reliably add closing parens, best-effort strip
|
|
423
|
+
if (full) {
|
|
424
|
+
let out = code.replace(/(?<![.\w])await\s+(?=[\w$("'\[`!~+\-/])/g, "");
|
|
425
|
+
out = out.replace(/(?<![.\w])async\s+(?=function[\s*(])/g, "");
|
|
426
|
+
out = out.replace(/(?<![.\w])async\s+(?=\()/g, "");
|
|
427
|
+
out = out.replace(/(?<![.\w])async\s+(?=\w+\s*=>)/g, "");
|
|
428
|
+
return out;
|
|
429
|
+
}
|
|
430
|
+
// topLevelOnly: strip top-level await keyword
|
|
431
|
+
return code.replace(
|
|
432
|
+
/(?<![.\w])await\s+(?=[\w$("'\[`!~+\-/])/g,
|
|
433
|
+
"",
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function esmToCjsViaRegex(code: string): string {
|
|
439
|
+
let out = code;
|
|
440
|
+
// strip TS type-only imports
|
|
441
|
+
out = out.replace(
|
|
442
|
+
/import\s+type\s+\{[^}]*\}\s+from\s+['"][^'"]+['"]\s*;?/g,
|
|
443
|
+
"",
|
|
444
|
+
);
|
|
445
|
+
out = out.replace(
|
|
446
|
+
/import\s+type\s+\w+\s+from\s+['"][^'"]+['"]\s*;?/g,
|
|
447
|
+
"",
|
|
448
|
+
);
|
|
449
|
+
out = out.replace(
|
|
450
|
+
/import\s+type\s+\*\s+as\s+\w+\s+from\s+['"][^'"]+['"]\s*;?/g,
|
|
451
|
+
"",
|
|
452
|
+
);
|
|
453
|
+
// remove inline type specifiers from mixed imports
|
|
454
|
+
out = out.replace(
|
|
455
|
+
/import\s+\{([^}]*\btype\s+\w+[^}]*)\}\s+from\s+['"]([^'"]+)['"]\s*;?/g,
|
|
456
|
+
(_m, specs: string, src: string) => {
|
|
457
|
+
const kept = specs
|
|
458
|
+
.split(",")
|
|
459
|
+
.filter((s: string) => !/^\s*type\s+\w+/.test(s))
|
|
460
|
+
.map((s: string) => s.trim())
|
|
461
|
+
.filter(Boolean);
|
|
462
|
+
if (kept.length === 0) return "";
|
|
463
|
+
const fixed = kept.join(", ").replace(/(\w+)\s+as\s+(\w+)/g, "$1: $2");
|
|
464
|
+
return `const {${fixed}} = require("${src}");`;
|
|
465
|
+
},
|
|
466
|
+
);
|
|
467
|
+
// strip TS type-only exports
|
|
468
|
+
out = out.replace(
|
|
469
|
+
/export\s+type\s+\{[^}]*\}\s+from\s+['"][^'"]+['"]\s*;?/g,
|
|
470
|
+
"",
|
|
471
|
+
);
|
|
472
|
+
out = out.replace(
|
|
473
|
+
/export\s+type\s+\{[^}]*\}\s*;?/g,
|
|
474
|
+
"",
|
|
475
|
+
);
|
|
476
|
+
out = out.replace(
|
|
477
|
+
/import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?/g,
|
|
478
|
+
'const $1 = require("$2");',
|
|
479
|
+
);
|
|
480
|
+
out = out.replace(
|
|
481
|
+
/import\s+(\w+)\s*,\s*\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]\s*;?/g,
|
|
482
|
+
(_m, def, named, src) => {
|
|
483
|
+
const tmp = `__import_${def}`;
|
|
484
|
+
const fixed = named.replace(/(\w+)\s+as\s+(\w+)/g, "$1: $2");
|
|
485
|
+
return `const ${tmp} = require("${src}"); const ${def} = ${tmp}.__esModule ? ${tmp}.default : ${tmp}; const {${fixed}} = ${tmp};`;
|
|
486
|
+
},
|
|
487
|
+
);
|
|
488
|
+
out = out.replace(
|
|
489
|
+
/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?/g,
|
|
490
|
+
'const $1 = require("$2");',
|
|
491
|
+
);
|
|
492
|
+
out = out.replace(
|
|
493
|
+
/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]\s*;?/g,
|
|
494
|
+
(_m, specs, src) => {
|
|
495
|
+
const fixed = specs.replace(/(\w+)\s+as\s+(\w+)/g, "$1: $2");
|
|
496
|
+
return `const {${fixed}} = require("${src}");`;
|
|
497
|
+
},
|
|
498
|
+
);
|
|
499
|
+
out = out.replace(
|
|
500
|
+
/import\s+['"]([^'"]+)['"]\s*;?/g,
|
|
501
|
+
'require("$1");',
|
|
502
|
+
);
|
|
503
|
+
// export default
|
|
504
|
+
out = out.replace(
|
|
505
|
+
/export\s+default\s+class\s+(\w+)/g,
|
|
506
|
+
"module.exports = class $1",
|
|
507
|
+
);
|
|
508
|
+
out = out.replace(
|
|
509
|
+
/export\s+default\s+function\s+(\w+)/g,
|
|
510
|
+
"module.exports = function $1",
|
|
511
|
+
);
|
|
512
|
+
out = out.replace(
|
|
513
|
+
/export\s+default\s+function\s*\(/g,
|
|
514
|
+
"module.exports = function(",
|
|
515
|
+
);
|
|
516
|
+
out = out.replace(/export\s+default\s+/g, "module.exports = ");
|
|
517
|
+
// re-exports
|
|
518
|
+
out = out.replace(
|
|
519
|
+
/export\s+\*\s+from\s+['"]([^'"]+)['"]\s*;?/g,
|
|
520
|
+
'Object.assign(exports, require("$1"));',
|
|
521
|
+
);
|
|
522
|
+
out = out.replace(
|
|
523
|
+
/export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]\s*;?/g,
|
|
524
|
+
(_m, specs, src) => {
|
|
525
|
+
const binds = specs
|
|
526
|
+
.split(",")
|
|
527
|
+
.map((s: string) => {
|
|
528
|
+
const parts = s.trim().split(/\s+as\s+/);
|
|
529
|
+
const local = parts[0].trim();
|
|
530
|
+
const exported = parts.length > 1 ? parts[1].trim() : local;
|
|
531
|
+
return `exports.${exported} = require("${src}").${local}`;
|
|
532
|
+
})
|
|
533
|
+
.join("; ");
|
|
534
|
+
return binds + ";";
|
|
535
|
+
},
|
|
536
|
+
);
|
|
537
|
+
out = out.replace(
|
|
538
|
+
/export\s+\{([^}]+)\}\s*;?/g,
|
|
539
|
+
(_m, specs) => {
|
|
540
|
+
const binds = specs
|
|
541
|
+
.split(",")
|
|
542
|
+
.map((s: string) => {
|
|
543
|
+
const parts = s.trim().split(/\s+as\s+/);
|
|
544
|
+
const local = parts[0].trim();
|
|
545
|
+
const exported = parts.length > 1 ? parts[1].trim() : local;
|
|
546
|
+
return `exports.${exported} = ${local}`;
|
|
547
|
+
})
|
|
548
|
+
.join("; ");
|
|
549
|
+
return binds + ";";
|
|
550
|
+
},
|
|
551
|
+
);
|
|
552
|
+
// named exports
|
|
553
|
+
out = out.replace(
|
|
554
|
+
/export\s+async\s+function\s+(\w+)/g,
|
|
555
|
+
"exports.$1 = async function $1",
|
|
556
|
+
);
|
|
557
|
+
out = out.replace(/export\s+function\s+(\w+)/g, "exports.$1 = function $1");
|
|
558
|
+
out = out.replace(/export\s+class\s+(\w+)/g, "exports.$1 = class $1");
|
|
559
|
+
out = out.replace(/export\s+(?:const|let|var)\s+(\w+)\s*=/g, "exports.$1 =");
|
|
560
|
+
return out;
|
|
561
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Web Worker entry point for ScriptEngine.
|
|
2
|
+
// Receives execution requests via Comlink, runs them in an isolated thread.
|
|
3
|
+
|
|
4
|
+
import { expose } from 'comlink';
|
|
5
|
+
import { MemoryVolume } from '../memory-volume';
|
|
6
|
+
import { ScriptEngine } from '../script-engine';
|
|
7
|
+
import type { VolumeSnapshot, EngineConfig, ExecutionOutcome } from '../engine-types';
|
|
8
|
+
|
|
9
|
+
let engine: ScriptEngine | null = null;
|
|
10
|
+
let volume: MemoryVolume | null = null;
|
|
11
|
+
let consoleForwarder: ((method: string, args: unknown[]) => void) | null = null;
|
|
12
|
+
|
|
13
|
+
const workerInterface = {
|
|
14
|
+
init(snapshot: VolumeSnapshot, config: EngineConfig): void {
|
|
15
|
+
volume = MemoryVolume.fromSnapshot(snapshot);
|
|
16
|
+
|
|
17
|
+
const engineConfig: EngineConfig = {
|
|
18
|
+
...config,
|
|
19
|
+
onConsole: (method, args) => {
|
|
20
|
+
if (consoleForwarder) consoleForwarder(method, args);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
engine = new ScriptEngine(volume, engineConfig);
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
setConsoleForwarder(cb: ((method: string, args: unknown[]) => void) | null): void {
|
|
28
|
+
consoleForwarder = cb;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
syncFile(filePath: string, content: string | null): void {
|
|
32
|
+
if (!volume) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (content === null) {
|
|
37
|
+
try { volume.unlinkSync(filePath); } catch { /* may not exist */ }
|
|
38
|
+
} else {
|
|
39
|
+
volume.writeFileSync(filePath, content);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (engine) engine.clearCache();
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async execute(code: string, filename?: string): Promise<ExecutionOutcome> {
|
|
46
|
+
if (!engine) throw new Error('Worker engine not initialized. Call init() first.');
|
|
47
|
+
return engine.execute(code, filename);
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async runFile(filename: string): Promise<ExecutionOutcome> {
|
|
51
|
+
if (!engine) throw new Error('Worker engine not initialized. Call init() first.');
|
|
52
|
+
return engine.runFile(filename);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
clearCache(): void {
|
|
56
|
+
if (engine) engine.clearCache();
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
getSnapshot(): VolumeSnapshot | null {
|
|
60
|
+
return volume?.toSnapshot() ?? null;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
expose(workerInterface);
|