sandboxedjs 0.1.16 → 0.1.17

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
@@ -44,7 +44,7 @@ Node 18.17+. Everything is pure JavaScript and WebAssembly — no compilation st
44
44
  | **Shell** | POSIX `sh` (54 builtins) — pipelines, redirection, here-docs, globbing, brace/parameter/arithmetic/command expansion, functions, `if`/`for`/`while`/`case`/`select`, job control, traps, arrays, `[[ ]]`, `(( ))` |
45
45
  | **Coreutils** | 154 programs — `ls cat cp mv rm mkdir grep sed awk find head tail sort uniq wc cut tr tee xargs chmod chown ln du df ps tar gzip base64 sha256sum diff curl wget` and the rest — plus 54 shell builtins |
46
46
  | **Node.js** | Real Node semantics via Nodepod — `require`, npm packages, `http`, `fs`, streams, `worker_threads` |
47
- | **Python** | MicroPython on WebAssembly, mounted on the *same* filesystem |
47
+ | **Python** | CPython 3.13 (Pyodide) on the *same* filesystem, with the real standard library |
48
48
  | **FFmpeg** | `ffmpeg` and `ffprobe` (FFmpeg 5.1) reading and writing container files directly — [optional install](#video-and-audio) |
49
49
  | **`/proc`** | Live and synthesised — `ps`, `top`, `free` and `uptime` all read the same source |
50
50
  | **Networking** | Virtual interfaces, `/etc/hosts` resolution, in-container HTTP servers, an optional bridge to a real host port |
@@ -404,7 +404,7 @@ your disk.
404
404
 
405
405
  ### Python
406
406
 
407
- Python is MicroPython compiled to WebAssembly, mounted on the container's filesystem:
407
+ Python is CPython 3.13, via Pyodide, mounted on the container's filesystem:
408
408
 
409
409
  ```ts
410
410
  await box.exec("python3 -c \"print(open('/etc/hostname').read())\"");
@@ -414,7 +414,7 @@ await box.exec("echo '1 2 3' | python3 -c \"import sys; print(sum(map(int, sys.s
414
414
 
415
415
  The bundled standard library includes `json`, `re`, `os`, `sys`, `math`, `random`, `hashlib`,
416
416
  `binascii`, `struct`, `time`, `collections`, `itertools`, `functools`, `asyncio` and more. It is
417
- MicroPython, not CPython there is no `numpy`, and `pip` can install pure-Python wheels only
417
+ Real CPython, so `sqlite3`, `dataclasses`, `decimal` and `typing` all work. Packages must be built for WebAssembly: `micropip` installs pure-Python wheels, and Pyodide's own distribution covers the rest
418
418
  (and only with outbound networking enabled).
419
419
 
420
420
  ### Filesystem from the host
@@ -478,7 +478,7 @@ line is measured on the spot rather than claimed, so a runtime that is missing s
478
478
  sandboxedjs — a Linux-like container inside Node.js
479
479
 
480
480
  node v22.12.0 npm, require, http servers
481
- python3 MicroPython no C extensions (numpy, pandas)
481
+ python3 CPython 3.13 only WebAssembly-built C extensions
482
482
  ffmpeg 5.1.4 video and audio
483
483
  network on npm install reaches the real registry
484
484
  ```
@@ -633,33 +633,35 @@ Those use dynamic imports, so they only fail if you call them.
633
633
 
634
634
  ### Python in a browser
635
635
 
636
- MicroPython does not currently start in a browser. Two separate things are involved.
637
-
638
- The first is ours and is fixed: Emscripten resolves `micropython.wasm` against the loader's own
639
- directory, which is right in Node and wrong once a bundler has rewritten the loader into a hashed
640
- chunk — the guess lands on a path the dev server answers with `index.html`, and instantiation
641
- dies on `expected magic word 00 61 73 6d, found 3c 21 64 6f` (the first bytes of `<!doctype`).
642
- Pass the real URL and that goes away:
636
+ Node resolves Pyodide from the installed package. A browser cannot resolve it by name, so tell it
637
+ where the module and the assets are — a CDN is fine:
643
638
 
644
639
  ```ts
645
- import wasm from "@micropython/micropython-webassembly-pyscript/micropython.wasm?url";
640
+ import { configurePython, createContainer } from "sandboxedjs";
641
+
642
+ configurePython({
643
+ engine: "cpython",
644
+ pyodideURL: "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/pyodide.mjs",
645
+ indexURL: "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/",
646
+ });
646
647
 
647
- await createContainer({ pod, python: { wasmUrl: wasm } });
648
+ const box = await createContainer();
649
+ await box.exec("python3 -c 'import sqlite3; print(sqlite3.sqlite_version)'");
648
650
  ```
649
651
 
650
- `?url` is Vite's spelling; every bundler has its own. `configurePython({ wasmUrl })` does the
651
- same thing outside a container.
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.
652
654
 
653
- The second is upstream and is not fixed. With the correct binary loaded, instantiation fails on
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
654
657
 
655
658
  ```
656
659
  LinkError: Import #45 "env" "__syscall_poll_nonblocking": function import requires a callable
657
660
  ```
658
661
 
659
662
  The symbol appears nowhere in the shipped `micropython.mjs`, so the published glue and `.wasm`
660
- disagree about their import table. Node is unaffected the Python tests pass there — and nothing
661
- in this package can supply a symbol the loader never declares. Treat `python3` as Node-only until
662
- the upstream package ships a matching pair.
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.
663
665
 
664
666
  ## Uploading files
665
667
 
@@ -744,7 +746,13 @@ Honest list of what does not work:
744
746
  This affects older toolchains — Vite 4 and its contemporaries. Current Vite ships its Rust
745
747
  pipeline as WebAssembly and runs fine, dev server included, as do Express, Koa, Fastify-style
746
748
  apps and plain `http` servers.
747
- - **Python is MicroPython**, so C extensions (`numpy`, `pandas`, `cryptography`) are unavailable.
749
+ - **Python is CPython via Pyodide.** The standard library is the real one. A C
750
+ extension works only if it has been built for WebAssembly — Pyodide ships
751
+ many, including `numpy`, but an arbitrary wheel from PyPI will not install.
752
+ Starting an interpreter costs about a second and a half; one is kept per
753
+ 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" })`.
748
756
  - **No real sockets.** HTTP servers work through the request proxy; raw TCP/UDP does not.
749
757
  - **No real processes.** Processes are cooperative async tasks: `kill -9` cannot interrupt a
750
758
  tight synchronous loop, and `SIGSTOP` only marks state.
package/dist/index.cjs CHANGED
@@ -21941,6 +21941,7 @@ function mountContainerFs(FS, opts) {
21941
21941
  if (isSysError(e)) return throwErrno(e.code);
21942
21942
  throw new FS.ErrnoError(emErrno("EIO"));
21943
21943
  };
21944
+ const mountRoot = opts.mountAt ? normalize(opts.mountAt) : "/";
21944
21945
  const backend = {
21945
21946
  /** Absolute container path for a node. */
21946
21947
  realPath(node2) {
@@ -21950,7 +21951,9 @@ function mountContainerFs(FS, opts) {
21950
21951
  parts.unshift(current.name);
21951
21952
  current = current.parent;
21952
21953
  }
21953
- return "/" + parts.join("/");
21954
+ const relative2 = parts.join("/");
21955
+ if (mountRoot === "/") return "/" + relative2;
21956
+ return relative2 ? join(mountRoot, relative2) : mountRoot;
21954
21957
  },
21955
21958
  mount(_mount) {
21956
21959
  return backend.createNode(null, "/", 16877 | 0, 0);
@@ -22140,6 +22143,14 @@ function mountContainerFs(FS, opts) {
22140
22143
  return position;
22141
22144
  }
22142
22145
  };
22146
+ if (opts.mountAt) {
22147
+ try {
22148
+ FS.mkdir(mountRoot);
22149
+ } catch {
22150
+ }
22151
+ FS.mount(backend, {}, mountRoot);
22152
+ return;
22153
+ }
22143
22154
  FS.root = null;
22144
22155
  FS.mount(backend, {}, "/");
22145
22156
  try {
@@ -22148,13 +22159,286 @@ function mountContainerFs(FS, opts) {
22148
22159
  }
22149
22160
  }
22150
22161
 
22162
+ // src/runtime/cpython.ts
22163
+ var RESERVED_FOR_INTERPRETER = /* @__PURE__ */ new Set(["lib", "dev", "proc"]);
22164
+ var pyodideModule = null;
22165
+ var indexUrl;
22166
+ var moduleUrl;
22167
+ function configureCPython(options = {}) {
22168
+ if (options.indexURL !== void 0) indexUrl = options.indexURL;
22169
+ if (options.moduleURL !== void 0) {
22170
+ moduleUrl = options.moduleURL;
22171
+ pyodideModule = null;
22172
+ }
22173
+ }
22174
+ function importPyodide() {
22175
+ if (!pyodideModule) {
22176
+ const specifier = moduleUrl ?? ["py", "odide"].join("");
22177
+ pyodideModule = import(
22178
+ /* @vite-ignore */
22179
+ /* webpackIgnore: true */
22180
+ specifier
22181
+ );
22182
+ }
22183
+ return pyodideModule;
22184
+ }
22185
+ var isNode2 = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
22186
+ async function resolveIndexUrl() {
22187
+ if (indexUrl) return indexUrl;
22188
+ if (!isNode2) return void 0;
22189
+ try {
22190
+ const { createRequire } = await nodeBuiltin("module");
22191
+ const path = await nodeBuiltin("path");
22192
+ const require2 = createRequire(path.join(process.cwd(), "index.js"));
22193
+ return `${path.dirname(require2.resolve("pyodide/package.json"))}/`;
22194
+ } catch {
22195
+ return void 0;
22196
+ }
22197
+ }
22198
+ async function isCPythonAvailable() {
22199
+ try {
22200
+ await importPyodide();
22201
+ return true;
22202
+ } catch {
22203
+ return false;
22204
+ }
22205
+ }
22206
+ var interpreters = /* @__PURE__ */ new WeakMap();
22207
+ async function interpreterFor(ctx) {
22208
+ const existing = interpreters.get(ctx.vfs);
22209
+ if (existing) return existing;
22210
+ const starting = (async () => {
22211
+ const { loadPyodide } = await importPyodide();
22212
+ const resolved = await resolveIndexUrl();
22213
+ const py = await loadPyodide({
22214
+ ...resolved ? { indexURL: resolved } : {},
22215
+ /* Output is rebound per program; these only catch anything printed
22216
+ * while the interpreter is still starting. */
22217
+ stdout: () => {
22218
+ },
22219
+ stderr: () => {
22220
+ }
22221
+ });
22222
+ mountContainerDirs(py, ctx);
22223
+ installRunner(py);
22224
+ return py;
22225
+ })();
22226
+ interpreters.set(ctx.vfs, starting);
22227
+ try {
22228
+ return await starting;
22229
+ } catch (error) {
22230
+ interpreters.delete(ctx.vfs);
22231
+ throw error;
22232
+ }
22233
+ }
22234
+ function installRunner(py) {
22235
+ py.__sbxRun = py.runPython(`
22236
+ from pyodide.code import eval_code_async
22237
+
22238
+ async def __sbx_run(source, scope):
22239
+ try:
22240
+ await eval_code_async(source, globals=scope)
22241
+ return 0
22242
+ except SystemExit as exit:
22243
+ code = exit.code
22244
+ if code is None:
22245
+ return 0
22246
+ return code if isinstance(code, int) else 1
22247
+
22248
+ __sbx_run
22249
+ `);
22250
+ }
22251
+ function mountContainerDirs(py, ctx) {
22252
+ const mounted = py.__sbxMounted ??= /* @__PURE__ */ new Set();
22253
+ let entries;
22254
+ try {
22255
+ entries = ctx.vfs.readdirWithTypes("/", ctx.cred);
22256
+ } catch {
22257
+ return;
22258
+ }
22259
+ for (const entry of entries) {
22260
+ if (entry.kind !== "directory") continue;
22261
+ if (RESERVED_FOR_INTERPRETER.has(entry.name)) continue;
22262
+ if (mounted.has(entry.name)) continue;
22263
+ try {
22264
+ mountContainerFs(py.FS, {
22265
+ vfs: ctx.vfs,
22266
+ cred: ctx.cred,
22267
+ mountAt: `/${entry.name}`
22268
+ });
22269
+ mounted.add(entry.name);
22270
+ } catch {
22271
+ }
22272
+ }
22273
+ }
22274
+ function bootstrap(py, ctx, argv, scriptDir) {
22275
+ const paths = [
22276
+ ...scriptDir ? [scriptDir] : [""],
22277
+ "/usr/lib/python3",
22278
+ "/usr/lib/python3/site-packages",
22279
+ "/usr/local/lib/python3/site-packages"
22280
+ ];
22281
+ py.runPython(`
22282
+ import sys, os
22283
+ sys.argv[:] = ${JSON.stringify(argv)}
22284
+ for __p in reversed(${JSON.stringify(paths)}):
22285
+ if __p and __p not in sys.path:
22286
+ sys.path.insert(0, __p)
22287
+ elif __p == "" and "" not in sys.path:
22288
+ sys.path.insert(0, "")
22289
+ try:
22290
+ del __p
22291
+ except NameError:
22292
+ pass
22293
+ os.environ.clear()
22294
+ os.environ.update(${JSON.stringify(ctx.env)})
22295
+ `);
22296
+ try {
22297
+ py.FS.chdir(ctx.cwd);
22298
+ } catch {
22299
+ }
22300
+ }
22301
+ function reportError(ctx, error) {
22302
+ const message = error instanceof Error ? error.message : String(error);
22303
+ const systemExit = /SystemExit:?\s*(-?\d+)?/.exec(message);
22304
+ if (systemExit) {
22305
+ return systemExit[1] !== void 0 ? Number(systemExit[1]) & 255 : 0;
22306
+ }
22307
+ if (/KeyboardInterrupt/.test(message)) {
22308
+ ctx.stderr.write("KeyboardInterrupt\n");
22309
+ return 130;
22310
+ }
22311
+ const text = message.replace(/^PythonError:\s*/, "");
22312
+ ctx.stderr.write(text.endsWith("\n") ? text : `${text}
22313
+ `);
22314
+ return 1;
22315
+ }
22316
+ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
22317
+ let py;
22318
+ try {
22319
+ py = await interpreterFor(ctx);
22320
+ } catch (error) {
22321
+ ctx.stderr.write(
22322
+ `python3: CPython is unavailable in this host (${error instanceof Error ? error.message : String(error)})
22323
+ `
22324
+ );
22325
+ return { exitCode: 127 };
22326
+ }
22327
+ mountContainerDirs(py, ctx);
22328
+ try {
22329
+ await py.loadPackagesFromImports(source, {
22330
+ messageCallback: () => {
22331
+ },
22332
+ errorCallback: () => {
22333
+ }
22334
+ });
22335
+ } catch {
22336
+ }
22337
+ const encoder6 = new TextEncoder();
22338
+ const decoder7 = new TextDecoder();
22339
+ py.setStdout({
22340
+ write: (buffer) => {
22341
+ ctx.write(decoder7.decode(buffer));
22342
+ return buffer.length;
22343
+ }
22344
+ });
22345
+ py.setStderr({
22346
+ write: (buffer) => {
22347
+ ctx.stderr.write(decoder7.decode(buffer));
22348
+ return buffer.length;
22349
+ }
22350
+ });
22351
+ if (stdinText !== null) {
22352
+ const bytes = encoder6.encode(stdinText);
22353
+ let offset = 0;
22354
+ py.setStdin({
22355
+ read: (buffer) => {
22356
+ const take = Math.min(buffer.length, bytes.length - offset);
22357
+ buffer.set(bytes.subarray(offset, offset + take));
22358
+ offset += take;
22359
+ return take;
22360
+ }
22361
+ });
22362
+ } else {
22363
+ py.setStdin({ read: () => 0 });
22364
+ }
22365
+ try {
22366
+ bootstrap(py, ctx, argv, scriptDir);
22367
+ } catch (error) {
22368
+ return { exitCode: reportError(ctx, error) };
22369
+ }
22370
+ const globals = py.globals.get("dict")();
22371
+ try {
22372
+ globals.set("__name__", "__main__");
22373
+ const exitCode = await py.__sbxRun(source, globals);
22374
+ return { exitCode: Number(exitCode) & 255 };
22375
+ } catch (error) {
22376
+ return { exitCode: reportError(ctx, error) };
22377
+ } finally {
22378
+ try {
22379
+ globals.destroy();
22380
+ } catch {
22381
+ }
22382
+ }
22383
+ }
22384
+ var micropip = defineCommand({
22385
+ name: "micropip",
22386
+ path: "/usr/bin/micropip",
22387
+ summary: "install Python packages into the running interpreter",
22388
+ usage: "micropip install <package>...",
22389
+ async run(ctx) {
22390
+ const [action, ...packages] = ctx.args;
22391
+ if (action !== "install" || packages.length === 0) {
22392
+ ctx.line("usage: micropip install <package>...");
22393
+ return action === void 0 ? 1 : 0;
22394
+ }
22395
+ let py;
22396
+ try {
22397
+ py = await interpreterFor(ctx);
22398
+ } catch {
22399
+ ctx.stderr.write("micropip: CPython is unavailable in this host\n");
22400
+ return 127;
22401
+ }
22402
+ try {
22403
+ await py.loadPackage("micropip");
22404
+ const micropipModule = py.pyimport("micropip");
22405
+ for (const name of packages) {
22406
+ ctx.line(`Collecting ${name}`);
22407
+ await micropipModule.install(name);
22408
+ ctx.line(`Successfully installed ${name}`);
22409
+ }
22410
+ return 0;
22411
+ } catch (error) {
22412
+ ctx.stderr.write(
22413
+ `micropip: ${error instanceof Error ? error.message : String(error)}
22414
+ `
22415
+ );
22416
+ return 1;
22417
+ }
22418
+ }
22419
+ });
22420
+
22151
22421
  // src/runtime/python.ts
22152
22422
  init_path();
22153
22423
  var PYTHON_VERSION = "3.4.0";
22154
22424
  var MICROPYTHON_BANNER = "MicroPython v1.28.0 on 2026-04-06; SandboxedJS with Emscripten";
22155
22425
  var loaderPromise = null;
22156
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
+ }
22157
22435
  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 });
22158
22442
  if (options.wasmUrl !== void 0) wasmUrl = options.wasmUrl;
22159
22443
  }
22160
22444
  async function getLoader() {
@@ -22229,6 +22513,9 @@ function reportPythonError(ctx, e) {
22229
22513
  return 1;
22230
22514
  }
22231
22515
  async function runProgram(ctx, source, argv, scriptDir, stdinText) {
22516
+ if (await useCPython()) {
22517
+ return runCPythonProgram(ctx, source, argv, scriptDir, stdinText);
22518
+ }
22232
22519
  let stdinOffset = 0;
22233
22520
  const stdinBytes = stdinText === null ? new Uint8Array(0) : new TextEncoder().encode(stdinText);
22234
22521
  const mp = await createInterpreter(ctx, {
@@ -22551,7 +22838,7 @@ async function readZip(data) {
22551
22838
  return entries;
22552
22839
  }
22553
22840
  function pythonCommands() {
22554
- return [python, pip];
22841
+ return [python, pip, micropip];
22555
22842
  }
22556
22843
 
22557
22844
  // src/runtime/ffmpeg.ts
@@ -22780,8 +23067,8 @@ function renameShadowedExports(source) {
22780
23067
  for (const [key, value] of Object.entries(node2)) {
22781
23068
  if (key === "type" || key === "start" || key === "end" || skip.includes(key)) continue;
22782
23069
  if (Array.isArray(value)) {
22783
- for (const item of value) if (isNode2(item)) visit(item, childScope);
22784
- } else if (isNode2(value)) {
23070
+ for (const item of value) if (isNode3(item)) visit(item, childScope);
23071
+ } else if (isNode3(value)) {
22785
23072
  visit(value, childScope);
22786
23073
  }
22787
23074
  }
@@ -22805,9 +23092,9 @@ function blockNames(body) {
22805
23092
  for (const declarator of node2.declarations) {
22806
23093
  collectPattern(declarator.id, names);
22807
23094
  }
22808
- } else if (node2.type === "ClassDeclaration" && isNode2(node2.id)) {
23095
+ } else if (node2.type === "ClassDeclaration" && isNode3(node2.id)) {
22809
23096
  names.add(node2.id.name);
22810
- } else if (node2.type === "FunctionDeclaration" && isNode2(node2.id)) {
23097
+ } else if (node2.type === "FunctionDeclaration" && isNode3(node2.id)) {
22811
23098
  names.add(node2.id.name);
22812
23099
  }
22813
23100
  }
@@ -22818,10 +23105,10 @@ function collectVars(nodes, names) {
22818
23105
  for (const item of nodes) collectVars(item, names);
22819
23106
  return;
22820
23107
  }
22821
- if (!isNode2(nodes)) return;
23108
+ if (!isNode3(nodes)) return;
22822
23109
  const node2 = nodes;
22823
23110
  if (node2.type === "FunctionDeclaration" || node2.type === "FunctionExpression" || node2.type === "ArrowFunctionExpression") {
22824
- if (isNode2(node2.id)) names.add(node2.id.name);
23111
+ if (isNode3(node2.id)) names.add(node2.id.name);
22825
23112
  return;
22826
23113
  }
22827
23114
  if (node2.type === "VariableDeclaration" && node2.kind === "var") {
@@ -22835,7 +23122,7 @@ function collectVars(nodes, names) {
22835
23122
  }
22836
23123
  }
22837
23124
  function collectPattern(node2, names) {
22838
- if (!isNode2(node2)) return;
23125
+ if (!isNode3(node2)) return;
22839
23126
  switch (node2.type) {
22840
23127
  case "Identifier":
22841
23128
  names.add(node2.name);
@@ -22859,7 +23146,7 @@ function collectPattern(node2, names) {
22859
23146
  }
22860
23147
  }
22861
23148
  function isExports(value) {
22862
- return isNode2(value) && value.type === "Identifier" && value.name === NAME;
23149
+ return isNode3(value) && value.type === "Identifier" && value.name === NAME;
22863
23150
  }
22864
23151
  function freshName(source) {
22865
23152
  let name = "__sandboxedjs_exports";
@@ -22867,7 +23154,7 @@ function freshName(source) {
22867
23154
  while (source.includes(name)) name = `__sandboxedjs_exports${++suffix}`;
22868
23155
  return name;
22869
23156
  }
22870
- function isNode2(value) {
23157
+ function isNode3(value) {
22871
23158
  return typeof value === "object" && value !== null && typeof value.type === "string";
22872
23159
  }
22873
23160
  var NOTHING = [];
@@ -24818,6 +25105,7 @@ exports.braceExpand = braceExpand;
24818
25105
  exports.buildRootfs = buildRootfs;
24819
25106
  exports.builtinNames = builtinNames;
24820
25107
  exports.captureStdio = captureStdio;
25108
+ exports.configureCPython = configureCPython;
24821
25109
  exports.configurePython = configurePython;
24822
25110
  exports.createContainer = createContainer;
24823
25111
  exports.createContext = createContext;
@@ -24836,6 +25124,7 @@ exports.globToRegex = globToRegex;
24836
25124
  exports.hasMagic = hasMagic;
24837
25125
  exports.installUserland = installUserland;
24838
25126
  exports.isBuiltinName = isBuiltinName;
25127
+ exports.isCPythonAvailable = isCPythonAvailable;
24839
25128
  exports.isPythonAvailable = isPythonAvailable;
24840
25129
  exports.isSysError = isSysError;
24841
25130
  exports.makeCred = makeCred;