sandboxedjs 0.1.49 → 0.1.51
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/README.md +16 -14
- package/dist/index.cjs +214 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +214 -41
- package/dist/index.js.map +1 -1
- package/dist/python/python.data +74 -79
- package/dist/python/python.js +1 -1
- package/dist/python/python.wasm +0 -0
- package/dist/python/runtime.json +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ it alongside their model provider.
|
|
|
89
89
|
| **Shell** | POSIX `sh` (54 builtins) — pipelines, redirection, here-docs, globbing, brace/parameter/arithmetic/command expansion, functions, `if`/`for`/`while`/`case`/`select`, job control, traps, arrays, `[[ ]]`, `(( ))` |
|
|
90
90
|
| **Coreutils** | 154 programs — `ls cat cp mv rm mkdir grep sed awk find head tail sort uniq wc cut tr tee xargs chmod chown ln du df ps tar gzip base64 sha256sum diff curl wget` and the rest — plus 54 shell builtins |
|
|
91
91
|
| **Node.js** | Its own module engine — `require` *and* `import`, live bindings, `exports` maps, top-level `await`, npm packages, `http`, `fs`, streams, `child_process` |
|
|
92
|
-
| **Python** | CPython 3.13
|
|
92
|
+
| **Python** | Source-built CPython 3.13/WASM on the *same* filesystem, with the real standard library |
|
|
93
93
|
| **WebAssembly** | A WASI (`preview1`) host — run any `wasm32-wasi` binary from clang, Rust, Zig or TinyGo as an ordinary process, on the same filesystem |
|
|
94
94
|
| **FFmpeg** | `ffmpeg` and `ffprobe` (FFmpeg 5.1) reading and writing container files directly — [optional install](#video-and-audio) |
|
|
95
95
|
| **`/proc`** | Live and synthesised — `ps`, `top`, `free` and `uptime` all read the same source |
|
|
@@ -391,7 +391,7 @@ your disk.
|
|
|
391
391
|
|
|
392
392
|
### Python
|
|
393
393
|
|
|
394
|
-
Python is CPython 3.13
|
|
394
|
+
Python is the bundled, source-built CPython 3.13 WebAssembly runtime mounted on the container's filesystem:
|
|
395
395
|
|
|
396
396
|
```ts
|
|
397
397
|
await box.exec("python3 -c \"print(open('/etc/hostname').read())\"");
|
|
@@ -401,8 +401,10 @@ await box.exec("echo '1 2 3' | python3 -c \"import sys; print(sum(map(int, sys.s
|
|
|
401
401
|
|
|
402
402
|
The bundled standard library includes `json`, `re`, `os`, `sys`, `math`, `random`, `hashlib`,
|
|
403
403
|
`binascii`, `struct`, `time`, `collections`, `itertools`, `functools`, `asyncio` and more. It is
|
|
404
|
-
|
|
405
|
-
|
|
404
|
+
It is real CPython, so `sqlite3`, `dataclasses`, `decimal`, `typing`, `ssl`, and
|
|
405
|
+
`asyncio` work. `pip` installs compatible wheels into the same filesystem and
|
|
406
|
+
creates their `console_scripts` in `/usr/local/bin` (with outbound networking
|
|
407
|
+
enabled). Native extensions still need an Emscripten-compatible build.
|
|
406
408
|
|
|
407
409
|
### WebAssembly binaries
|
|
408
410
|
|
|
@@ -701,20 +703,21 @@ Those use dynamic imports, so they only fail if you call them.
|
|
|
701
703
|
|
|
702
704
|
### Python in a browser
|
|
703
705
|
|
|
704
|
-
Python works lazily in a browser without extra configuration.
|
|
705
|
-
|
|
706
|
+
Python works lazily in a browser without extra configuration. The owned CPython
|
|
707
|
+
runtime and its pthread worker are shipped with the npm package:
|
|
706
708
|
|
|
707
709
|
```ts
|
|
708
710
|
const box = await createContainer();
|
|
709
711
|
await box.exec("python3 -c 'import sqlite3; print(sqlite3.sqlite_version)'");
|
|
710
712
|
```
|
|
711
713
|
|
|
712
|
-
|
|
714
|
+
Pyodide remains available as an explicit compatibility backend:
|
|
713
715
|
|
|
714
716
|
```ts
|
|
715
717
|
import { configurePython, createContainer } from "sandboxedjs";
|
|
716
718
|
|
|
717
719
|
configurePython({
|
|
720
|
+
backend: "pyodide",
|
|
718
721
|
pyodideURL: "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/pyodide.mjs",
|
|
719
722
|
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/",
|
|
720
723
|
});
|
|
@@ -723,8 +726,8 @@ const box = await createContainer();
|
|
|
723
726
|
await box.exec("python3 -c 'import sqlite3; print(sqlite3.sqlite_version)'");
|
|
724
727
|
```
|
|
725
728
|
|
|
726
|
-
Python never falls back to a host interpreter
|
|
727
|
-
Pyodide module and
|
|
729
|
+
Python never falls back to a host interpreter. When selecting the optional
|
|
730
|
+
Pyodide backend, its module and asset directory must use the same version.
|
|
728
731
|
|
|
729
732
|
## Uploading files
|
|
730
733
|
|
|
@@ -972,11 +975,10 @@ Honest list of what does not work:
|
|
|
972
975
|
now?" and run `npm install && npm run dev` from the shell instead.
|
|
973
976
|
- **No `net`, `tls`, `worker_threads` or `vm`.** `http` and `https` are served by a virtual stack
|
|
974
977
|
that `request()` talks to directly, so servers work; raw sockets do not.
|
|
975
|
-
- **Python is CPython
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
container, and each program runs in its own namespace.
|
|
978
|
+
- **Python is source-built CPython/WASM.** Each program gets its own interpreter
|
|
979
|
+
process worker. Pure-Python wheels install normally; native extensions must
|
|
980
|
+
be linked or published for Emscripten. Pydantic 2's `pydantic-core` is not
|
|
981
|
+
currently available, so use a Pydantic 1 constraint for FastAPI deployments.
|
|
980
982
|
- **No real sockets.** HTTP servers work through the request proxy; raw TCP/UDP does not.
|
|
981
983
|
- **No real processes.** Processes are cooperative async tasks: `kill -9` cannot interrupt a
|
|
982
984
|
tight synchronous loop, and `SIGSTOP` only marks state.
|
package/dist/index.cjs
CHANGED
|
@@ -19107,7 +19107,7 @@ async function startNodeWorker(url, workerData) {
|
|
|
19107
19107
|
/* webpackIgnore: true */
|
|
19108
19108
|
specifier
|
|
19109
19109
|
);
|
|
19110
|
-
const worker = new NodeWorker(url, { workerData });
|
|
19110
|
+
const worker = new NodeWorker(url, { workerData, execArgv: [] });
|
|
19111
19111
|
return {
|
|
19112
19112
|
postMessage: (message) => worker.postMessage(message),
|
|
19113
19113
|
onMessage: (listener) => worker.on("message", listener),
|
|
@@ -19376,27 +19376,40 @@ function compareRelease(left, right) {
|
|
|
19376
19376
|
function isPreRelease(version) {
|
|
19377
19377
|
return /(a|b|rc|dev|post)\d*$/i.test(version.replace(/^\d+(\.\d+)*/, ""));
|
|
19378
19378
|
}
|
|
19379
|
-
async function
|
|
19379
|
+
async function resolvePackageCandidates(client, requirement, environment) {
|
|
19380
19380
|
const index = await client.json(`https://pypi.org/pypi/${requirement.name}/json`);
|
|
19381
19381
|
const candidates = Object.keys(index.releases).filter((version) => requirement.specifiers.every((s) => compareVersions(version, s.version, s.op))).sort((a, b) => compareRelease(releaseOf(a), releaseOf(b)));
|
|
19382
19382
|
const stable = candidates.filter((version) => !isPreRelease(version));
|
|
19383
|
-
const
|
|
19383
|
+
const newestFirst = (stable.length > 0 ? stable : candidates).reverse();
|
|
19384
|
+
const probeIndexes = [
|
|
19385
|
+
0,
|
|
19386
|
+
Math.floor(newestFirst.length / 2),
|
|
19387
|
+
Math.floor(newestFirst.length / 4),
|
|
19388
|
+
Math.floor(newestFirst.length * 3 / 4),
|
|
19389
|
+
newestFirst.length - 1
|
|
19390
|
+
];
|
|
19391
|
+
const probes = [...new Set(probeIndexes)].map((index2) => newestFirst[index2]).filter((v) => Boolean(v));
|
|
19392
|
+
const probeVersions = new Set(probes);
|
|
19393
|
+
const ordered = [...probes, ...newestFirst.filter((version) => !probeVersions.has(version))];
|
|
19384
19394
|
let sawSourceOnly = false;
|
|
19395
|
+
const resolved = [];
|
|
19385
19396
|
for (const version of ordered) {
|
|
19386
19397
|
const files = (index.releases[version] ?? []).filter((file3) => !file3.yanked);
|
|
19387
19398
|
const wheel = pickWheel(files);
|
|
19388
19399
|
if (wheel) {
|
|
19389
|
-
|
|
19400
|
+
resolved.push({
|
|
19390
19401
|
name: requirement.name,
|
|
19391
19402
|
version,
|
|
19392
19403
|
url: wheel.url,
|
|
19393
19404
|
filename: wheel.filename,
|
|
19394
19405
|
sha256: wheel.digests?.sha256 ?? "",
|
|
19395
19406
|
requires: []
|
|
19396
|
-
};
|
|
19407
|
+
});
|
|
19408
|
+
continue;
|
|
19397
19409
|
}
|
|
19398
19410
|
if (files.some((file3) => file3.packagetype === "sdist")) sawSourceOnly = true;
|
|
19399
19411
|
}
|
|
19412
|
+
if (resolved.length > 0) return resolved;
|
|
19400
19413
|
if (ordered.length === 0) {
|
|
19401
19414
|
throw new Error(
|
|
19402
19415
|
`no version of ${requirement.name} matches ${describe(requirement)}`
|
|
@@ -19424,6 +19437,7 @@ function splitOnce(text2, separator) {
|
|
|
19424
19437
|
|
|
19425
19438
|
// src/runtime/python/install.ts
|
|
19426
19439
|
var SITE_PACKAGES = "/usr/lib/python3.13/site-packages";
|
|
19440
|
+
var SCRIPTS = "/usr/local/bin";
|
|
19427
19441
|
function markerEnvironment(pythonVersion) {
|
|
19428
19442
|
const [major = "3", minor = "13"] = pythonVersion.split(".");
|
|
19429
19443
|
return {
|
|
@@ -19444,18 +19458,39 @@ async function installRequirements(options) {
|
|
|
19444
19458
|
const requirement = parseRequirement(text2);
|
|
19445
19459
|
if (requirement) queue.push({ requirement, extras: requirement.extras });
|
|
19446
19460
|
}
|
|
19447
|
-
const
|
|
19448
|
-
const
|
|
19449
|
-
|
|
19450
|
-
|
|
19461
|
+
const archives = /* @__PURE__ */ new Map();
|
|
19462
|
+
const unavailable2 = /* @__PURE__ */ new Set();
|
|
19463
|
+
const unsatisfiable = /* @__PURE__ */ new Map();
|
|
19464
|
+
const candidateCache = /* @__PURE__ */ new Map();
|
|
19465
|
+
let candidateAttempts = 0;
|
|
19466
|
+
const MAX_CANDIDATE_ATTEMPTS = 96;
|
|
19467
|
+
const solve = async (pending, state) => {
|
|
19468
|
+
if (pending.length === 0) return state;
|
|
19469
|
+
const [first, ...tail2] = pending;
|
|
19470
|
+
let { requirement, extras } = first;
|
|
19471
|
+
const rest = [];
|
|
19472
|
+
const mergedSpecifiers = [...requirement.specifiers];
|
|
19473
|
+
const mergedExtras = new Set(extras);
|
|
19474
|
+
for (const item of tail2) {
|
|
19475
|
+
if (item.requirement.name === requirement.name && markerApplies(item.requirement.marker, environment, item.extras)) {
|
|
19476
|
+
mergedSpecifiers.push(...item.requirement.specifiers);
|
|
19477
|
+
for (const extra of item.extras) mergedExtras.add(extra);
|
|
19478
|
+
} else {
|
|
19479
|
+
rest.push(item);
|
|
19480
|
+
}
|
|
19481
|
+
}
|
|
19482
|
+
requirement = { ...requirement, specifiers: mergedSpecifiers };
|
|
19483
|
+
extras = [...mergedExtras];
|
|
19451
19484
|
if (!markerApplies(requirement.marker, environment, extras)) {
|
|
19452
|
-
|
|
19453
|
-
|
|
19485
|
+
return solve(rest, {
|
|
19486
|
+
...state,
|
|
19487
|
+
skipped: [...state.skipped, { name: requirement.name, marker: requirement.marker ?? "" }]
|
|
19488
|
+
});
|
|
19454
19489
|
}
|
|
19455
|
-
const
|
|
19456
|
-
|
|
19457
|
-
|
|
19458
|
-
const already = chosen.get(requirement.name);
|
|
19490
|
+
const solveKey = `${requirement.name}:${requirement.specifiers.map((s) => `${s.op}${s.version}`).sort().join(",")}:${[...extras].sort().join(",")}`;
|
|
19491
|
+
const knownFailure = unsatisfiable.get(solveKey);
|
|
19492
|
+
if (knownFailure) throw knownFailure;
|
|
19493
|
+
const already = state.chosen.get(requirement.name);
|
|
19459
19494
|
if (already) {
|
|
19460
19495
|
const satisfied = requirement.specifiers.every(
|
|
19461
19496
|
(s) => compareVersions(already.version, s.version, s.op)
|
|
@@ -19465,38 +19500,152 @@ async function installRequirements(options) {
|
|
|
19465
19500
|
`${requirement.name} ${already.version} is already selected but another dependency needs ` + requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ")
|
|
19466
19501
|
);
|
|
19467
19502
|
}
|
|
19468
|
-
|
|
19503
|
+
return solve(rest, state);
|
|
19469
19504
|
}
|
|
19470
19505
|
options.progress.collecting(requirement.name);
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
const
|
|
19475
|
-
|
|
19476
|
-
|
|
19477
|
-
|
|
19478
|
-
|
|
19479
|
-
|
|
19480
|
-
|
|
19481
|
-
|
|
19482
|
-
|
|
19483
|
-
}
|
|
19484
|
-
|
|
19485
|
-
|
|
19486
|
-
|
|
19506
|
+
if (unavailable2.has(requirement.name)) {
|
|
19507
|
+
throw new Error(`${requirement.name} has no wheel this runtime can use`);
|
|
19508
|
+
}
|
|
19509
|
+
const candidateKey = `${requirement.name}:${requirement.specifiers.map((s) => `${s.op}${s.version}`).join(",")}`;
|
|
19510
|
+
let candidatePromise = candidateCache.get(candidateKey);
|
|
19511
|
+
if (!candidatePromise) {
|
|
19512
|
+
candidatePromise = resolvePackageCandidates(options.client, requirement);
|
|
19513
|
+
candidateCache.set(candidateKey, candidatePromise);
|
|
19514
|
+
}
|
|
19515
|
+
let candidates;
|
|
19516
|
+
try {
|
|
19517
|
+
candidates = await candidatePromise;
|
|
19518
|
+
} catch (error) {
|
|
19519
|
+
const anyKey = `${requirement.name}:`;
|
|
19520
|
+
let anyPromise = candidateCache.get(anyKey);
|
|
19521
|
+
if (!anyPromise) {
|
|
19522
|
+
anyPromise = resolvePackageCandidates(
|
|
19523
|
+
options.client,
|
|
19524
|
+
{ ...requirement, specifiers: [] });
|
|
19525
|
+
candidateCache.set(anyKey, anyPromise);
|
|
19526
|
+
}
|
|
19527
|
+
try {
|
|
19528
|
+
await anyPromise;
|
|
19529
|
+
} catch {
|
|
19530
|
+
unavailable2.add(requirement.name);
|
|
19531
|
+
}
|
|
19532
|
+
throw error;
|
|
19533
|
+
}
|
|
19534
|
+
let failure2;
|
|
19535
|
+
for (const resolved of candidates) {
|
|
19536
|
+
try {
|
|
19537
|
+
candidateAttempts += 1;
|
|
19538
|
+
if (candidateAttempts > MAX_CANDIDATE_ATTEMPTS) {
|
|
19539
|
+
throw new Error(
|
|
19540
|
+
"dependency resolution exceeded 96 wheel candidates; add version constraints for packages whose newest generation requires unavailable native WASM extensions"
|
|
19541
|
+
);
|
|
19542
|
+
}
|
|
19543
|
+
let entries = archives.get(resolved.url);
|
|
19544
|
+
if (!entries) {
|
|
19545
|
+
options.progress.downloading(resolved.name, resolved.version);
|
|
19546
|
+
entries = readZip(await options.client.bytes(resolved.url));
|
|
19547
|
+
archives.set(resolved.url, entries);
|
|
19548
|
+
}
|
|
19549
|
+
const dependencies = [];
|
|
19550
|
+
const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
|
|
19551
|
+
if (metadata) {
|
|
19552
|
+
for (const line of new TextDecoder().decode(metadata.data()).split(/\r?\n/)) {
|
|
19553
|
+
if (!line.startsWith("Requires-Dist:")) continue;
|
|
19554
|
+
const dependency = parseRequirement(line.slice("Requires-Dist:".length));
|
|
19555
|
+
if (dependency && markerApplies(dependency.marker, environment, extras)) {
|
|
19556
|
+
dependencies.push({ requirement: dependency, extras: dependency.extras });
|
|
19557
|
+
}
|
|
19558
|
+
}
|
|
19559
|
+
}
|
|
19560
|
+
const chosen = new Map(state.chosen);
|
|
19561
|
+
chosen.set(requirement.name, resolved);
|
|
19562
|
+
return await solve([...dependencies, ...rest], {
|
|
19563
|
+
chosen,
|
|
19564
|
+
staged: [...state.staged, ...stageWheel(entries, resolved)],
|
|
19565
|
+
installed: [...state.installed, { name: resolved.name, version: resolved.version }],
|
|
19566
|
+
skipped: state.skipped
|
|
19567
|
+
});
|
|
19568
|
+
} catch (error) {
|
|
19569
|
+
if (error?.message?.startsWith("dependency resolution exceeded")) throw error;
|
|
19570
|
+
failure2 = error;
|
|
19571
|
+
}
|
|
19572
|
+
}
|
|
19573
|
+
const resolutionError = failure2 instanceof Error ? failure2 : new Error(`could not resolve ${requirement.name}`);
|
|
19574
|
+
unsatisfiable.set(solveKey, resolutionError);
|
|
19575
|
+
throw resolutionError;
|
|
19576
|
+
};
|
|
19577
|
+
const solved = await solve(queue, {
|
|
19578
|
+
chosen: /* @__PURE__ */ new Map(),
|
|
19579
|
+
staged: [],
|
|
19580
|
+
installed: [],
|
|
19581
|
+
skipped: []
|
|
19582
|
+
});
|
|
19583
|
+
report.installed = solved.installed;
|
|
19584
|
+
report.skipped = solved.skipped;
|
|
19585
|
+
commit(options.vfs, options.cred, solved.staged);
|
|
19487
19586
|
return report;
|
|
19488
19587
|
}
|
|
19489
|
-
function
|
|
19588
|
+
function stageWheel(entries, resolved) {
|
|
19490
19589
|
const staged = [];
|
|
19491
19590
|
for (const entry of entries) {
|
|
19492
19591
|
if (entry.isDirectory) continue;
|
|
19592
|
+
const dataPath = /^[^/]+\.data\/(purelib|platlib|scripts)\/(.+)$/.exec(entry.name);
|
|
19593
|
+
if (dataPath) {
|
|
19594
|
+
const [, scheme, relative2] = dataPath;
|
|
19595
|
+
staged.push({
|
|
19596
|
+
path: scheme === "scripts" ? `${SCRIPTS}/${relative2}` : `${SITE_PACKAGES}/${relative2}`,
|
|
19597
|
+
data: entry.data(),
|
|
19598
|
+
...scheme === "scripts" ? { mode: 493 } : {}
|
|
19599
|
+
});
|
|
19600
|
+
continue;
|
|
19601
|
+
}
|
|
19493
19602
|
if (/^[^/]+\.data\//.test(entry.name)) continue;
|
|
19494
19603
|
staged.push({ path: `${SITE_PACKAGES}/${entry.name}`, data: entry.data() });
|
|
19495
19604
|
}
|
|
19605
|
+
const entryPoints = entries.find((entry) => /\.dist-info\/entry_points\.txt$/.test(entry.name));
|
|
19606
|
+
if (entryPoints) {
|
|
19607
|
+
for (const [name, target] of consoleScripts(new TextDecoder().decode(entryPoints.data()))) {
|
|
19608
|
+
staged.push({ path: `${SCRIPTS}/${name}`, data: new TextEncoder().encode(consoleLauncher(target)), mode: 493 });
|
|
19609
|
+
}
|
|
19610
|
+
}
|
|
19611
|
+
return staged;
|
|
19612
|
+
}
|
|
19613
|
+
function commit(vfs, cred, staged) {
|
|
19496
19614
|
const directories = /* @__PURE__ */ new Set();
|
|
19497
19615
|
for (const file3 of staged) directories.add(file3.path.slice(0, file3.path.lastIndexOf("/")));
|
|
19498
19616
|
for (const directory2 of directories) vfs.mkdir(directory2, { recursive: true, cred });
|
|
19499
|
-
for (const file3 of staged)
|
|
19617
|
+
for (const file3 of staged) {
|
|
19618
|
+
vfs.writeFile(file3.path, file3.data, { cred, ...file3.mode === void 0 ? {} : { mode: file3.mode } });
|
|
19619
|
+
if (file3.mode !== void 0) vfs.chmod(file3.path, file3.mode, cred);
|
|
19620
|
+
}
|
|
19621
|
+
}
|
|
19622
|
+
function consoleScripts(text2) {
|
|
19623
|
+
const result = [];
|
|
19624
|
+
let section = "";
|
|
19625
|
+
for (const raw of text2.split(/\r?\n/)) {
|
|
19626
|
+
const line = raw.trim();
|
|
19627
|
+
const heading = /^\[([^\]]+)\]$/.exec(line);
|
|
19628
|
+
if (heading) {
|
|
19629
|
+
section = heading[1];
|
|
19630
|
+
continue;
|
|
19631
|
+
}
|
|
19632
|
+
if (section !== "console_scripts" || line === "" || line.startsWith("#")) continue;
|
|
19633
|
+
const assignment = /^([^=\s]+)\s*=\s*([^\s;]+)(?:\s*;.*)?$/.exec(line);
|
|
19634
|
+
if (assignment) result.push([assignment[1], assignment[2].replace(/\s*\[.*\]$/, "")]);
|
|
19635
|
+
}
|
|
19636
|
+
return result;
|
|
19637
|
+
}
|
|
19638
|
+
function consoleLauncher(target) {
|
|
19639
|
+
const [module, attribute = ""] = target.split(":", 2);
|
|
19640
|
+
return `#!/usr/bin/python3
|
|
19641
|
+
import sys
|
|
19642
|
+
from importlib import import_module
|
|
19643
|
+
entry = import_module(${JSON.stringify(module)})
|
|
19644
|
+
for part in ${JSON.stringify(attribute)}.split('.'):
|
|
19645
|
+
if part:
|
|
19646
|
+
entry = getattr(entry, part)
|
|
19647
|
+
raise SystemExit(entry())
|
|
19648
|
+
`;
|
|
19500
19649
|
}
|
|
19501
19650
|
|
|
19502
19651
|
// src/runtime/python/backend.ts
|
|
@@ -19504,7 +19653,11 @@ function withSitePackages(env2) {
|
|
|
19504
19653
|
const existing = env2.PYTHONPATH;
|
|
19505
19654
|
return {
|
|
19506
19655
|
...env2,
|
|
19507
|
-
PYTHONPATH: existing ? `${SITE_PACKAGES}:${existing}` : SITE_PACKAGES
|
|
19656
|
+
PYTHONPATH: existing ? `${SITE_PACKAGES}:${existing}` : SITE_PACKAGES,
|
|
19657
|
+
/* Installed packages live on a host-mounted VFS. Rewriting hundreds of
|
|
19658
|
+
* cache files on every short-lived WASM process is wasted work and can
|
|
19659
|
+
* exhaust the synchronous host-call bridge during large import graphs. */
|
|
19660
|
+
PYTHONDONTWRITEBYTECODE: env2.PYTHONDONTWRITEBYTECODE ?? "1"
|
|
19508
19661
|
};
|
|
19509
19662
|
}
|
|
19510
19663
|
var INTERPRETER_OWNED = /* @__PURE__ */ new Set(["lib", "dev", "proc", "usr"]);
|
|
@@ -19594,6 +19747,12 @@ function containerMounts(ctx) {
|
|
|
19594
19747
|
}
|
|
19595
19748
|
} catch {
|
|
19596
19749
|
}
|
|
19750
|
+
try {
|
|
19751
|
+
if (ctx.vfs.stat(SCRIPTS, { cred: ctx.cred }).isDirectory()) {
|
|
19752
|
+
mounts.push({ guest: SCRIPTS, container: SCRIPTS });
|
|
19753
|
+
}
|
|
19754
|
+
} catch {
|
|
19755
|
+
}
|
|
19597
19756
|
return mounts;
|
|
19598
19757
|
}
|
|
19599
19758
|
|
|
@@ -19647,16 +19806,30 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
|
|
|
19647
19806
|
"outbound network access is disabled for this container (enable with network: { allowOutbound: true })"
|
|
19648
19807
|
);
|
|
19649
19808
|
}
|
|
19809
|
+
const jsonCache = /* @__PURE__ */ new Map();
|
|
19810
|
+
const bytesCache = /* @__PURE__ */ new Map();
|
|
19650
19811
|
const client = {
|
|
19651
19812
|
async json(url) {
|
|
19652
|
-
|
|
19653
|
-
if (!
|
|
19654
|
-
|
|
19813
|
+
let pending = jsonCache.get(url);
|
|
19814
|
+
if (!pending) {
|
|
19815
|
+
pending = fetch(url).then((response) => {
|
|
19816
|
+
if (!response.ok) throw new Error(`${url} answered ${response.status}`);
|
|
19817
|
+
return response.json();
|
|
19818
|
+
});
|
|
19819
|
+
jsonCache.set(url, pending);
|
|
19820
|
+
}
|
|
19821
|
+
return pending;
|
|
19655
19822
|
},
|
|
19656
19823
|
async bytes(url) {
|
|
19657
|
-
|
|
19658
|
-
if (!
|
|
19659
|
-
|
|
19824
|
+
let pending = bytesCache.get(url);
|
|
19825
|
+
if (!pending) {
|
|
19826
|
+
pending = fetch(url).then(async (response) => {
|
|
19827
|
+
if (!response.ok) throw new Error(`${url} answered ${response.status}`);
|
|
19828
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
19829
|
+
});
|
|
19830
|
+
bytesCache.set(url, pending);
|
|
19831
|
+
}
|
|
19832
|
+
return pending;
|
|
19660
19833
|
}
|
|
19661
19834
|
};
|
|
19662
19835
|
try {
|