@secure-exec/core 0.1.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/bridge/active-handles.d.ts +21 -0
- package/dist/bridge/active-handles.js +60 -0
- package/dist/bridge/child-process.d.ts +90 -0
- package/dist/bridge/child-process.js +606 -0
- package/dist/bridge/fs.d.ts +281 -0
- package/dist/bridge/fs.js +2151 -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 +308 -0
- package/dist/bridge/network.d.ts +249 -0
- package/dist/bridge/network.js +1416 -0
- package/dist/bridge/os.d.ts +13 -0
- package/dist/bridge/os.js +256 -0
- package/dist/bridge/polyfills.d.ts +2 -0
- package/dist/bridge/polyfills.js +11 -0
- package/dist/bridge/process.d.ts +86 -0
- package/dist/bridge/process.js +938 -0
- package/dist/bridge-setup.d.ts +6 -0
- package/dist/bridge-setup.js +9 -0
- package/dist/bridge.js +11538 -0
- package/dist/esm-compiler.d.ts +14 -0
- package/dist/esm-compiler.js +68 -0
- package/dist/fs-helpers.d.ts +23 -0
- package/dist/fs-helpers.js +41 -0
- package/dist/generated/isolate-runtime.d.ts +19 -0
- package/dist/generated/isolate-runtime.js +21 -0
- package/dist/generated/polyfills.d.ts +82 -0
- package/dist/generated/polyfills.js +82 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +25 -0
- package/dist/isolate-runtime/apply-custom-global-policy.js +54 -0
- package/dist/isolate-runtime/apply-timing-mitigation-freeze.js +44 -0
- package/dist/isolate-runtime/apply-timing-mitigation-off.js +14 -0
- package/dist/isolate-runtime/bridge-attach.js +29 -0
- package/dist/isolate-runtime/bridge-initial-globals.js +246 -0
- package/dist/isolate-runtime/eval-script-result.js +8 -0
- package/dist/isolate-runtime/global-exposure-helpers.js +36 -0
- package/dist/isolate-runtime/init-commonjs-module-globals.js +28 -0
- package/dist/isolate-runtime/override-process-cwd.js +8 -0
- package/dist/isolate-runtime/override-process-env.js +8 -0
- package/dist/isolate-runtime/require-setup.js +650 -0
- package/dist/isolate-runtime/set-commonjs-file-globals.js +36 -0
- package/dist/isolate-runtime/set-stdin-data.js +10 -0
- package/dist/isolate-runtime/setup-dynamic-import.js +64 -0
- package/dist/isolate-runtime/setup-fs-facade.js +48 -0
- package/dist/module-resolver.d.ts +25 -0
- package/dist/module-resolver.js +264 -0
- package/dist/package-bundler.d.ts +36 -0
- package/dist/package-bundler.js +497 -0
- package/dist/python-runtime.d.ts +16 -0
- package/dist/python-runtime.js +45 -0
- package/dist/runtime-driver.d.ts +62 -0
- package/dist/runtime-driver.js +1 -0
- package/dist/runtime.d.ts +31 -0
- package/dist/runtime.js +69 -0
- package/dist/shared/api-types.d.ts +71 -0
- package/dist/shared/api-types.js +1 -0
- package/dist/shared/bridge-contract.d.ts +302 -0
- package/dist/shared/bridge-contract.js +82 -0
- package/dist/shared/console-formatter.d.ts +22 -0
- package/dist/shared/console-formatter.js +157 -0
- package/dist/shared/constants.d.ts +3 -0
- package/dist/shared/constants.js +3 -0
- package/dist/shared/errors.d.ts +16 -0
- package/dist/shared/errors.js +21 -0
- package/dist/shared/esm-utils.d.ts +28 -0
- package/dist/shared/esm-utils.js +97 -0
- package/dist/shared/global-exposure.d.ts +38 -0
- package/dist/shared/global-exposure.js +406 -0
- package/dist/shared/in-memory-fs.d.ts +42 -0
- package/dist/shared/in-memory-fs.js +341 -0
- package/dist/shared/permissions.d.ts +38 -0
- package/dist/shared/permissions.js +283 -0
- package/dist/shared/require-setup.d.ts +6 -0
- package/dist/shared/require-setup.js +9 -0
- package/dist/types.d.ts +206 -0
- package/dist/types.js +1 -0
- package/package.json +107 -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,16 @@
|
|
|
1
|
+
import type { ExecOptions, ExecResult, PythonRunOptions, PythonRunResult, StdioHook } from "./shared/api-types.js";
|
|
2
|
+
import type { PythonRuntimeDriverFactory, SystemDriver } from "./types.js";
|
|
3
|
+
export interface PythonRuntimeOptions {
|
|
4
|
+
systemDriver: SystemDriver;
|
|
5
|
+
runtimeDriverFactory: PythonRuntimeDriverFactory;
|
|
6
|
+
cpuTimeLimitMs?: number;
|
|
7
|
+
onStdio?: StdioHook;
|
|
8
|
+
}
|
|
9
|
+
export declare class PythonRuntime {
|
|
10
|
+
private readonly runtimeDriver;
|
|
11
|
+
constructor(options: PythonRuntimeOptions);
|
|
12
|
+
run<T = unknown>(code: string, options?: PythonRunOptions): Promise<PythonRunResult<T>>;
|
|
13
|
+
exec(code: string, options?: ExecOptions): Promise<ExecResult>;
|
|
14
|
+
dispose(): void;
|
|
15
|
+
terminate(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { filterEnv } from "./shared/permissions.js";
|
|
2
|
+
const DEFAULT_SANDBOX_CWD = "/root";
|
|
3
|
+
const DEFAULT_SANDBOX_HOME = "/root";
|
|
4
|
+
const DEFAULT_SANDBOX_TMPDIR = "/tmp";
|
|
5
|
+
export class PythonRuntime {
|
|
6
|
+
runtimeDriver;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
const { systemDriver, runtimeDriverFactory } = options;
|
|
9
|
+
const processConfig = {
|
|
10
|
+
...(systemDriver.runtime.process ?? {}),
|
|
11
|
+
};
|
|
12
|
+
processConfig.cwd ??= DEFAULT_SANDBOX_CWD;
|
|
13
|
+
processConfig.env = filterEnv(processConfig.env, systemDriver.permissions);
|
|
14
|
+
const osConfig = {
|
|
15
|
+
...(systemDriver.runtime.os ?? {}),
|
|
16
|
+
};
|
|
17
|
+
osConfig.homedir ??= DEFAULT_SANDBOX_HOME;
|
|
18
|
+
osConfig.tmpdir ??= DEFAULT_SANDBOX_TMPDIR;
|
|
19
|
+
this.runtimeDriver = runtimeDriverFactory.createRuntimeDriver({
|
|
20
|
+
system: systemDriver,
|
|
21
|
+
runtime: {
|
|
22
|
+
process: processConfig,
|
|
23
|
+
os: osConfig,
|
|
24
|
+
},
|
|
25
|
+
cpuTimeLimitMs: options.cpuTimeLimitMs,
|
|
26
|
+
onStdio: options.onStdio,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async run(code, options = {}) {
|
|
30
|
+
return this.runtimeDriver.run(code, options);
|
|
31
|
+
}
|
|
32
|
+
async exec(code, options) {
|
|
33
|
+
return this.runtimeDriver.exec(code, options);
|
|
34
|
+
}
|
|
35
|
+
dispose() {
|
|
36
|
+
this.runtimeDriver.dispose();
|
|
37
|
+
}
|
|
38
|
+
async terminate() {
|
|
39
|
+
if (this.runtimeDriver.terminate) {
|
|
40
|
+
await this.runtimeDriver.terminate();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.runtimeDriver.dispose();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { StdioHook, ExecOptions, ExecResult, OSConfig, PythonRunOptions, PythonRunResult, ProcessConfig, RunResult, TimingMitigation } from "./shared/api-types.js";
|
|
2
|
+
import type { CommandExecutor, NetworkAdapter, Permissions, VirtualFileSystem } from "./types.js";
|
|
3
|
+
export interface DriverRuntimeConfig {
|
|
4
|
+
process: ProcessConfig;
|
|
5
|
+
os: OSConfig;
|
|
6
|
+
}
|
|
7
|
+
export interface ResourceBudgets {
|
|
8
|
+
/** Maximum total stdout/stderr bytes before subsequent writes are silently dropped. */
|
|
9
|
+
maxOutputBytes?: number;
|
|
10
|
+
/** Maximum total bridge calls (fs, network, timers, child_process) before errors are returned. */
|
|
11
|
+
maxBridgeCalls?: number;
|
|
12
|
+
/** Maximum concurrent host-side timers (setTimeout/setInterval with delay > 0). */
|
|
13
|
+
maxTimers?: number;
|
|
14
|
+
/** Maximum child_process.spawn() invocations per execution. */
|
|
15
|
+
maxChildProcesses?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface RuntimeDriverOptions {
|
|
18
|
+
system: SystemDriver;
|
|
19
|
+
runtime: DriverRuntimeConfig;
|
|
20
|
+
memoryLimit?: number;
|
|
21
|
+
cpuTimeLimitMs?: number;
|
|
22
|
+
timingMitigation?: TimingMitigation;
|
|
23
|
+
onStdio?: StdioHook;
|
|
24
|
+
payloadLimits?: {
|
|
25
|
+
base64TransferBytes?: number;
|
|
26
|
+
jsonPayloadBytes?: number;
|
|
27
|
+
};
|
|
28
|
+
resourceBudgets?: ResourceBudgets;
|
|
29
|
+
}
|
|
30
|
+
export interface SharedRuntimeDriver {
|
|
31
|
+
exec(code: string, options?: ExecOptions): Promise<ExecResult>;
|
|
32
|
+
dispose(): void;
|
|
33
|
+
terminate?(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export interface NodeRuntimeDriver extends SharedRuntimeDriver {
|
|
36
|
+
run<T = unknown>(code: string, filePath?: string): Promise<RunResult<T>>;
|
|
37
|
+
readonly network?: Pick<NetworkAdapter, "fetch" | "dnsLookup" | "httpRequest">;
|
|
38
|
+
unsafeIsolate?: unknown;
|
|
39
|
+
createUnsafeContext?(options?: {
|
|
40
|
+
env?: Record<string, string>;
|
|
41
|
+
cwd?: string;
|
|
42
|
+
filePath?: string;
|
|
43
|
+
}): Promise<unknown>;
|
|
44
|
+
}
|
|
45
|
+
export interface PythonRuntimeDriver extends SharedRuntimeDriver {
|
|
46
|
+
run<T = unknown>(code: string, options?: PythonRunOptions): Promise<PythonRunResult<T>>;
|
|
47
|
+
}
|
|
48
|
+
export interface NodeRuntimeDriverFactory {
|
|
49
|
+
createRuntimeDriver(options: RuntimeDriverOptions): NodeRuntimeDriver;
|
|
50
|
+
}
|
|
51
|
+
export interface PythonRuntimeDriverFactory {
|
|
52
|
+
createRuntimeDriver(options: RuntimeDriverOptions): PythonRuntimeDriver;
|
|
53
|
+
}
|
|
54
|
+
export interface SystemDriver {
|
|
55
|
+
filesystem?: VirtualFileSystem;
|
|
56
|
+
network?: NetworkAdapter;
|
|
57
|
+
commandExecutor?: CommandExecutor;
|
|
58
|
+
permissions?: Permissions;
|
|
59
|
+
runtime: DriverRuntimeConfig;
|
|
60
|
+
}
|
|
61
|
+
export type RuntimeDriver = NodeRuntimeDriver;
|
|
62
|
+
export type RuntimeDriverFactory = NodeRuntimeDriverFactory;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { NetworkAdapter, NodeRuntimeDriverFactory, SystemDriver } from "./types.js";
|
|
2
|
+
import type { StdioHook, ExecOptions, ExecResult, RunResult, TimingMitigation } from "./shared/api-types.js";
|
|
3
|
+
import type { ResourceBudgets } from "./runtime-driver.js";
|
|
4
|
+
export interface NodeRuntimeOptions {
|
|
5
|
+
systemDriver: SystemDriver;
|
|
6
|
+
runtimeDriverFactory: NodeRuntimeDriverFactory;
|
|
7
|
+
memoryLimit?: number;
|
|
8
|
+
cpuTimeLimitMs?: number;
|
|
9
|
+
timingMitigation?: TimingMitigation;
|
|
10
|
+
onStdio?: StdioHook;
|
|
11
|
+
payloadLimits?: {
|
|
12
|
+
base64TransferBytes?: number;
|
|
13
|
+
jsonPayloadBytes?: number;
|
|
14
|
+
};
|
|
15
|
+
resourceBudgets?: ResourceBudgets;
|
|
16
|
+
}
|
|
17
|
+
export declare class NodeRuntime {
|
|
18
|
+
private readonly runtimeDriver;
|
|
19
|
+
constructor(options: NodeRuntimeOptions);
|
|
20
|
+
get network(): Pick<NetworkAdapter, "fetch" | "dnsLookup" | "httpRequest">;
|
|
21
|
+
get __unsafeIsoalte(): unknown;
|
|
22
|
+
__unsafeCreateContext(options?: {
|
|
23
|
+
env?: Record<string, string>;
|
|
24
|
+
cwd?: string;
|
|
25
|
+
filePath?: string;
|
|
26
|
+
}): Promise<unknown>;
|
|
27
|
+
run<T = unknown>(code: string, filePath?: string): Promise<RunResult<T>>;
|
|
28
|
+
exec(code: string, options?: ExecOptions): Promise<ExecResult>;
|
|
29
|
+
dispose(): void;
|
|
30
|
+
terminate(): Promise<void>;
|
|
31
|
+
}
|