sandboxedjs 0.1.16 → 0.1.18

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.d.cts CHANGED
@@ -1298,7 +1298,20 @@ declare class Shell {
1298
1298
  */
1299
1299
 
1300
1300
  declare const PYTHON_VERSION = "3.4.0";
1301
+ /**
1302
+ * Which interpreter `python3` is.
1303
+ *
1304
+ * `auto` prefers CPython and falls back to MicroPython, so a host that has not
1305
+ * installed Pyodide keeps working exactly as it did before.
1306
+ */
1307
+ type PythonEngine = "auto" | "cpython" | "micropython";
1301
1308
  interface PythonOptions {
1309
+ /** Interpreter to run. Defaults to `auto`. */
1310
+ engine?: PythonEngine;
1311
+ /** Where Pyodide's assets live; only a browser needs to say. */
1312
+ indexURL?: string;
1313
+ /** URL of the Pyodide ES module, for a browser with no bundler. */
1314
+ pyodideURL?: string;
1302
1315
  /**
1303
1316
  * Absolute URL of `micropython.wasm`.
1304
1317
  *
@@ -1318,7 +1331,6 @@ interface PythonOptions {
1318
1331
  */
1319
1332
  wasmUrl?: string;
1320
1333
  }
1321
- /** Point the Python runtime at its WebAssembly binary. */
1322
1334
  declare function configurePython(options?: PythonOptions): void;
1323
1335
  /** True when a Python interpreter can be started in this process. */
1324
1336
  declare function isPythonAvailable(): Promise<boolean>;
@@ -2004,6 +2016,42 @@ declare function buildRootfs(vfs: Vfs, opts?: RootfsOptions): void;
2004
2016
 
2005
2017
  declare const NODE_VERSION = "v22.12.0";
2006
2018
 
2019
+ /**
2020
+ * The CPython runtime: Pyodide, wired to the container the same way
2021
+ * MicroPython is — filesystem, argv, environment and standard streams.
2022
+ *
2023
+ * This is real CPython, so `sqlite3`, `dataclasses`, `decimal`, `typing` and
2024
+ * the rest of the standard library are present, and `micropip` can install
2025
+ * pure-Python wheels. MicroPython remains for hosts that do not want a 13 MB
2026
+ * interpreter: see `configurePython`.
2027
+ *
2028
+ * Two things differ from the MicroPython path, and both are forced by CPython
2029
+ * keeping its own library inside the Emscripten filesystem:
2030
+ *
2031
+ * - the container is mounted subtree by subtree rather than over `/`, so
2032
+ * `/lib/python314.zip` survives;
2033
+ * - an interpreter costs roughly a second and a half to start, so one is kept
2034
+ * per container and each program is run in a fresh namespace instead.
2035
+ */
2036
+
2037
+ interface CPythonOptions {
2038
+ /**
2039
+ * Where Pyodide's own assets live.
2040
+ *
2041
+ * Node resolves them from the installed package, so this is only needed in a
2042
+ * browser, where they are served or taken from a CDN.
2043
+ */
2044
+ indexURL?: string;
2045
+ /**
2046
+ * URL of the Pyodide ES module, for hosts that cannot resolve the package by
2047
+ * name — a browser without a bundler, typically.
2048
+ */
2049
+ moduleURL?: string;
2050
+ }
2051
+ declare function configureCPython(options?: CPythonOptions): void;
2052
+ /** True when this host can start CPython at all. */
2053
+ declare function isCPythonAvailable(): Promise<boolean>;
2054
+
2007
2055
  /**
2008
2056
  * Package managers: `npm`/`npx`/`yarn`/`pnpm` on top of Nodepod's installer,
2009
2057
  * and an `apt`-shaped front end for the things a container image would ship.
@@ -2029,4 +2077,4 @@ declare const NPM_VERSION = "10.9.0";
2029
2077
  * ```
2030
2078
  */
2031
2079
 
2032
- export { ArithError, BufferSink, CallbackSink, type Command, CommandRegistry, Container, ContainerFs, type ContainerOptions, type ContextInit, type Cred, type DirEntry, ERRNO, type Env, type ErrnoCode, type ExecContext, type ExecOptions, type ExecResult, type FileData, FileInput, FileOutput, type GroupEntry, type HttpResponse, IncompleteInputError, type InputStream, type Job, KERNEL_NAME, KERNEL_RELEASE, Kernel, type KernelOptions, type ListeningPort, type MountEntry, NODE_VERSION, NPM_VERSION, type NetInterface, type NetworkOptions, NetworkStack, NullInput, NullOutput, OS_RELEASE, type OutputStream, PYTHON_VERSION, type PasswdEntry, Pipe, Process, type ProcessKind, type ProcessOptions, type ProcessState, ProcessTable, type PythonOptions, ROOT_CRED, type ResolvedExecutable, type RootfsOptions, type RunOptions, type RunResult, SIGNALS, SIGNAL_NAMES, Session, type SessionInit, type SessionResult, type SessionRunOptions, Shell, ShellExit, type ShellIO, type ShellInit, Lexer as ShellLexer, type ShellOptions, ShellSyntaxError, type SpawnHandle, Stats, type Stdio, SysError, TeeOutput, Terminal, type TerminalOptions, UserDatabase, Variables, Vfs, type VirtualNode, type VirtualProvider, type WriteOptions, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, configurePython, createContainer, createContext, createContainer as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isPythonAvailable, isSysError, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path as posixPath, resetPidCounter, shellQuote, strerror, unameInfo };
2080
+ export { ArithError, BufferSink, type CPythonOptions, CallbackSink, type Command, CommandRegistry, Container, ContainerFs, type ContainerOptions, type ContextInit, type Cred, type DirEntry, ERRNO, type Env, type ErrnoCode, type ExecContext, type ExecOptions, type ExecResult, type FileData, FileInput, FileOutput, type GroupEntry, type HttpResponse, IncompleteInputError, type InputStream, type Job, KERNEL_NAME, KERNEL_RELEASE, Kernel, type KernelOptions, type ListeningPort, type MountEntry, NODE_VERSION, NPM_VERSION, type NetInterface, type NetworkOptions, NetworkStack, NullInput, NullOutput, OS_RELEASE, type OutputStream, PYTHON_VERSION, type PasswdEntry, Pipe, Process, type ProcessKind, type ProcessOptions, type ProcessState, ProcessTable, type PythonEngine, type PythonOptions, ROOT_CRED, type ResolvedExecutable, type RootfsOptions, type RunOptions, type RunResult, SIGNALS, SIGNAL_NAMES, Session, type SessionInit, type SessionResult, type SessionRunOptions, Shell, ShellExit, type ShellIO, type ShellInit, Lexer as ShellLexer, type ShellOptions, ShellSyntaxError, type SpawnHandle, Stats, type Stdio, SysError, TeeOutput, Terminal, type TerminalOptions, UserDatabase, Variables, Vfs, type VirtualNode, type VirtualProvider, type WriteOptions, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, configureCPython, configurePython, createContainer, createContext, createContainer as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isCPythonAvailable, isPythonAvailable, isSysError, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path as posixPath, resetPidCounter, shellQuote, strerror, unameInfo };
package/dist/index.d.ts CHANGED
@@ -1298,7 +1298,20 @@ declare class Shell {
1298
1298
  */
1299
1299
 
1300
1300
  declare const PYTHON_VERSION = "3.4.0";
1301
+ /**
1302
+ * Which interpreter `python3` is.
1303
+ *
1304
+ * `auto` prefers CPython and falls back to MicroPython, so a host that has not
1305
+ * installed Pyodide keeps working exactly as it did before.
1306
+ */
1307
+ type PythonEngine = "auto" | "cpython" | "micropython";
1301
1308
  interface PythonOptions {
1309
+ /** Interpreter to run. Defaults to `auto`. */
1310
+ engine?: PythonEngine;
1311
+ /** Where Pyodide's assets live; only a browser needs to say. */
1312
+ indexURL?: string;
1313
+ /** URL of the Pyodide ES module, for a browser with no bundler. */
1314
+ pyodideURL?: string;
1302
1315
  /**
1303
1316
  * Absolute URL of `micropython.wasm`.
1304
1317
  *
@@ -1318,7 +1331,6 @@ interface PythonOptions {
1318
1331
  */
1319
1332
  wasmUrl?: string;
1320
1333
  }
1321
- /** Point the Python runtime at its WebAssembly binary. */
1322
1334
  declare function configurePython(options?: PythonOptions): void;
1323
1335
  /** True when a Python interpreter can be started in this process. */
1324
1336
  declare function isPythonAvailable(): Promise<boolean>;
@@ -2004,6 +2016,42 @@ declare function buildRootfs(vfs: Vfs, opts?: RootfsOptions): void;
2004
2016
 
2005
2017
  declare const NODE_VERSION = "v22.12.0";
2006
2018
 
2019
+ /**
2020
+ * The CPython runtime: Pyodide, wired to the container the same way
2021
+ * MicroPython is — filesystem, argv, environment and standard streams.
2022
+ *
2023
+ * This is real CPython, so `sqlite3`, `dataclasses`, `decimal`, `typing` and
2024
+ * the rest of the standard library are present, and `micropip` can install
2025
+ * pure-Python wheels. MicroPython remains for hosts that do not want a 13 MB
2026
+ * interpreter: see `configurePython`.
2027
+ *
2028
+ * Two things differ from the MicroPython path, and both are forced by CPython
2029
+ * keeping its own library inside the Emscripten filesystem:
2030
+ *
2031
+ * - the container is mounted subtree by subtree rather than over `/`, so
2032
+ * `/lib/python314.zip` survives;
2033
+ * - an interpreter costs roughly a second and a half to start, so one is kept
2034
+ * per container and each program is run in a fresh namespace instead.
2035
+ */
2036
+
2037
+ interface CPythonOptions {
2038
+ /**
2039
+ * Where Pyodide's own assets live.
2040
+ *
2041
+ * Node resolves them from the installed package, so this is only needed in a
2042
+ * browser, where they are served or taken from a CDN.
2043
+ */
2044
+ indexURL?: string;
2045
+ /**
2046
+ * URL of the Pyodide ES module, for hosts that cannot resolve the package by
2047
+ * name — a browser without a bundler, typically.
2048
+ */
2049
+ moduleURL?: string;
2050
+ }
2051
+ declare function configureCPython(options?: CPythonOptions): void;
2052
+ /** True when this host can start CPython at all. */
2053
+ declare function isCPythonAvailable(): Promise<boolean>;
2054
+
2007
2055
  /**
2008
2056
  * Package managers: `npm`/`npx`/`yarn`/`pnpm` on top of Nodepod's installer,
2009
2057
  * and an `apt`-shaped front end for the things a container image would ship.
@@ -2029,4 +2077,4 @@ declare const NPM_VERSION = "10.9.0";
2029
2077
  * ```
2030
2078
  */
2031
2079
 
2032
- export { ArithError, BufferSink, CallbackSink, type Command, CommandRegistry, Container, ContainerFs, type ContainerOptions, type ContextInit, type Cred, type DirEntry, ERRNO, type Env, type ErrnoCode, type ExecContext, type ExecOptions, type ExecResult, type FileData, FileInput, FileOutput, type GroupEntry, type HttpResponse, IncompleteInputError, type InputStream, type Job, KERNEL_NAME, KERNEL_RELEASE, Kernel, type KernelOptions, type ListeningPort, type MountEntry, NODE_VERSION, NPM_VERSION, type NetInterface, type NetworkOptions, NetworkStack, NullInput, NullOutput, OS_RELEASE, type OutputStream, PYTHON_VERSION, type PasswdEntry, Pipe, Process, type ProcessKind, type ProcessOptions, type ProcessState, ProcessTable, type PythonOptions, ROOT_CRED, type ResolvedExecutable, type RootfsOptions, type RunOptions, type RunResult, SIGNALS, SIGNAL_NAMES, Session, type SessionInit, type SessionResult, type SessionRunOptions, Shell, ShellExit, type ShellIO, type ShellInit, Lexer as ShellLexer, type ShellOptions, ShellSyntaxError, type SpawnHandle, Stats, type Stdio, SysError, TeeOutput, Terminal, type TerminalOptions, UserDatabase, Variables, Vfs, type VirtualNode, type VirtualProvider, type WriteOptions, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, configurePython, createContainer, createContext, createContainer as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isPythonAvailable, isSysError, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path as posixPath, resetPidCounter, shellQuote, strerror, unameInfo };
2080
+ export { ArithError, BufferSink, type CPythonOptions, CallbackSink, type Command, CommandRegistry, Container, ContainerFs, type ContainerOptions, type ContextInit, type Cred, type DirEntry, ERRNO, type Env, type ErrnoCode, type ExecContext, type ExecOptions, type ExecResult, type FileData, FileInput, FileOutput, type GroupEntry, type HttpResponse, IncompleteInputError, type InputStream, type Job, KERNEL_NAME, KERNEL_RELEASE, Kernel, type KernelOptions, type ListeningPort, type MountEntry, NODE_VERSION, NPM_VERSION, type NetInterface, type NetworkOptions, NetworkStack, NullInput, NullOutput, OS_RELEASE, type OutputStream, PYTHON_VERSION, type PasswdEntry, Pipe, Process, type ProcessKind, type ProcessOptions, type ProcessState, ProcessTable, type PythonEngine, type PythonOptions, ROOT_CRED, type ResolvedExecutable, type RootfsOptions, type RunOptions, type RunResult, SIGNALS, SIGNAL_NAMES, Session, type SessionInit, type SessionResult, type SessionRunOptions, Shell, ShellExit, type ShellIO, type ShellInit, Lexer as ShellLexer, type ShellOptions, ShellSyntaxError, type SpawnHandle, Stats, type Stdio, SysError, TeeOutput, Terminal, type TerminalOptions, UserDatabase, Variables, Vfs, type VirtualNode, type VirtualProvider, type WriteOptions, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, configureCPython, configurePython, createContainer, createContext, createContainer as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isCPythonAvailable, isPythonAvailable, isSysError, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path as posixPath, resetPidCounter, shellQuote, strerror, unameInfo };
package/dist/index.js CHANGED
@@ -21936,6 +21936,7 @@ function mountContainerFs(FS, opts) {
21936
21936
  if (isSysError(e)) return throwErrno(e.code);
21937
21937
  throw new FS.ErrnoError(emErrno("EIO"));
21938
21938
  };
21939
+ const mountRoot = opts.mountAt ? normalize(opts.mountAt) : "/";
21939
21940
  const backend = {
21940
21941
  /** Absolute container path for a node. */
21941
21942
  realPath(node2) {
@@ -21945,7 +21946,9 @@ function mountContainerFs(FS, opts) {
21945
21946
  parts.unshift(current.name);
21946
21947
  current = current.parent;
21947
21948
  }
21948
- return "/" + parts.join("/");
21949
+ const relative2 = parts.join("/");
21950
+ if (mountRoot === "/") return "/" + relative2;
21951
+ return relative2 ? join(mountRoot, relative2) : mountRoot;
21949
21952
  },
21950
21953
  mount(_mount) {
21951
21954
  return backend.createNode(null, "/", 16877 | 0, 0);
@@ -22135,6 +22138,14 @@ function mountContainerFs(FS, opts) {
22135
22138
  return position;
22136
22139
  }
22137
22140
  };
22141
+ if (opts.mountAt) {
22142
+ try {
22143
+ FS.mkdir(mountRoot);
22144
+ } catch {
22145
+ }
22146
+ FS.mount(backend, {}, mountRoot);
22147
+ return;
22148
+ }
22138
22149
  FS.root = null;
22139
22150
  FS.mount(backend, {}, "/");
22140
22151
  try {
@@ -22143,13 +22154,286 @@ function mountContainerFs(FS, opts) {
22143
22154
  }
22144
22155
  }
22145
22156
 
22157
+ // src/runtime/cpython.ts
22158
+ var RESERVED_FOR_INTERPRETER = /* @__PURE__ */ new Set(["lib", "dev", "proc"]);
22159
+ var pyodideModule = null;
22160
+ var indexUrl;
22161
+ var moduleUrl;
22162
+ function configureCPython(options = {}) {
22163
+ if (options.indexURL !== void 0) indexUrl = options.indexURL;
22164
+ if (options.moduleURL !== void 0) {
22165
+ moduleUrl = options.moduleURL;
22166
+ pyodideModule = null;
22167
+ }
22168
+ }
22169
+ function importPyodide() {
22170
+ if (!pyodideModule) {
22171
+ const specifier = moduleUrl ?? ["py", "odide"].join("");
22172
+ pyodideModule = import(
22173
+ /* @vite-ignore */
22174
+ /* webpackIgnore: true */
22175
+ specifier
22176
+ );
22177
+ }
22178
+ return pyodideModule;
22179
+ }
22180
+ var isNode2 = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
22181
+ async function resolveIndexUrl() {
22182
+ if (indexUrl) return indexUrl;
22183
+ if (!isNode2) return void 0;
22184
+ try {
22185
+ const { createRequire } = await nodeBuiltin("module");
22186
+ const path = await nodeBuiltin("path");
22187
+ const require2 = createRequire(path.join(process.cwd(), "index.js"));
22188
+ return `${path.dirname(require2.resolve("pyodide/package.json"))}/`;
22189
+ } catch {
22190
+ return void 0;
22191
+ }
22192
+ }
22193
+ async function isCPythonAvailable() {
22194
+ try {
22195
+ await importPyodide();
22196
+ return true;
22197
+ } catch {
22198
+ return false;
22199
+ }
22200
+ }
22201
+ var interpreters = /* @__PURE__ */ new WeakMap();
22202
+ async function interpreterFor(ctx) {
22203
+ const existing = interpreters.get(ctx.vfs);
22204
+ if (existing) return existing;
22205
+ const starting = (async () => {
22206
+ const { loadPyodide } = await importPyodide();
22207
+ const resolved = await resolveIndexUrl();
22208
+ const py = await loadPyodide({
22209
+ ...resolved ? { indexURL: resolved } : {},
22210
+ /* Output is rebound per program; these only catch anything printed
22211
+ * while the interpreter is still starting. */
22212
+ stdout: () => {
22213
+ },
22214
+ stderr: () => {
22215
+ }
22216
+ });
22217
+ mountContainerDirs(py, ctx);
22218
+ installRunner(py);
22219
+ return py;
22220
+ })();
22221
+ interpreters.set(ctx.vfs, starting);
22222
+ try {
22223
+ return await starting;
22224
+ } catch (error) {
22225
+ interpreters.delete(ctx.vfs);
22226
+ throw error;
22227
+ }
22228
+ }
22229
+ function installRunner(py) {
22230
+ py.__sbxRun = py.runPython(`
22231
+ from pyodide.code import eval_code_async
22232
+
22233
+ async def __sbx_run(source, scope):
22234
+ try:
22235
+ await eval_code_async(source, globals=scope)
22236
+ return 0
22237
+ except SystemExit as exit:
22238
+ code = exit.code
22239
+ if code is None:
22240
+ return 0
22241
+ return code if isinstance(code, int) else 1
22242
+
22243
+ __sbx_run
22244
+ `);
22245
+ }
22246
+ function mountContainerDirs(py, ctx) {
22247
+ const mounted = py.__sbxMounted ??= /* @__PURE__ */ new Set();
22248
+ let entries;
22249
+ try {
22250
+ entries = ctx.vfs.readdirWithTypes("/", ctx.cred);
22251
+ } catch {
22252
+ return;
22253
+ }
22254
+ for (const entry of entries) {
22255
+ if (entry.kind !== "directory") continue;
22256
+ if (RESERVED_FOR_INTERPRETER.has(entry.name)) continue;
22257
+ if (mounted.has(entry.name)) continue;
22258
+ try {
22259
+ mountContainerFs(py.FS, {
22260
+ vfs: ctx.vfs,
22261
+ cred: ctx.cred,
22262
+ mountAt: `/${entry.name}`
22263
+ });
22264
+ mounted.add(entry.name);
22265
+ } catch {
22266
+ }
22267
+ }
22268
+ }
22269
+ function bootstrap(py, ctx, argv, scriptDir) {
22270
+ const paths = [
22271
+ ...scriptDir ? [scriptDir] : [""],
22272
+ "/usr/lib/python3",
22273
+ "/usr/lib/python3/site-packages",
22274
+ "/usr/local/lib/python3/site-packages"
22275
+ ];
22276
+ py.runPython(`
22277
+ import sys, os
22278
+ sys.argv[:] = ${JSON.stringify(argv)}
22279
+ for __p in reversed(${JSON.stringify(paths)}):
22280
+ if __p and __p not in sys.path:
22281
+ sys.path.insert(0, __p)
22282
+ elif __p == "" and "" not in sys.path:
22283
+ sys.path.insert(0, "")
22284
+ try:
22285
+ del __p
22286
+ except NameError:
22287
+ pass
22288
+ os.environ.clear()
22289
+ os.environ.update(${JSON.stringify(ctx.env)})
22290
+ `);
22291
+ try {
22292
+ py.FS.chdir(ctx.cwd);
22293
+ } catch {
22294
+ }
22295
+ }
22296
+ function reportError(ctx, error) {
22297
+ const message = error instanceof Error ? error.message : String(error);
22298
+ const systemExit = /SystemExit:?\s*(-?\d+)?/.exec(message);
22299
+ if (systemExit) {
22300
+ return systemExit[1] !== void 0 ? Number(systemExit[1]) & 255 : 0;
22301
+ }
22302
+ if (/KeyboardInterrupt/.test(message)) {
22303
+ ctx.stderr.write("KeyboardInterrupt\n");
22304
+ return 130;
22305
+ }
22306
+ const text = message.replace(/^PythonError:\s*/, "");
22307
+ ctx.stderr.write(text.endsWith("\n") ? text : `${text}
22308
+ `);
22309
+ return 1;
22310
+ }
22311
+ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
22312
+ let py;
22313
+ try {
22314
+ py = await interpreterFor(ctx);
22315
+ } catch (error) {
22316
+ ctx.stderr.write(
22317
+ `python3: CPython is unavailable in this host (${error instanceof Error ? error.message : String(error)})
22318
+ `
22319
+ );
22320
+ return { exitCode: 127 };
22321
+ }
22322
+ mountContainerDirs(py, ctx);
22323
+ try {
22324
+ await py.loadPackagesFromImports(source, {
22325
+ messageCallback: () => {
22326
+ },
22327
+ errorCallback: () => {
22328
+ }
22329
+ });
22330
+ } catch {
22331
+ }
22332
+ const encoder6 = new TextEncoder();
22333
+ const decoder7 = new TextDecoder();
22334
+ py.setStdout({
22335
+ write: (buffer) => {
22336
+ ctx.write(decoder7.decode(buffer));
22337
+ return buffer.length;
22338
+ }
22339
+ });
22340
+ py.setStderr({
22341
+ write: (buffer) => {
22342
+ ctx.stderr.write(decoder7.decode(buffer));
22343
+ return buffer.length;
22344
+ }
22345
+ });
22346
+ if (stdinText !== null) {
22347
+ const bytes = encoder6.encode(stdinText);
22348
+ let offset = 0;
22349
+ py.setStdin({
22350
+ read: (buffer) => {
22351
+ const take = Math.min(buffer.length, bytes.length - offset);
22352
+ buffer.set(bytes.subarray(offset, offset + take));
22353
+ offset += take;
22354
+ return take;
22355
+ }
22356
+ });
22357
+ } else {
22358
+ py.setStdin({ read: () => 0 });
22359
+ }
22360
+ try {
22361
+ bootstrap(py, ctx, argv, scriptDir);
22362
+ } catch (error) {
22363
+ return { exitCode: reportError(ctx, error) };
22364
+ }
22365
+ const globals = py.globals.get("dict")();
22366
+ try {
22367
+ globals.set("__name__", "__main__");
22368
+ const exitCode = await py.__sbxRun(source, globals);
22369
+ return { exitCode: Number(exitCode) & 255 };
22370
+ } catch (error) {
22371
+ return { exitCode: reportError(ctx, error) };
22372
+ } finally {
22373
+ try {
22374
+ globals.destroy();
22375
+ } catch {
22376
+ }
22377
+ }
22378
+ }
22379
+ var micropip = defineCommand({
22380
+ name: "micropip",
22381
+ path: "/usr/bin/micropip",
22382
+ summary: "install Python packages into the running interpreter",
22383
+ usage: "micropip install <package>...",
22384
+ async run(ctx) {
22385
+ const [action, ...packages] = ctx.args;
22386
+ if (action !== "install" || packages.length === 0) {
22387
+ ctx.line("usage: micropip install <package>...");
22388
+ return action === void 0 ? 1 : 0;
22389
+ }
22390
+ let py;
22391
+ try {
22392
+ py = await interpreterFor(ctx);
22393
+ } catch {
22394
+ ctx.stderr.write("micropip: CPython is unavailable in this host\n");
22395
+ return 127;
22396
+ }
22397
+ try {
22398
+ await py.loadPackage("micropip");
22399
+ const micropipModule = py.pyimport("micropip");
22400
+ for (const name of packages) {
22401
+ ctx.line(`Collecting ${name}`);
22402
+ await micropipModule.install(name);
22403
+ ctx.line(`Successfully installed ${name}`);
22404
+ }
22405
+ return 0;
22406
+ } catch (error) {
22407
+ ctx.stderr.write(
22408
+ `micropip: ${error instanceof Error ? error.message : String(error)}
22409
+ `
22410
+ );
22411
+ return 1;
22412
+ }
22413
+ }
22414
+ });
22415
+
22146
22416
  // src/runtime/python.ts
22147
22417
  init_path();
22148
22418
  var PYTHON_VERSION = "3.4.0";
22149
22419
  var MICROPYTHON_BANNER = "MicroPython v1.28.0 on 2026-04-06; SandboxedJS with Emscripten";
22150
22420
  var loaderPromise = null;
22151
22421
  var wasmUrl;
22422
+ var engine = "auto";
22423
+ var cpythonUsable = null;
22424
+ async function useCPython() {
22425
+ if (engine === "micropython") return false;
22426
+ if (engine === "cpython") return true;
22427
+ cpythonUsable ??= isCPythonAvailable();
22428
+ return cpythonUsable;
22429
+ }
22152
22430
  function configurePython(options = {}) {
22431
+ if (options.engine !== void 0) {
22432
+ engine = options.engine;
22433
+ cpythonUsable = null;
22434
+ }
22435
+ if (options.indexURL !== void 0) configureCPython({ indexURL: options.indexURL });
22436
+ if (options.pyodideURL !== void 0) configureCPython({ moduleURL: options.pyodideURL });
22153
22437
  if (options.wasmUrl !== void 0) wasmUrl = options.wasmUrl;
22154
22438
  }
22155
22439
  async function getLoader() {
@@ -22224,6 +22508,9 @@ function reportPythonError(ctx, e) {
22224
22508
  return 1;
22225
22509
  }
22226
22510
  async function runProgram(ctx, source, argv, scriptDir, stdinText) {
22511
+ if (await useCPython()) {
22512
+ return runCPythonProgram(ctx, source, argv, scriptDir, stdinText);
22513
+ }
22227
22514
  let stdinOffset = 0;
22228
22515
  const stdinBytes = stdinText === null ? new Uint8Array(0) : new TextEncoder().encode(stdinText);
22229
22516
  const mp = await createInterpreter(ctx, {
@@ -22546,7 +22833,7 @@ async function readZip(data) {
22546
22833
  return entries;
22547
22834
  }
22548
22835
  function pythonCommands() {
22549
- return [python, pip];
22836
+ return [python, pip, micropip];
22550
22837
  }
22551
22838
 
22552
22839
  // src/runtime/ffmpeg.ts
@@ -22775,8 +23062,8 @@ function renameShadowedExports(source) {
22775
23062
  for (const [key, value] of Object.entries(node2)) {
22776
23063
  if (key === "type" || key === "start" || key === "end" || skip.includes(key)) continue;
22777
23064
  if (Array.isArray(value)) {
22778
- for (const item of value) if (isNode2(item)) visit(item, childScope);
22779
- } else if (isNode2(value)) {
23065
+ for (const item of value) if (isNode3(item)) visit(item, childScope);
23066
+ } else if (isNode3(value)) {
22780
23067
  visit(value, childScope);
22781
23068
  }
22782
23069
  }
@@ -22800,9 +23087,9 @@ function blockNames(body) {
22800
23087
  for (const declarator of node2.declarations) {
22801
23088
  collectPattern(declarator.id, names);
22802
23089
  }
22803
- } else if (node2.type === "ClassDeclaration" && isNode2(node2.id)) {
23090
+ } else if (node2.type === "ClassDeclaration" && isNode3(node2.id)) {
22804
23091
  names.add(node2.id.name);
22805
- } else if (node2.type === "FunctionDeclaration" && isNode2(node2.id)) {
23092
+ } else if (node2.type === "FunctionDeclaration" && isNode3(node2.id)) {
22806
23093
  names.add(node2.id.name);
22807
23094
  }
22808
23095
  }
@@ -22813,10 +23100,10 @@ function collectVars(nodes, names) {
22813
23100
  for (const item of nodes) collectVars(item, names);
22814
23101
  return;
22815
23102
  }
22816
- if (!isNode2(nodes)) return;
23103
+ if (!isNode3(nodes)) return;
22817
23104
  const node2 = nodes;
22818
23105
  if (node2.type === "FunctionDeclaration" || node2.type === "FunctionExpression" || node2.type === "ArrowFunctionExpression") {
22819
- if (isNode2(node2.id)) names.add(node2.id.name);
23106
+ if (isNode3(node2.id)) names.add(node2.id.name);
22820
23107
  return;
22821
23108
  }
22822
23109
  if (node2.type === "VariableDeclaration" && node2.kind === "var") {
@@ -22830,7 +23117,7 @@ function collectVars(nodes, names) {
22830
23117
  }
22831
23118
  }
22832
23119
  function collectPattern(node2, names) {
22833
- if (!isNode2(node2)) return;
23120
+ if (!isNode3(node2)) return;
22834
23121
  switch (node2.type) {
22835
23122
  case "Identifier":
22836
23123
  names.add(node2.name);
@@ -22854,7 +23141,7 @@ function collectPattern(node2, names) {
22854
23141
  }
22855
23142
  }
22856
23143
  function isExports(value) {
22857
- return isNode2(value) && value.type === "Identifier" && value.name === NAME;
23144
+ return isNode3(value) && value.type === "Identifier" && value.name === NAME;
22858
23145
  }
22859
23146
  function freshName(source) {
22860
23147
  let name = "__sandboxedjs_exports";
@@ -22862,7 +23149,7 @@ function freshName(source) {
22862
23149
  while (source.includes(name)) name = `__sandboxedjs_exports${++suffix}`;
22863
23150
  return name;
22864
23151
  }
22865
- function isNode2(value) {
23152
+ function isNode3(value) {
22866
23153
  return typeof value === "object" && value !== null && typeof value.type === "string";
22867
23154
  }
22868
23155
  var NOTHING = [];
@@ -24775,6 +25062,6 @@ init_expand();
24775
25062
  init_builtins();
24776
25063
  var src_default = createContainer;
24777
25064
 
24778
- export { ArithError, BufferSink, CallbackSink, CommandRegistry, Container, ContainerFs, ERRNO, FileInput, FileOutput, IncompleteInputError, KERNEL_NAME, KERNEL_RELEASE, Kernel, NODE_VERSION, NPM_VERSION, NetworkStack, NullInput, NullOutput, OS_RELEASE, PYTHON_VERSION, Pipe, Process, ProcessTable, ROOT_CRED, SIGNALS, SIGNAL_NAMES, Session, Shell, ShellExit, Lexer as ShellLexer, ShellSyntaxError, Stats, SysError, TeeOutput, Terminal, UserDatabase, Variables, Vfs, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, configurePython, createContainer, createContext, src_default as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isPythonAvailable, isSysError, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path_exports as posixPath, resetPidCounter, shellQuote, strerror, unameInfo };
25065
+ export { ArithError, BufferSink, CallbackSink, CommandRegistry, Container, ContainerFs, ERRNO, FileInput, FileOutput, IncompleteInputError, KERNEL_NAME, KERNEL_RELEASE, Kernel, NODE_VERSION, NPM_VERSION, NetworkStack, NullInput, NullOutput, OS_RELEASE, PYTHON_VERSION, Pipe, Process, ProcessTable, ROOT_CRED, SIGNALS, SIGNAL_NAMES, Session, Shell, ShellExit, Lexer as ShellLexer, ShellSyntaxError, Stats, SysError, TeeOutput, Terminal, UserDatabase, Variables, Vfs, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, configureCPython, configurePython, createContainer, createContext, src_default as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isCPythonAvailable, isPythonAvailable, isSysError, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path_exports as posixPath, resetPidCounter, shellQuote, strerror, unameInfo };
24779
25066
  //# sourceMappingURL=index.js.map
24780
25067
  //# sourceMappingURL=index.js.map