sandboxedjs 0.1.76 → 0.1.78

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/dist/index.cjs CHANGED
@@ -7819,7 +7819,7 @@ fi
7819
7819
  `;
7820
7820
  var MOTD = `Welcome to SandboxedJS \u2014 a Linux-like container running inside Node.js.
7821
7821
 
7822
- * Node.js, npm and CPython (Pyodide) are preinstalled.
7822
+ * Node.js, npm and CPython are preinstalled.
7823
7823
  * The filesystem is virtual: nothing here touches your host.
7824
7824
  * Run 'help' for the list of built-in commands.
7825
7825
 
@@ -19068,6 +19068,49 @@ var NOTHING = [];
19068
19068
  var PROPERTY = ["property", "key"];
19069
19069
  var LABEL = ["label"];
19070
19070
 
19071
+ // src/node/env.ts
19072
+ function parseEnv(text2) {
19073
+ if (typeof text2 !== "string") {
19074
+ throw Object.assign(new TypeError('The "content" argument must be of type string'), { code: "ERR_INVALID_ARG_TYPE" });
19075
+ }
19076
+ const out = {};
19077
+ const lines = text2.split(/\r?\n/);
19078
+ for (let index = 0; index < lines.length; index++) {
19079
+ const raw = lines[index];
19080
+ const match2 = /^[ \t]*(?:export[ \t]+)?([^=#]+?)[ \t]*=[ \t]*(.*)$/.exec(raw);
19081
+ if (!match2) continue;
19082
+ const name = match2[1];
19083
+ let value = match2[2];
19084
+ const quote3 = value[0];
19085
+ if (quote3 === '"' || quote3 === "'" || quote3 === "`") {
19086
+ const start2 = index;
19087
+ const original = value;
19088
+ value = value.slice(1);
19089
+ while (true) {
19090
+ const end = value.indexOf(quote3);
19091
+ if (end >= 0) {
19092
+ value = value.slice(0, end);
19093
+ break;
19094
+ }
19095
+ if (++index >= lines.length) {
19096
+ index = start2;
19097
+ value = original;
19098
+ break;
19099
+ }
19100
+ value += `
19101
+ ${lines[index]}`;
19102
+ }
19103
+ if (quote3 === '"') value = value.replace(/\\n/g, "\n");
19104
+ } else {
19105
+ const comment = value.indexOf("#");
19106
+ if (comment >= 0) value = value.slice(0, comment);
19107
+ value = value.trim();
19108
+ }
19109
+ Object.defineProperty(out, name, { value, writable: true, enumerable: true, configurable: true });
19110
+ }
19111
+ return out;
19112
+ }
19113
+
19071
19114
  // src/node/node.ts
19072
19115
  var NODE_VERSION = "v22.12.0";
19073
19116
  new TextEncoder();
@@ -19334,7 +19377,7 @@ The filesystem a script sees is the container's filesystem.`,
19334
19377
  `);
19335
19378
  return 9;
19336
19379
  }
19337
- Object.assign(loaded, parseEnvFile(text2));
19380
+ Object.assign(loaded, parseEnv(text2));
19338
19381
  }
19339
19382
  env2 = { ...loaded, ...ctx.env };
19340
19383
  }
@@ -19571,27 +19614,6 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
19571
19614
  "--report-dir",
19572
19615
  "--report-filename"
19573
19616
  ]);
19574
- function parseEnvFile(text2) {
19575
- const out = {};
19576
- for (const raw of text2.split(/\r?\n/)) {
19577
- const line = raw.trim();
19578
- if (!line || line.startsWith("#")) continue;
19579
- const match2 = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.*)$/.exec(line);
19580
- if (!match2) continue;
19581
- let value = match2[2];
19582
- const quote3 = value[0];
19583
- if ((quote3 === '"' || quote3 === "'" || quote3 === "`") && value.length >= 2 && value.endsWith(quote3)) {
19584
- value = value.slice(1, -1);
19585
- if (quote3 === '"') value = value.replace(/\\n/g, "\n");
19586
- } else {
19587
- const comment = value.indexOf(" #");
19588
- if (comment >= 0) value = value.slice(0, comment);
19589
- value = value.trim();
19590
- }
19591
- out[match2[1]] = value;
19592
- }
19593
- return out;
19594
- }
19595
19617
  var AsyncFunction = Object.getPrototypeOf(async function() {
19596
19618
  }).constructor;
19597
19619
  function checkSyntax(ctx, script) {
@@ -19745,9 +19767,6 @@ function nodeCommands() {
19745
19767
  return [node, nodeVersionFile];
19746
19768
  }
19747
19769
 
19748
- // src/python/python.ts
19749
- init_path();
19750
-
19751
19770
  // src/python/host-abi.ts
19752
19771
  var SBX_HOST_ABI_VERSION = 1;
19753
19772
  var SBX_REQUEST_HEADER_BYTES = 16;
@@ -20218,7 +20237,7 @@ var wheels_default = {
20218
20237
 
20219
20238
  // package.json
20220
20239
  var package_default = {
20221
- version: "0.1.76"};
20240
+ version: "0.1.78"};
20222
20241
 
20223
20242
  // src/python/config.ts
20224
20243
  function runtimeModuleUrl() {
@@ -20250,11 +20269,15 @@ var embeddedWheels = {
20250
20269
  "pydantic_core-2.23.2-cp313-cp313-emscripten_5_0_6_wasm32.whl": pydantic_core_2_23_2_cp313_cp313_emscripten_5_0_6_wasm32_default,
20251
20270
  "pydantic_core-2.46.5-cp313-cp313-emscripten_5_0_6_wasm32.whl": pydantic_core_2_46_5_cp313_cp313_emscripten_5_0_6_wasm32_default
20252
20271
  };
20253
- var bundledWheelIndex = {
20254
- baseUrl: new URL("./wheels", runtimeModuleUrl()).href,
20255
- wheels: wheels_default.wheels,
20256
- files: embeddedWheels
20257
- };
20272
+ function wheelIndexFor(moduleUrl) {
20273
+ return {
20274
+ baseUrl: new URL("./wheels", moduleUrl).href,
20275
+ wheels: wheels_default.wheels,
20276
+ files: embeddedWheels
20277
+ };
20278
+ }
20279
+ var bundledWheelIndex = wheelIndexFor(runtimeModuleUrl());
20280
+ var wheelIndexIsExplicit = false;
20258
20281
  var config = {
20259
20282
  backend: "sbx-cpython-wasm",
20260
20283
  manifest: bundledManifest,
@@ -20269,9 +20292,17 @@ var config = {
20269
20292
  buildFromSource: typeof process !== "undefined" && Boolean(process.versions?.node) && (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)).startsWith("file:")
20270
20293
  };
20271
20294
  function setPythonBackend(options) {
20272
- if (options.wheelIndex !== void 0) config.wheelIndex = options.wheelIndex;
20295
+ if (options.wheelIndex !== void 0) {
20296
+ config.wheelIndex = options.wheelIndex;
20297
+ wheelIndexIsExplicit = true;
20298
+ }
20273
20299
  if (options.buildFromSource !== void 0) config.buildFromSource = options.buildFromSource;
20274
- if (options.manifest !== void 0) config.manifest = validateManifest(options.manifest);
20300
+ if (options.manifest !== void 0) {
20301
+ config.manifest = validateManifest(options.manifest);
20302
+ if (!wheelIndexIsExplicit) {
20303
+ config.wheelIndex = wheelIndexFor(config.manifest.artifacts.moduleUrl);
20304
+ }
20305
+ }
20275
20306
  if (options.workerUrl !== void 0) config.workerUrl = options.workerUrl;
20276
20307
  if (options.backend !== void 0) {
20277
20308
  if (options.backend === "sbx-cpython-wasm" && !config.manifest) {
@@ -22789,6 +22820,7 @@ async function installRequirements(options) {
22789
22820
  }
22790
22821
  options.progress.downloading(distribution.name, distribution.version);
22791
22822
  const bytes2 = await options.client.bytes(distribution.url, { timeoutMs: 12e4 });
22823
+ requireArchive(distribution, bytes2);
22792
22824
  verifyDigest(distribution, bytes2);
22793
22825
  staged.push(...stageWheel(readZip(bytes2)));
22794
22826
  installed2.push({ name: distribution.name, version: distribution.version });
@@ -22796,6 +22828,14 @@ async function installRequirements(options) {
22796
22828
  commit(options.vfs, options.cred, staged);
22797
22829
  return { installed: installed2, skipped: solved.skipped };
22798
22830
  }
22831
+ function requireArchive(distribution, bytes2) {
22832
+ if (bytes2.length > 1 && bytes2[0] === 80 && bytes2[1] === 75) return;
22833
+ const opening = new TextDecoder().decode(bytes2.subarray(0, 80)).replace(/\s+/g, " ").trim();
22834
+ const looksLikeMarkup = /^<!doctype|^<html/i.test(opening);
22835
+ throw new Error(
22836
+ `${distribution.filename} did not arrive as a wheel from ${distribution.url}: ` + (looksLikeMarkup ? "the server answered with an HTML page, which usually means the wheel is not published at that address and the server returned its index page instead" : `expected a zip archive, got ${bytes2.length} bytes beginning ${JSON.stringify(opening)}`)
22837
+ );
22838
+ }
22799
22839
  function verifyDigest(distribution, bytes2) {
22800
22840
  if (!distribution.sha256) {
22801
22841
  throw new Error(
@@ -23080,9 +23120,6 @@ async function resolveWorkerUrl(explicit) {
23080
23120
  "the SandboxedJs Python process worker has not been built; run `npm run build` in the package, or pass configurePython({ workerUrl })."
23081
23121
  );
23082
23122
  }
23083
- function usingOwnedPython() {
23084
- return pythonBackend().backend === "sbx-cpython-wasm";
23085
- }
23086
23123
  async function runOwnedPython(ctx, argv) {
23087
23124
  const { manifest, workerUrl } = pythonBackend();
23088
23125
  if (!manifest) throw new Error("no Python runtime manifest is configured");
@@ -23571,6 +23608,75 @@ function listInstalled(ctx) {
23571
23608
  return 0;
23572
23609
  }
23573
23610
 
23611
+ // src/python/python.ts
23612
+ var PYTHON_VERSION = "3.13";
23613
+ function configurePython(options = {}) {
23614
+ setPythonBackend({
23615
+ ...options.backend !== void 0 ? { backend: options.backend } : {},
23616
+ ...options.manifest !== void 0 ? { manifest: options.manifest } : {},
23617
+ ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {},
23618
+ ...options.wheelIndex !== void 0 ? { wheelIndex: options.wheelIndex } : {},
23619
+ ...options.buildFromSource !== void 0 ? { buildFromSource: options.buildFromSource } : {}
23620
+ });
23621
+ }
23622
+ async function isPythonAvailable() {
23623
+ return pythonBackend().manifest !== null;
23624
+ }
23625
+ function withTopLevelAwait(source) {
23626
+ if (!/\bawait\b/.test(source)) return source;
23627
+ return `import ast as _sbx_ast, inspect as _sbx_inspect
23628
+ _sbx_src = ${JSON.stringify(source)}
23629
+ try:
23630
+ _sbx_code = compile(_sbx_src, "<string>", "exec")
23631
+ except SyntaxError:
23632
+ _sbx_code = compile(_sbx_src, "<string>", "exec", _sbx_ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
23633
+ _sbx_async = bool(_sbx_code.co_flags & _sbx_inspect.CO_COROUTINE)
23634
+ _sbx_globals = globals()
23635
+ for _sbx_name in ("_sbx_ast", "_sbx_inspect", "_sbx_src", "_sbx_name"):
23636
+ _sbx_globals.pop(_sbx_name, None)
23637
+ if _sbx_async:
23638
+ import asyncio as _sbx_asyncio
23639
+ _sbx_asyncio.run(eval(_sbx_code, _sbx_globals))
23640
+ else:
23641
+ exec(_sbx_code, _sbx_globals)
23642
+ `;
23643
+ }
23644
+ function withTopLevelAwaitArgs(args) {
23645
+ for (let i = 0; i < args.length; i++) {
23646
+ const arg = args[i];
23647
+ if (arg === "-c") {
23648
+ if (i + 1 >= args.length) return args;
23649
+ const rewritten = withTopLevelAwait(args[i + 1]);
23650
+ if (rewritten === args[i + 1]) return args;
23651
+ const copy = [...args];
23652
+ copy[i + 1] = rewritten;
23653
+ return copy;
23654
+ }
23655
+ if (arg === "-m" || !arg.startsWith("-") || arg === "-") return args;
23656
+ }
23657
+ return args;
23658
+ }
23659
+ var python = defineCommand({
23660
+ name: "python3",
23661
+ path: "/usr/bin/python3",
23662
+ aliases: ["python"],
23663
+ summary: "run CPython",
23664
+ usage: "python3 [-c command | -m module | script.py] [arguments]",
23665
+ manual: `CPython built for WebAssembly, one interpreter per process. Scripts
23666
+ use the container's virtual filesystem, modules in /workspace are importable,
23667
+ and packages are installed with pip.`,
23668
+ async run(ctx) {
23669
+ try {
23670
+ return await runOwnedPython(ctx, withTopLevelAwaitArgs(ctx.args));
23671
+ } catch (error) {
23672
+ return ctx.fail(error.message ?? String(error));
23673
+ }
23674
+ }
23675
+ });
23676
+ function pythonCommands() {
23677
+ return [python, pipCommand];
23678
+ }
23679
+
23574
23680
  // src/fs/emscripten-fs.ts
23575
23681
  init_errno();
23576
23682
  init_path();
@@ -23663,1462 +23769,167 @@ function mountContainerFs(FS, opts) {
23663
23769
  vfs.chmod(path, attr.mode & 4095, cred);
23664
23770
  node2.mode = attr.mode;
23665
23771
  }
23666
- if (attr.timestamp !== void 0) vfs.utimes(path, attr.timestamp, attr.timestamp, cred);
23667
- if (attr.size !== void 0) {
23668
- vfs.truncate(path, attr.size, cred);
23669
- opts.onWrite?.(path);
23670
- }
23671
- } catch (e) {
23672
- translate(e);
23673
- }
23674
- },
23675
- lookup(parent, name) {
23676
- const path = join(backend.realPath(parent), name);
23677
- let mode;
23678
- try {
23679
- mode = vfs.lstat(path).mode;
23680
- } catch {
23681
- throw new FS.ErrnoError(EM_ERRNO.ENOENT);
23682
- }
23683
- return backend.createNode(parent, name, mode, 0);
23684
- },
23685
- mknod(parent, name, mode, dev) {
23686
- const path = join(backend.realPath(parent), name);
23687
- try {
23688
- if (FS.isDir(mode)) {
23689
- vfs.mkdir(path, { cred, mode: mode & 4095 });
23690
- } else if (FS.isFile(mode)) {
23691
- vfs.writeFile(path, new Uint8Array(0), { cred, mode: mode & 4095 });
23692
- } else {
23693
- return throwErrno("ENOTSUP");
23694
- }
23695
- opts.onWrite?.(path);
23696
- } catch (e) {
23697
- translate(e);
23698
- }
23699
- return backend.createNode(parent, name, mode, dev);
23700
- },
23701
- rename(oldNode, newDir, newName) {
23702
- const from = backend.realPath(oldNode);
23703
- const to = join(backend.realPath(newDir), newName);
23704
- try {
23705
- vfs.rename(from, to, cred);
23706
- opts.onWrite?.(to);
23707
- } catch (e) {
23708
- translate(e);
23709
- }
23710
- oldNode.name = newName;
23711
- oldNode.parent = newDir;
23712
- },
23713
- unlink(parent, name) {
23714
- const path = join(backend.realPath(parent), name);
23715
- try {
23716
- vfs.unlink(path, cred);
23717
- opts.onWrite?.(path);
23718
- } catch (e) {
23719
- translate(e);
23720
- }
23721
- },
23722
- rmdir(parent, name) {
23723
- const path = join(backend.realPath(parent), name);
23724
- try {
23725
- vfs.rmdir(path, cred);
23726
- opts.onWrite?.(path);
23727
- } catch (e) {
23728
- translate(e);
23729
- }
23730
- },
23731
- readdir(node2) {
23732
- const path = backend.realPath(node2);
23733
- try {
23734
- return [".", "..", ...vfs.readdir(path, cred)];
23735
- } catch (e) {
23736
- return translate(e);
23737
- }
23738
- },
23739
- symlink(parent, newName, oldPath) {
23740
- const path = join(backend.realPath(parent), newName);
23741
- try {
23742
- vfs.symlink(oldPath, path, cred);
23743
- opts.onWrite?.(path);
23744
- } catch (e) {
23745
- translate(e);
23746
- }
23747
- },
23748
- readlink(node2) {
23749
- const path = backend.realPath(node2);
23750
- try {
23751
- return vfs.readlink(path, cred);
23752
- } catch (e) {
23753
- return translate(e);
23754
- }
23755
- }
23756
- };
23757
- const streamOps = {
23758
- open(stream) {
23759
- const path = backend.realPath(stream.node);
23760
- if (FS.isDir(stream.node.mode)) return;
23761
- try {
23762
- stream.sbxBuffer = vfs.lexists(path) ? vfs.readFile(path, cred) : new Uint8Array(0);
23763
- stream.sbxDirty = false;
23764
- } catch (e) {
23765
- translate(e);
23766
- }
23767
- },
23768
- close(stream) {
23769
- if (!stream.sbxDirty) return;
23770
- const path = backend.realPath(stream.node);
23771
- try {
23772
- vfs.writeFile(path, stream.sbxBuffer, { cred });
23773
- opts.onWrite?.(path);
23774
- } catch (e) {
23775
- translate(e);
23776
- }
23777
- stream.sbxDirty = false;
23778
- },
23779
- read(stream, buffer, offset, length, position) {
23780
- const source = stream.sbxBuffer ?? new Uint8Array(0);
23781
- if (position >= source.length) return 0;
23782
- const size = Math.min(length, source.length - position);
23783
- buffer.set(source.subarray(position, position + size), offset);
23784
- return size;
23785
- },
23786
- write(stream, buffer, offset, length, position) {
23787
- const current = stream.sbxBuffer ?? new Uint8Array(0);
23788
- const end = Math.max(current.length, position + length);
23789
- const next = new Uint8Array(end);
23790
- next.set(current);
23791
- next.set(buffer.subarray(offset, offset + length), position);
23792
- stream.sbxBuffer = next;
23793
- stream.sbxDirty = true;
23794
- stream.node.size = end;
23795
- const path = backend.realPath(stream.node);
23796
- try {
23797
- vfs.writeFile(path, next, { cred });
23798
- opts.onWrite?.(path);
23799
- stream.sbxDirty = false;
23800
- } catch (e) {
23801
- translate(e);
23802
- }
23803
- return length;
23804
- },
23805
- llseek(stream, offset, whence) {
23806
- let position = offset;
23807
- if (whence === 1) position += stream.position;
23808
- else if (whence === 2) position += (stream.sbxBuffer ?? new Uint8Array(0)).length;
23809
- if (position < 0) throw new FS.ErrnoError(EM_ERRNO.EINVAL);
23810
- return position;
23811
- }
23812
- };
23813
- if (opts.mountAt) {
23814
- try {
23815
- FS.mkdir(mountRoot);
23816
- } catch {
23817
- }
23818
- FS.mount(backend, {}, mountRoot);
23819
- return;
23820
- }
23821
- FS.root = null;
23822
- FS.mount(backend, {}, "/");
23823
- try {
23824
- FS.chdir("/");
23825
- } catch {
23826
- }
23827
- }
23828
-
23829
- // src/python/cpython.ts
23830
- init_binary();
23831
-
23832
- // src/python/python-syscalls.ts
23833
- var EMPTY = new Uint8Array(0);
23834
- function createStdinHost(ctx) {
23835
- let pending = EMPTY;
23836
- let eof = false;
23837
- const take = (size) => {
23838
- const n = Math.min(size, pending.length);
23839
- const out = pending.subarray(0, n);
23840
- pending = pending.subarray(n);
23841
- return out;
23842
- };
23843
- return {
23844
- /**
23845
- * Drain a non-interactive stdin up front.
23846
- *
23847
- * A pipe ends, so reading it eagerly costs nothing and leaves every read
23848
- * answerable without suspending — which is what keeps piped input working
23849
- * on hosts with no stack-switching. A terminal never ends, so priming one
23850
- * would hang before the program had printed its prompt.
23851
- */
23852
- async prime() {
23853
- if (ctx.stdin.isTTY || ctx.stdin.interactive) return;
23854
- try {
23855
- pending = await ctx.stdin.readAll();
23856
- } catch {
23857
- pending = EMPTY;
23858
- }
23859
- eof = true;
23860
- },
23861
- buffered(size) {
23862
- if (pending.length > 0) return take(size);
23863
- return eof ? EMPTY : void 0;
23864
- },
23865
- async read(size) {
23866
- if (pending.length > 0) return take(size);
23867
- if (eof) return EMPTY;
23868
- const chunk = await ctx.stdin.read(size);
23869
- if (chunk === null || chunk.length === 0) {
23870
- eof = true;
23871
- return EMPTY;
23872
- }
23873
- pending = chunk;
23874
- return take(size);
23875
- },
23876
- isatty() {
23877
- return Boolean(ctx.stdin.isTTY);
23878
- }
23879
- };
23880
- }
23881
- var BRIDGE_PY = `
23882
- import builtins, io, sys
23883
- import _sbx_host as _host
23884
- from pyodide.ffi import can_run_sync, run_sync
23885
-
23886
-
23887
- def _to_bytes(value):
23888
- if value is None:
23889
- return b""
23890
- to_bytes = getattr(value, "to_bytes", None)
23891
- if to_bytes is not None:
23892
- return to_bytes()
23893
- return bytes(value.to_py())
23894
-
23895
-
23896
- class _SbxStdin(io.RawIOBase):
23897
- """The container's standard input, as a blocking raw stream."""
23898
-
23899
- def readable(self):
23900
- return True
23901
-
23902
- def fileno(self):
23903
- return 0
23904
-
23905
- def isatty(self):
23906
- return bool(_host.isatty())
23907
-
23908
- def readinto(self, target):
23909
- want = len(target)
23910
- if want == 0:
23911
- return 0
23912
- ready = _host.buffered(want)
23913
- if ready is None:
23914
- if not can_run_sync():
23915
- raise OSError(
23916
- "this host cannot wait for interactive input: WebAssembly "
23917
- "stack switching (JSPI) is unavailable. Pipe the input in, "
23918
- "or use a browser or Node build that supports it."
23919
- )
23920
- ready = run_sync(_host.read(want))
23921
- data = _to_bytes(ready)
23922
- target[: len(data)] = data
23923
- return len(data)
23924
-
23925
-
23926
- def _install():
23927
- stdin = io.TextIOWrapper(
23928
- io.BufferedReader(_SbxStdin()), encoding="utf-8", errors="replace", line_buffering=True
23929
- )
23930
- sys.stdin = stdin
23931
- sys.__stdin__ = stdin
23932
-
23933
- def input(prompt=""):
23934
- # CPython writes the prompt to stdout and strips exactly one trailing
23935
- # newline; anything else changes what a program reads back.
23936
- if prompt != "":
23937
- sys.stdout.write(str(prompt))
23938
- sys.stdout.flush()
23939
- line = sys.stdin.readline()
23940
- if not line:
23941
- raise EOFError("EOF when reading a line")
23942
- if line.endswith("\\n"):
23943
- line = line[:-1]
23944
- if line.endswith("\\r"):
23945
- line = line[:-1]
23946
- return line
23947
-
23948
- builtins.input = input
23949
-
23950
-
23951
- _install()
23952
- `;
23953
- var PROCESS_PY = `
23954
- import io, os, shlex, subprocess, sys
23955
- import _sbx_host as _host
23956
- from pyodide.ffi import can_run_sync, run_sync
23957
-
23958
- _PIPE = subprocess.PIPE
23959
- _DEVNULL = subprocess.DEVNULL
23960
- _STDOUT = subprocess.STDOUT
23961
-
23962
-
23963
- def _argv_for(args, shell):
23964
- if shell:
23965
- line = args if isinstance(args, str) else " ".join(str(a) for a in args)
23966
- return ["sh", "-c", line]
23967
- if isinstance(args, (str, bytes)):
23968
- return shlex.split(args if isinstance(args, str) else args.decode())
23969
- return [str(a) for a in args]
23970
-
23971
-
23972
- def _require_blocking(what):
23973
- if not can_run_sync():
23974
- raise OSError(
23975
- "cannot " + what + " on this host: WebAssembly stack switching "
23976
- "(JSPI) is unavailable."
23977
- )
23978
-
23979
-
23980
- def _to_bytes_result(value):
23981
- if value is None:
23982
- return b""
23983
- to_bytes = getattr(value, "to_bytes", None)
23984
- return to_bytes() if to_bytes is not None else bytes(value.to_py())
23985
-
23986
-
23987
- class _ChildReader(io.RawIOBase):
23988
- """One of a running child's output streams.
23989
-
23990
- Reads suspend only for as long as the child takes to produce the next
23991
- bytes, so \`for line in p.stdout\` follows a child that is still running
23992
- instead of waiting for it to exit.
23993
- """
23994
-
23995
- def __init__(self, child_id, which):
23996
- self._id = child_id
23997
- self._which = which
23998
-
23999
- def readable(self):
24000
- return True
24001
-
24002
- def readinto(self, target):
24003
- want = len(target)
24004
- if want == 0:
24005
- return 0
24006
- _require_blocking("read from a child process")
24007
- data = _to_bytes_result(run_sync(_host.proc_read(self._id, self._which, want)))
24008
- target[: len(data)] = data
24009
- return len(data)
24010
-
24011
-
24012
- class _ChildWriter(io.RawIOBase):
24013
- """A running child's standard input."""
24014
-
24015
- def __init__(self, child_id):
24016
- self._id = child_id
24017
-
24018
- def writable(self):
24019
- return True
24020
-
24021
- def write(self, data):
24022
- if isinstance(data, str):
24023
- text = data
24024
- else:
24025
- text = bytes(data).decode("utf-8", "replace")
24026
- _host.proc_write(self._id, text)
24027
- return len(data)
24028
-
24029
- def close(self):
24030
- if not self.closed:
24031
- _host.proc_close_stdin(self._id)
24032
- super().close()
24033
-
24034
-
24035
- class SbxPopen:
24036
- """A child process, run through the container's kernel.
24037
-
24038
- The child is a real, live process: it starts when \`Popen\` is constructed
24039
- and runs on the host's event loop while Python carries on. The parent only
24040
- suspends when it actually reads, waits, or communicates \u2014 which is what
24041
- makes streaming from a long-running child work rather than deadlocking on
24042
- a single interpreter stack.
24043
- """
24044
-
24045
- def __init__(self, args, bufsize=-1, executable=None, stdin=None, stdout=None,
24046
- stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None,
24047
- env=None, universal_newlines=None, startupinfo=None, creationflags=0,
24048
- restore_signals=True, start_new_session=False, pass_fds=(), *,
24049
- text=None, encoding=None, errors=None, **kwargs):
24050
- self.args = args
24051
- self.returncode = None
24052
- self._text = bool(text or encoding or errors or universal_newlines)
24053
- self._encoding = encoding or "utf-8"
24054
- self._errors = errors or "replace"
24055
- self._merge_err = stderr == _STDOUT
24056
- self._closed = False
24057
-
24058
- argv = _argv_for(args, shell)
24059
- env_map = None if env is None else {str(k): str(v) for k, v in dict(env).items()}
24060
- self._id = _host.proc_start(
24061
- argv, None if cwd is None else str(cwd), env_map, self._merge_err
24062
- )
24063
- self.pid = int(_host.proc_pid(self._id))
24064
-
24065
- self.stdin = self._wrap_writer() if stdin == _PIPE else None
24066
- self.stdout = self._wrap_reader(1) if stdout == _PIPE else None
24067
- # When merged there is only one stream, and it is stream 1.
24068
- self.stderr = self._wrap_reader(2) if stderr == _PIPE and not self._merge_err else None
24069
-
24070
- # Output nobody captured belongs on the parent's own streams, or a
24071
- # program's diagnostics would vanish rather than being seen.
24072
- self._drain_out = stdout is None
24073
- self._drain_err = stderr is None and not self._merge_err
24074
-
24075
- def _wrap_reader(self, which):
24076
- raw = io.BufferedReader(_ChildReader(self._id, which))
24077
- if self._text:
24078
- return io.TextIOWrapper(raw, encoding=self._encoding, errors=self._errors)
24079
- return raw
24080
-
24081
- def _wrap_writer(self):
24082
- raw = io.BufferedWriter(_ChildWriter(self._id))
24083
- if self._text:
24084
- return io.TextIOWrapper(raw, encoding=self._encoding, errors=self._errors)
24085
- return raw
24086
-
24087
- def _read_all(self, which):
24088
- _require_blocking("read from a child process")
24089
- parts = []
24090
- while True:
24091
- chunk = _to_bytes_result(run_sync(_host.proc_read(self._id, which, 65536)))
24092
- if not chunk:
24093
- break
24094
- parts.append(chunk)
24095
- return b"".join(parts)
24096
-
24097
- def _decode(self, raw):
24098
- return raw.decode(self._encoding, self._errors) if self._text else raw
24099
-
24100
- def communicate(self, input=None, timeout=None):
24101
- if input is not None and self.stdin is not None:
24102
- if isinstance(input, bytes):
24103
- input = input.decode(self._encoding, "replace")
24104
- self.stdin.write(input)
24105
- if self.stdin is not None:
24106
- try:
24107
- self.stdin.close()
24108
- except Exception:
24109
- pass
24110
-
24111
- out = self.stdout.read() if self.stdout is not None else None
24112
- err = self.stderr.read() if self.stderr is not None else None
24113
- self.wait()
24114
- self._flush_uncaptured()
24115
- return out, err
24116
-
24117
- def _flush_uncaptured(self):
24118
- if self._drain_out:
24119
- text = self._decode_raw(self._read_all(1))
24120
- if text:
24121
- sys.stdout.write(text)
24122
- if self._drain_err:
24123
- text = self._decode_raw(self._read_all(2))
24124
- if text:
24125
- sys.stderr.write(text)
24126
- self._drain_out = False
24127
- self._drain_err = False
24128
-
24129
- def _decode_raw(self, raw):
24130
- return raw.decode(self._encoding, "replace")
24131
-
24132
- def poll(self):
24133
- code = _host.proc_poll(self._id)
24134
- if code is not None:
24135
- self.returncode = int(code)
24136
- return self.returncode
24137
-
24138
- def wait(self, timeout=None):
24139
- _require_blocking("wait for a child process")
24140
- self.returncode = int(run_sync(_host.proc_wait(self._id)))
24141
- return self.returncode
24142
-
24143
- def kill(self):
24144
- self.send_signal("SIGKILL")
24145
-
24146
- def terminate(self):
24147
- self.send_signal("SIGTERM")
24148
-
24149
- def send_signal(self, sig):
24150
- _host.proc_kill(self._id, sig if isinstance(sig, str) else "SIGTERM")
24151
-
24152
- def __del__(self):
24153
- try:
24154
- _host.proc_release(self._id)
24155
- except Exception:
24156
- pass
24157
-
24158
- def __enter__(self):
24159
- return self
24160
-
24161
- def __exit__(self, *exc):
24162
- if self.stdin is not None:
24163
- try:
24164
- self.stdin.close()
24165
- except Exception:
24166
- pass
24167
- self.wait()
24168
- self._flush_uncaptured()
24169
- for stream in (self.stdout, self.stderr):
24170
- if stream is not None:
24171
- try:
24172
- stream.close()
24173
- except Exception:
24174
- pass
24175
- # The host holds the process and its pipes until it is told the child
24176
- # is finished with; a loop spawning children would otherwise keep every
24177
- # one of them alive. Safe here only because the child has been waited
24178
- # for and everything anyone wanted has been read.
24179
- _host.proc_release(self._id)
24180
- return False
24181
-
24182
-
24183
- def _system(command):
24184
- with SbxPopen(command, shell=True) as child:
24185
- pass
24186
- # os.system returns a wait status, not an exit code.
24187
- return (child.returncode or 0) << 8
24188
-
24189
-
24190
- def _popen(command, mode="r", buffering=-1):
24191
- if "w" in mode:
24192
- raise OSError("os.popen with mode 'w' is not supported in this container")
24193
- child = SbxPopen(command, shell=True, stdout=_PIPE, text=True)
24194
- stream = child.stdout
24195
- inner = stream.close
24196
-
24197
- def close():
24198
- inner()
24199
- code = child.wait()
24200
- return None if code == 0 else code << 8
24201
-
24202
- stream.close = close
24203
- return stream
24204
-
24205
-
24206
- def _install_processes():
24207
- subprocess.Popen = SbxPopen
24208
- os.system = _system
24209
- os.popen = _popen
24210
-
24211
-
24212
- _install_processes()
24213
- `;
24214
- var ASYNCIO_PY = `
24215
- import asyncio
24216
- from pyodide.ffi import can_run_sync, run_sync
24217
-
24218
- _orig_run = asyncio.run
24219
-
24220
-
24221
- def _run(main, *, debug=None, loop_factory=None):
24222
- if not asyncio.iscoroutine(main) and not asyncio.isfuture(main):
24223
- raise ValueError("a coroutine was expected, got {!r}".format(main))
24224
- if not can_run_sync():
24225
- # No stack switching: the original at least raises a comprehensible
24226
- # error rather than this shim inventing a new one.
24227
- return _orig_run(main, debug=debug, loop_factory=loop_factory)
24228
- return run_sync(main)
24229
-
24230
-
24231
- def _run_until_complete(self, future):
24232
- if not can_run_sync():
24233
- raise RuntimeError(
24234
- "cannot wait for a coroutine on this host: WebAssembly stack "
24235
- "switching (JSPI) is unavailable."
24236
- )
24237
- return run_sync(future)
24238
-
24239
-
24240
- async def _create_server(self, *args, **kwargs):
24241
- # Pyodide's loop raises a bare NotImplementedError from deep inside
24242
- # asyncio, which tells a reader nothing about why their server will not
24243
- # start. Naming the limit is the least this can do until the container can
24244
- # route connections to Python.
24245
- raise NotImplementedError(
24246
- "this container cannot yet accept network connections from Python: "
24247
- "asyncio's create_server is not implemented on Pyodide's event loop, "
24248
- "so an ASGI/WSGI server such as uvicorn will install and import but "
24249
- "not bind a port. Outbound requests do work."
24250
- )
24251
-
24252
-
24253
- def _install_asyncio():
24254
- asyncio.run = _run
24255
- asyncio.runners.run = _run
24256
- loop_type = type(asyncio.get_event_loop())
24257
- loop_type.run_until_complete = _run_until_complete
24258
- loop_type.create_server = _create_server
24259
-
24260
-
24261
- _install_asyncio()
24262
- `;
24263
- var NETWORK_PY = `
24264
- import builtins, email.message, io, sys, urllib.error, urllib.request, urllib.response
24265
- import _sbx_host as _host
24266
- from pyodide.ffi import can_run_sync, run_sync
24267
-
24268
-
24269
- def _fetch(request):
24270
- if not can_run_sync():
24271
- raise urllib.error.URLError(
24272
- "cannot make a request on this host: WebAssembly stack switching "
24273
- "(JSPI) is unavailable."
24274
- )
24275
- body = request.data
24276
- if isinstance(body, str):
24277
- body = body.encode()
24278
- try:
24279
- result = run_sync(
24280
- _host.http(
24281
- request.full_url,
24282
- request.get_method(),
24283
- [[k, v] for k, v in request.header_items()],
24284
- body,
24285
- )
24286
- )
24287
- except Exception as error:
24288
- raise urllib.error.URLError(str(error)) from None
24289
-
24290
- headers = email.message.Message()
24291
- for pair in result.headers.to_py():
24292
- headers[str(pair[0])] = str(pair[1])
24293
- payload = result.body.to_bytes()
24294
- response = urllib.response.addinfourl(
24295
- io.BytesIO(payload), headers, str(result.url), int(result.status)
24296
- )
24297
- response.msg = str(result.statusText)
24298
- return response
24299
-
24300
-
24301
- class _SbxHTTPHandler(urllib.request.HTTPHandler):
24302
- """Both schemes, from one handler.
24303
-
24304
- Pyodide builds urllib without \`ssl\`, so \`HTTPSHandler\` does not exist to
24305
- subclass \u2014 and https is the scheme most callers actually want. Serving
24306
- both from a subclass of the one handler that does exist keeps
24307
- \`build_opener\` treating this as a replacement rather than stacking it
24308
- alongside the original.
24309
- """
24310
-
24311
- def http_open(self, request):
24312
- return _fetch(request)
24313
-
24314
- def https_open(self, request):
24315
- return _fetch(request)
24316
-
24317
- https_request = urllib.request.HTTPHandler.do_request_
24318
-
24319
-
24320
- def _raw_fetch(url, method, headers, body):
24321
- """One request, through the container's network path."""
24322
- if not can_run_sync():
24323
- raise urllib.error.URLError(
24324
- "cannot make a request on this host: WebAssembly stack switching "
24325
- "(JSPI) is unavailable."
24326
- )
24327
- if isinstance(body, str):
24328
- body = body.encode()
24329
- result = run_sync(
24330
- _host.http(url, method, [[str(k), str(v)] for k, v in dict(headers).items()], body)
24331
- )
24332
- return {
24333
- "status": int(result.status),
24334
- "reason": str(result.statusText),
24335
- "headers": [(str(p[0]), str(p[1])) for p in result.headers.to_py()],
24336
- "body": result.body.to_bytes(),
24337
- "url": str(result.url),
24338
- }
24339
-
24340
-
24341
- # \u2500\u2500 Routing third-party HTTP stacks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
24342
- #
24343
- # A library that brings its own transport does not go through urllib, and in
24344
- # Pyodide several of them reach JavaScript's fetch directly \u2014 which leaves the
24345
- # container's network policy behind entirely. They cannot all be patched at
24346
- # boot either, because pip installs them later.
24347
- #
24348
- # So: adapters are registered by module name and applied the moment that module
24349
- # is first imported, whenever that happens. Supporting another stack is a small
24350
- # function registered here, not a change to the machinery.
24351
-
24352
- _ADAPTERS = {}
24353
-
24354
-
24355
- def _register_adapter(name):
24356
- def decorate(fn):
24357
- _ADAPTERS[name] = fn
24358
- return fn
24359
-
24360
- return decorate
24361
-
24362
-
24363
- def _apply_adapter(name):
24364
- fn = _ADAPTERS.pop(name, None)
24365
- if fn is None:
24366
- return
24367
- try:
24368
- fn()
24369
- except Exception:
24370
- # A stack we cannot adapt must not stop the import that triggered it.
24371
- pass
24372
-
24373
-
24374
- def _install_import_hook():
24375
- real_import = builtins.__import__
24376
-
24377
- def hooked(name, globals=None, locals=None, fromlist=(), level=0):
24378
- module = real_import(name, globals, locals, fromlist, level)
24379
- root = name.split(".")[0] if level == 0 else None
24380
- if root in _ADAPTERS:
24381
- _apply_adapter(root)
24382
- return module
24383
-
24384
- builtins.__import__ = hooked
24385
-
24386
-
24387
- @_register_adapter("requests")
24388
- def _adapt_requests():
24389
- """Replace the transport, not the API.
24390
-
24391
- \`HTTPAdapter.send\` is the single seam every requests call passes through,
24392
- below sessions, redirects, cookies and retries and above the urllib3
24393
- transport that would otherwise reach the network on its own. Replacing it
24394
- leaves everything callers actually use intact.
24395
- """
24396
- import requests
24397
- from requests.adapters import HTTPAdapter
24398
- from requests.structures import CaseInsensitiveDict
24399
-
24400
- native_send = HTTPAdapter.send
24401
-
24402
- def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
24403
- if not can_run_sync():
24404
- # Without stack switching this transport cannot run at all. Falling
24405
- # back to the library's own is better than breaking requests on
24406
- # such a host \u2014 but the container's network policy is not the part
24407
- # that degrades, so a forbidden host is still refused.
24408
- if not _host.policy_allows(request.url):
24409
- raise requests.exceptions.ConnectionError(
24410
- "outbound network access is disabled for this container "
24411
- "(enable with network: { allowOutbound: true })"
24412
- )
24413
- return native_send(self, request, stream, timeout, verify, cert, proxies)
24414
- try:
24415
- result = _raw_fetch(request.url, request.method, request.headers, request.body)
24416
- except Exception as error:
24417
- raise requests.exceptions.ConnectionError(str(error)) from None
24418
- response = requests.Response()
24419
- response.status_code = result["status"]
24420
- response.reason = result["reason"]
24421
- response.headers = CaseInsensitiveDict(result["headers"])
24422
- response.url = result["url"]
24423
- response.request = request
24424
- response.raw = io.BytesIO(result["body"])
24425
- response.encoding = requests.utils.get_encoding_from_headers(response.headers)
24426
- return response
24427
-
24428
- HTTPAdapter.send = send
24429
-
24430
-
24431
- def _install_network():
24432
- urllib.request.HTTPHandler = _SbxHTTPHandler
24433
- urllib.request.HTTPSHandler = _SbxHTTPHandler
24434
- urllib.request.install_opener(urllib.request.build_opener(_SbxHTTPHandler))
24435
- _install_import_hook()
24436
- for already_imported in [n for n in _ADAPTERS if n in sys.modules]:
24437
- _apply_adapter(already_imported)
24438
-
24439
-
24440
- _install_network()
24441
- `;
24442
- async function httpForPython(slot, url, method, headers, body) {
24443
- const current = slot.current;
24444
- if (!current) throw new Error("no program is bound");
24445
- const result = await performRequest(current.ctx, new URL(url), {
24446
- method,
24447
- headers: Object.fromEntries(headers),
24448
- ...body ? { body } : {}
24449
- });
24450
- return {
24451
- status: result.status,
24452
- statusText: result.statusText,
24453
- headers: Object.entries(result.headers),
24454
- body: result.body,
24455
- url: result.url
24456
- };
24457
- }
24458
- async function resuming(py, slot, work) {
24459
- const parent = slot.current;
24460
- try {
24461
- return await work();
24462
- } finally {
24463
- if (parent && slot.current !== parent) bindProgram(py, parent);
24464
- }
24465
- }
24466
- function installSyscallBridge(py) {
24467
- const slot = py.__sbxSlot ??= { current: null };
24468
- const children = /* @__PURE__ */ new Map();
24469
- let lastChildId = 0;
24470
- py.registerJsModule("_sbx_host", {
24471
- /* Not `?.` and `??`: `buffered` returns undefined to mean "you will have
24472
- * to wait", and a nullish default would quietly turn that into end-of-file —
24473
- * which is exactly the silent EOF this bridge exists to remove. */
24474
- buffered: (size) => slot.current ? slot.current.stdin.buffered(size) : EMPTY,
24475
- read: (size) => slot.current ? slot.current.stdin.read(size) : Promise.resolve(EMPTY),
24476
- isatty: () => slot.current?.stdin.isatty() ?? false,
24477
- proc_start: (argv, cwd, env2, merge) => {
24478
- const current = slot.current;
24479
- if (!current) throw new Error("no program is bound");
24480
- const { ctx } = current;
24481
- const stdin = new Pipe();
24482
- stdin.interactive = true;
24483
- const stdout = new Pipe();
24484
- const stderr = merge ? stdout : new Pipe();
24485
- const proc = ctx.kernel.spawn(argv, {
24486
- cwd: cwd ?? ctx.cwd,
24487
- env: env2 ?? ctx.env,
24488
- cred: ctx.cred,
24489
- stdin,
24490
- stdout,
24491
- stderr
24492
- });
24493
- void proc.wait().then(() => {
24494
- stdout.end();
24495
- if (stderr !== stdout) stderr.end();
24496
- });
24497
- const id = ++lastChildId;
24498
- children.set(id, { proc, stdin, stdout, stderr });
24499
- return id;
24500
- },
24501
- proc_read: (id, which2, size) => {
24502
- const child = children.get(id);
24503
- if (!child) return Promise.resolve(EMPTY);
24504
- const pipe = which2 === 2 ? child.stderr : child.stdout;
24505
- return resuming(py, slot, async () => await pipe.read(size) ?? EMPTY);
24506
- },
24507
- proc_write: (id, text2) => {
24508
- children.get(id)?.stdin.write(text2);
24509
- },
24510
- proc_close_stdin: (id) => {
24511
- children.get(id)?.stdin.end();
24512
- },
24513
- /* Non-blocking on purpose: `poll()` must be able to say "still running". */
24514
- proc_poll: (id) => children.get(id)?.proc.exitCode ?? void 0,
24515
- proc_pid: (id) => children.get(id)?.proc.pid ?? 0,
24516
- proc_wait: (id) => {
24517
- const child = children.get(id);
24518
- if (!child) return Promise.resolve(0);
24519
- return resuming(py, slot, () => child.proc.wait());
24520
- },
24521
- proc_kill: (id, signal) => {
24522
- const child = children.get(id);
24523
- if (!child) return;
24524
- const current = slot.current;
24525
- current?.ctx.kernel.procs.signal(child.proc.pid, signal);
24526
- },
24527
- proc_release: (id) => {
24528
- children.delete(id);
24529
- },
24530
- http: (url, method, headers, body) => resuming(py, slot, () => httpForPython(slot, url, method, headers, body)),
24531
- /* So Python can enforce the container's policy even on a host where it
24532
- * cannot route the request itself. */
24533
- policy_allows: (url) => {
24534
- const current = slot.current;
24535
- if (!current) return false;
24536
- const net = current.ctx.kernel.net;
24537
- try {
24538
- return net.isLocal(new URL(url).hostname) || net.outboundAllowed(url);
24539
- } catch {
24540
- return false;
24541
- }
24542
- }
24543
- });
24544
- py.runPython(BRIDGE_PY);
24545
- py.runPython(PROCESS_PY);
24546
- py.runPython(NETWORK_PY);
24547
- py.runPython(ASYNCIO_PY);
24548
- }
24549
- function bindProgram(py, binding) {
24550
- py.__sbxSlot.current = binding;
24551
- const { ctx, stdin } = binding;
24552
- const decoder9 = new TextDecoder();
24553
- py.setStdout({
24554
- write: (buffer) => (ctx.write(decoder9.decode(buffer)), buffer.length)
24555
- });
24556
- py.setStderr({
24557
- write: (buffer) => (ctx.stderr.write(decoder9.decode(buffer)), buffer.length)
24558
- });
24559
- py.setStdin({
24560
- read: (buffer) => {
24561
- const ready = stdin.buffered(buffer.length);
24562
- if (!ready || ready.length === 0) return 0;
24563
- buffer.set(ready);
24564
- return ready.length;
24565
- }
24566
- });
24567
- }
24568
-
24569
- // src/python/cpython.ts
24570
- var RESERVED_FOR_INTERPRETER = /* @__PURE__ */ new Set(["lib", "dev", "proc"]);
24571
- var pyodideModule = null;
24572
- var indexUrl;
24573
- var moduleUrl;
24574
- var DEFAULT_BROWSER_INDEX_URL = "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/";
24575
- var isNode3 = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
24576
- function configureCPython(options = {}) {
24577
- if (options.indexURL !== void 0) indexUrl = options.indexURL;
24578
- if (options.moduleURL !== void 0) {
24579
- moduleUrl = options.moduleURL;
24580
- pyodideModule = null;
24581
- }
24582
- }
24583
- function importPyodide() {
24584
- if (!pyodideModule) {
24585
- if (moduleUrl) {
24586
- pyodideModule = import(
24587
- /* @vite-ignore */
24588
- /* webpackIgnore: true */
24589
- moduleUrl
24590
- );
24591
- } else if (isNode3) {
24592
- pyodideModule = nodeOnlyModule("pyodide");
24593
- } else {
24594
- pyodideModule = import(
24595
- /* @vite-ignore */
24596
- /* webpackIgnore: true */
24597
- `${DEFAULT_BROWSER_INDEX_URL}pyodide.mjs`
24598
- );
24599
- }
24600
- }
24601
- return pyodideModule;
24602
- }
24603
- async function resolveIndexUrl() {
24604
- if (indexUrl) return indexUrl;
24605
- if (!isNode3) return DEFAULT_BROWSER_INDEX_URL;
24606
- try {
24607
- const { createRequire } = await nodeBuiltin("module");
24608
- const path = await nodeBuiltin("path");
24609
- const require2 = createRequire(path.join(process.cwd(), "index.js"));
24610
- return `${path.dirname(require2.resolve("pyodide/package.json"))}/`;
24611
- } catch {
24612
- return void 0;
24613
- }
24614
- }
24615
- async function isCPythonAvailable() {
24616
- try {
24617
- await importPyodide();
24618
- return true;
24619
- } catch {
24620
- return false;
24621
- }
24622
- }
24623
- var interpreters = /* @__PURE__ */ new WeakMap();
24624
- async function interpreterFor(ctx) {
24625
- const existing = interpreters.get(ctx.vfs);
24626
- if (existing) return existing;
24627
- const starting = (async () => {
24628
- const { loadPyodide } = await importPyodide();
24629
- const resolved = await resolveIndexUrl();
24630
- const py = await loadPyodide({
24631
- ...resolved ? { indexURL: resolved } : {},
24632
- /* Output is rebound per program; these only catch anything printed
24633
- * while the interpreter is still starting. */
24634
- stdout: () => {
24635
- },
24636
- stderr: () => {
24637
- }
24638
- });
24639
- mountContainerDirs(py, ctx);
24640
- installRunner(py);
24641
- installSyscallBridge(py);
24642
- return py;
24643
- })();
24644
- interpreters.set(ctx.vfs, starting);
24645
- try {
24646
- return await starting;
24647
- } catch (error) {
24648
- interpreters.delete(ctx.vfs);
24649
- throw error;
24650
- }
24651
- }
24652
- function installRunner(py) {
24653
- py.__sbxRun = py.runPython(`
24654
- from pyodide.code import eval_code_async
24655
-
24656
- async def __sbx_run(source, scope):
24657
- try:
24658
- await eval_code_async(source, globals=scope)
24659
- return 0
24660
- except SystemExit as exit:
24661
- code = exit.code
24662
- if code is None:
24663
- return 0
24664
- return code if isinstance(code, int) else 1
24665
-
24666
- __sbx_run
24667
- `);
24668
- }
24669
- function mountContainerDirs(py, ctx) {
24670
- const mounted = py.__sbxMounted ??= /* @__PURE__ */ new Set();
24671
- let entries;
24672
- try {
24673
- entries = ctx.vfs.readdirWithTypes("/", ctx.cred);
24674
- } catch {
24675
- return;
24676
- }
24677
- for (const entry of entries) {
24678
- if (entry.kind !== "directory") continue;
24679
- if (RESERVED_FOR_INTERPRETER.has(entry.name)) continue;
24680
- if (mounted.has(entry.name)) continue;
24681
- try {
24682
- mountContainerFs(py.FS, {
24683
- vfs: ctx.vfs,
24684
- cred: ctx.cred,
24685
- mountAt: `/${entry.name}`
24686
- });
24687
- mounted.add(entry.name);
24688
- } catch {
24689
- }
24690
- }
24691
- }
24692
- function bootstrap(py, ctx, argv, scriptDir) {
24693
- const paths = [
24694
- ...scriptDir ? [scriptDir] : [""],
24695
- "/workspace",
24696
- "/usr/lib/python3",
24697
- "/usr/lib/python3/site-packages",
24698
- "/usr/local/lib/python3/site-packages"
24699
- ];
24700
- py.runPython(`
24701
- import sys, os, importlib
24702
- sys.argv[:] = ${JSON.stringify(argv)}
24703
- for __p in reversed(${JSON.stringify(paths)}):
24704
- if __p and __p not in sys.path:
24705
- sys.path.insert(0, __p)
24706
- elif __p == "" and "" not in sys.path:
24707
- sys.path.insert(0, "")
24708
- try:
24709
- del __p
24710
- except NameError:
24711
- pass
24712
- os.environ.clear()
24713
- os.environ.update(${JSON.stringify(ctx.env)})
24714
- importlib.invalidate_caches()
24715
- `);
24716
- try {
24717
- py.FS.chdir(ctx.cwd);
24718
- } catch {
24719
- }
24720
- }
24721
- function reportError(ctx, error) {
24722
- const message = error instanceof Error ? error.message : String(error);
24723
- const systemExit = /SystemExit:?\s*(-?\d+)?/.exec(message);
24724
- if (systemExit) {
24725
- return systemExit[1] !== void 0 ? Number(systemExit[1]) & 255 : 0;
24726
- }
24727
- if (/KeyboardInterrupt/.test(message)) {
24728
- ctx.stderr.write("KeyboardInterrupt\n");
24729
- return 130;
24730
- }
24731
- const text2 = message.replace(/^PythonError:\s*/, "");
24732
- ctx.stderr.write(text2.endsWith("\n") ? text2 : `${text2}
24733
- `);
24734
- return 1;
24735
- }
24736
- async function runCPythonProgram(ctx, source, argv, scriptDir = null) {
24737
- let py;
24738
- try {
24739
- py = await interpreterFor(ctx);
24740
- } catch (error) {
24741
- ctx.stderr.write(
24742
- `python3: CPython is unavailable in this host (${error instanceof Error ? error.message : String(error)})
24743
- `
24744
- );
24745
- return { exitCode: 127 };
24746
- }
24747
- mountContainerDirs(py, ctx);
24748
- try {
24749
- await py.loadPackagesFromImports(source, {
24750
- messageCallback: () => {
24751
- },
24752
- errorCallback: () => {
24753
- }
24754
- });
24755
- } catch {
24756
- }
24757
- const stdinHost = createStdinHost(ctx);
24758
- await stdinHost.prime();
24759
- bindProgram(py, { ctx, stdin: stdinHost });
24760
- try {
24761
- bootstrap(py, ctx, argv, scriptDir);
24762
- } catch (error) {
24763
- return { exitCode: reportError(ctx, error) };
24764
- }
24765
- const globals = py.globals.get("dict")();
24766
- try {
24767
- globals.set("__name__", "__main__");
24768
- const exitCode = await py.__sbxRun(source, globals);
24769
- return { exitCode: Number(exitCode) & 255 };
24770
- } catch (error) {
24771
- return { exitCode: reportError(ctx, error) };
24772
- } finally {
24773
- try {
24774
- globals.destroy();
24775
- } catch {
24776
- }
24777
- }
24778
- }
24779
- async function cpythonVersion(ctx) {
24780
- try {
24781
- const py = await interpreterFor(ctx);
24782
- return String(py.runPython("import sys; sys.version.split()[0]"));
24783
- } catch {
24784
- return null;
24785
- }
24786
- }
24787
- async function runCPythonRepl(ctx) {
24788
- let py;
24789
- try {
24790
- py = await interpreterFor(ctx);
24791
- } catch (error) {
24792
- ctx.stderr.write(`python3: Pyodide is unavailable (${error instanceof Error ? error.message : String(error)})
24793
- `);
24794
- return 127;
24795
- }
24796
- mountContainerDirs(py, ctx);
24797
- bootstrap(py, ctx, [""], null);
24798
- bindProgram(py, { ctx, stdin: createStdinHost(ctx) });
24799
- ctx.line(`Python ${String(py.runPython("import sys; sys.version.split()[0]"))} (Pyodide)`);
24800
- ctx.line('Type "help()" for more information.');
24801
- let source = "";
24802
- for (; ; ) {
24803
- ctx.write(source ? "... " : ">>> ");
24804
- const line = await ctx.stdin.readLine();
24805
- if (line === null) {
24806
- ctx.line("");
24807
- break;
24808
- }
24809
- if (!source && ["exit()", "quit()"].includes(line.trim())) break;
24810
- source += `${source ? "\n" : ""}${line}`;
24811
- if (/[:\\]\s*$/.test(line) || source.includes("\n") && line.trim() !== "" && /^\s+/.test(line)) continue;
24812
- try {
24813
- await py.runPythonAsync(source);
24814
- } catch (error) {
24815
- reportError(ctx, error);
24816
- }
24817
- source = "";
24818
- }
24819
- return 0;
24820
- }
24821
- var PIP_PY = `
24822
- import json, re
24823
-
24824
- import micropip
24825
-
24826
- _MISSING = re.compile(r"Can't find a pure Python 3 wheel for '([^']+)'")
24827
- _EXTRAS = re.compile(r"^\\s*([A-Za-z0-9._-]+)\\s*\\[([^\\]]+)\\](.*)$")
24828
-
24829
-
24830
- def _name_of(spec):
24831
- return re.split(r"[\\[<>=!~;\\s]", spec.strip(), 1)[0]
24832
-
24833
-
24834
- def _installed_version(name):
24835
- try:
24836
- found = micropip.list()[name.replace("_", "-").lower()]
24837
- return getattr(found, "version", None)
24838
- except Exception:
24839
- return None
24840
-
24841
-
24842
- async def _install_one(spec):
24843
- """Install one requirement, retrying without extras if they are impossible."""
24844
- attempt = spec
24845
- dropped = None
24846
- while True:
24847
- try:
24848
- await micropip.install(attempt)
24849
- except ValueError as error:
24850
- message = str(error)
24851
- missing = _MISSING.findall(message)
24852
- extras = _EXTRAS.match(attempt)
24853
- if missing and extras and dropped is None:
24854
- dropped = [e.strip() for e in extras.group(2).split(",")]
24855
- attempt = extras.group(1) + extras.group(3)
24856
- continue
24857
- return {
24858
- "spec": spec, "ok": False, "missing": missing,
24859
- "reason": message.strip().split("\\n")[0],
24860
- }
24861
- except Exception as error:
24862
- return {"spec": spec, "ok": False, "missing": [], "reason": str(error).strip().split("\\n")[0]}
24863
-
24864
- name = _name_of(attempt)
24865
- return {
24866
- "spec": spec, "ok": True, "name": name,
24867
- "version": _installed_version(name), "dropped": dropped,
24868
- }
24869
-
24870
-
24871
- async def __sbx_pip(specs_json):
24872
- results = []
24873
- for spec in json.loads(specs_json):
24874
- try:
24875
- results.append(await _install_one(spec))
24876
- except Exception as error:
24877
- results.append({"spec": spec, "ok": False, "missing": [], "reason": str(error)})
24878
- return json.dumps(results)
24879
-
24880
- __sbx_pip
24881
- `;
24882
- var micropip = defineCommand({
24883
- name: "micropip",
24884
- path: "/usr/bin/micropip",
24885
- aliases: ["pip", "pip3"],
24886
- summary: "install Python packages into the running interpreter",
24887
- usage: "micropip install <package>...",
24888
- async run(ctx) {
24889
- if (usingOwnedPython()) {
24890
- ctx.warn(
24891
- "pip here is micropip, which installs into the Pyodide interpreter \u2014 not the source-built CPython this container is running, so the packages would not be importable.\nRun the container with the pyodide backend if you need micropip, or wait for venv and pip support on the owned runtime (see docs/python/release-gates.md, M4)."
24892
- );
24893
- return 1;
24894
- }
24895
- const [action, ...args] = ctx.args;
24896
- if (action === "--version" || action === "-V") {
24897
- ctx.line("pip (micropip, Pyodide)");
24898
- return 0;
24899
- }
24900
- if (action !== "install") {
24901
- ctx.line("usage: pip install [-r requirements.txt] <package>...");
24902
- return action === void 0 ? 1 : 0;
24903
- }
24904
- const packages = args.filter((arg) => !arg.startsWith("-"));
24905
- const requirementIndex = args.findIndex((arg) => arg === "-r" || arg === "--requirement");
24906
- if (requirementIndex >= 0) {
24907
- const file3 = args[requirementIndex + 1];
24908
- if (!file3) {
24909
- ctx.stderr.write("pip: option -r requires a file\n");
24910
- return 2;
23772
+ if (attr.timestamp !== void 0) vfs.utimes(path, attr.timestamp, attr.timestamp, cred);
23773
+ if (attr.size !== void 0) {
23774
+ vfs.truncate(path, attr.size, cred);
23775
+ opts.onWrite?.(path);
23776
+ }
23777
+ } catch (e) {
23778
+ translate(e);
24911
23779
  }
23780
+ },
23781
+ lookup(parent, name) {
23782
+ const path = join(backend.realPath(parent), name);
23783
+ let mode;
24912
23784
  try {
24913
- packages.splice(packages.indexOf(file3), 1);
24914
- packages.push(...ctx.vfs.readText(ctx.path(file3), ctx.cred).split(/\r?\n/).map((line) => line.replace(/#.*$/, "").trim()).filter(Boolean));
23785
+ mode = vfs.lstat(path).mode;
24915
23786
  } catch {
24916
- ctx.stderr.write(`pip: could not open requirements file '${file3}'
24917
- `);
24918
- return 1;
23787
+ throw new FS.ErrnoError(EM_ERRNO.ENOENT);
24919
23788
  }
24920
- }
24921
- if (packages.length === 0) {
24922
- ctx.stderr.write("pip: no packages specified\n");
24923
- return 1;
24924
- }
24925
- if (!ctx.kernel.net.outboundAllowed("https://pypi.org")) {
24926
- ctx.stderr.write(
24927
- "pip: outbound network access is disabled for this container (enable with network: { allowOutbound: true })\n"
24928
- );
24929
- return 1;
24930
- }
24931
- let py;
24932
- try {
24933
- py = await interpreterFor(ctx);
24934
- } catch {
24935
- ctx.stderr.write("pip: Pyodide is unavailable in this host\n");
24936
- return 127;
24937
- }
24938
- let outcomes;
24939
- try {
24940
- await py.loadPackage("micropip");
24941
- for (const name of packages) ctx.line(`Collecting ${name}`);
24942
- const install2 = py.runPython(PIP_PY);
24943
- outcomes = JSON.parse(String(await install2(JSON.stringify(packages))));
24944
- } catch (error) {
24945
- ctx.stderr.write(
24946
- `pip: installation failed: ${error instanceof Error ? error.message : String(error)}
24947
- `
24948
- );
24949
- return 1;
24950
- }
24951
- const installed2 = [];
24952
- for (const outcome of outcomes) {
24953
- if (outcome.ok) {
24954
- installed2.push(
24955
- outcome.version ? `${outcome.name}-${outcome.version}` : String(outcome.name)
24956
- );
24957
- if (outcome.dropped?.length) {
24958
- ctx.stderr.write(
24959
- ` WARNING: ${outcome.name}: the ${outcome.dropped.map((extra) => `'${extra}'`).join(", ")} extra needs native code with no WebAssembly build; installed ${outcome.name} without it
24960
- `
24961
- );
23789
+ return backend.createNode(parent, name, mode, 0);
23790
+ },
23791
+ mknod(parent, name, mode, dev) {
23792
+ const path = join(backend.realPath(parent), name);
23793
+ try {
23794
+ if (FS.isDir(mode)) {
23795
+ vfs.mkdir(path, { cred, mode: mode & 4095 });
23796
+ } else if (FS.isFile(mode)) {
23797
+ vfs.writeFile(path, new Uint8Array(0), { cred, mode: mode & 4095 });
23798
+ } else {
23799
+ return throwErrno("ENOTSUP");
24962
23800
  }
24963
- continue;
23801
+ opts.onWrite?.(path);
23802
+ } catch (e) {
23803
+ translate(e);
24964
23804
  }
24965
- const missing = outcome.missing ?? [];
24966
- ctx.stderr.write(
24967
- missing.length > 0 ? `ERROR: could not install ${outcome.spec}: no WebAssembly build exists for ${missing.map((name) => name.split(/[<>=!~;\s]/)[0]).join(", ")}
24968
- ` : `ERROR: could not install ${outcome.spec}: ${outcome.reason ?? "unknown error"}
24969
- `
24970
- );
24971
- }
24972
- if (installed2.length > 0) ctx.line(`Successfully installed ${installed2.join(" ")}`);
24973
- return outcomes.every((outcome) => outcome.ok) ? 0 : 1;
24974
- }
24975
- });
24976
-
24977
- // src/python/python.ts
24978
- var PYTHON_VERSION = "3.13";
24979
- function configurePython(options = {}) {
24980
- configureCPython({ indexURL: options.indexURL, moduleURL: options.pyodideURL });
24981
- setPythonBackend({
24982
- ...options.backend !== void 0 ? { backend: options.backend } : {},
24983
- ...options.manifest !== void 0 ? { manifest: options.manifest } : {},
24984
- ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {},
24985
- ...options.wheelIndex !== void 0 ? { wheelIndex: options.wheelIndex } : {},
24986
- ...options.buildFromSource !== void 0 ? { buildFromSource: options.buildFromSource } : {}
24987
- });
24988
- }
24989
- var isPythonAvailable = isCPythonAvailable;
24990
- var python = defineCommand({
24991
- name: "python3",
24992
- path: "/usr/bin/python3",
24993
- aliases: ["python"],
24994
- summary: "run Python using CPython (Pyodide)",
24995
- usage: "python3 [-c command | -m module | script.py] [arguments]",
24996
- manual: `Python is CPython compiled to WebAssembly by Pyodide. Scripts use
24997
- the container's virtual filesystem, modules in /workspace are importable, and
24998
- compatible packages can be installed with pip (micropip).`,
24999
- async run(ctx) {
25000
- if (usingOwnedPython()) {
23805
+ return backend.createNode(parent, name, mode, dev);
23806
+ },
23807
+ rename(oldNode, newDir, newName) {
23808
+ const from = backend.realPath(oldNode);
23809
+ const to = join(backend.realPath(newDir), newName);
25001
23810
  try {
25002
- return await runOwnedPython(ctx, withTopLevelAwaitArgs(ctx.args));
25003
- } catch (error) {
25004
- return ctx.fail(error.message ?? String(error));
23811
+ vfs.rename(from, to, cred);
23812
+ opts.onWrite?.(to);
23813
+ } catch (e) {
23814
+ translate(e);
25005
23815
  }
25006
- }
25007
- const argv = ctx.args;
25008
- let i = 0;
25009
- let command;
25010
- let moduleName;
25011
- let script;
25012
- for (; i < argv.length; i++) {
25013
- const arg = argv[i];
25014
- if (arg === "-V" || arg === "--version") {
25015
- ctx.line(`Python ${await cpythonVersion(ctx) ?? PYTHON_VERSION}`);
25016
- return 0;
23816
+ oldNode.name = newName;
23817
+ oldNode.parent = newDir;
23818
+ },
23819
+ unlink(parent, name) {
23820
+ const path = join(backend.realPath(parent), name);
23821
+ try {
23822
+ vfs.unlink(path, cred);
23823
+ opts.onWrite?.(path);
23824
+ } catch (e) {
23825
+ translate(e);
25017
23826
  }
25018
- if (arg === "-h" || arg === "--help") {
25019
- printHelp2(ctx);
25020
- return 0;
23827
+ },
23828
+ rmdir(parent, name) {
23829
+ const path = join(backend.realPath(parent), name);
23830
+ try {
23831
+ vfs.rmdir(path, cred);
23832
+ opts.onWrite?.(path);
23833
+ } catch (e) {
23834
+ translate(e);
25021
23835
  }
25022
- if (arg === "-c") {
25023
- command = argv[++i] ?? "";
25024
- i++;
25025
- break;
23836
+ },
23837
+ readdir(node2) {
23838
+ const path = backend.realPath(node2);
23839
+ try {
23840
+ return [".", "..", ...vfs.readdir(path, cred)];
23841
+ } catch (e) {
23842
+ return translate(e);
25026
23843
  }
25027
- if (arg === "-m") {
25028
- moduleName = argv[++i] ?? "";
25029
- i++;
25030
- break;
23844
+ },
23845
+ symlink(parent, newName, oldPath) {
23846
+ const path = join(backend.realPath(parent), newName);
23847
+ try {
23848
+ vfs.symlink(oldPath, path, cred);
23849
+ opts.onWrite?.(path);
23850
+ } catch (e) {
23851
+ translate(e);
25031
23852
  }
25032
- if (arg === "-") {
25033
- script = "-";
25034
- i++;
25035
- break;
23853
+ },
23854
+ readlink(node2) {
23855
+ const path = backend.realPath(node2);
23856
+ try {
23857
+ return vfs.readlink(path, cred);
23858
+ } catch (e) {
23859
+ return translate(e);
25036
23860
  }
25037
- if (["-i", "-u", "-B", "-E", "-s", "-S", "-O"].includes(arg)) continue;
25038
- if (arg.startsWith("-")) continue;
25039
- script = arg;
25040
- i++;
25041
- break;
25042
- }
25043
- const rest = argv.slice(i);
25044
- if (command !== void 0) {
25045
- return (await runCPythonProgram(ctx, withTopLevelAwait(command), ["-c", ...rest], null)).exitCode;
25046
23861
  }
25047
- if (moduleName !== void 0) {
25048
- const program = `
25049
- import runpy, sys
25050
- sys.argv = ${JSON.stringify([moduleName, ...rest])}
25051
- try:
25052
- runpy.run_module(${JSON.stringify(moduleName)}, run_name="__main__", alter_sys=True)
25053
- except ImportError:
25054
- print("/usr/bin/python3: No module named " + ${JSON.stringify(moduleName)}, file=sys.stderr)
25055
- raise SystemExit(1)
25056
- `;
25057
- return (await runCPythonProgram(ctx, program, [moduleName, ...rest], null)).exitCode;
25058
- }
25059
- if (script === void 0) {
25060
- if (ctx.stdin.isTTY) return runCPythonRepl(ctx);
25061
- const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
25062
- return source2.trim() ? (await runCPythonProgram(ctx, source2, ["", ...rest])).exitCode : 0;
25063
- }
25064
- if (script === "-") {
25065
- const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
25066
- return (await runCPythonProgram(ctx, source2, ["-", ...rest])).exitCode;
23862
+ };
23863
+ const streamOps = {
23864
+ open(stream) {
23865
+ const path = backend.realPath(stream.node);
23866
+ if (FS.isDir(stream.node.mode)) return;
23867
+ try {
23868
+ stream.sbxBuffer = vfs.lexists(path) ? vfs.readFile(path, cred) : new Uint8Array(0);
23869
+ stream.sbxDirty = false;
23870
+ } catch (e) {
23871
+ translate(e);
23872
+ }
23873
+ },
23874
+ close(stream) {
23875
+ if (!stream.sbxDirty) return;
23876
+ const path = backend.realPath(stream.node);
23877
+ try {
23878
+ vfs.writeFile(path, stream.sbxBuffer, { cred });
23879
+ opts.onWrite?.(path);
23880
+ } catch (e) {
23881
+ translate(e);
23882
+ }
23883
+ stream.sbxDirty = false;
23884
+ },
23885
+ read(stream, buffer, offset, length, position) {
23886
+ const source = stream.sbxBuffer ?? new Uint8Array(0);
23887
+ if (position >= source.length) return 0;
23888
+ const size = Math.min(length, source.length - position);
23889
+ buffer.set(source.subarray(position, position + size), offset);
23890
+ return size;
23891
+ },
23892
+ write(stream, buffer, offset, length, position) {
23893
+ const current = stream.sbxBuffer ?? new Uint8Array(0);
23894
+ const end = Math.max(current.length, position + length);
23895
+ const next = new Uint8Array(end);
23896
+ next.set(current);
23897
+ next.set(buffer.subarray(offset, offset + length), position);
23898
+ stream.sbxBuffer = next;
23899
+ stream.sbxDirty = true;
23900
+ stream.node.size = end;
23901
+ const path = backend.realPath(stream.node);
23902
+ try {
23903
+ vfs.writeFile(path, next, { cred });
23904
+ opts.onWrite?.(path);
23905
+ stream.sbxDirty = false;
23906
+ } catch (e) {
23907
+ translate(e);
23908
+ }
23909
+ return length;
23910
+ },
23911
+ llseek(stream, offset, whence) {
23912
+ let position = offset;
23913
+ if (whence === 1) position += stream.position;
23914
+ else if (whence === 2) position += (stream.sbxBuffer ?? new Uint8Array(0)).length;
23915
+ if (position < 0) throw new FS.ErrnoError(EM_ERRNO.EINVAL);
23916
+ return position;
25067
23917
  }
25068
- const abs = ctx.path(script);
25069
- let source;
23918
+ };
23919
+ if (opts.mountAt) {
25070
23920
  try {
25071
- source = ctx.vfs.readText(abs, ctx.cred);
23921
+ FS.mkdir(mountRoot);
25072
23922
  } catch {
25073
- ctx.stderr.write(`${ctx.name}: can't open file '${abs}': [Errno 2] No such file or directory
25074
- `);
25075
- return 2;
25076
23923
  }
25077
- return (await runCPythonProgram(ctx, source, [abs, ...rest], dirname(abs))).exitCode;
23924
+ FS.mount(backend, {}, mountRoot);
23925
+ return;
25078
23926
  }
25079
- });
25080
- function withTopLevelAwait(source) {
25081
- if (!/\bawait\b/.test(source)) return source;
25082
- return `import ast as _sbx_ast, inspect as _sbx_inspect
25083
- _sbx_src = ${JSON.stringify(source)}
25084
- try:
25085
- _sbx_code = compile(_sbx_src, "<string>", "exec")
25086
- except SyntaxError:
25087
- _sbx_code = compile(_sbx_src, "<string>", "exec", _sbx_ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
25088
- _sbx_async = bool(_sbx_code.co_flags & _sbx_inspect.CO_COROUTINE)
25089
- _sbx_globals = globals()
25090
- for _sbx_name in ("_sbx_ast", "_sbx_inspect", "_sbx_src", "_sbx_name"):
25091
- _sbx_globals.pop(_sbx_name, None)
25092
- if _sbx_async:
25093
- import asyncio as _sbx_asyncio
25094
- _sbx_asyncio.run(eval(_sbx_code, _sbx_globals))
25095
- else:
25096
- exec(_sbx_code, _sbx_globals)
25097
- `;
25098
- }
25099
- function withTopLevelAwaitArgs(args) {
25100
- for (let i = 0; i < args.length; i++) {
25101
- const arg = args[i];
25102
- if (arg === "-c") {
25103
- if (i + 1 >= args.length) return args;
25104
- const rewritten = withTopLevelAwait(args[i + 1]);
25105
- if (rewritten === args[i + 1]) return args;
25106
- const copy = [...args];
25107
- copy[i + 1] = rewritten;
25108
- return copy;
25109
- }
25110
- if (arg === "-m" || !arg.startsWith("-") || arg === "-") return args;
23927
+ FS.root = null;
23928
+ FS.mount(backend, {}, "/");
23929
+ try {
23930
+ FS.chdir("/");
23931
+ } catch {
25111
23932
  }
25112
- return args;
25113
- }
25114
- function printHelp2(ctx) {
25115
- ctx.line("usage: python3 [option] ... [-c cmd | -m mod | file | -] [arg] ...");
25116
- ctx.line("-c cmd : program passed in as string");
25117
- ctx.line("-m mod : run library module as a script");
25118
- ctx.line("-V : print the Python version and exit");
25119
- }
25120
- function pythonCommands() {
25121
- return usingOwnedPython() ? [python, pipCommand] : [python, micropip];
25122
23933
  }
25123
23934
 
25124
23935
  // src/tools/ffmpeg.ts
@@ -25812,8 +24623,8 @@ function renameShadowedExports(source) {
25812
24623
  for (const [key, value] of Object.entries(node2)) {
25813
24624
  if (key === "type" || key === "start" || key === "end" || skip.includes(key)) continue;
25814
24625
  if (Array.isArray(value)) {
25815
- for (const item of value) if (isNode4(item)) visit(item, childScope);
25816
- } else if (isNode4(value)) {
24626
+ for (const item of value) if (isNode3(item)) visit(item, childScope);
24627
+ } else if (isNode3(value)) {
25817
24628
  visit(value, childScope);
25818
24629
  }
25819
24630
  }
@@ -25837,9 +24648,9 @@ function blockNames2(body) {
25837
24648
  for (const declarator of node2.declarations) {
25838
24649
  collectPattern2(declarator.id, names);
25839
24650
  }
25840
- } else if (node2.type === "ClassDeclaration" && isNode4(node2.id)) {
24651
+ } else if (node2.type === "ClassDeclaration" && isNode3(node2.id)) {
25841
24652
  names.add(node2.id.name);
25842
- } else if (node2.type === "FunctionDeclaration" && isNode4(node2.id)) {
24653
+ } else if (node2.type === "FunctionDeclaration" && isNode3(node2.id)) {
25843
24654
  names.add(node2.id.name);
25844
24655
  }
25845
24656
  }
@@ -25850,10 +24661,10 @@ function collectVars2(nodes, names) {
25850
24661
  for (const item of nodes) collectVars2(item, names);
25851
24662
  return;
25852
24663
  }
25853
- if (!isNode4(nodes)) return;
24664
+ if (!isNode3(nodes)) return;
25854
24665
  const node2 = nodes;
25855
24666
  if (node2.type === "FunctionDeclaration" || node2.type === "FunctionExpression" || node2.type === "ArrowFunctionExpression") {
25856
- if (isNode4(node2.id)) names.add(node2.id.name);
24667
+ if (isNode3(node2.id)) names.add(node2.id.name);
25857
24668
  return;
25858
24669
  }
25859
24670
  if (node2.type === "VariableDeclaration" && node2.kind === "var") {
@@ -25867,7 +24678,7 @@ function collectVars2(nodes, names) {
25867
24678
  }
25868
24679
  }
25869
24680
  function collectPattern2(node2, names) {
25870
- if (!isNode4(node2)) return;
24681
+ if (!isNode3(node2)) return;
25871
24682
  switch (node2.type) {
25872
24683
  case "Identifier":
25873
24684
  names.add(node2.name);
@@ -25891,7 +24702,7 @@ function collectPattern2(node2, names) {
25891
24702
  }
25892
24703
  }
25893
24704
  function isExports(value) {
25894
- return isNode4(value) && value.type === "Identifier" && value.name === NAME;
24705
+ return isNode3(value) && value.type === "Identifier" && value.name === NAME;
25895
24706
  }
25896
24707
  function freshName(source) {
25897
24708
  let name = "__sandboxedjs_exports";
@@ -25899,7 +24710,7 @@ function freshName(source) {
25899
24710
  while (source.includes(name)) name = `__sandboxedjs_exports${++suffix}`;
25900
24711
  return name;
25901
24712
  }
25902
- function isNode4(value) {
24713
+ function isNode3(value) {
25903
24714
  return typeof value === "object" && value !== null && typeof value.type === "string";
25904
24715
  }
25905
24716
  var NOTHING2 = [];
@@ -26430,7 +25241,7 @@ var apt = defineCommand({
26430
25241
  const provided = {
26431
25242
  nodejs: { version: NODE_VERSION.replace(/^v/, ""), description: "Node.js event-based server-side JavaScript engine" },
26432
25243
  npm: { version: NPM_VERSION, description: "package manager for Node.js" },
26433
- python3: { version: PYTHON_VERSION, description: "CPython interpreter powered by Pyodide" },
25244
+ python3: { version: PYTHON_VERSION, description: "CPython interpreter" },
26434
25245
  "python3-pip": { version: "24.0", description: "Python package installer" },
26435
25246
  coreutils: { version: "9.4", description: "GNU core utilities" },
26436
25247
  grep: { version: "3.11", description: "GNU grep, egrep and fgrep" },
@@ -26534,7 +25345,7 @@ var dpkg = defineCommand({
26534
25345
  ctx.line("||/ Name Version Architecture Description");
26535
25346
  ctx.line("+++-==============-============-============-=================================");
26536
25347
  ctx.line(`ii nodejs ${NODE_VERSION.replace(/^v/, "").padEnd(12)} amd64 Node.js JavaScript runtime`);
26537
- ctx.line(`ii python3 ${PYTHON_VERSION.padEnd(12)} wasm32 CPython (Pyodide) interpreter`);
25348
+ ctx.line(`ii python3 ${PYTHON_VERSION.padEnd(12)} wasm32 CPython interpreter`);
26538
25349
  return 0;
26539
25350
  }
26540
25351
  ctx.line("dpkg 1.22.6 (amd64)");
@@ -27542,6 +26353,9 @@ function render(value, depth, seen, options) {
27542
26353
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
27543
26354
  if (value instanceof RegExp) return String(value);
27544
26355
  if (Buffer2.isBuffer(value)) return renderBuffer(value);
26356
+ if (value instanceof DataView) {
26357
+ return `DataView { byteLength: ${value.byteLength}, byteOffset: ${value.byteOffset} }`;
26358
+ }
27545
26359
  if (ArrayBuffer.isView(value)) return renderTypedArray(value);
27546
26360
  seen.add(object);
27547
26361
  try {
@@ -27603,7 +26417,13 @@ function renderBuffer(value) {
27603
26417
  }
27604
26418
  function renderTypedArray(value) {
27605
26419
  const name = value.constructor?.name ?? "TypedArray";
27606
- const items = [...value].slice(0, MAX_ARRAY);
26420
+ const items = [];
26421
+ const indexed = value;
26422
+ for (let i = 0; i < Math.min(value.length, MAX_ARRAY); i++) {
26423
+ const item = indexed[i];
26424
+ items.push(typeof item === "bigint" ? `${item}n` : String(item));
26425
+ }
26426
+ if (value.length > MAX_ARRAY) items.push(`... ${value.length - MAX_ARRAY} more items`);
27607
26427
  return `${name}(${value.length}) [ ${items.join(", ")} ]`;
27608
26428
  }
27609
26429
  function prefixed(name, size, parts) {
@@ -27859,6 +26679,7 @@ var legacy = {
27859
26679
  };
27860
26680
  inspect.custom = customInspect;
27861
26681
  var utilModule = {
26682
+ parseEnv,
27862
26683
  format,
27863
26684
  formatWithOptions,
27864
26685
  inspect,
@@ -30463,6 +29284,14 @@ function createCoreModules(options) {
30463
29284
  processObject.stdin = stdin;
30464
29285
  Reflect.deleteProperty(processObject, "browser");
30465
29286
  const fs = createFsModule(volume, () => cwd, options.stdinPath, defer);
29287
+ processObject.loadEnvFile = (path2 = ".env") => {
29288
+ const parsed = parseEnv(fs.readFileSync(path2, "utf8"));
29289
+ for (const [key, value] of Object.entries(parsed)) {
29290
+ if (!Object.prototype.hasOwnProperty.call(processObject.env, key)) {
29291
+ Object.defineProperty(processObject.env, key, { value, writable: true, enumerable: true, configurable: true });
29292
+ }
29293
+ }
29294
+ };
30466
29295
  const path = createPathModule(() => cwd);
30467
29296
  const consoleObject = new Console(stdoutWrite, stderrWrite);
30468
29297
  const os = createOsModule();
@@ -35504,7 +34333,6 @@ exports.braceExpand = braceExpand;
35504
34333
  exports.buildRootfs = buildRootfs;
35505
34334
  exports.builtinNames = builtinNames;
35506
34335
  exports.captureStdio = captureStdio;
35507
- exports.configureCPython = configureCPython;
35508
34336
  exports.configurePython = configurePython;
35509
34337
  exports.containerTarget = containerTarget;
35510
34338
  exports.createChildProcessModule = createChildProcessModule;
@@ -35536,7 +34364,6 @@ exports.inspectElf = inspectElf;
35536
34364
  exports.installUserland = installUserland;
35537
34365
  exports.installWasmCommands = installWasmCommands;
35538
34366
  exports.isBuiltinName = isBuiltinName;
35539
- exports.isCPythonAvailable = isCPythonAvailable;
35540
34367
  exports.isElfBinary = isElfBinary;
35541
34368
  exports.isPythonAvailable = isPythonAvailable;
35542
34369
  exports.isSysError = isSysError;