sandboxedjs 0.1.19 → 0.1.21
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 +2 -16
- package/bin/sandboxedjs.mjs +1 -1
- package/dist/index.cjs +113 -356
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -74
- package/dist/index.d.ts +37 -74
- package/dist/index.js +113 -356
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -640,7 +640,6 @@ where the module and the assets are — a CDN is fine:
|
|
|
640
640
|
import { configurePython, createContainer } from "sandboxedjs";
|
|
641
641
|
|
|
642
642
|
configurePython({
|
|
643
|
-
engine: "cpython",
|
|
644
643
|
pyodideURL: "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/pyodide.mjs",
|
|
645
644
|
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/",
|
|
646
645
|
});
|
|
@@ -649,19 +648,8 @@ const box = await createContainer();
|
|
|
649
648
|
await box.exec("python3 -c 'import sqlite3; print(sqlite3.sqlite_version)'");
|
|
650
649
|
```
|
|
651
650
|
|
|
652
|
-
Without that,
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
**MicroPython in a browser still does not start**, and that part is upstream. With the correct
|
|
656
|
-
`.wasm` loaded — see `configurePython({ wasmUrl })` — instantiation fails on
|
|
657
|
-
|
|
658
|
-
```
|
|
659
|
-
LinkError: Import #45 "env" "__syscall_poll_nonblocking": function import requires a callable
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
The symbol appears nowhere in the shipped `micropython.mjs`, so the published glue and `.wasm`
|
|
663
|
-
disagree about their import table. Node is unaffected. Since CPython is now the default engine,
|
|
664
|
-
this only matters if you have deliberately pinned `engine: "micropython"` in a browser.
|
|
651
|
+
Without that configuration, a browser with no package resolver cannot locate
|
|
652
|
+
Pyodide. Python never falls back to a host interpreter or another runtime.
|
|
665
653
|
|
|
666
654
|
## Uploading files
|
|
667
655
|
|
|
@@ -751,8 +739,6 @@ Honest list of what does not work:
|
|
|
751
739
|
many, including `numpy`, but an arbitrary wheel from PyPI will not install.
|
|
752
740
|
Starting an interpreter costs about a second and a half; one is kept per
|
|
753
741
|
container, and each program runs in its own namespace.
|
|
754
|
-
- **MicroPython is still there** for hosts that would rather not carry a 13 MB
|
|
755
|
-
interpreter: `configurePython({ engine: "micropython" })`.
|
|
756
742
|
- **No real sockets.** HTTP servers work through the request proxy; raw TCP/UDP does not.
|
|
757
743
|
- **No real processes.** Processes are cooperative async tasks: `kill -9` cannot interrupt a
|
|
758
744
|
tight synchronous loop, and `SIGSTOP` only marks state.
|
package/bin/sandboxedjs.mjs
CHANGED
|
@@ -185,7 +185,7 @@ async function printReplBanner(box, opts) {
|
|
|
185
185
|
`${BOLD}sandboxedjs${RESET} — a Linux-like container inside Node.js`,
|
|
186
186
|
"",
|
|
187
187
|
row("node", node, "npm, require, http servers"),
|
|
188
|
-
row("python3", python
|
|
188
|
+
row("python3", python, "CPython via Pyodide; WebAssembly wheels only"),
|
|
189
189
|
row("ffmpeg", ffmpeg, ffmpeg ? "video and audio" : "npm install @ffmpeg/core"),
|
|
190
190
|
row(
|
|
191
191
|
"network",
|
package/dist/index.cjs
CHANGED
|
@@ -8588,10 +8588,6 @@ async function inflate(data) {
|
|
|
8588
8588
|
if (isNode) return new Uint8Array((await nodeZlib()).inflateSync(data));
|
|
8589
8589
|
return throughStream(data, new DecompressionStream("deflate"));
|
|
8590
8590
|
}
|
|
8591
|
-
async function inflateRaw(data) {
|
|
8592
|
-
if (isNode) return new Uint8Array((await nodeZlib()).inflateRawSync(data));
|
|
8593
|
-
return throughStream(data, new DecompressionStream("deflate-raw"));
|
|
8594
|
-
}
|
|
8595
8591
|
var cryptoPromise = null;
|
|
8596
8592
|
function nodeCrypto() {
|
|
8597
8593
|
cryptoPromise ??= nodeBuiltin("crypto");
|
|
@@ -10931,7 +10927,7 @@ fi
|
|
|
10931
10927
|
`;
|
|
10932
10928
|
var MOTD = `Welcome to SandboxedJS \u2014 a Linux-like container running inside Node.js.
|
|
10933
10929
|
|
|
10934
|
-
* Node.js, npm and
|
|
10930
|
+
* Node.js, npm and CPython (Pyodide) are preinstalled.
|
|
10935
10931
|
* The filesystem is virtual: nothing here touches your host.
|
|
10936
10932
|
* Run 'help' for the list of built-in commands.
|
|
10937
10933
|
|
|
@@ -21904,6 +21900,9 @@ function nodeCommands() {
|
|
|
21904
21900
|
return [node, nodeVersionFile];
|
|
21905
21901
|
}
|
|
21906
21902
|
|
|
21903
|
+
// src/runtime/python.ts
|
|
21904
|
+
init_path();
|
|
21905
|
+
|
|
21907
21906
|
// src/runtime/emscripten-fs.ts
|
|
21908
21907
|
init_errno();
|
|
21909
21908
|
init_path();
|
|
@@ -22274,12 +22273,13 @@ function mountContainerDirs(py, ctx) {
|
|
|
22274
22273
|
function bootstrap(py, ctx, argv, scriptDir) {
|
|
22275
22274
|
const paths = [
|
|
22276
22275
|
...scriptDir ? [scriptDir] : [""],
|
|
22276
|
+
"/workspace",
|
|
22277
22277
|
"/usr/lib/python3",
|
|
22278
22278
|
"/usr/lib/python3/site-packages",
|
|
22279
22279
|
"/usr/local/lib/python3/site-packages"
|
|
22280
22280
|
];
|
|
22281
22281
|
py.runPython(`
|
|
22282
|
-
import sys, os
|
|
22282
|
+
import sys, os, importlib
|
|
22283
22283
|
sys.argv[:] = ${JSON.stringify(argv)}
|
|
22284
22284
|
for __p in reversed(${JSON.stringify(paths)}):
|
|
22285
22285
|
if __p and __p not in sys.path:
|
|
@@ -22292,6 +22292,7 @@ except NameError:
|
|
|
22292
22292
|
pass
|
|
22293
22293
|
os.environ.clear()
|
|
22294
22294
|
os.environ.update(${JSON.stringify(ctx.env)})
|
|
22295
|
+
importlib.invalidate_caches()
|
|
22295
22296
|
`);
|
|
22296
22297
|
try {
|
|
22297
22298
|
py.FS.chdir(ctx.cwd);
|
|
@@ -22381,22 +22382,92 @@ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
|
|
|
22381
22382
|
}
|
|
22382
22383
|
}
|
|
22383
22384
|
}
|
|
22385
|
+
async function cpythonVersion(ctx) {
|
|
22386
|
+
try {
|
|
22387
|
+
const py = await interpreterFor(ctx);
|
|
22388
|
+
return String(py.runPython("import sys; sys.version.split()[0]"));
|
|
22389
|
+
} catch {
|
|
22390
|
+
return null;
|
|
22391
|
+
}
|
|
22392
|
+
}
|
|
22393
|
+
async function runCPythonRepl(ctx) {
|
|
22394
|
+
let py;
|
|
22395
|
+
try {
|
|
22396
|
+
py = await interpreterFor(ctx);
|
|
22397
|
+
} catch (error) {
|
|
22398
|
+
ctx.stderr.write(`python3: Pyodide is unavailable (${error instanceof Error ? error.message : String(error)})
|
|
22399
|
+
`);
|
|
22400
|
+
return 127;
|
|
22401
|
+
}
|
|
22402
|
+
mountContainerDirs(py, ctx);
|
|
22403
|
+
bootstrap(py, ctx, [""], null);
|
|
22404
|
+
const decoder7 = new TextDecoder();
|
|
22405
|
+
py.setStdout({ write: (data) => (ctx.write(decoder7.decode(data)), data.length) });
|
|
22406
|
+
py.setStderr({ write: (data) => (ctx.stderr.write(decoder7.decode(data)), data.length) });
|
|
22407
|
+
ctx.line(`Python ${String(py.runPython("import sys; sys.version.split()[0]"))} (Pyodide)`);
|
|
22408
|
+
ctx.line('Type "help()" for more information.');
|
|
22409
|
+
let source = "";
|
|
22410
|
+
for (; ; ) {
|
|
22411
|
+
ctx.write(source ? "... " : ">>> ");
|
|
22412
|
+
const line = await ctx.stdin.readLine();
|
|
22413
|
+
if (line === null) {
|
|
22414
|
+
ctx.line("");
|
|
22415
|
+
break;
|
|
22416
|
+
}
|
|
22417
|
+
if (!source && ["exit()", "quit()"].includes(line.trim())) break;
|
|
22418
|
+
source += `${source ? "\n" : ""}${line}`;
|
|
22419
|
+
if (/[:\\]\s*$/.test(line) || source.includes("\n") && line.trim() !== "" && /^\s+/.test(line)) continue;
|
|
22420
|
+
try {
|
|
22421
|
+
await py.runPythonAsync(source);
|
|
22422
|
+
} catch (error) {
|
|
22423
|
+
reportError(ctx, error);
|
|
22424
|
+
}
|
|
22425
|
+
source = "";
|
|
22426
|
+
}
|
|
22427
|
+
return 0;
|
|
22428
|
+
}
|
|
22384
22429
|
var micropip = defineCommand({
|
|
22385
22430
|
name: "micropip",
|
|
22386
22431
|
path: "/usr/bin/micropip",
|
|
22432
|
+
aliases: ["pip", "pip3"],
|
|
22387
22433
|
summary: "install Python packages into the running interpreter",
|
|
22388
22434
|
usage: "micropip install <package>...",
|
|
22389
22435
|
async run(ctx) {
|
|
22390
|
-
const [action, ...
|
|
22391
|
-
if (action
|
|
22392
|
-
ctx.line("
|
|
22436
|
+
const [action, ...args] = ctx.args;
|
|
22437
|
+
if (action === "--version" || action === "-V") {
|
|
22438
|
+
ctx.line("pip (micropip, Pyodide)");
|
|
22439
|
+
return 0;
|
|
22440
|
+
}
|
|
22441
|
+
if (action !== "install") {
|
|
22442
|
+
ctx.line("usage: pip install [-r requirements.txt] <package>...");
|
|
22393
22443
|
return action === void 0 ? 1 : 0;
|
|
22394
22444
|
}
|
|
22445
|
+
const packages = args.filter((arg) => !arg.startsWith("-"));
|
|
22446
|
+
const requirementIndex = args.findIndex((arg) => arg === "-r" || arg === "--requirement");
|
|
22447
|
+
if (requirementIndex >= 0) {
|
|
22448
|
+
const file3 = args[requirementIndex + 1];
|
|
22449
|
+
if (!file3) {
|
|
22450
|
+
ctx.stderr.write("pip: option -r requires a file\n");
|
|
22451
|
+
return 2;
|
|
22452
|
+
}
|
|
22453
|
+
try {
|
|
22454
|
+
packages.splice(packages.indexOf(file3), 1);
|
|
22455
|
+
packages.push(...ctx.vfs.readText(ctx.path(file3), ctx.cred).split(/\r?\n/).map((line) => line.replace(/#.*$/, "").trim()).filter(Boolean));
|
|
22456
|
+
} catch {
|
|
22457
|
+
ctx.stderr.write(`pip: could not open requirements file '${file3}'
|
|
22458
|
+
`);
|
|
22459
|
+
return 1;
|
|
22460
|
+
}
|
|
22461
|
+
}
|
|
22462
|
+
if (packages.length === 0) {
|
|
22463
|
+
ctx.stderr.write("pip: no packages specified\n");
|
|
22464
|
+
return 1;
|
|
22465
|
+
}
|
|
22395
22466
|
let py;
|
|
22396
22467
|
try {
|
|
22397
22468
|
py = await interpreterFor(ctx);
|
|
22398
22469
|
} catch {
|
|
22399
|
-
ctx.stderr.write("
|
|
22470
|
+
ctx.stderr.write("pip: Pyodide is unavailable in this host\n");
|
|
22400
22471
|
return 127;
|
|
22401
22472
|
}
|
|
22402
22473
|
try {
|
|
@@ -22410,7 +22481,7 @@ var micropip = defineCommand({
|
|
|
22410
22481
|
return 0;
|
|
22411
22482
|
} catch (error) {
|
|
22412
22483
|
ctx.stderr.write(
|
|
22413
|
-
`
|
|
22484
|
+
`Package installation failed: ${error instanceof Error ? error.message : String(error)}
|
|
22414
22485
|
`
|
|
22415
22486
|
);
|
|
22416
22487
|
return 1;
|
|
@@ -22419,138 +22490,25 @@ var micropip = defineCommand({
|
|
|
22419
22490
|
});
|
|
22420
22491
|
|
|
22421
22492
|
// src/runtime/python.ts
|
|
22422
|
-
|
|
22423
|
-
var PYTHON_VERSION = "3.4.0";
|
|
22424
|
-
var MICROPYTHON_BANNER = "MicroPython v1.28.0 on 2026-04-06; SandboxedJS with Emscripten";
|
|
22425
|
-
var loaderPromise = null;
|
|
22426
|
-
var wasmUrl;
|
|
22427
|
-
var engine = "auto";
|
|
22428
|
-
var cpythonUsable = null;
|
|
22429
|
-
async function useCPython() {
|
|
22430
|
-
if (engine === "micropython") return false;
|
|
22431
|
-
if (engine === "cpython") return true;
|
|
22432
|
-
cpythonUsable ??= isCPythonAvailable();
|
|
22433
|
-
return cpythonUsable;
|
|
22434
|
-
}
|
|
22493
|
+
var PYTHON_VERSION = "3.13";
|
|
22435
22494
|
function configurePython(options = {}) {
|
|
22436
|
-
|
|
22437
|
-
engine = options.engine;
|
|
22438
|
-
cpythonUsable = null;
|
|
22439
|
-
}
|
|
22440
|
-
if (options.indexURL !== void 0) configureCPython({ indexURL: options.indexURL });
|
|
22441
|
-
if (options.pyodideURL !== void 0) configureCPython({ moduleURL: options.pyodideURL });
|
|
22442
|
-
if (options.wasmUrl !== void 0) wasmUrl = options.wasmUrl;
|
|
22443
|
-
}
|
|
22444
|
-
async function getLoader() {
|
|
22445
|
-
if (!loaderPromise) {
|
|
22446
|
-
loaderPromise = import('@micropython/micropython-webassembly-pyscript/micropython.mjs').then(
|
|
22447
|
-
(m) => m.loadMicroPython
|
|
22448
|
-
);
|
|
22449
|
-
}
|
|
22450
|
-
return loaderPromise;
|
|
22451
|
-
}
|
|
22452
|
-
async function isPythonAvailable() {
|
|
22453
|
-
try {
|
|
22454
|
-
await getLoader();
|
|
22455
|
-
return true;
|
|
22456
|
-
} catch {
|
|
22457
|
-
return false;
|
|
22458
|
-
}
|
|
22459
|
-
}
|
|
22460
|
-
async function createInterpreter(ctx, opts) {
|
|
22461
|
-
const loadMicroPython = await getLoader();
|
|
22462
|
-
const mp = await loadMicroPython({
|
|
22463
|
-
stdout: opts.stdout,
|
|
22464
|
-
stderr: opts.stderr,
|
|
22465
|
-
...opts.stdin ? { stdin: opts.stdin } : {},
|
|
22466
|
-
...wasmUrl ? { url: wasmUrl } : {},
|
|
22467
|
-
linebuffer: false,
|
|
22468
|
-
heapsize: opts.heapsize ?? 64 * 1024 * 1024
|
|
22469
|
-
});
|
|
22470
|
-
mountContainerFs(mp.FS, { vfs: ctx.vfs, cred: ctx.cred });
|
|
22471
|
-
try {
|
|
22472
|
-
mp.FS.chdir(ctx.cwd);
|
|
22473
|
-
} catch {
|
|
22474
|
-
}
|
|
22475
|
-
return mp;
|
|
22476
|
-
}
|
|
22477
|
-
function bootstrapPython(mp, ctx, argv, scriptDir) {
|
|
22478
|
-
const envEntries = Object.entries(ctx.env).map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`).join(", ");
|
|
22479
|
-
const paths = [
|
|
22480
|
-
...scriptDir ? [scriptDir] : [""],
|
|
22481
|
-
"/usr/lib/python3",
|
|
22482
|
-
"/usr/lib/python3/site-packages",
|
|
22483
|
-
"/usr/local/lib/python3/site-packages"
|
|
22484
|
-
];
|
|
22485
|
-
mp.runPython(`
|
|
22486
|
-
import sys, os
|
|
22487
|
-
sys.argv[:] = ${JSON.stringify(argv)}
|
|
22488
|
-
for __p in reversed(${JSON.stringify(paths)}):
|
|
22489
|
-
if __p not in sys.path:
|
|
22490
|
-
sys.path.insert(0, __p)
|
|
22491
|
-
del __p
|
|
22492
|
-
os.environ = {${envEntries}}
|
|
22493
|
-
os.getenv = lambda k, d=None: os.environ.get(k, d)
|
|
22494
|
-
os.putenv = lambda k, v: os.environ.__setitem__(k, v)
|
|
22495
|
-
os.unsetenv = lambda k: os.environ.pop(k, None)
|
|
22496
|
-
os.sep = '/'
|
|
22497
|
-
os.linesep = '\\n'
|
|
22498
|
-
os.name = 'posix'
|
|
22499
|
-
`);
|
|
22500
|
-
}
|
|
22501
|
-
function reportPythonError(ctx, e) {
|
|
22502
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
22503
|
-
const systemExit = /SystemExit:?\s*(-?\d+)?/.exec(message);
|
|
22504
|
-
if (systemExit && /SystemExit/.test(message)) {
|
|
22505
|
-
return systemExit[1] !== void 0 ? Number(systemExit[1]) & 255 : 0;
|
|
22506
|
-
}
|
|
22507
|
-
if (/KeyboardInterrupt/.test(message)) {
|
|
22508
|
-
ctx.stderr.write("KeyboardInterrupt\n");
|
|
22509
|
-
return 130;
|
|
22510
|
-
}
|
|
22511
|
-
const text = message.replace(/^PythonError:\s*/, "");
|
|
22512
|
-
ctx.stderr.write(text.endsWith("\n") ? text : text + "\n");
|
|
22513
|
-
return 1;
|
|
22514
|
-
}
|
|
22515
|
-
async function runProgram(ctx, source, argv, scriptDir, stdinText) {
|
|
22516
|
-
if (await useCPython()) {
|
|
22517
|
-
return runCPythonProgram(ctx, source, argv, scriptDir, stdinText);
|
|
22518
|
-
}
|
|
22519
|
-
let stdinOffset = 0;
|
|
22520
|
-
const stdinBytes = stdinText === null ? new Uint8Array(0) : new TextEncoder().encode(stdinText);
|
|
22521
|
-
const mp = await createInterpreter(ctx, {
|
|
22522
|
-
stdout: (chunk) => ctx.write(chunk),
|
|
22523
|
-
stderr: (chunk) => ctx.stderr.write(chunk),
|
|
22524
|
-
stdin: stdinText === null ? void 0 : () => stdinOffset < stdinBytes.length ? stdinBytes[stdinOffset++] : null
|
|
22525
|
-
});
|
|
22526
|
-
try {
|
|
22527
|
-
bootstrapPython(mp, ctx, argv, scriptDir);
|
|
22528
|
-
} catch (e) {
|
|
22529
|
-
return { exitCode: reportPythonError(ctx, e) };
|
|
22530
|
-
}
|
|
22531
|
-
try {
|
|
22532
|
-
await mp.runPythonAsync(source);
|
|
22533
|
-
return { exitCode: 0 };
|
|
22534
|
-
} catch (e) {
|
|
22535
|
-
return { exitCode: reportPythonError(ctx, e) };
|
|
22536
|
-
}
|
|
22495
|
+
configureCPython({ indexURL: options.indexURL, moduleURL: options.pyodideURL });
|
|
22537
22496
|
}
|
|
22497
|
+
var isPythonAvailable = isCPythonAvailable;
|
|
22538
22498
|
async function readStdin(ctx) {
|
|
22539
22499
|
if (ctx.stdin.isTTY || ctx.stdin.interactive) return null;
|
|
22540
22500
|
const bytes = await ctx.stdin.readAll();
|
|
22541
|
-
|
|
22542
|
-
return new TextDecoder().decode(bytes);
|
|
22501
|
+
return bytes.length ? new TextDecoder().decode(bytes) : null;
|
|
22543
22502
|
}
|
|
22544
22503
|
var python = defineCommand({
|
|
22545
22504
|
name: "python3",
|
|
22546
22505
|
path: "/usr/bin/python3",
|
|
22547
|
-
aliases: ["python"
|
|
22548
|
-
summary: "run
|
|
22506
|
+
aliases: ["python"],
|
|
22507
|
+
summary: "run Python using CPython (Pyodide)",
|
|
22549
22508
|
usage: "python3 [-c command | -m module | script.py] [arguments]",
|
|
22550
|
-
manual: `Python is
|
|
22551
|
-
|
|
22552
|
-
|
|
22553
|
-
Scripts see the container's filesystem, so open('/etc/passwd') works.`,
|
|
22509
|
+
manual: `Python is CPython compiled to WebAssembly by Pyodide. Scripts use
|
|
22510
|
+
the container's virtual filesystem, modules in /workspace are importable, and
|
|
22511
|
+
compatible packages can be installed with pip (micropip).`,
|
|
22554
22512
|
async run(ctx) {
|
|
22555
22513
|
const argv = ctx.args;
|
|
22556
22514
|
let i = 0;
|
|
@@ -22560,7 +22518,7 @@ Scripts see the container's filesystem, so open('/etc/passwd') works.`,
|
|
|
22560
22518
|
for (; i < argv.length; i++) {
|
|
22561
22519
|
const arg = argv[i];
|
|
22562
22520
|
if (arg === "-V" || arg === "--version") {
|
|
22563
|
-
ctx.line(`Python ${
|
|
22521
|
+
ctx.line(`Python ${await cpythonVersion(ctx) ?? PYTHON_VERSION}`);
|
|
22564
22522
|
return 0;
|
|
22565
22523
|
}
|
|
22566
22524
|
if (arg === "-h" || arg === "--help") {
|
|
@@ -22582,7 +22540,7 @@ Scripts see the container's filesystem, so open('/etc/passwd') works.`,
|
|
|
22582
22540
|
i++;
|
|
22583
22541
|
break;
|
|
22584
22542
|
}
|
|
22585
|
-
if (
|
|
22543
|
+
if (["-i", "-u", "-B", "-E", "-s", "-S", "-O"].includes(arg)) continue;
|
|
22586
22544
|
if (arg.startsWith("-")) continue;
|
|
22587
22545
|
script = arg;
|
|
22588
22546
|
i++;
|
|
@@ -22590,38 +22548,28 @@ Scripts see the container's filesystem, so open('/etc/passwd') works.`,
|
|
|
22590
22548
|
}
|
|
22591
22549
|
const rest = argv.slice(i);
|
|
22592
22550
|
if (command !== void 0) {
|
|
22593
|
-
|
|
22594
|
-
const outcome2 = await runProgram(ctx, command, ["-c", ...rest], null, stdin2);
|
|
22595
|
-
return outcome2.exitCode;
|
|
22551
|
+
return (await runCPythonProgram(ctx, command, ["-c", ...rest], null, await readStdin(ctx))).exitCode;
|
|
22596
22552
|
}
|
|
22597
22553
|
if (moduleName !== void 0) {
|
|
22598
|
-
const stdin2 = await readStdin(ctx);
|
|
22599
22554
|
const program = `
|
|
22600
|
-
import sys
|
|
22555
|
+
import runpy, sys
|
|
22601
22556
|
sys.argv = ${JSON.stringify([moduleName, ...rest])}
|
|
22602
22557
|
try:
|
|
22603
|
-
|
|
22604
|
-
except ImportError
|
|
22605
|
-
print("
|
|
22558
|
+
runpy.run_module(${JSON.stringify(moduleName)}, run_name="__main__", alter_sys=True)
|
|
22559
|
+
except ImportError:
|
|
22560
|
+
print("/usr/bin/python3: No module named " + ${JSON.stringify(moduleName)}, file=sys.stderr)
|
|
22606
22561
|
raise SystemExit(1)
|
|
22607
|
-
_main = getattr(__mod, "main", None)
|
|
22608
|
-
if callable(_main):
|
|
22609
|
-
_main()
|
|
22610
22562
|
`;
|
|
22611
|
-
|
|
22612
|
-
return outcome2.exitCode;
|
|
22563
|
+
return (await runCPythonProgram(ctx, program, [moduleName, ...rest], null, await readStdin(ctx))).exitCode;
|
|
22613
22564
|
}
|
|
22614
22565
|
if (script === void 0) {
|
|
22615
|
-
if (ctx.stdin.isTTY) return
|
|
22566
|
+
if (ctx.stdin.isTTY) return runCPythonRepl(ctx);
|
|
22616
22567
|
const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
|
|
22617
|
-
|
|
22618
|
-
const outcome2 = await runProgram(ctx, source2, ["", ...rest], null, null);
|
|
22619
|
-
return outcome2.exitCode;
|
|
22568
|
+
return source2.trim() ? (await runCPythonProgram(ctx, source2, ["", ...rest], null, null)).exitCode : 0;
|
|
22620
22569
|
}
|
|
22621
22570
|
if (script === "-") {
|
|
22622
22571
|
const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
|
|
22623
|
-
|
|
22624
|
-
return outcome2.exitCode;
|
|
22572
|
+
return (await runCPythonProgram(ctx, source2, ["-", ...rest], null, null)).exitCode;
|
|
22625
22573
|
}
|
|
22626
22574
|
const abs = ctx.path(script);
|
|
22627
22575
|
let source;
|
|
@@ -22632,213 +22580,17 @@ if callable(_main):
|
|
|
22632
22580
|
`);
|
|
22633
22581
|
return 2;
|
|
22634
22582
|
}
|
|
22635
|
-
|
|
22636
|
-
const outcome = await runProgram(ctx, source, [abs, ...rest], dirname(abs), stdin);
|
|
22637
|
-
return outcome.exitCode;
|
|
22583
|
+
return (await runCPythonProgram(ctx, source, [abs, ...rest], dirname(abs), await readStdin(ctx))).exitCode;
|
|
22638
22584
|
}
|
|
22639
22585
|
});
|
|
22640
|
-
async function runRepl2(ctx) {
|
|
22641
|
-
ctx.line(MICROPYTHON_BANNER);
|
|
22642
|
-
ctx.line('Type "help()" for more information.');
|
|
22643
|
-
const mp = await createInterpreter(ctx, {
|
|
22644
|
-
stdout: (chunk) => ctx.write(chunk),
|
|
22645
|
-
stderr: (chunk) => ctx.stderr.write(chunk)
|
|
22646
|
-
});
|
|
22647
|
-
bootstrapPython(mp, ctx, [""], null);
|
|
22648
|
-
let buffer = "";
|
|
22649
|
-
for (; ; ) {
|
|
22650
|
-
ctx.write(buffer === "" ? ">>> " : "... ");
|
|
22651
|
-
const line = await ctx.stdin.readLine();
|
|
22652
|
-
if (line === null) {
|
|
22653
|
-
ctx.line("");
|
|
22654
|
-
break;
|
|
22655
|
-
}
|
|
22656
|
-
if (buffer === "" && (line.trim() === "exit()" || line.trim() === "quit()")) break;
|
|
22657
|
-
buffer += (buffer === "" ? "" : "\n") + line;
|
|
22658
|
-
if (/[:\\]\s*$/.test(line) || buffer !== "" && line.trim() !== "" && /^\s+/.test(line)) continue;
|
|
22659
|
-
try {
|
|
22660
|
-
const trimmed = buffer.trim();
|
|
22661
|
-
const isExpression = !/^(import|from|def|class|if|for|while|try|with|return|raise|pass|del|global|nonlocal|assert|@)\b/.test(trimmed) && !trimmed.includes("\n") && !/^[A-Za-z_][A-Za-z0-9_]*\s*(=[^=]|[+\-*/|&^]=)/.test(trimmed);
|
|
22662
|
-
if (isExpression) {
|
|
22663
|
-
mp.runPython(`__sbx_repl = (${trimmed})
|
|
22664
|
-
if __sbx_repl is not None: print(repr(__sbx_repl))`);
|
|
22665
|
-
} else {
|
|
22666
|
-
mp.runPython(buffer);
|
|
22667
|
-
}
|
|
22668
|
-
} catch (e) {
|
|
22669
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
22670
|
-
if (/SystemExit/.test(message)) break;
|
|
22671
|
-
try {
|
|
22672
|
-
mp.runPython(buffer);
|
|
22673
|
-
} catch (inner) {
|
|
22674
|
-
const text = (inner instanceof Error ? inner.message : String(inner)).replace(/^PythonError:\s*/, "");
|
|
22675
|
-
ctx.stderr.write(text.endsWith("\n") ? text : text + "\n");
|
|
22676
|
-
}
|
|
22677
|
-
}
|
|
22678
|
-
buffer = "";
|
|
22679
|
-
}
|
|
22680
|
-
return 0;
|
|
22681
|
-
}
|
|
22682
22586
|
function printHelp2(ctx) {
|
|
22683
22587
|
ctx.line("usage: python3 [option] ... [-c cmd | -m mod | file | -] [arg] ...");
|
|
22684
|
-
ctx.line("Options:");
|
|
22685
22588
|
ctx.line("-c cmd : program passed in as string");
|
|
22686
22589
|
ctx.line("-m mod : run library module as a script");
|
|
22687
|
-
ctx.line("-V : print the Python version
|
|
22688
|
-
ctx.line("file : program read from script file");
|
|
22689
|
-
ctx.line("- : program read from stdin");
|
|
22690
|
-
}
|
|
22691
|
-
var pip = defineCommand({
|
|
22692
|
-
name: "pip",
|
|
22693
|
-
path: "/usr/bin/pip",
|
|
22694
|
-
aliases: ["pip3"],
|
|
22695
|
-
summary: "install Python packages",
|
|
22696
|
-
usage: "pip install PACKAGE...",
|
|
22697
|
-
async run(ctx) {
|
|
22698
|
-
const [subcommand, ...rest] = ctx.args;
|
|
22699
|
-
const siteDir = "/usr/lib/python3/site-packages";
|
|
22700
|
-
if (subcommand === void 0 || subcommand === "--help" || subcommand === "-h") {
|
|
22701
|
-
ctx.line("Usage: pip <command> [options]");
|
|
22702
|
-
ctx.line("");
|
|
22703
|
-
ctx.line("Commands:");
|
|
22704
|
-
ctx.line(" install Install packages");
|
|
22705
|
-
ctx.line(" list List installed packages");
|
|
22706
|
-
ctx.line(" show Show information about installed packages");
|
|
22707
|
-
ctx.line(" uninstall Uninstall packages");
|
|
22708
|
-
return 0;
|
|
22709
|
-
}
|
|
22710
|
-
if (subcommand === "--version" || subcommand === "-V") {
|
|
22711
|
-
ctx.line(`pip 24.0 from ${siteDir}/pip (python ${PYTHON_VERSION})`);
|
|
22712
|
-
return 0;
|
|
22713
|
-
}
|
|
22714
|
-
if (subcommand === "list") {
|
|
22715
|
-
if (!ctx.vfs.lexists(siteDir)) {
|
|
22716
|
-
ctx.line("Package Version");
|
|
22717
|
-
ctx.line("---------- -------");
|
|
22718
|
-
return 0;
|
|
22719
|
-
}
|
|
22720
|
-
ctx.line("Package Version");
|
|
22721
|
-
ctx.line("---------- -------");
|
|
22722
|
-
for (const name of ctx.vfs.readdir(siteDir, ctx.cred)) {
|
|
22723
|
-
if (name.endsWith(".dist-info")) {
|
|
22724
|
-
const [pkg, version] = name.replace(".dist-info", "").split("-");
|
|
22725
|
-
ctx.line(`${(pkg ?? name).padEnd(10)} ${version ?? "0.0.0"}`);
|
|
22726
|
-
}
|
|
22727
|
-
}
|
|
22728
|
-
return 0;
|
|
22729
|
-
}
|
|
22730
|
-
if (subcommand === "uninstall") {
|
|
22731
|
-
let status2 = 0;
|
|
22732
|
-
for (const name of rest.filter((a) => !a.startsWith("-"))) {
|
|
22733
|
-
const target = join(siteDir, name);
|
|
22734
|
-
if (ctx.vfs.lexists(target)) {
|
|
22735
|
-
ctx.vfs.rmrf(target, ctx.cred);
|
|
22736
|
-
ctx.line(`Successfully uninstalled ${name}`);
|
|
22737
|
-
} else {
|
|
22738
|
-
ctx.warn(`WARNING: Skipping ${name} as it is not installed.`);
|
|
22739
|
-
status2 = 1;
|
|
22740
|
-
}
|
|
22741
|
-
}
|
|
22742
|
-
return status2;
|
|
22743
|
-
}
|
|
22744
|
-
if (subcommand !== "install") {
|
|
22745
|
-
ctx.warn(`ERROR: unknown command "${subcommand}"`);
|
|
22746
|
-
return 1;
|
|
22747
|
-
}
|
|
22748
|
-
const packages = rest.filter((a) => !a.startsWith("-"));
|
|
22749
|
-
if (packages.length === 0) {
|
|
22750
|
-
ctx.warn("ERROR: You must give at least one requirement to install");
|
|
22751
|
-
return 1;
|
|
22752
|
-
}
|
|
22753
|
-
if (!ctx.kernel.net.options.allowOutbound) {
|
|
22754
|
-
ctx.warn("ERROR: Could not find a version that satisfies the requirement");
|
|
22755
|
-
ctx.warn("Outbound network access is disabled for this container.");
|
|
22756
|
-
ctx.warn("Enable it with createContainer({ network: { allowOutbound: true } }).");
|
|
22757
|
-
return 1;
|
|
22758
|
-
}
|
|
22759
|
-
ctx.vfs.mkdir(siteDir, { recursive: true, cred: ctx.cred, mode: 493 });
|
|
22760
|
-
let status = 0;
|
|
22761
|
-
for (const spec of packages) {
|
|
22762
|
-
const [name, version] = spec.split(/[=<>~!]+/);
|
|
22763
|
-
const url = `https://pypi.org/pypi/${name}/json`;
|
|
22764
|
-
if (!ctx.kernel.net.outboundAllowed(url)) {
|
|
22765
|
-
ctx.warn(`ERROR: host pypi.org is not in the allowed list`);
|
|
22766
|
-
status = 1;
|
|
22767
|
-
continue;
|
|
22768
|
-
}
|
|
22769
|
-
ctx.line(`Collecting ${spec}`);
|
|
22770
|
-
try {
|
|
22771
|
-
const response = await fetch(url);
|
|
22772
|
-
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
22773
|
-
const meta = await response.json();
|
|
22774
|
-
const chosen = version && meta.releases[version] ? version : meta.info.version;
|
|
22775
|
-
const files = meta.releases[chosen] ?? [];
|
|
22776
|
-
const wheel = files.find((f) => f.packagetype === "bdist_wheel" && f.filename.includes("py3-none-any"));
|
|
22777
|
-
if (!wheel) {
|
|
22778
|
-
ctx.warn(`ERROR: no pure-Python wheel available for ${name} ${chosen}`);
|
|
22779
|
-
status = 1;
|
|
22780
|
-
continue;
|
|
22781
|
-
}
|
|
22782
|
-
const data = new Uint8Array(await (await fetch(wheel.url)).arrayBuffer());
|
|
22783
|
-
ctx.line(` Downloading ${wheel.filename} (${Math.round(data.length / 1024)} kB)`);
|
|
22784
|
-
await installWheel(ctx, siteDir, data, name, chosen);
|
|
22785
|
-
ctx.line(`Successfully installed ${name}-${chosen}`);
|
|
22786
|
-
} catch (e) {
|
|
22787
|
-
ctx.warn(`ERROR: Could not install ${spec}: ${e instanceof Error ? e.message : String(e)}`);
|
|
22788
|
-
status = 1;
|
|
22789
|
-
}
|
|
22790
|
-
}
|
|
22791
|
-
return status;
|
|
22792
|
-
}
|
|
22793
|
-
});
|
|
22794
|
-
async function installWheel(ctx, siteDir, data, name, version) {
|
|
22795
|
-
const entries = await readZip(data);
|
|
22796
|
-
for (const entry of entries) {
|
|
22797
|
-
if (entry.name.endsWith("/")) continue;
|
|
22798
|
-
const target = join(siteDir, entry.name);
|
|
22799
|
-
const parent = dirname(target);
|
|
22800
|
-
if (!ctx.vfs.lexists(parent)) ctx.vfs.mkdir(parent, { recursive: true, cred: ctx.cred, mode: 493 });
|
|
22801
|
-
ctx.vfs.writeFile(target, entry.data, { cred: ctx.cred, mode: 420 });
|
|
22802
|
-
}
|
|
22803
|
-
const distInfo = join(siteDir, `${name}-${version}.dist-info`);
|
|
22804
|
-
if (!ctx.vfs.lexists(distInfo)) ctx.vfs.mkdir(distInfo, { recursive: true, cred: ctx.cred, mode: 493 });
|
|
22805
|
-
ctx.vfs.writeFile(join(distInfo, "METADATA"), `Name: ${name}
|
|
22806
|
-
Version: ${version}
|
|
22807
|
-
`, { cred: ctx.cred });
|
|
22808
|
-
}
|
|
22809
|
-
async function readZip(data) {
|
|
22810
|
-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
22811
|
-
const entries = [];
|
|
22812
|
-
let eocd = -1;
|
|
22813
|
-
for (let i = data.length - 22; i >= 0 && i > data.length - 65558; i--) {
|
|
22814
|
-
if (view.getUint32(i, true) === 101010256) {
|
|
22815
|
-
eocd = i;
|
|
22816
|
-
break;
|
|
22817
|
-
}
|
|
22818
|
-
}
|
|
22819
|
-
if (eocd < 0) return entries;
|
|
22820
|
-
const count = view.getUint16(eocd + 10, true);
|
|
22821
|
-
let offset = view.getUint32(eocd + 16, true);
|
|
22822
|
-
for (let i = 0; i < count; i++) {
|
|
22823
|
-
if (view.getUint32(offset, true) !== 33639248) break;
|
|
22824
|
-
const method = view.getUint16(offset + 10, true);
|
|
22825
|
-
const compressedSize = view.getUint32(offset + 20, true);
|
|
22826
|
-
const nameLength = view.getUint16(offset + 28, true);
|
|
22827
|
-
const extraLength = view.getUint16(offset + 30, true);
|
|
22828
|
-
const commentLength = view.getUint16(offset + 32, true);
|
|
22829
|
-
const localOffset = view.getUint32(offset + 42, true);
|
|
22830
|
-
const name = new TextDecoder().decode(data.subarray(offset + 46, offset + 46 + nameLength));
|
|
22831
|
-
const localNameLength = view.getUint16(localOffset + 26, true);
|
|
22832
|
-
const localExtraLength = view.getUint16(localOffset + 28, true);
|
|
22833
|
-
const dataStart = localOffset + 30 + localNameLength + localExtraLength;
|
|
22834
|
-
const raw = data.subarray(dataStart, dataStart + compressedSize);
|
|
22835
|
-
entries.push({ name, data: method === 0 ? raw.slice() : await inflateRaw(raw) });
|
|
22836
|
-
offset += 46 + nameLength + extraLength + commentLength;
|
|
22837
|
-
}
|
|
22838
|
-
return entries;
|
|
22590
|
+
ctx.line("-V : print the Python version and exit");
|
|
22839
22591
|
}
|
|
22840
22592
|
function pythonCommands() {
|
|
22841
|
-
return [python,
|
|
22593
|
+
return [python, micropip];
|
|
22842
22594
|
}
|
|
22843
22595
|
|
|
22844
22596
|
// src/runtime/ffmpeg.ts
|
|
@@ -23652,7 +23404,7 @@ var apt = defineCommand({
|
|
|
23652
23404
|
const provided = {
|
|
23653
23405
|
nodejs: { version: NODE_VERSION.replace(/^v/, ""), description: "Node.js event-based server-side JavaScript engine" },
|
|
23654
23406
|
npm: { version: NPM_VERSION, description: "package manager for Node.js" },
|
|
23655
|
-
python3: { version:
|
|
23407
|
+
python3: { version: PYTHON_VERSION, description: "CPython interpreter powered by Pyodide" },
|
|
23656
23408
|
"python3-pip": { version: "24.0", description: "Python package installer" },
|
|
23657
23409
|
coreutils: { version: "9.4", description: "GNU core utilities" },
|
|
23658
23410
|
grep: { version: "3.11", description: "GNU grep, egrep and fgrep" },
|
|
@@ -23756,7 +23508,7 @@ var dpkg = defineCommand({
|
|
|
23756
23508
|
ctx.line("||/ Name Version Architecture Description");
|
|
23757
23509
|
ctx.line("+++-==============-============-============-=================================");
|
|
23758
23510
|
ctx.line(`ii nodejs ${NODE_VERSION.replace(/^v/, "").padEnd(12)} amd64 Node.js JavaScript runtime`);
|
|
23759
|
-
ctx.line(`ii python3
|
|
23511
|
+
ctx.line(`ii python3 ${PYTHON_VERSION.padEnd(12)} wasm32 CPython (Pyodide) interpreter`);
|
|
23760
23512
|
return 0;
|
|
23761
23513
|
}
|
|
23762
23514
|
ctx.line("dpkg 1.22.6 (amd64)");
|
|
@@ -24607,7 +24359,12 @@ function isNodeRuntime() {
|
|
|
24607
24359
|
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
24608
24360
|
}
|
|
24609
24361
|
async function bootBrowserPod(opts) {
|
|
24610
|
-
const { Nodepod } = await import('@scelar/nodepod');
|
|
24362
|
+
const { Nodepod, createBrowserHost, getRuntimeHost, setRuntimeHost } = await import('@scelar/nodepod');
|
|
24363
|
+
try {
|
|
24364
|
+
getRuntimeHost();
|
|
24365
|
+
} catch {
|
|
24366
|
+
setRuntimeHost(createBrowserHost());
|
|
24367
|
+
}
|
|
24611
24368
|
return Nodepod.boot({
|
|
24612
24369
|
env: opts.env ?? {},
|
|
24613
24370
|
workdir: opts.cwd ?? "/",
|