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 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, `engine: "auto"` finds no interpreter and falls back to MicroPython, which is why a
653
- browser build of this package carries no Python runtime it was not asked for.
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.
@@ -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 && `MicroPython`, "no C extensions (numpy, pandas)"),
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 MicroPython are preinstalled.
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, ...packages] = ctx.args;
22391
- if (action !== "install" || packages.length === 0) {
22392
- ctx.line("usage: micropip install <package>...");
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("micropip: CPython is unavailable in this host\n");
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
- `micropip: ${error instanceof Error ? error.message : String(error)}
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
- init_path();
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
- if (options.engine !== void 0) {
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
- if (bytes.length === 0) return null;
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", "micropython"],
22548
- summary: "run a Python program (MicroPython)",
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 provided by MicroPython compiled to WebAssembly. The
22551
- standard library subset includes json, re, os, sys, math, random, hashlib,
22552
- binascii, struct, time, collections, itertools, functools, asyncio and more.
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 ${PYTHON_VERSION} (MicroPython v1.28.0)`);
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 (arg === "-i" || arg === "-u" || arg === "-B" || arg === "-E" || arg === "-s" || arg === "-S" || arg === "-O") continue;
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
- const stdin2 = await readStdin(ctx);
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
- __mod = __import__(${JSON.stringify(moduleName)})
22604
- except ImportError as exc:
22605
- print("${"/usr/bin/python3"}: No module named " + ${JSON.stringify(moduleName)}, file=sys.stderr)
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
- const outcome2 = await runProgram(ctx, program, [moduleName, ...rest], null, stdin2);
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 runRepl2(ctx);
22566
+ if (ctx.stdin.isTTY) return runCPythonRepl(ctx);
22616
22567
  const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
22617
- if (source2.trim() === "") return 0;
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
- const outcome2 = await runProgram(ctx, source2, ["-", ...rest], null, null);
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
- const stdin = await readStdin(ctx);
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 number and exit");
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, pip, micropip];
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: "3.4.0-micropython", description: "interactive high-level object-oriented language" },
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 3.4.0 amd64 MicroPython interpreter`);
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 ?? "/",