@secure-exec/nodejs 0.2.0-rc.1
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 +191 -0
- package/README.md +7 -0
- package/dist/bindings.d.ts +31 -0
- package/dist/bindings.js +67 -0
- package/dist/bridge/active-handles.d.ts +22 -0
- package/dist/bridge/active-handles.js +112 -0
- package/dist/bridge/child-process.d.ts +99 -0
- package/dist/bridge/child-process.js +672 -0
- package/dist/bridge/dispatch.d.ts +2 -0
- package/dist/bridge/dispatch.js +40 -0
- package/dist/bridge/fs.d.ts +502 -0
- package/dist/bridge/fs.js +3307 -0
- package/dist/bridge/index.d.ts +10 -0
- package/dist/bridge/index.js +41 -0
- package/dist/bridge/module.d.ts +75 -0
- package/dist/bridge/module.js +325 -0
- package/dist/bridge/network.d.ts +1093 -0
- package/dist/bridge/network.js +8651 -0
- package/dist/bridge/os.d.ts +13 -0
- package/dist/bridge/os.js +256 -0
- package/dist/bridge/polyfills.d.ts +9 -0
- package/dist/bridge/polyfills.js +67 -0
- package/dist/bridge/process.d.ts +121 -0
- package/dist/bridge/process.js +1382 -0
- package/dist/bridge/whatwg-url.d.ts +67 -0
- package/dist/bridge/whatwg-url.js +712 -0
- package/dist/bridge-contract.d.ts +774 -0
- package/dist/bridge-contract.js +172 -0
- package/dist/bridge-handlers.d.ts +199 -0
- package/dist/bridge-handlers.js +4263 -0
- package/dist/bridge-loader.d.ts +9 -0
- package/dist/bridge-loader.js +87 -0
- package/dist/bridge-setup.d.ts +1 -0
- package/dist/bridge-setup.js +3 -0
- package/dist/bridge.js +21652 -0
- package/dist/builtin-modules.d.ts +25 -0
- package/dist/builtin-modules.js +312 -0
- package/dist/default-network-adapter.d.ts +13 -0
- package/dist/default-network-adapter.js +351 -0
- package/dist/driver.d.ts +87 -0
- package/dist/driver.js +191 -0
- package/dist/esm-compiler.d.ts +14 -0
- package/dist/esm-compiler.js +68 -0
- package/dist/execution-driver.d.ts +37 -0
- package/dist/execution-driver.js +977 -0
- package/dist/host-network-adapter.d.ts +7 -0
- package/dist/host-network-adapter.js +279 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +23 -0
- package/dist/isolate-bootstrap.d.ts +86 -0
- package/dist/isolate-bootstrap.js +125 -0
- package/dist/ivm-compat.d.ts +7 -0
- package/dist/ivm-compat.js +31 -0
- package/dist/kernel-runtime.d.ts +58 -0
- package/dist/kernel-runtime.js +535 -0
- package/dist/module-access.d.ts +75 -0
- package/dist/module-access.js +606 -0
- package/dist/module-resolver.d.ts +8 -0
- package/dist/module-resolver.js +150 -0
- package/dist/os-filesystem.d.ts +42 -0
- package/dist/os-filesystem.js +161 -0
- package/dist/package-bundler.d.ts +36 -0
- package/dist/package-bundler.js +497 -0
- package/dist/polyfills.d.ts +17 -0
- package/dist/polyfills.js +97 -0
- package/dist/worker-adapter.d.ts +21 -0
- package/dist/worker-adapter.js +34 -0
- package/package.json +123 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
// Path utilities (since we can't use node:path in a way that works in isolate)
|
|
2
|
+
function dirname(p) {
|
|
3
|
+
const lastSlash = p.lastIndexOf("/");
|
|
4
|
+
if (lastSlash === -1)
|
|
5
|
+
return ".";
|
|
6
|
+
if (lastSlash === 0)
|
|
7
|
+
return "/";
|
|
8
|
+
return p.slice(0, lastSlash);
|
|
9
|
+
}
|
|
10
|
+
function join(...parts) {
|
|
11
|
+
const segments = [];
|
|
12
|
+
for (const part of parts) {
|
|
13
|
+
if (part.startsWith("/")) {
|
|
14
|
+
segments.length = 0;
|
|
15
|
+
}
|
|
16
|
+
for (const seg of part.split("/")) {
|
|
17
|
+
if (seg === "..") {
|
|
18
|
+
segments.pop();
|
|
19
|
+
}
|
|
20
|
+
else if (seg && seg !== ".") {
|
|
21
|
+
segments.push(seg);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return `/${segments.join("/")}`;
|
|
26
|
+
}
|
|
27
|
+
const FILE_EXTENSIONS = [".js", ".json", ".mjs", ".cjs"];
|
|
28
|
+
export function createResolutionCache() {
|
|
29
|
+
return {
|
|
30
|
+
resolveResults: new Map(),
|
|
31
|
+
packageJsonResults: new Map(),
|
|
32
|
+
existsResults: new Map(),
|
|
33
|
+
statResults: new Map(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve a module request to an absolute path in the virtual filesystem
|
|
38
|
+
*/
|
|
39
|
+
export async function resolveModule(request, fromDir, fs, mode = "require", cache) {
|
|
40
|
+
// Check top-level cache
|
|
41
|
+
if (cache) {
|
|
42
|
+
const cacheKey = `${request}\0${fromDir}\0${mode}`;
|
|
43
|
+
if (cache.resolveResults.has(cacheKey)) {
|
|
44
|
+
return cache.resolveResults.get(cacheKey);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let result;
|
|
48
|
+
// Absolute paths - resolve directly
|
|
49
|
+
if (request.startsWith("/")) {
|
|
50
|
+
result = await resolveAbsolute(request, fs, mode, cache);
|
|
51
|
+
}
|
|
52
|
+
else if (
|
|
53
|
+
// Relative imports (including bare '.' and '..')
|
|
54
|
+
request.startsWith("./") ||
|
|
55
|
+
request.startsWith("../") ||
|
|
56
|
+
request === "." ||
|
|
57
|
+
request === "..") {
|
|
58
|
+
result = await resolveRelative(request, fromDir, fs, mode, cache);
|
|
59
|
+
}
|
|
60
|
+
else if (request.startsWith("#")) {
|
|
61
|
+
// Package import maps, e.g. "#dev"
|
|
62
|
+
result = await resolvePackageImports(request, fromDir, fs, mode, cache);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Bare imports - walk up node_modules
|
|
66
|
+
result = await resolveNodeModules(request, fromDir, fs, mode, cache);
|
|
67
|
+
}
|
|
68
|
+
// Store in top-level cache
|
|
69
|
+
if (cache) {
|
|
70
|
+
const cacheKey = `${request}\0${fromDir}\0${mode}`;
|
|
71
|
+
cache.resolveResults.set(cacheKey, result);
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
/** Resolve `#`-prefixed import-map specifiers by walking up to find the nearest package.json with `imports`. */
|
|
76
|
+
async function resolvePackageImports(request, fromDir, fs, mode, cache) {
|
|
77
|
+
let dir = fromDir;
|
|
78
|
+
while (dir !== "" && dir !== ".") {
|
|
79
|
+
const pkgJsonPath = join(dir, "package.json");
|
|
80
|
+
const pkgJson = await readPackageJson(fs, pkgJsonPath, cache);
|
|
81
|
+
if (pkgJson?.imports !== undefined) {
|
|
82
|
+
const target = resolveImportsTarget(pkgJson.imports, request, mode);
|
|
83
|
+
if (!target) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (target.startsWith("#")) {
|
|
87
|
+
// Avoid recursive import-map loops.
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const targetPath = target.startsWith("/")
|
|
91
|
+
? target
|
|
92
|
+
: join(dir, normalizePackagePath(target));
|
|
93
|
+
return resolvePath(targetPath, fs, mode, cache);
|
|
94
|
+
}
|
|
95
|
+
if (dir === "/") {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
dir = dirname(dir);
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve an absolute path
|
|
104
|
+
*/
|
|
105
|
+
async function resolveAbsolute(request, fs, mode, cache) {
|
|
106
|
+
return resolvePath(request, fs, mode, cache);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a relative import
|
|
110
|
+
*/
|
|
111
|
+
async function resolveRelative(request, fromDir, fs, mode, cache) {
|
|
112
|
+
const basePath = join(fromDir, request);
|
|
113
|
+
return resolvePath(basePath, fs, mode, cache);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a bare module import by walking up node_modules
|
|
117
|
+
*/
|
|
118
|
+
/** Walk up from `fromDir` checking `node_modules/` (including pnpm virtual-store layouts) for the package. */
|
|
119
|
+
async function resolveNodeModules(request, fromDir, fs, mode, cache) {
|
|
120
|
+
// Handle scoped packages: @scope/package
|
|
121
|
+
let packageName;
|
|
122
|
+
let subpath;
|
|
123
|
+
if (request.startsWith("@")) {
|
|
124
|
+
// Scoped package: @scope/package or @scope/package/subpath
|
|
125
|
+
const parts = request.split("/");
|
|
126
|
+
if (parts.length >= 2) {
|
|
127
|
+
packageName = `${parts[0]}/${parts[1]}`;
|
|
128
|
+
subpath = parts.slice(2).join("/");
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Regular package: package or package/subpath
|
|
136
|
+
const slashIndex = request.indexOf("/");
|
|
137
|
+
if (slashIndex === -1) {
|
|
138
|
+
packageName = request;
|
|
139
|
+
subpath = "";
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
packageName = request.slice(0, slashIndex);
|
|
143
|
+
subpath = request.slice(slashIndex + 1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
let dir = fromDir;
|
|
147
|
+
while (dir !== "" && dir !== ".") {
|
|
148
|
+
const candidatePackageDirs = getNodeModulesCandidatePackageDirs(dir, packageName);
|
|
149
|
+
for (const packageDir of candidatePackageDirs) {
|
|
150
|
+
let entry;
|
|
151
|
+
try {
|
|
152
|
+
entry = await resolvePackageEntryFromDir(packageDir, subpath, fs, mode, cache);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (isPermissionProbeError(error)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
if (entry) {
|
|
161
|
+
return entry;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (dir === "/")
|
|
165
|
+
break;
|
|
166
|
+
dir = dirname(dir);
|
|
167
|
+
}
|
|
168
|
+
// Also check root node_modules
|
|
169
|
+
const rootPackageDir = join("/node_modules", packageName);
|
|
170
|
+
let rootEntry;
|
|
171
|
+
try {
|
|
172
|
+
rootEntry = await resolvePackageEntryFromDir(rootPackageDir, subpath, fs, mode, cache);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (isPermissionProbeError(error)) {
|
|
176
|
+
rootEntry = null;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (rootEntry) {
|
|
183
|
+
return rootEntry;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
function getNodeModulesCandidatePackageDirs(dir, packageName) {
|
|
188
|
+
const candidates = new Set();
|
|
189
|
+
candidates.add(join(dir, "node_modules", packageName));
|
|
190
|
+
candidates.add(join(dir, "node_modules", ".pnpm", "node_modules", packageName));
|
|
191
|
+
// Match Node's "parent node_modules" lookup when the current directory is
|
|
192
|
+
// already a node_modules folder.
|
|
193
|
+
if (dir === "/node_modules" || dir.endsWith("/node_modules")) {
|
|
194
|
+
candidates.add(join(dir, packageName));
|
|
195
|
+
}
|
|
196
|
+
// Support pnpm virtual-store layouts where transitive dependencies are linked
|
|
197
|
+
// under <root>/node_modules/.pnpm/node_modules.
|
|
198
|
+
const nodeModulesSegment = "/node_modules/";
|
|
199
|
+
const nodeModulesIndex = dir.lastIndexOf(nodeModulesSegment);
|
|
200
|
+
if (nodeModulesIndex !== -1) {
|
|
201
|
+
const nodeModulesRoot = dir.slice(0, nodeModulesIndex + nodeModulesSegment.length - 1);
|
|
202
|
+
candidates.add(join(nodeModulesRoot, ".pnpm", "node_modules", packageName));
|
|
203
|
+
}
|
|
204
|
+
return Array.from(candidates);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Given a package directory and optional subpath, resolve the entry file using
|
|
208
|
+
* `exports` map (if present), then `main`, then `index.js` fallback. When
|
|
209
|
+
* `exports` is defined, no fallback to `main` occurs (Node.js semantics).
|
|
210
|
+
*/
|
|
211
|
+
async function resolvePackageEntryFromDir(packageDir, subpath, fs, mode, cache) {
|
|
212
|
+
const pkgJsonPath = join(packageDir, "package.json");
|
|
213
|
+
const pkgJson = await readPackageJson(fs, pkgJsonPath, cache);
|
|
214
|
+
if (!pkgJson && !(await cachedSafeExists(fs, packageDir, cache))) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
// If package uses "exports", follow it and do not fall back to main/subpath
|
|
218
|
+
if (pkgJson?.exports !== undefined) {
|
|
219
|
+
const exportsTarget = resolveExportsTarget(pkgJson.exports, subpath ? `./${subpath}` : ".", mode);
|
|
220
|
+
if (!exportsTarget) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const targetPath = join(packageDir, normalizePackagePath(exportsTarget));
|
|
224
|
+
const resolvedTarget = await resolvePath(targetPath, fs, mode, cache);
|
|
225
|
+
return resolvedTarget ?? targetPath;
|
|
226
|
+
}
|
|
227
|
+
// Bare subpath import without exports map: package/sub/path
|
|
228
|
+
if (subpath) {
|
|
229
|
+
return resolvePath(join(packageDir, subpath), fs, mode, cache);
|
|
230
|
+
}
|
|
231
|
+
// Root package import
|
|
232
|
+
const entryField = getPackageEntryField(pkgJson, mode);
|
|
233
|
+
if (entryField) {
|
|
234
|
+
const entryPath = join(packageDir, normalizePackagePath(entryField));
|
|
235
|
+
const resolved = await resolvePath(entryPath, fs, mode, cache);
|
|
236
|
+
if (resolved)
|
|
237
|
+
return resolved;
|
|
238
|
+
if (pkgJson) {
|
|
239
|
+
return entryPath;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Default fallback
|
|
243
|
+
return resolvePath(join(packageDir, "index"), fs, mode, cache);
|
|
244
|
+
}
|
|
245
|
+
async function resolvePath(basePath, fs, mode, cache) {
|
|
246
|
+
let isDirectory = false;
|
|
247
|
+
// Use cached stat when available
|
|
248
|
+
const statResult = await cachedStat(fs, basePath, cache);
|
|
249
|
+
if (statResult !== null) {
|
|
250
|
+
if (!statResult.isDirectory) {
|
|
251
|
+
return basePath;
|
|
252
|
+
}
|
|
253
|
+
isDirectory = true;
|
|
254
|
+
}
|
|
255
|
+
// For extensionless specifiers, try files before directory resolution.
|
|
256
|
+
for (const ext of FILE_EXTENSIONS) {
|
|
257
|
+
const withExt = `${basePath}${ext}`;
|
|
258
|
+
if (await cachedSafeExists(fs, withExt, cache)) {
|
|
259
|
+
return withExt;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (isDirectory) {
|
|
263
|
+
const pkgJsonPath = join(basePath, "package.json");
|
|
264
|
+
const pkgJson = await readPackageJson(fs, pkgJsonPath, cache);
|
|
265
|
+
const entryField = getPackageEntryField(pkgJson, mode);
|
|
266
|
+
if (entryField) {
|
|
267
|
+
const entryPath = join(basePath, normalizePackagePath(entryField));
|
|
268
|
+
// Avoid directory self-reference loops like "main": "."
|
|
269
|
+
if (entryPath !== basePath) {
|
|
270
|
+
const entry = await resolvePath(entryPath, fs, mode, cache);
|
|
271
|
+
if (entry)
|
|
272
|
+
return entry;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
for (const ext of FILE_EXTENSIONS) {
|
|
276
|
+
const indexPath = join(basePath, `index${ext}`);
|
|
277
|
+
if (await cachedSafeExists(fs, indexPath, cache)) {
|
|
278
|
+
return indexPath;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
async function readPackageJson(fs, pkgJsonPath, cache) {
|
|
285
|
+
if (cache?.packageJsonResults.has(pkgJsonPath)) {
|
|
286
|
+
return cache.packageJsonResults.get(pkgJsonPath);
|
|
287
|
+
}
|
|
288
|
+
if (!(await cachedSafeExists(fs, pkgJsonPath, cache))) {
|
|
289
|
+
cache?.packageJsonResults.set(pkgJsonPath, null);
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
const result = JSON.parse(await fs.readTextFile(pkgJsonPath));
|
|
294
|
+
cache?.packageJsonResults.set(pkgJsonPath, result);
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
cache?.packageJsonResults.set(pkgJsonPath, null);
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/** Treat EACCES/EPERM as "path not available" during resolution probing. */
|
|
303
|
+
function isPermissionProbeError(error) {
|
|
304
|
+
const err = error;
|
|
305
|
+
return err?.code === "EACCES" || err?.code === "EPERM";
|
|
306
|
+
}
|
|
307
|
+
async function safeExists(fs, path) {
|
|
308
|
+
try {
|
|
309
|
+
return await fs.exists(path);
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
if (isPermissionProbeError(error)) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** Cached wrapper around safeExists — avoids repeated VFS probes for the same path. */
|
|
319
|
+
async function cachedSafeExists(fs, path, cache) {
|
|
320
|
+
if (cache?.existsResults.has(path)) {
|
|
321
|
+
return cache.existsResults.get(path);
|
|
322
|
+
}
|
|
323
|
+
const result = await safeExists(fs, path);
|
|
324
|
+
cache?.existsResults.set(path, result);
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
/** Cached stat — returns { isDirectory } or null for ENOENT. */
|
|
328
|
+
async function cachedStat(fs, path, cache) {
|
|
329
|
+
if (cache?.statResults.has(path)) {
|
|
330
|
+
return cache.statResults.get(path);
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const statInfo = await fs.stat(path);
|
|
334
|
+
const result = { isDirectory: statInfo.isDirectory };
|
|
335
|
+
cache?.statResults.set(path, result);
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
const err = error;
|
|
340
|
+
if (err?.code && err.code !== "ENOENT") {
|
|
341
|
+
throw err;
|
|
342
|
+
}
|
|
343
|
+
cache?.statResults.set(path, null);
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function normalizePackagePath(value) {
|
|
348
|
+
return value.replace(/^\.\//, "").replace(/\/$/, "");
|
|
349
|
+
}
|
|
350
|
+
function getPackageEntryField(pkgJson, _mode) {
|
|
351
|
+
if (!pkgJson)
|
|
352
|
+
return "index.js";
|
|
353
|
+
// Match Node's package entrypoint precedence when exports is absent.
|
|
354
|
+
if (typeof pkgJson.main === "string")
|
|
355
|
+
return pkgJson.main;
|
|
356
|
+
return "index.js";
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Implement Node.js `package.json` "exports" resolution. Handles string, array,
|
|
360
|
+
* conditions-object, subpath keys, and wildcard `*` patterns.
|
|
361
|
+
*/
|
|
362
|
+
function resolveExportsTarget(exportsField, subpath, mode) {
|
|
363
|
+
// "exports": "./dist/index.js"
|
|
364
|
+
if (typeof exportsField === "string") {
|
|
365
|
+
return subpath === "." ? exportsField : null;
|
|
366
|
+
}
|
|
367
|
+
// "exports": ["./a.js", "./b.js"]
|
|
368
|
+
if (Array.isArray(exportsField)) {
|
|
369
|
+
for (const item of exportsField) {
|
|
370
|
+
const resolved = resolveExportsTarget(item, subpath, mode);
|
|
371
|
+
if (resolved)
|
|
372
|
+
return resolved;
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
if (!exportsField || typeof exportsField !== "object") {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
const record = exportsField;
|
|
380
|
+
// Root conditions object (no "./" keys)
|
|
381
|
+
if (subpath === "." && !Object.keys(record).some((key) => key.startsWith("./"))) {
|
|
382
|
+
return resolveConditionalTarget(record, mode);
|
|
383
|
+
}
|
|
384
|
+
// Exact subpath key first
|
|
385
|
+
if (subpath in record) {
|
|
386
|
+
return resolveExportsTarget(record[subpath], ".", mode);
|
|
387
|
+
}
|
|
388
|
+
// Pattern keys like "./*"
|
|
389
|
+
for (const [key, value] of Object.entries(record)) {
|
|
390
|
+
if (!key.includes("*"))
|
|
391
|
+
continue;
|
|
392
|
+
const [prefix, suffix] = key.split("*");
|
|
393
|
+
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix))
|
|
394
|
+
continue;
|
|
395
|
+
const wildcard = subpath.slice(prefix.length, subpath.length - suffix.length);
|
|
396
|
+
const resolved = resolveExportsTarget(value, ".", mode);
|
|
397
|
+
if (!resolved)
|
|
398
|
+
continue;
|
|
399
|
+
return resolved.replaceAll("*", wildcard);
|
|
400
|
+
}
|
|
401
|
+
// Root key may still be present in object with subpaths
|
|
402
|
+
if (subpath === "." && "." in record) {
|
|
403
|
+
return resolveExportsTarget(record["."], ".", mode);
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
/** Pick the first matching condition key (import/require/node/default) from an exports conditions object. */
|
|
408
|
+
function resolveConditionalTarget(record, mode) {
|
|
409
|
+
const order = mode === "import"
|
|
410
|
+
? ["import", "node", "module", "default", "require"]
|
|
411
|
+
: ["require", "node", "default", "import", "module"];
|
|
412
|
+
for (const key of order) {
|
|
413
|
+
if (!(key in record))
|
|
414
|
+
continue;
|
|
415
|
+
const resolved = resolveExportsTarget(record[key], ".", mode);
|
|
416
|
+
if (resolved)
|
|
417
|
+
return resolved;
|
|
418
|
+
}
|
|
419
|
+
// Last resort: first key that resolves
|
|
420
|
+
for (const value of Object.values(record)) {
|
|
421
|
+
const resolved = resolveExportsTarget(value, ".", mode);
|
|
422
|
+
if (resolved)
|
|
423
|
+
return resolved;
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
/** Resolve a `#`-prefixed specifier against a package.json `imports` field, including wildcard patterns. */
|
|
428
|
+
function resolveImportsTarget(importsField, specifier, mode) {
|
|
429
|
+
if (typeof importsField === "string") {
|
|
430
|
+
return importsField;
|
|
431
|
+
}
|
|
432
|
+
if (Array.isArray(importsField)) {
|
|
433
|
+
for (const item of importsField) {
|
|
434
|
+
const resolved = resolveImportsTarget(item, specifier, mode);
|
|
435
|
+
if (resolved) {
|
|
436
|
+
return resolved;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
if (!importsField || typeof importsField !== "object") {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
const record = importsField;
|
|
445
|
+
if (specifier in record) {
|
|
446
|
+
return resolveExportsTarget(record[specifier], ".", mode);
|
|
447
|
+
}
|
|
448
|
+
for (const [key, value] of Object.entries(record)) {
|
|
449
|
+
if (!key.includes("*"))
|
|
450
|
+
continue;
|
|
451
|
+
const [prefix, suffix] = key.split("*");
|
|
452
|
+
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix))
|
|
453
|
+
continue;
|
|
454
|
+
const wildcard = specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
455
|
+
const resolved = resolveExportsTarget(value, ".", mode);
|
|
456
|
+
if (!resolved)
|
|
457
|
+
continue;
|
|
458
|
+
return resolved.replaceAll("*", wildcard);
|
|
459
|
+
}
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Load a file's content from the virtual filesystem
|
|
464
|
+
*/
|
|
465
|
+
export async function loadFile(path, fs) {
|
|
466
|
+
try {
|
|
467
|
+
return await fs.readTextFile(path);
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Legacy function - bundle a package from node_modules (simple approach)
|
|
475
|
+
* This is kept for backwards compatibility but the new dynamic resolution is preferred
|
|
476
|
+
*/
|
|
477
|
+
export async function bundlePackage(packageName, fs) {
|
|
478
|
+
// Resolve the package entry point
|
|
479
|
+
const entryPath = await resolveNodeModules(packageName, "/", fs, "require");
|
|
480
|
+
if (!entryPath) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
const entryCode = await fs.readTextFile(entryPath);
|
|
485
|
+
// Wrap the code in an IIFE that sets up module.exports
|
|
486
|
+
const wrappedCode = `(function() {
|
|
487
|
+
var module = { exports: {} };
|
|
488
|
+
var exports = module.exports;
|
|
489
|
+
${entryCode}
|
|
490
|
+
return module.exports;
|
|
491
|
+
})()`;
|
|
492
|
+
return wrappedCode;
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundle a stdlib polyfill module using esbuild
|
|
3
|
+
*/
|
|
4
|
+
export declare function bundlePolyfill(moduleName: string): Promise<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Get all available stdlib modules (those with non-null polyfills)
|
|
7
|
+
*/
|
|
8
|
+
export declare function getAvailableStdlib(): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Check if a module has a polyfill available
|
|
11
|
+
* Note: fs returns null from node-stdlib-browser since we provide our own implementation
|
|
12
|
+
*/
|
|
13
|
+
export declare function hasPolyfill(moduleName: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Pre-bundle all polyfills (for faster startup)
|
|
16
|
+
*/
|
|
17
|
+
export declare function prebundleAllPolyfills(): Promise<Map<string, string>>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as esbuild from "esbuild";
|
|
2
|
+
import stdLibBrowser from "node-stdlib-browser";
|
|
3
|
+
// Cache bundled polyfills
|
|
4
|
+
const polyfillCache = new Map();
|
|
5
|
+
// node-stdlib-browser provides the mapping from Node.js stdlib to polyfill paths
|
|
6
|
+
// e.g., { path: "/path/to/path-browserify/index.js", fs: null, ... }
|
|
7
|
+
// We use this mapping instead of maintaining our own
|
|
8
|
+
/**
|
|
9
|
+
* Bundle a stdlib polyfill module using esbuild
|
|
10
|
+
*/
|
|
11
|
+
export async function bundlePolyfill(moduleName) {
|
|
12
|
+
const cached = polyfillCache.get(moduleName);
|
|
13
|
+
if (cached)
|
|
14
|
+
return cached;
|
|
15
|
+
// Get the polyfill entry point from node-stdlib-browser
|
|
16
|
+
const entryPoint = stdLibBrowser[moduleName];
|
|
17
|
+
if (!entryPoint) {
|
|
18
|
+
throw new Error(`No polyfill available for module: ${moduleName}`);
|
|
19
|
+
}
|
|
20
|
+
// Build alias mappings for all Node.js builtins
|
|
21
|
+
// This ensures nested dependencies (like crypto -> stream) are resolved correctly
|
|
22
|
+
const alias = {};
|
|
23
|
+
for (const [name, path] of Object.entries(stdLibBrowser)) {
|
|
24
|
+
if (path !== null) {
|
|
25
|
+
alias[name] = path;
|
|
26
|
+
alias[`node:${name}`] = path;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Bundle using esbuild with CommonJS format
|
|
30
|
+
// This ensures proper module.exports handling for all module types including JSON
|
|
31
|
+
const result = await esbuild.build({
|
|
32
|
+
entryPoints: [entryPoint],
|
|
33
|
+
bundle: true,
|
|
34
|
+
write: false,
|
|
35
|
+
format: "cjs",
|
|
36
|
+
platform: "browser",
|
|
37
|
+
target: "es2020",
|
|
38
|
+
minify: false,
|
|
39
|
+
alias,
|
|
40
|
+
define: {
|
|
41
|
+
"process.env.NODE_ENV": '"production"',
|
|
42
|
+
global: "globalThis",
|
|
43
|
+
},
|
|
44
|
+
// Externalize 'process' - we provide our own process polyfill in the bridge.
|
|
45
|
+
// Without this, node-stdlib-browser's process polyfill gets bundled and
|
|
46
|
+
// overwrites globalThis.process, breaking process.argv modifications.
|
|
47
|
+
external: ["process"],
|
|
48
|
+
});
|
|
49
|
+
const code = result.outputFiles[0].text;
|
|
50
|
+
// Check if this is a JSON module (esbuild creates *_default but doesn't export it)
|
|
51
|
+
// For JSON modules, look for the default export pattern and extract it
|
|
52
|
+
const defaultExportMatch = code.match(/var\s+(\w+_default)\s*=\s*\{/);
|
|
53
|
+
let wrappedCode;
|
|
54
|
+
if (defaultExportMatch && !code.includes("module.exports")) {
|
|
55
|
+
// JSON module: wrap and return the default export object
|
|
56
|
+
const defaultVar = defaultExportMatch[1];
|
|
57
|
+
wrappedCode = `(function() {
|
|
58
|
+
${code}
|
|
59
|
+
return ${defaultVar};
|
|
60
|
+
})()`;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Regular CommonJS module: wrap and return module.exports
|
|
64
|
+
wrappedCode = `(function() {
|
|
65
|
+
var module = { exports: {} };
|
|
66
|
+
var exports = module.exports;
|
|
67
|
+
${code}
|
|
68
|
+
return module.exports;
|
|
69
|
+
})()`;
|
|
70
|
+
}
|
|
71
|
+
polyfillCache.set(moduleName, wrappedCode);
|
|
72
|
+
return wrappedCode;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get all available stdlib modules (those with non-null polyfills)
|
|
76
|
+
*/
|
|
77
|
+
export function getAvailableStdlib() {
|
|
78
|
+
return Object.keys(stdLibBrowser).filter((key) => stdLibBrowser[key] !== null);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if a module has a polyfill available
|
|
82
|
+
* Note: fs returns null from node-stdlib-browser since we provide our own implementation
|
|
83
|
+
*/
|
|
84
|
+
export function hasPolyfill(moduleName) {
|
|
85
|
+
// Strip node: prefix
|
|
86
|
+
const name = moduleName.replace(/^node:/, "");
|
|
87
|
+
const polyfill = stdLibBrowser[name];
|
|
88
|
+
return polyfill !== undefined && polyfill !== null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Pre-bundle all polyfills (for faster startup)
|
|
92
|
+
*/
|
|
93
|
+
export async function prebundleAllPolyfills() {
|
|
94
|
+
const modules = getAvailableStdlib();
|
|
95
|
+
await Promise.all(modules.map((m) => bundlePolyfill(m)));
|
|
96
|
+
return new Map(polyfillCache);
|
|
97
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js worker adapter.
|
|
3
|
+
*
|
|
4
|
+
* Wraps node:worker_threads for spawning Workers.
|
|
5
|
+
* Used by the WasmVM runtime for WASM process execution.
|
|
6
|
+
*/
|
|
7
|
+
export interface WorkerHandle {
|
|
8
|
+
postMessage(data: unknown, transferList?: Transferable[]): void;
|
|
9
|
+
onMessage(handler: (data: unknown) => void): void;
|
|
10
|
+
onError(handler: (err: Error) => void): void;
|
|
11
|
+
onExit(handler: (code: number) => void): void;
|
|
12
|
+
terminate(): Promise<number>;
|
|
13
|
+
}
|
|
14
|
+
export declare class NodeWorkerAdapter {
|
|
15
|
+
/**
|
|
16
|
+
* Spawn a Worker for the given script.
|
|
17
|
+
*/
|
|
18
|
+
static create(scriptPath: string | URL, options?: {
|
|
19
|
+
workerData?: unknown;
|
|
20
|
+
}): WorkerHandle;
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js worker adapter.
|
|
3
|
+
*
|
|
4
|
+
* Wraps node:worker_threads for spawning Workers.
|
|
5
|
+
* Used by the WasmVM runtime for WASM process execution.
|
|
6
|
+
*/
|
|
7
|
+
import { Worker } from "node:worker_threads";
|
|
8
|
+
export class NodeWorkerAdapter {
|
|
9
|
+
/**
|
|
10
|
+
* Spawn a Worker for the given script.
|
|
11
|
+
*/
|
|
12
|
+
static create(scriptPath, options) {
|
|
13
|
+
const worker = new Worker(scriptPath, {
|
|
14
|
+
workerData: options?.workerData,
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
postMessage(data, transferList) {
|
|
18
|
+
worker.postMessage(data, transferList);
|
|
19
|
+
},
|
|
20
|
+
onMessage(handler) {
|
|
21
|
+
worker.on("message", handler);
|
|
22
|
+
},
|
|
23
|
+
onError(handler) {
|
|
24
|
+
worker.on("error", handler);
|
|
25
|
+
},
|
|
26
|
+
onExit(handler) {
|
|
27
|
+
worker.on("exit", handler);
|
|
28
|
+
},
|
|
29
|
+
terminate() {
|
|
30
|
+
return worker.terminate();
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|