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/agent.d.cts +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/{container-X8yZRFvW.d.ts → container-CUB12Moo.d.ts} +18 -53
- package/dist/{container-NoLUp_fd.d.cts → container-LwAV9JTH.d.cts} +18 -53
- package/dist/index.cjs +314 -1487
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +315 -1486
- package/dist/index.js.map +1 -1
- package/dist/worker-entry.js +62 -1
- package/dist/worker-entry.js.map +1 -1
- package/package.json +2 -4
package/dist/index.js
CHANGED
|
@@ -7802,7 +7802,7 @@ fi
|
|
|
7802
7802
|
`;
|
|
7803
7803
|
var MOTD = `Welcome to SandboxedJS \u2014 a Linux-like container running inside Node.js.
|
|
7804
7804
|
|
|
7805
|
-
* Node.js, npm and CPython
|
|
7805
|
+
* Node.js, npm and CPython are preinstalled.
|
|
7806
7806
|
* The filesystem is virtual: nothing here touches your host.
|
|
7807
7807
|
* Run 'help' for the list of built-in commands.
|
|
7808
7808
|
|
|
@@ -19051,6 +19051,49 @@ var NOTHING = [];
|
|
|
19051
19051
|
var PROPERTY = ["property", "key"];
|
|
19052
19052
|
var LABEL = ["label"];
|
|
19053
19053
|
|
|
19054
|
+
// src/node/env.ts
|
|
19055
|
+
function parseEnv(text2) {
|
|
19056
|
+
if (typeof text2 !== "string") {
|
|
19057
|
+
throw Object.assign(new TypeError('The "content" argument must be of type string'), { code: "ERR_INVALID_ARG_TYPE" });
|
|
19058
|
+
}
|
|
19059
|
+
const out = {};
|
|
19060
|
+
const lines = text2.split(/\r?\n/);
|
|
19061
|
+
for (let index = 0; index < lines.length; index++) {
|
|
19062
|
+
const raw = lines[index];
|
|
19063
|
+
const match2 = /^[ \t]*(?:export[ \t]+)?([^=#]+?)[ \t]*=[ \t]*(.*)$/.exec(raw);
|
|
19064
|
+
if (!match2) continue;
|
|
19065
|
+
const name = match2[1];
|
|
19066
|
+
let value = match2[2];
|
|
19067
|
+
const quote3 = value[0];
|
|
19068
|
+
if (quote3 === '"' || quote3 === "'" || quote3 === "`") {
|
|
19069
|
+
const start2 = index;
|
|
19070
|
+
const original = value;
|
|
19071
|
+
value = value.slice(1);
|
|
19072
|
+
while (true) {
|
|
19073
|
+
const end = value.indexOf(quote3);
|
|
19074
|
+
if (end >= 0) {
|
|
19075
|
+
value = value.slice(0, end);
|
|
19076
|
+
break;
|
|
19077
|
+
}
|
|
19078
|
+
if (++index >= lines.length) {
|
|
19079
|
+
index = start2;
|
|
19080
|
+
value = original;
|
|
19081
|
+
break;
|
|
19082
|
+
}
|
|
19083
|
+
value += `
|
|
19084
|
+
${lines[index]}`;
|
|
19085
|
+
}
|
|
19086
|
+
if (quote3 === '"') value = value.replace(/\\n/g, "\n");
|
|
19087
|
+
} else {
|
|
19088
|
+
const comment = value.indexOf("#");
|
|
19089
|
+
if (comment >= 0) value = value.slice(0, comment);
|
|
19090
|
+
value = value.trim();
|
|
19091
|
+
}
|
|
19092
|
+
Object.defineProperty(out, name, { value, writable: true, enumerable: true, configurable: true });
|
|
19093
|
+
}
|
|
19094
|
+
return out;
|
|
19095
|
+
}
|
|
19096
|
+
|
|
19054
19097
|
// src/node/node.ts
|
|
19055
19098
|
var NODE_VERSION = "v22.12.0";
|
|
19056
19099
|
new TextEncoder();
|
|
@@ -19317,7 +19360,7 @@ The filesystem a script sees is the container's filesystem.`,
|
|
|
19317
19360
|
`);
|
|
19318
19361
|
return 9;
|
|
19319
19362
|
}
|
|
19320
|
-
Object.assign(loaded,
|
|
19363
|
+
Object.assign(loaded, parseEnv(text2));
|
|
19321
19364
|
}
|
|
19322
19365
|
env2 = { ...loaded, ...ctx.env };
|
|
19323
19366
|
}
|
|
@@ -19554,27 +19597,6 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
19554
19597
|
"--report-dir",
|
|
19555
19598
|
"--report-filename"
|
|
19556
19599
|
]);
|
|
19557
|
-
function parseEnvFile(text2) {
|
|
19558
|
-
const out = {};
|
|
19559
|
-
for (const raw of text2.split(/\r?\n/)) {
|
|
19560
|
-
const line = raw.trim();
|
|
19561
|
-
if (!line || line.startsWith("#")) continue;
|
|
19562
|
-
const match2 = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.*)$/.exec(line);
|
|
19563
|
-
if (!match2) continue;
|
|
19564
|
-
let value = match2[2];
|
|
19565
|
-
const quote3 = value[0];
|
|
19566
|
-
if ((quote3 === '"' || quote3 === "'" || quote3 === "`") && value.length >= 2 && value.endsWith(quote3)) {
|
|
19567
|
-
value = value.slice(1, -1);
|
|
19568
|
-
if (quote3 === '"') value = value.replace(/\\n/g, "\n");
|
|
19569
|
-
} else {
|
|
19570
|
-
const comment = value.indexOf(" #");
|
|
19571
|
-
if (comment >= 0) value = value.slice(0, comment);
|
|
19572
|
-
value = value.trim();
|
|
19573
|
-
}
|
|
19574
|
-
out[match2[1]] = value;
|
|
19575
|
-
}
|
|
19576
|
-
return out;
|
|
19577
|
-
}
|
|
19578
19600
|
var AsyncFunction = Object.getPrototypeOf(async function() {
|
|
19579
19601
|
}).constructor;
|
|
19580
19602
|
function checkSyntax(ctx, script) {
|
|
@@ -19728,9 +19750,6 @@ function nodeCommands() {
|
|
|
19728
19750
|
return [node, nodeVersionFile];
|
|
19729
19751
|
}
|
|
19730
19752
|
|
|
19731
|
-
// src/python/python.ts
|
|
19732
|
-
init_path();
|
|
19733
|
-
|
|
19734
19753
|
// src/python/host-abi.ts
|
|
19735
19754
|
var SBX_HOST_ABI_VERSION = 1;
|
|
19736
19755
|
var SBX_REQUEST_HEADER_BYTES = 16;
|
|
@@ -20201,7 +20220,7 @@ var wheels_default = {
|
|
|
20201
20220
|
|
|
20202
20221
|
// package.json
|
|
20203
20222
|
var package_default = {
|
|
20204
|
-
version: "0.1.
|
|
20223
|
+
version: "0.1.78"};
|
|
20205
20224
|
|
|
20206
20225
|
// src/python/config.ts
|
|
20207
20226
|
function runtimeModuleUrl() {
|
|
@@ -20233,11 +20252,15 @@ var embeddedWheels = {
|
|
|
20233
20252
|
"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,
|
|
20234
20253
|
"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
|
|
20235
20254
|
};
|
|
20236
|
-
|
|
20237
|
-
|
|
20238
|
-
|
|
20239
|
-
|
|
20240
|
-
|
|
20255
|
+
function wheelIndexFor(moduleUrl) {
|
|
20256
|
+
return {
|
|
20257
|
+
baseUrl: new URL("./wheels", moduleUrl).href,
|
|
20258
|
+
wheels: wheels_default.wheels,
|
|
20259
|
+
files: embeddedWheels
|
|
20260
|
+
};
|
|
20261
|
+
}
|
|
20262
|
+
var bundledWheelIndex = wheelIndexFor(runtimeModuleUrl());
|
|
20263
|
+
var wheelIndexIsExplicit = false;
|
|
20241
20264
|
var config = {
|
|
20242
20265
|
backend: "sbx-cpython-wasm",
|
|
20243
20266
|
manifest: bundledManifest,
|
|
@@ -20252,9 +20275,17 @@ var config = {
|
|
|
20252
20275
|
buildFromSource: typeof process !== "undefined" && Boolean(process.versions?.node) && import.meta.url.startsWith("file:")
|
|
20253
20276
|
};
|
|
20254
20277
|
function setPythonBackend(options) {
|
|
20255
|
-
if (options.wheelIndex !== void 0)
|
|
20278
|
+
if (options.wheelIndex !== void 0) {
|
|
20279
|
+
config.wheelIndex = options.wheelIndex;
|
|
20280
|
+
wheelIndexIsExplicit = true;
|
|
20281
|
+
}
|
|
20256
20282
|
if (options.buildFromSource !== void 0) config.buildFromSource = options.buildFromSource;
|
|
20257
|
-
if (options.manifest !== void 0)
|
|
20283
|
+
if (options.manifest !== void 0) {
|
|
20284
|
+
config.manifest = validateManifest(options.manifest);
|
|
20285
|
+
if (!wheelIndexIsExplicit) {
|
|
20286
|
+
config.wheelIndex = wheelIndexFor(config.manifest.artifacts.moduleUrl);
|
|
20287
|
+
}
|
|
20288
|
+
}
|
|
20258
20289
|
if (options.workerUrl !== void 0) config.workerUrl = options.workerUrl;
|
|
20259
20290
|
if (options.backend !== void 0) {
|
|
20260
20291
|
if (options.backend === "sbx-cpython-wasm" && !config.manifest) {
|
|
@@ -22772,6 +22803,7 @@ async function installRequirements(options) {
|
|
|
22772
22803
|
}
|
|
22773
22804
|
options.progress.downloading(distribution.name, distribution.version);
|
|
22774
22805
|
const bytes2 = await options.client.bytes(distribution.url, { timeoutMs: 12e4 });
|
|
22806
|
+
requireArchive(distribution, bytes2);
|
|
22775
22807
|
verifyDigest(distribution, bytes2);
|
|
22776
22808
|
staged.push(...stageWheel(readZip(bytes2)));
|
|
22777
22809
|
installed2.push({ name: distribution.name, version: distribution.version });
|
|
@@ -22779,6 +22811,14 @@ async function installRequirements(options) {
|
|
|
22779
22811
|
commit(options.vfs, options.cred, staged);
|
|
22780
22812
|
return { installed: installed2, skipped: solved.skipped };
|
|
22781
22813
|
}
|
|
22814
|
+
function requireArchive(distribution, bytes2) {
|
|
22815
|
+
if (bytes2.length > 1 && bytes2[0] === 80 && bytes2[1] === 75) return;
|
|
22816
|
+
const opening = new TextDecoder().decode(bytes2.subarray(0, 80)).replace(/\s+/g, " ").trim();
|
|
22817
|
+
const looksLikeMarkup = /^<!doctype|^<html/i.test(opening);
|
|
22818
|
+
throw new Error(
|
|
22819
|
+
`${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)}`)
|
|
22820
|
+
);
|
|
22821
|
+
}
|
|
22782
22822
|
function verifyDigest(distribution, bytes2) {
|
|
22783
22823
|
if (!distribution.sha256) {
|
|
22784
22824
|
throw new Error(
|
|
@@ -23063,9 +23103,6 @@ async function resolveWorkerUrl(explicit) {
|
|
|
23063
23103
|
"the SandboxedJs Python process worker has not been built; run `npm run build` in the package, or pass configurePython({ workerUrl })."
|
|
23064
23104
|
);
|
|
23065
23105
|
}
|
|
23066
|
-
function usingOwnedPython() {
|
|
23067
|
-
return pythonBackend().backend === "sbx-cpython-wasm";
|
|
23068
|
-
}
|
|
23069
23106
|
async function runOwnedPython(ctx, argv) {
|
|
23070
23107
|
const { manifest, workerUrl } = pythonBackend();
|
|
23071
23108
|
if (!manifest) throw new Error("no Python runtime manifest is configured");
|
|
@@ -23554,6 +23591,75 @@ function listInstalled(ctx) {
|
|
|
23554
23591
|
return 0;
|
|
23555
23592
|
}
|
|
23556
23593
|
|
|
23594
|
+
// src/python/python.ts
|
|
23595
|
+
var PYTHON_VERSION = "3.13";
|
|
23596
|
+
function configurePython(options = {}) {
|
|
23597
|
+
setPythonBackend({
|
|
23598
|
+
...options.backend !== void 0 ? { backend: options.backend } : {},
|
|
23599
|
+
...options.manifest !== void 0 ? { manifest: options.manifest } : {},
|
|
23600
|
+
...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {},
|
|
23601
|
+
...options.wheelIndex !== void 0 ? { wheelIndex: options.wheelIndex } : {},
|
|
23602
|
+
...options.buildFromSource !== void 0 ? { buildFromSource: options.buildFromSource } : {}
|
|
23603
|
+
});
|
|
23604
|
+
}
|
|
23605
|
+
async function isPythonAvailable() {
|
|
23606
|
+
return pythonBackend().manifest !== null;
|
|
23607
|
+
}
|
|
23608
|
+
function withTopLevelAwait(source) {
|
|
23609
|
+
if (!/\bawait\b/.test(source)) return source;
|
|
23610
|
+
return `import ast as _sbx_ast, inspect as _sbx_inspect
|
|
23611
|
+
_sbx_src = ${JSON.stringify(source)}
|
|
23612
|
+
try:
|
|
23613
|
+
_sbx_code = compile(_sbx_src, "<string>", "exec")
|
|
23614
|
+
except SyntaxError:
|
|
23615
|
+
_sbx_code = compile(_sbx_src, "<string>", "exec", _sbx_ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
|
|
23616
|
+
_sbx_async = bool(_sbx_code.co_flags & _sbx_inspect.CO_COROUTINE)
|
|
23617
|
+
_sbx_globals = globals()
|
|
23618
|
+
for _sbx_name in ("_sbx_ast", "_sbx_inspect", "_sbx_src", "_sbx_name"):
|
|
23619
|
+
_sbx_globals.pop(_sbx_name, None)
|
|
23620
|
+
if _sbx_async:
|
|
23621
|
+
import asyncio as _sbx_asyncio
|
|
23622
|
+
_sbx_asyncio.run(eval(_sbx_code, _sbx_globals))
|
|
23623
|
+
else:
|
|
23624
|
+
exec(_sbx_code, _sbx_globals)
|
|
23625
|
+
`;
|
|
23626
|
+
}
|
|
23627
|
+
function withTopLevelAwaitArgs(args) {
|
|
23628
|
+
for (let i = 0; i < args.length; i++) {
|
|
23629
|
+
const arg = args[i];
|
|
23630
|
+
if (arg === "-c") {
|
|
23631
|
+
if (i + 1 >= args.length) return args;
|
|
23632
|
+
const rewritten = withTopLevelAwait(args[i + 1]);
|
|
23633
|
+
if (rewritten === args[i + 1]) return args;
|
|
23634
|
+
const copy = [...args];
|
|
23635
|
+
copy[i + 1] = rewritten;
|
|
23636
|
+
return copy;
|
|
23637
|
+
}
|
|
23638
|
+
if (arg === "-m" || !arg.startsWith("-") || arg === "-") return args;
|
|
23639
|
+
}
|
|
23640
|
+
return args;
|
|
23641
|
+
}
|
|
23642
|
+
var python = defineCommand({
|
|
23643
|
+
name: "python3",
|
|
23644
|
+
path: "/usr/bin/python3",
|
|
23645
|
+
aliases: ["python"],
|
|
23646
|
+
summary: "run CPython",
|
|
23647
|
+
usage: "python3 [-c command | -m module | script.py] [arguments]",
|
|
23648
|
+
manual: `CPython built for WebAssembly, one interpreter per process. Scripts
|
|
23649
|
+
use the container's virtual filesystem, modules in /workspace are importable,
|
|
23650
|
+
and packages are installed with pip.`,
|
|
23651
|
+
async run(ctx) {
|
|
23652
|
+
try {
|
|
23653
|
+
return await runOwnedPython(ctx, withTopLevelAwaitArgs(ctx.args));
|
|
23654
|
+
} catch (error) {
|
|
23655
|
+
return ctx.fail(error.message ?? String(error));
|
|
23656
|
+
}
|
|
23657
|
+
}
|
|
23658
|
+
});
|
|
23659
|
+
function pythonCommands() {
|
|
23660
|
+
return [python, pipCommand];
|
|
23661
|
+
}
|
|
23662
|
+
|
|
23557
23663
|
// src/fs/emscripten-fs.ts
|
|
23558
23664
|
init_errno();
|
|
23559
23665
|
init_path();
|
|
@@ -23646,1462 +23752,167 @@ function mountContainerFs(FS, opts) {
|
|
|
23646
23752
|
vfs.chmod(path, attr.mode & 4095, cred);
|
|
23647
23753
|
node2.mode = attr.mode;
|
|
23648
23754
|
}
|
|
23649
|
-
if (attr.timestamp !== void 0) vfs.utimes(path, attr.timestamp, attr.timestamp, cred);
|
|
23650
|
-
if (attr.size !== void 0) {
|
|
23651
|
-
vfs.truncate(path, attr.size, cred);
|
|
23652
|
-
opts.onWrite?.(path);
|
|
23653
|
-
}
|
|
23654
|
-
} catch (e) {
|
|
23655
|
-
translate(e);
|
|
23656
|
-
}
|
|
23657
|
-
},
|
|
23658
|
-
lookup(parent, name) {
|
|
23659
|
-
const path = join(backend.realPath(parent), name);
|
|
23660
|
-
let mode;
|
|
23661
|
-
try {
|
|
23662
|
-
mode = vfs.lstat(path).mode;
|
|
23663
|
-
} catch {
|
|
23664
|
-
throw new FS.ErrnoError(EM_ERRNO.ENOENT);
|
|
23665
|
-
}
|
|
23666
|
-
return backend.createNode(parent, name, mode, 0);
|
|
23667
|
-
},
|
|
23668
|
-
mknod(parent, name, mode, dev) {
|
|
23669
|
-
const path = join(backend.realPath(parent), name);
|
|
23670
|
-
try {
|
|
23671
|
-
if (FS.isDir(mode)) {
|
|
23672
|
-
vfs.mkdir(path, { cred, mode: mode & 4095 });
|
|
23673
|
-
} else if (FS.isFile(mode)) {
|
|
23674
|
-
vfs.writeFile(path, new Uint8Array(0), { cred, mode: mode & 4095 });
|
|
23675
|
-
} else {
|
|
23676
|
-
return throwErrno("ENOTSUP");
|
|
23677
|
-
}
|
|
23678
|
-
opts.onWrite?.(path);
|
|
23679
|
-
} catch (e) {
|
|
23680
|
-
translate(e);
|
|
23681
|
-
}
|
|
23682
|
-
return backend.createNode(parent, name, mode, dev);
|
|
23683
|
-
},
|
|
23684
|
-
rename(oldNode, newDir, newName) {
|
|
23685
|
-
const from = backend.realPath(oldNode);
|
|
23686
|
-
const to = join(backend.realPath(newDir), newName);
|
|
23687
|
-
try {
|
|
23688
|
-
vfs.rename(from, to, cred);
|
|
23689
|
-
opts.onWrite?.(to);
|
|
23690
|
-
} catch (e) {
|
|
23691
|
-
translate(e);
|
|
23692
|
-
}
|
|
23693
|
-
oldNode.name = newName;
|
|
23694
|
-
oldNode.parent = newDir;
|
|
23695
|
-
},
|
|
23696
|
-
unlink(parent, name) {
|
|
23697
|
-
const path = join(backend.realPath(parent), name);
|
|
23698
|
-
try {
|
|
23699
|
-
vfs.unlink(path, cred);
|
|
23700
|
-
opts.onWrite?.(path);
|
|
23701
|
-
} catch (e) {
|
|
23702
|
-
translate(e);
|
|
23703
|
-
}
|
|
23704
|
-
},
|
|
23705
|
-
rmdir(parent, name) {
|
|
23706
|
-
const path = join(backend.realPath(parent), name);
|
|
23707
|
-
try {
|
|
23708
|
-
vfs.rmdir(path, cred);
|
|
23709
|
-
opts.onWrite?.(path);
|
|
23710
|
-
} catch (e) {
|
|
23711
|
-
translate(e);
|
|
23712
|
-
}
|
|
23713
|
-
},
|
|
23714
|
-
readdir(node2) {
|
|
23715
|
-
const path = backend.realPath(node2);
|
|
23716
|
-
try {
|
|
23717
|
-
return [".", "..", ...vfs.readdir(path, cred)];
|
|
23718
|
-
} catch (e) {
|
|
23719
|
-
return translate(e);
|
|
23720
|
-
}
|
|
23721
|
-
},
|
|
23722
|
-
symlink(parent, newName, oldPath) {
|
|
23723
|
-
const path = join(backend.realPath(parent), newName);
|
|
23724
|
-
try {
|
|
23725
|
-
vfs.symlink(oldPath, path, cred);
|
|
23726
|
-
opts.onWrite?.(path);
|
|
23727
|
-
} catch (e) {
|
|
23728
|
-
translate(e);
|
|
23729
|
-
}
|
|
23730
|
-
},
|
|
23731
|
-
readlink(node2) {
|
|
23732
|
-
const path = backend.realPath(node2);
|
|
23733
|
-
try {
|
|
23734
|
-
return vfs.readlink(path, cred);
|
|
23735
|
-
} catch (e) {
|
|
23736
|
-
return translate(e);
|
|
23737
|
-
}
|
|
23738
|
-
}
|
|
23739
|
-
};
|
|
23740
|
-
const streamOps = {
|
|
23741
|
-
open(stream) {
|
|
23742
|
-
const path = backend.realPath(stream.node);
|
|
23743
|
-
if (FS.isDir(stream.node.mode)) return;
|
|
23744
|
-
try {
|
|
23745
|
-
stream.sbxBuffer = vfs.lexists(path) ? vfs.readFile(path, cred) : new Uint8Array(0);
|
|
23746
|
-
stream.sbxDirty = false;
|
|
23747
|
-
} catch (e) {
|
|
23748
|
-
translate(e);
|
|
23749
|
-
}
|
|
23750
|
-
},
|
|
23751
|
-
close(stream) {
|
|
23752
|
-
if (!stream.sbxDirty) return;
|
|
23753
|
-
const path = backend.realPath(stream.node);
|
|
23754
|
-
try {
|
|
23755
|
-
vfs.writeFile(path, stream.sbxBuffer, { cred });
|
|
23756
|
-
opts.onWrite?.(path);
|
|
23757
|
-
} catch (e) {
|
|
23758
|
-
translate(e);
|
|
23759
|
-
}
|
|
23760
|
-
stream.sbxDirty = false;
|
|
23761
|
-
},
|
|
23762
|
-
read(stream, buffer, offset, length, position) {
|
|
23763
|
-
const source = stream.sbxBuffer ?? new Uint8Array(0);
|
|
23764
|
-
if (position >= source.length) return 0;
|
|
23765
|
-
const size = Math.min(length, source.length - position);
|
|
23766
|
-
buffer.set(source.subarray(position, position + size), offset);
|
|
23767
|
-
return size;
|
|
23768
|
-
},
|
|
23769
|
-
write(stream, buffer, offset, length, position) {
|
|
23770
|
-
const current = stream.sbxBuffer ?? new Uint8Array(0);
|
|
23771
|
-
const end = Math.max(current.length, position + length);
|
|
23772
|
-
const next = new Uint8Array(end);
|
|
23773
|
-
next.set(current);
|
|
23774
|
-
next.set(buffer.subarray(offset, offset + length), position);
|
|
23775
|
-
stream.sbxBuffer = next;
|
|
23776
|
-
stream.sbxDirty = true;
|
|
23777
|
-
stream.node.size = end;
|
|
23778
|
-
const path = backend.realPath(stream.node);
|
|
23779
|
-
try {
|
|
23780
|
-
vfs.writeFile(path, next, { cred });
|
|
23781
|
-
opts.onWrite?.(path);
|
|
23782
|
-
stream.sbxDirty = false;
|
|
23783
|
-
} catch (e) {
|
|
23784
|
-
translate(e);
|
|
23785
|
-
}
|
|
23786
|
-
return length;
|
|
23787
|
-
},
|
|
23788
|
-
llseek(stream, offset, whence) {
|
|
23789
|
-
let position = offset;
|
|
23790
|
-
if (whence === 1) position += stream.position;
|
|
23791
|
-
else if (whence === 2) position += (stream.sbxBuffer ?? new Uint8Array(0)).length;
|
|
23792
|
-
if (position < 0) throw new FS.ErrnoError(EM_ERRNO.EINVAL);
|
|
23793
|
-
return position;
|
|
23794
|
-
}
|
|
23795
|
-
};
|
|
23796
|
-
if (opts.mountAt) {
|
|
23797
|
-
try {
|
|
23798
|
-
FS.mkdir(mountRoot);
|
|
23799
|
-
} catch {
|
|
23800
|
-
}
|
|
23801
|
-
FS.mount(backend, {}, mountRoot);
|
|
23802
|
-
return;
|
|
23803
|
-
}
|
|
23804
|
-
FS.root = null;
|
|
23805
|
-
FS.mount(backend, {}, "/");
|
|
23806
|
-
try {
|
|
23807
|
-
FS.chdir("/");
|
|
23808
|
-
} catch {
|
|
23809
|
-
}
|
|
23810
|
-
}
|
|
23811
|
-
|
|
23812
|
-
// src/python/cpython.ts
|
|
23813
|
-
init_binary();
|
|
23814
|
-
|
|
23815
|
-
// src/python/python-syscalls.ts
|
|
23816
|
-
var EMPTY = new Uint8Array(0);
|
|
23817
|
-
function createStdinHost(ctx) {
|
|
23818
|
-
let pending = EMPTY;
|
|
23819
|
-
let eof = false;
|
|
23820
|
-
const take = (size) => {
|
|
23821
|
-
const n = Math.min(size, pending.length);
|
|
23822
|
-
const out = pending.subarray(0, n);
|
|
23823
|
-
pending = pending.subarray(n);
|
|
23824
|
-
return out;
|
|
23825
|
-
};
|
|
23826
|
-
return {
|
|
23827
|
-
/**
|
|
23828
|
-
* Drain a non-interactive stdin up front.
|
|
23829
|
-
*
|
|
23830
|
-
* A pipe ends, so reading it eagerly costs nothing and leaves every read
|
|
23831
|
-
* answerable without suspending — which is what keeps piped input working
|
|
23832
|
-
* on hosts with no stack-switching. A terminal never ends, so priming one
|
|
23833
|
-
* would hang before the program had printed its prompt.
|
|
23834
|
-
*/
|
|
23835
|
-
async prime() {
|
|
23836
|
-
if (ctx.stdin.isTTY || ctx.stdin.interactive) return;
|
|
23837
|
-
try {
|
|
23838
|
-
pending = await ctx.stdin.readAll();
|
|
23839
|
-
} catch {
|
|
23840
|
-
pending = EMPTY;
|
|
23841
|
-
}
|
|
23842
|
-
eof = true;
|
|
23843
|
-
},
|
|
23844
|
-
buffered(size) {
|
|
23845
|
-
if (pending.length > 0) return take(size);
|
|
23846
|
-
return eof ? EMPTY : void 0;
|
|
23847
|
-
},
|
|
23848
|
-
async read(size) {
|
|
23849
|
-
if (pending.length > 0) return take(size);
|
|
23850
|
-
if (eof) return EMPTY;
|
|
23851
|
-
const chunk = await ctx.stdin.read(size);
|
|
23852
|
-
if (chunk === null || chunk.length === 0) {
|
|
23853
|
-
eof = true;
|
|
23854
|
-
return EMPTY;
|
|
23855
|
-
}
|
|
23856
|
-
pending = chunk;
|
|
23857
|
-
return take(size);
|
|
23858
|
-
},
|
|
23859
|
-
isatty() {
|
|
23860
|
-
return Boolean(ctx.stdin.isTTY);
|
|
23861
|
-
}
|
|
23862
|
-
};
|
|
23863
|
-
}
|
|
23864
|
-
var BRIDGE_PY = `
|
|
23865
|
-
import builtins, io, sys
|
|
23866
|
-
import _sbx_host as _host
|
|
23867
|
-
from pyodide.ffi import can_run_sync, run_sync
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
def _to_bytes(value):
|
|
23871
|
-
if value is None:
|
|
23872
|
-
return b""
|
|
23873
|
-
to_bytes = getattr(value, "to_bytes", None)
|
|
23874
|
-
if to_bytes is not None:
|
|
23875
|
-
return to_bytes()
|
|
23876
|
-
return bytes(value.to_py())
|
|
23877
|
-
|
|
23878
|
-
|
|
23879
|
-
class _SbxStdin(io.RawIOBase):
|
|
23880
|
-
"""The container's standard input, as a blocking raw stream."""
|
|
23881
|
-
|
|
23882
|
-
def readable(self):
|
|
23883
|
-
return True
|
|
23884
|
-
|
|
23885
|
-
def fileno(self):
|
|
23886
|
-
return 0
|
|
23887
|
-
|
|
23888
|
-
def isatty(self):
|
|
23889
|
-
return bool(_host.isatty())
|
|
23890
|
-
|
|
23891
|
-
def readinto(self, target):
|
|
23892
|
-
want = len(target)
|
|
23893
|
-
if want == 0:
|
|
23894
|
-
return 0
|
|
23895
|
-
ready = _host.buffered(want)
|
|
23896
|
-
if ready is None:
|
|
23897
|
-
if not can_run_sync():
|
|
23898
|
-
raise OSError(
|
|
23899
|
-
"this host cannot wait for interactive input: WebAssembly "
|
|
23900
|
-
"stack switching (JSPI) is unavailable. Pipe the input in, "
|
|
23901
|
-
"or use a browser or Node build that supports it."
|
|
23902
|
-
)
|
|
23903
|
-
ready = run_sync(_host.read(want))
|
|
23904
|
-
data = _to_bytes(ready)
|
|
23905
|
-
target[: len(data)] = data
|
|
23906
|
-
return len(data)
|
|
23907
|
-
|
|
23908
|
-
|
|
23909
|
-
def _install():
|
|
23910
|
-
stdin = io.TextIOWrapper(
|
|
23911
|
-
io.BufferedReader(_SbxStdin()), encoding="utf-8", errors="replace", line_buffering=True
|
|
23912
|
-
)
|
|
23913
|
-
sys.stdin = stdin
|
|
23914
|
-
sys.__stdin__ = stdin
|
|
23915
|
-
|
|
23916
|
-
def input(prompt=""):
|
|
23917
|
-
# CPython writes the prompt to stdout and strips exactly one trailing
|
|
23918
|
-
# newline; anything else changes what a program reads back.
|
|
23919
|
-
if prompt != "":
|
|
23920
|
-
sys.stdout.write(str(prompt))
|
|
23921
|
-
sys.stdout.flush()
|
|
23922
|
-
line = sys.stdin.readline()
|
|
23923
|
-
if not line:
|
|
23924
|
-
raise EOFError("EOF when reading a line")
|
|
23925
|
-
if line.endswith("\\n"):
|
|
23926
|
-
line = line[:-1]
|
|
23927
|
-
if line.endswith("\\r"):
|
|
23928
|
-
line = line[:-1]
|
|
23929
|
-
return line
|
|
23930
|
-
|
|
23931
|
-
builtins.input = input
|
|
23932
|
-
|
|
23933
|
-
|
|
23934
|
-
_install()
|
|
23935
|
-
`;
|
|
23936
|
-
var PROCESS_PY = `
|
|
23937
|
-
import io, os, shlex, subprocess, sys
|
|
23938
|
-
import _sbx_host as _host
|
|
23939
|
-
from pyodide.ffi import can_run_sync, run_sync
|
|
23940
|
-
|
|
23941
|
-
_PIPE = subprocess.PIPE
|
|
23942
|
-
_DEVNULL = subprocess.DEVNULL
|
|
23943
|
-
_STDOUT = subprocess.STDOUT
|
|
23944
|
-
|
|
23945
|
-
|
|
23946
|
-
def _argv_for(args, shell):
|
|
23947
|
-
if shell:
|
|
23948
|
-
line = args if isinstance(args, str) else " ".join(str(a) for a in args)
|
|
23949
|
-
return ["sh", "-c", line]
|
|
23950
|
-
if isinstance(args, (str, bytes)):
|
|
23951
|
-
return shlex.split(args if isinstance(args, str) else args.decode())
|
|
23952
|
-
return [str(a) for a in args]
|
|
23953
|
-
|
|
23954
|
-
|
|
23955
|
-
def _require_blocking(what):
|
|
23956
|
-
if not can_run_sync():
|
|
23957
|
-
raise OSError(
|
|
23958
|
-
"cannot " + what + " on this host: WebAssembly stack switching "
|
|
23959
|
-
"(JSPI) is unavailable."
|
|
23960
|
-
)
|
|
23961
|
-
|
|
23962
|
-
|
|
23963
|
-
def _to_bytes_result(value):
|
|
23964
|
-
if value is None:
|
|
23965
|
-
return b""
|
|
23966
|
-
to_bytes = getattr(value, "to_bytes", None)
|
|
23967
|
-
return to_bytes() if to_bytes is not None else bytes(value.to_py())
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
class _ChildReader(io.RawIOBase):
|
|
23971
|
-
"""One of a running child's output streams.
|
|
23972
|
-
|
|
23973
|
-
Reads suspend only for as long as the child takes to produce the next
|
|
23974
|
-
bytes, so \`for line in p.stdout\` follows a child that is still running
|
|
23975
|
-
instead of waiting for it to exit.
|
|
23976
|
-
"""
|
|
23977
|
-
|
|
23978
|
-
def __init__(self, child_id, which):
|
|
23979
|
-
self._id = child_id
|
|
23980
|
-
self._which = which
|
|
23981
|
-
|
|
23982
|
-
def readable(self):
|
|
23983
|
-
return True
|
|
23984
|
-
|
|
23985
|
-
def readinto(self, target):
|
|
23986
|
-
want = len(target)
|
|
23987
|
-
if want == 0:
|
|
23988
|
-
return 0
|
|
23989
|
-
_require_blocking("read from a child process")
|
|
23990
|
-
data = _to_bytes_result(run_sync(_host.proc_read(self._id, self._which, want)))
|
|
23991
|
-
target[: len(data)] = data
|
|
23992
|
-
return len(data)
|
|
23993
|
-
|
|
23994
|
-
|
|
23995
|
-
class _ChildWriter(io.RawIOBase):
|
|
23996
|
-
"""A running child's standard input."""
|
|
23997
|
-
|
|
23998
|
-
def __init__(self, child_id):
|
|
23999
|
-
self._id = child_id
|
|
24000
|
-
|
|
24001
|
-
def writable(self):
|
|
24002
|
-
return True
|
|
24003
|
-
|
|
24004
|
-
def write(self, data):
|
|
24005
|
-
if isinstance(data, str):
|
|
24006
|
-
text = data
|
|
24007
|
-
else:
|
|
24008
|
-
text = bytes(data).decode("utf-8", "replace")
|
|
24009
|
-
_host.proc_write(self._id, text)
|
|
24010
|
-
return len(data)
|
|
24011
|
-
|
|
24012
|
-
def close(self):
|
|
24013
|
-
if not self.closed:
|
|
24014
|
-
_host.proc_close_stdin(self._id)
|
|
24015
|
-
super().close()
|
|
24016
|
-
|
|
24017
|
-
|
|
24018
|
-
class SbxPopen:
|
|
24019
|
-
"""A child process, run through the container's kernel.
|
|
24020
|
-
|
|
24021
|
-
The child is a real, live process: it starts when \`Popen\` is constructed
|
|
24022
|
-
and runs on the host's event loop while Python carries on. The parent only
|
|
24023
|
-
suspends when it actually reads, waits, or communicates \u2014 which is what
|
|
24024
|
-
makes streaming from a long-running child work rather than deadlocking on
|
|
24025
|
-
a single interpreter stack.
|
|
24026
|
-
"""
|
|
24027
|
-
|
|
24028
|
-
def __init__(self, args, bufsize=-1, executable=None, stdin=None, stdout=None,
|
|
24029
|
-
stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None,
|
|
24030
|
-
env=None, universal_newlines=None, startupinfo=None, creationflags=0,
|
|
24031
|
-
restore_signals=True, start_new_session=False, pass_fds=(), *,
|
|
24032
|
-
text=None, encoding=None, errors=None, **kwargs):
|
|
24033
|
-
self.args = args
|
|
24034
|
-
self.returncode = None
|
|
24035
|
-
self._text = bool(text or encoding or errors or universal_newlines)
|
|
24036
|
-
self._encoding = encoding or "utf-8"
|
|
24037
|
-
self._errors = errors or "replace"
|
|
24038
|
-
self._merge_err = stderr == _STDOUT
|
|
24039
|
-
self._closed = False
|
|
24040
|
-
|
|
24041
|
-
argv = _argv_for(args, shell)
|
|
24042
|
-
env_map = None if env is None else {str(k): str(v) for k, v in dict(env).items()}
|
|
24043
|
-
self._id = _host.proc_start(
|
|
24044
|
-
argv, None if cwd is None else str(cwd), env_map, self._merge_err
|
|
24045
|
-
)
|
|
24046
|
-
self.pid = int(_host.proc_pid(self._id))
|
|
24047
|
-
|
|
24048
|
-
self.stdin = self._wrap_writer() if stdin == _PIPE else None
|
|
24049
|
-
self.stdout = self._wrap_reader(1) if stdout == _PIPE else None
|
|
24050
|
-
# When merged there is only one stream, and it is stream 1.
|
|
24051
|
-
self.stderr = self._wrap_reader(2) if stderr == _PIPE and not self._merge_err else None
|
|
24052
|
-
|
|
24053
|
-
# Output nobody captured belongs on the parent's own streams, or a
|
|
24054
|
-
# program's diagnostics would vanish rather than being seen.
|
|
24055
|
-
self._drain_out = stdout is None
|
|
24056
|
-
self._drain_err = stderr is None and not self._merge_err
|
|
24057
|
-
|
|
24058
|
-
def _wrap_reader(self, which):
|
|
24059
|
-
raw = io.BufferedReader(_ChildReader(self._id, which))
|
|
24060
|
-
if self._text:
|
|
24061
|
-
return io.TextIOWrapper(raw, encoding=self._encoding, errors=self._errors)
|
|
24062
|
-
return raw
|
|
24063
|
-
|
|
24064
|
-
def _wrap_writer(self):
|
|
24065
|
-
raw = io.BufferedWriter(_ChildWriter(self._id))
|
|
24066
|
-
if self._text:
|
|
24067
|
-
return io.TextIOWrapper(raw, encoding=self._encoding, errors=self._errors)
|
|
24068
|
-
return raw
|
|
24069
|
-
|
|
24070
|
-
def _read_all(self, which):
|
|
24071
|
-
_require_blocking("read from a child process")
|
|
24072
|
-
parts = []
|
|
24073
|
-
while True:
|
|
24074
|
-
chunk = _to_bytes_result(run_sync(_host.proc_read(self._id, which, 65536)))
|
|
24075
|
-
if not chunk:
|
|
24076
|
-
break
|
|
24077
|
-
parts.append(chunk)
|
|
24078
|
-
return b"".join(parts)
|
|
24079
|
-
|
|
24080
|
-
def _decode(self, raw):
|
|
24081
|
-
return raw.decode(self._encoding, self._errors) if self._text else raw
|
|
24082
|
-
|
|
24083
|
-
def communicate(self, input=None, timeout=None):
|
|
24084
|
-
if input is not None and self.stdin is not None:
|
|
24085
|
-
if isinstance(input, bytes):
|
|
24086
|
-
input = input.decode(self._encoding, "replace")
|
|
24087
|
-
self.stdin.write(input)
|
|
24088
|
-
if self.stdin is not None:
|
|
24089
|
-
try:
|
|
24090
|
-
self.stdin.close()
|
|
24091
|
-
except Exception:
|
|
24092
|
-
pass
|
|
24093
|
-
|
|
24094
|
-
out = self.stdout.read() if self.stdout is not None else None
|
|
24095
|
-
err = self.stderr.read() if self.stderr is not None else None
|
|
24096
|
-
self.wait()
|
|
24097
|
-
self._flush_uncaptured()
|
|
24098
|
-
return out, err
|
|
24099
|
-
|
|
24100
|
-
def _flush_uncaptured(self):
|
|
24101
|
-
if self._drain_out:
|
|
24102
|
-
text = self._decode_raw(self._read_all(1))
|
|
24103
|
-
if text:
|
|
24104
|
-
sys.stdout.write(text)
|
|
24105
|
-
if self._drain_err:
|
|
24106
|
-
text = self._decode_raw(self._read_all(2))
|
|
24107
|
-
if text:
|
|
24108
|
-
sys.stderr.write(text)
|
|
24109
|
-
self._drain_out = False
|
|
24110
|
-
self._drain_err = False
|
|
24111
|
-
|
|
24112
|
-
def _decode_raw(self, raw):
|
|
24113
|
-
return raw.decode(self._encoding, "replace")
|
|
24114
|
-
|
|
24115
|
-
def poll(self):
|
|
24116
|
-
code = _host.proc_poll(self._id)
|
|
24117
|
-
if code is not None:
|
|
24118
|
-
self.returncode = int(code)
|
|
24119
|
-
return self.returncode
|
|
24120
|
-
|
|
24121
|
-
def wait(self, timeout=None):
|
|
24122
|
-
_require_blocking("wait for a child process")
|
|
24123
|
-
self.returncode = int(run_sync(_host.proc_wait(self._id)))
|
|
24124
|
-
return self.returncode
|
|
24125
|
-
|
|
24126
|
-
def kill(self):
|
|
24127
|
-
self.send_signal("SIGKILL")
|
|
24128
|
-
|
|
24129
|
-
def terminate(self):
|
|
24130
|
-
self.send_signal("SIGTERM")
|
|
24131
|
-
|
|
24132
|
-
def send_signal(self, sig):
|
|
24133
|
-
_host.proc_kill(self._id, sig if isinstance(sig, str) else "SIGTERM")
|
|
24134
|
-
|
|
24135
|
-
def __del__(self):
|
|
24136
|
-
try:
|
|
24137
|
-
_host.proc_release(self._id)
|
|
24138
|
-
except Exception:
|
|
24139
|
-
pass
|
|
24140
|
-
|
|
24141
|
-
def __enter__(self):
|
|
24142
|
-
return self
|
|
24143
|
-
|
|
24144
|
-
def __exit__(self, *exc):
|
|
24145
|
-
if self.stdin is not None:
|
|
24146
|
-
try:
|
|
24147
|
-
self.stdin.close()
|
|
24148
|
-
except Exception:
|
|
24149
|
-
pass
|
|
24150
|
-
self.wait()
|
|
24151
|
-
self._flush_uncaptured()
|
|
24152
|
-
for stream in (self.stdout, self.stderr):
|
|
24153
|
-
if stream is not None:
|
|
24154
|
-
try:
|
|
24155
|
-
stream.close()
|
|
24156
|
-
except Exception:
|
|
24157
|
-
pass
|
|
24158
|
-
# The host holds the process and its pipes until it is told the child
|
|
24159
|
-
# is finished with; a loop spawning children would otherwise keep every
|
|
24160
|
-
# one of them alive. Safe here only because the child has been waited
|
|
24161
|
-
# for and everything anyone wanted has been read.
|
|
24162
|
-
_host.proc_release(self._id)
|
|
24163
|
-
return False
|
|
24164
|
-
|
|
24165
|
-
|
|
24166
|
-
def _system(command):
|
|
24167
|
-
with SbxPopen(command, shell=True) as child:
|
|
24168
|
-
pass
|
|
24169
|
-
# os.system returns a wait status, not an exit code.
|
|
24170
|
-
return (child.returncode or 0) << 8
|
|
24171
|
-
|
|
24172
|
-
|
|
24173
|
-
def _popen(command, mode="r", buffering=-1):
|
|
24174
|
-
if "w" in mode:
|
|
24175
|
-
raise OSError("os.popen with mode 'w' is not supported in this container")
|
|
24176
|
-
child = SbxPopen(command, shell=True, stdout=_PIPE, text=True)
|
|
24177
|
-
stream = child.stdout
|
|
24178
|
-
inner = stream.close
|
|
24179
|
-
|
|
24180
|
-
def close():
|
|
24181
|
-
inner()
|
|
24182
|
-
code = child.wait()
|
|
24183
|
-
return None if code == 0 else code << 8
|
|
24184
|
-
|
|
24185
|
-
stream.close = close
|
|
24186
|
-
return stream
|
|
24187
|
-
|
|
24188
|
-
|
|
24189
|
-
def _install_processes():
|
|
24190
|
-
subprocess.Popen = SbxPopen
|
|
24191
|
-
os.system = _system
|
|
24192
|
-
os.popen = _popen
|
|
24193
|
-
|
|
24194
|
-
|
|
24195
|
-
_install_processes()
|
|
24196
|
-
`;
|
|
24197
|
-
var ASYNCIO_PY = `
|
|
24198
|
-
import asyncio
|
|
24199
|
-
from pyodide.ffi import can_run_sync, run_sync
|
|
24200
|
-
|
|
24201
|
-
_orig_run = asyncio.run
|
|
24202
|
-
|
|
24203
|
-
|
|
24204
|
-
def _run(main, *, debug=None, loop_factory=None):
|
|
24205
|
-
if not asyncio.iscoroutine(main) and not asyncio.isfuture(main):
|
|
24206
|
-
raise ValueError("a coroutine was expected, got {!r}".format(main))
|
|
24207
|
-
if not can_run_sync():
|
|
24208
|
-
# No stack switching: the original at least raises a comprehensible
|
|
24209
|
-
# error rather than this shim inventing a new one.
|
|
24210
|
-
return _orig_run(main, debug=debug, loop_factory=loop_factory)
|
|
24211
|
-
return run_sync(main)
|
|
24212
|
-
|
|
24213
|
-
|
|
24214
|
-
def _run_until_complete(self, future):
|
|
24215
|
-
if not can_run_sync():
|
|
24216
|
-
raise RuntimeError(
|
|
24217
|
-
"cannot wait for a coroutine on this host: WebAssembly stack "
|
|
24218
|
-
"switching (JSPI) is unavailable."
|
|
24219
|
-
)
|
|
24220
|
-
return run_sync(future)
|
|
24221
|
-
|
|
24222
|
-
|
|
24223
|
-
async def _create_server(self, *args, **kwargs):
|
|
24224
|
-
# Pyodide's loop raises a bare NotImplementedError from deep inside
|
|
24225
|
-
# asyncio, which tells a reader nothing about why their server will not
|
|
24226
|
-
# start. Naming the limit is the least this can do until the container can
|
|
24227
|
-
# route connections to Python.
|
|
24228
|
-
raise NotImplementedError(
|
|
24229
|
-
"this container cannot yet accept network connections from Python: "
|
|
24230
|
-
"asyncio's create_server is not implemented on Pyodide's event loop, "
|
|
24231
|
-
"so an ASGI/WSGI server such as uvicorn will install and import but "
|
|
24232
|
-
"not bind a port. Outbound requests do work."
|
|
24233
|
-
)
|
|
24234
|
-
|
|
24235
|
-
|
|
24236
|
-
def _install_asyncio():
|
|
24237
|
-
asyncio.run = _run
|
|
24238
|
-
asyncio.runners.run = _run
|
|
24239
|
-
loop_type = type(asyncio.get_event_loop())
|
|
24240
|
-
loop_type.run_until_complete = _run_until_complete
|
|
24241
|
-
loop_type.create_server = _create_server
|
|
24242
|
-
|
|
24243
|
-
|
|
24244
|
-
_install_asyncio()
|
|
24245
|
-
`;
|
|
24246
|
-
var NETWORK_PY = `
|
|
24247
|
-
import builtins, email.message, io, sys, urllib.error, urllib.request, urllib.response
|
|
24248
|
-
import _sbx_host as _host
|
|
24249
|
-
from pyodide.ffi import can_run_sync, run_sync
|
|
24250
|
-
|
|
24251
|
-
|
|
24252
|
-
def _fetch(request):
|
|
24253
|
-
if not can_run_sync():
|
|
24254
|
-
raise urllib.error.URLError(
|
|
24255
|
-
"cannot make a request on this host: WebAssembly stack switching "
|
|
24256
|
-
"(JSPI) is unavailable."
|
|
24257
|
-
)
|
|
24258
|
-
body = request.data
|
|
24259
|
-
if isinstance(body, str):
|
|
24260
|
-
body = body.encode()
|
|
24261
|
-
try:
|
|
24262
|
-
result = run_sync(
|
|
24263
|
-
_host.http(
|
|
24264
|
-
request.full_url,
|
|
24265
|
-
request.get_method(),
|
|
24266
|
-
[[k, v] for k, v in request.header_items()],
|
|
24267
|
-
body,
|
|
24268
|
-
)
|
|
24269
|
-
)
|
|
24270
|
-
except Exception as error:
|
|
24271
|
-
raise urllib.error.URLError(str(error)) from None
|
|
24272
|
-
|
|
24273
|
-
headers = email.message.Message()
|
|
24274
|
-
for pair in result.headers.to_py():
|
|
24275
|
-
headers[str(pair[0])] = str(pair[1])
|
|
24276
|
-
payload = result.body.to_bytes()
|
|
24277
|
-
response = urllib.response.addinfourl(
|
|
24278
|
-
io.BytesIO(payload), headers, str(result.url), int(result.status)
|
|
24279
|
-
)
|
|
24280
|
-
response.msg = str(result.statusText)
|
|
24281
|
-
return response
|
|
24282
|
-
|
|
24283
|
-
|
|
24284
|
-
class _SbxHTTPHandler(urllib.request.HTTPHandler):
|
|
24285
|
-
"""Both schemes, from one handler.
|
|
24286
|
-
|
|
24287
|
-
Pyodide builds urllib without \`ssl\`, so \`HTTPSHandler\` does not exist to
|
|
24288
|
-
subclass \u2014 and https is the scheme most callers actually want. Serving
|
|
24289
|
-
both from a subclass of the one handler that does exist keeps
|
|
24290
|
-
\`build_opener\` treating this as a replacement rather than stacking it
|
|
24291
|
-
alongside the original.
|
|
24292
|
-
"""
|
|
24293
|
-
|
|
24294
|
-
def http_open(self, request):
|
|
24295
|
-
return _fetch(request)
|
|
24296
|
-
|
|
24297
|
-
def https_open(self, request):
|
|
24298
|
-
return _fetch(request)
|
|
24299
|
-
|
|
24300
|
-
https_request = urllib.request.HTTPHandler.do_request_
|
|
24301
|
-
|
|
24302
|
-
|
|
24303
|
-
def _raw_fetch(url, method, headers, body):
|
|
24304
|
-
"""One request, through the container's network path."""
|
|
24305
|
-
if not can_run_sync():
|
|
24306
|
-
raise urllib.error.URLError(
|
|
24307
|
-
"cannot make a request on this host: WebAssembly stack switching "
|
|
24308
|
-
"(JSPI) is unavailable."
|
|
24309
|
-
)
|
|
24310
|
-
if isinstance(body, str):
|
|
24311
|
-
body = body.encode()
|
|
24312
|
-
result = run_sync(
|
|
24313
|
-
_host.http(url, method, [[str(k), str(v)] for k, v in dict(headers).items()], body)
|
|
24314
|
-
)
|
|
24315
|
-
return {
|
|
24316
|
-
"status": int(result.status),
|
|
24317
|
-
"reason": str(result.statusText),
|
|
24318
|
-
"headers": [(str(p[0]), str(p[1])) for p in result.headers.to_py()],
|
|
24319
|
-
"body": result.body.to_bytes(),
|
|
24320
|
-
"url": str(result.url),
|
|
24321
|
-
}
|
|
24322
|
-
|
|
24323
|
-
|
|
24324
|
-
# \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
|
|
24325
|
-
#
|
|
24326
|
-
# A library that brings its own transport does not go through urllib, and in
|
|
24327
|
-
# Pyodide several of them reach JavaScript's fetch directly \u2014 which leaves the
|
|
24328
|
-
# container's network policy behind entirely. They cannot all be patched at
|
|
24329
|
-
# boot either, because pip installs them later.
|
|
24330
|
-
#
|
|
24331
|
-
# So: adapters are registered by module name and applied the moment that module
|
|
24332
|
-
# is first imported, whenever that happens. Supporting another stack is a small
|
|
24333
|
-
# function registered here, not a change to the machinery.
|
|
24334
|
-
|
|
24335
|
-
_ADAPTERS = {}
|
|
24336
|
-
|
|
24337
|
-
|
|
24338
|
-
def _register_adapter(name):
|
|
24339
|
-
def decorate(fn):
|
|
24340
|
-
_ADAPTERS[name] = fn
|
|
24341
|
-
return fn
|
|
24342
|
-
|
|
24343
|
-
return decorate
|
|
24344
|
-
|
|
24345
|
-
|
|
24346
|
-
def _apply_adapter(name):
|
|
24347
|
-
fn = _ADAPTERS.pop(name, None)
|
|
24348
|
-
if fn is None:
|
|
24349
|
-
return
|
|
24350
|
-
try:
|
|
24351
|
-
fn()
|
|
24352
|
-
except Exception:
|
|
24353
|
-
# A stack we cannot adapt must not stop the import that triggered it.
|
|
24354
|
-
pass
|
|
24355
|
-
|
|
24356
|
-
|
|
24357
|
-
def _install_import_hook():
|
|
24358
|
-
real_import = builtins.__import__
|
|
24359
|
-
|
|
24360
|
-
def hooked(name, globals=None, locals=None, fromlist=(), level=0):
|
|
24361
|
-
module = real_import(name, globals, locals, fromlist, level)
|
|
24362
|
-
root = name.split(".")[0] if level == 0 else None
|
|
24363
|
-
if root in _ADAPTERS:
|
|
24364
|
-
_apply_adapter(root)
|
|
24365
|
-
return module
|
|
24366
|
-
|
|
24367
|
-
builtins.__import__ = hooked
|
|
24368
|
-
|
|
24369
|
-
|
|
24370
|
-
@_register_adapter("requests")
|
|
24371
|
-
def _adapt_requests():
|
|
24372
|
-
"""Replace the transport, not the API.
|
|
24373
|
-
|
|
24374
|
-
\`HTTPAdapter.send\` is the single seam every requests call passes through,
|
|
24375
|
-
below sessions, redirects, cookies and retries and above the urllib3
|
|
24376
|
-
transport that would otherwise reach the network on its own. Replacing it
|
|
24377
|
-
leaves everything callers actually use intact.
|
|
24378
|
-
"""
|
|
24379
|
-
import requests
|
|
24380
|
-
from requests.adapters import HTTPAdapter
|
|
24381
|
-
from requests.structures import CaseInsensitiveDict
|
|
24382
|
-
|
|
24383
|
-
native_send = HTTPAdapter.send
|
|
24384
|
-
|
|
24385
|
-
def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
|
|
24386
|
-
if not can_run_sync():
|
|
24387
|
-
# Without stack switching this transport cannot run at all. Falling
|
|
24388
|
-
# back to the library's own is better than breaking requests on
|
|
24389
|
-
# such a host \u2014 but the container's network policy is not the part
|
|
24390
|
-
# that degrades, so a forbidden host is still refused.
|
|
24391
|
-
if not _host.policy_allows(request.url):
|
|
24392
|
-
raise requests.exceptions.ConnectionError(
|
|
24393
|
-
"outbound network access is disabled for this container "
|
|
24394
|
-
"(enable with network: { allowOutbound: true })"
|
|
24395
|
-
)
|
|
24396
|
-
return native_send(self, request, stream, timeout, verify, cert, proxies)
|
|
24397
|
-
try:
|
|
24398
|
-
result = _raw_fetch(request.url, request.method, request.headers, request.body)
|
|
24399
|
-
except Exception as error:
|
|
24400
|
-
raise requests.exceptions.ConnectionError(str(error)) from None
|
|
24401
|
-
response = requests.Response()
|
|
24402
|
-
response.status_code = result["status"]
|
|
24403
|
-
response.reason = result["reason"]
|
|
24404
|
-
response.headers = CaseInsensitiveDict(result["headers"])
|
|
24405
|
-
response.url = result["url"]
|
|
24406
|
-
response.request = request
|
|
24407
|
-
response.raw = io.BytesIO(result["body"])
|
|
24408
|
-
response.encoding = requests.utils.get_encoding_from_headers(response.headers)
|
|
24409
|
-
return response
|
|
24410
|
-
|
|
24411
|
-
HTTPAdapter.send = send
|
|
24412
|
-
|
|
24413
|
-
|
|
24414
|
-
def _install_network():
|
|
24415
|
-
urllib.request.HTTPHandler = _SbxHTTPHandler
|
|
24416
|
-
urllib.request.HTTPSHandler = _SbxHTTPHandler
|
|
24417
|
-
urllib.request.install_opener(urllib.request.build_opener(_SbxHTTPHandler))
|
|
24418
|
-
_install_import_hook()
|
|
24419
|
-
for already_imported in [n for n in _ADAPTERS if n in sys.modules]:
|
|
24420
|
-
_apply_adapter(already_imported)
|
|
24421
|
-
|
|
24422
|
-
|
|
24423
|
-
_install_network()
|
|
24424
|
-
`;
|
|
24425
|
-
async function httpForPython(slot, url, method, headers, body) {
|
|
24426
|
-
const current = slot.current;
|
|
24427
|
-
if (!current) throw new Error("no program is bound");
|
|
24428
|
-
const result = await performRequest(current.ctx, new URL(url), {
|
|
24429
|
-
method,
|
|
24430
|
-
headers: Object.fromEntries(headers),
|
|
24431
|
-
...body ? { body } : {}
|
|
24432
|
-
});
|
|
24433
|
-
return {
|
|
24434
|
-
status: result.status,
|
|
24435
|
-
statusText: result.statusText,
|
|
24436
|
-
headers: Object.entries(result.headers),
|
|
24437
|
-
body: result.body,
|
|
24438
|
-
url: result.url
|
|
24439
|
-
};
|
|
24440
|
-
}
|
|
24441
|
-
async function resuming(py, slot, work) {
|
|
24442
|
-
const parent = slot.current;
|
|
24443
|
-
try {
|
|
24444
|
-
return await work();
|
|
24445
|
-
} finally {
|
|
24446
|
-
if (parent && slot.current !== parent) bindProgram(py, parent);
|
|
24447
|
-
}
|
|
24448
|
-
}
|
|
24449
|
-
function installSyscallBridge(py) {
|
|
24450
|
-
const slot = py.__sbxSlot ??= { current: null };
|
|
24451
|
-
const children = /* @__PURE__ */ new Map();
|
|
24452
|
-
let lastChildId = 0;
|
|
24453
|
-
py.registerJsModule("_sbx_host", {
|
|
24454
|
-
/* Not `?.` and `??`: `buffered` returns undefined to mean "you will have
|
|
24455
|
-
* to wait", and a nullish default would quietly turn that into end-of-file —
|
|
24456
|
-
* which is exactly the silent EOF this bridge exists to remove. */
|
|
24457
|
-
buffered: (size) => slot.current ? slot.current.stdin.buffered(size) : EMPTY,
|
|
24458
|
-
read: (size) => slot.current ? slot.current.stdin.read(size) : Promise.resolve(EMPTY),
|
|
24459
|
-
isatty: () => slot.current?.stdin.isatty() ?? false,
|
|
24460
|
-
proc_start: (argv, cwd, env2, merge) => {
|
|
24461
|
-
const current = slot.current;
|
|
24462
|
-
if (!current) throw new Error("no program is bound");
|
|
24463
|
-
const { ctx } = current;
|
|
24464
|
-
const stdin = new Pipe();
|
|
24465
|
-
stdin.interactive = true;
|
|
24466
|
-
const stdout = new Pipe();
|
|
24467
|
-
const stderr = merge ? stdout : new Pipe();
|
|
24468
|
-
const proc = ctx.kernel.spawn(argv, {
|
|
24469
|
-
cwd: cwd ?? ctx.cwd,
|
|
24470
|
-
env: env2 ?? ctx.env,
|
|
24471
|
-
cred: ctx.cred,
|
|
24472
|
-
stdin,
|
|
24473
|
-
stdout,
|
|
24474
|
-
stderr
|
|
24475
|
-
});
|
|
24476
|
-
void proc.wait().then(() => {
|
|
24477
|
-
stdout.end();
|
|
24478
|
-
if (stderr !== stdout) stderr.end();
|
|
24479
|
-
});
|
|
24480
|
-
const id = ++lastChildId;
|
|
24481
|
-
children.set(id, { proc, stdin, stdout, stderr });
|
|
24482
|
-
return id;
|
|
24483
|
-
},
|
|
24484
|
-
proc_read: (id, which2, size) => {
|
|
24485
|
-
const child = children.get(id);
|
|
24486
|
-
if (!child) return Promise.resolve(EMPTY);
|
|
24487
|
-
const pipe = which2 === 2 ? child.stderr : child.stdout;
|
|
24488
|
-
return resuming(py, slot, async () => await pipe.read(size) ?? EMPTY);
|
|
24489
|
-
},
|
|
24490
|
-
proc_write: (id, text2) => {
|
|
24491
|
-
children.get(id)?.stdin.write(text2);
|
|
24492
|
-
},
|
|
24493
|
-
proc_close_stdin: (id) => {
|
|
24494
|
-
children.get(id)?.stdin.end();
|
|
24495
|
-
},
|
|
24496
|
-
/* Non-blocking on purpose: `poll()` must be able to say "still running". */
|
|
24497
|
-
proc_poll: (id) => children.get(id)?.proc.exitCode ?? void 0,
|
|
24498
|
-
proc_pid: (id) => children.get(id)?.proc.pid ?? 0,
|
|
24499
|
-
proc_wait: (id) => {
|
|
24500
|
-
const child = children.get(id);
|
|
24501
|
-
if (!child) return Promise.resolve(0);
|
|
24502
|
-
return resuming(py, slot, () => child.proc.wait());
|
|
24503
|
-
},
|
|
24504
|
-
proc_kill: (id, signal) => {
|
|
24505
|
-
const child = children.get(id);
|
|
24506
|
-
if (!child) return;
|
|
24507
|
-
const current = slot.current;
|
|
24508
|
-
current?.ctx.kernel.procs.signal(child.proc.pid, signal);
|
|
24509
|
-
},
|
|
24510
|
-
proc_release: (id) => {
|
|
24511
|
-
children.delete(id);
|
|
24512
|
-
},
|
|
24513
|
-
http: (url, method, headers, body) => resuming(py, slot, () => httpForPython(slot, url, method, headers, body)),
|
|
24514
|
-
/* So Python can enforce the container's policy even on a host where it
|
|
24515
|
-
* cannot route the request itself. */
|
|
24516
|
-
policy_allows: (url) => {
|
|
24517
|
-
const current = slot.current;
|
|
24518
|
-
if (!current) return false;
|
|
24519
|
-
const net = current.ctx.kernel.net;
|
|
24520
|
-
try {
|
|
24521
|
-
return net.isLocal(new URL(url).hostname) || net.outboundAllowed(url);
|
|
24522
|
-
} catch {
|
|
24523
|
-
return false;
|
|
24524
|
-
}
|
|
24525
|
-
}
|
|
24526
|
-
});
|
|
24527
|
-
py.runPython(BRIDGE_PY);
|
|
24528
|
-
py.runPython(PROCESS_PY);
|
|
24529
|
-
py.runPython(NETWORK_PY);
|
|
24530
|
-
py.runPython(ASYNCIO_PY);
|
|
24531
|
-
}
|
|
24532
|
-
function bindProgram(py, binding) {
|
|
24533
|
-
py.__sbxSlot.current = binding;
|
|
24534
|
-
const { ctx, stdin } = binding;
|
|
24535
|
-
const decoder9 = new TextDecoder();
|
|
24536
|
-
py.setStdout({
|
|
24537
|
-
write: (buffer) => (ctx.write(decoder9.decode(buffer)), buffer.length)
|
|
24538
|
-
});
|
|
24539
|
-
py.setStderr({
|
|
24540
|
-
write: (buffer) => (ctx.stderr.write(decoder9.decode(buffer)), buffer.length)
|
|
24541
|
-
});
|
|
24542
|
-
py.setStdin({
|
|
24543
|
-
read: (buffer) => {
|
|
24544
|
-
const ready = stdin.buffered(buffer.length);
|
|
24545
|
-
if (!ready || ready.length === 0) return 0;
|
|
24546
|
-
buffer.set(ready);
|
|
24547
|
-
return ready.length;
|
|
24548
|
-
}
|
|
24549
|
-
});
|
|
24550
|
-
}
|
|
24551
|
-
|
|
24552
|
-
// src/python/cpython.ts
|
|
24553
|
-
var RESERVED_FOR_INTERPRETER = /* @__PURE__ */ new Set(["lib", "dev", "proc"]);
|
|
24554
|
-
var pyodideModule = null;
|
|
24555
|
-
var indexUrl;
|
|
24556
|
-
var moduleUrl;
|
|
24557
|
-
var DEFAULT_BROWSER_INDEX_URL = "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/";
|
|
24558
|
-
var isNode3 = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
24559
|
-
function configureCPython(options = {}) {
|
|
24560
|
-
if (options.indexURL !== void 0) indexUrl = options.indexURL;
|
|
24561
|
-
if (options.moduleURL !== void 0) {
|
|
24562
|
-
moduleUrl = options.moduleURL;
|
|
24563
|
-
pyodideModule = null;
|
|
24564
|
-
}
|
|
24565
|
-
}
|
|
24566
|
-
function importPyodide() {
|
|
24567
|
-
if (!pyodideModule) {
|
|
24568
|
-
if (moduleUrl) {
|
|
24569
|
-
pyodideModule = import(
|
|
24570
|
-
/* @vite-ignore */
|
|
24571
|
-
/* webpackIgnore: true */
|
|
24572
|
-
moduleUrl
|
|
24573
|
-
);
|
|
24574
|
-
} else if (isNode3) {
|
|
24575
|
-
pyodideModule = nodeOnlyModule("pyodide");
|
|
24576
|
-
} else {
|
|
24577
|
-
pyodideModule = import(
|
|
24578
|
-
/* @vite-ignore */
|
|
24579
|
-
/* webpackIgnore: true */
|
|
24580
|
-
`${DEFAULT_BROWSER_INDEX_URL}pyodide.mjs`
|
|
24581
|
-
);
|
|
24582
|
-
}
|
|
24583
|
-
}
|
|
24584
|
-
return pyodideModule;
|
|
24585
|
-
}
|
|
24586
|
-
async function resolveIndexUrl() {
|
|
24587
|
-
if (indexUrl) return indexUrl;
|
|
24588
|
-
if (!isNode3) return DEFAULT_BROWSER_INDEX_URL;
|
|
24589
|
-
try {
|
|
24590
|
-
const { createRequire } = await nodeBuiltin("module");
|
|
24591
|
-
const path = await nodeBuiltin("path");
|
|
24592
|
-
const require2 = createRequire(path.join(process.cwd(), "index.js"));
|
|
24593
|
-
return `${path.dirname(require2.resolve("pyodide/package.json"))}/`;
|
|
24594
|
-
} catch {
|
|
24595
|
-
return void 0;
|
|
24596
|
-
}
|
|
24597
|
-
}
|
|
24598
|
-
async function isCPythonAvailable() {
|
|
24599
|
-
try {
|
|
24600
|
-
await importPyodide();
|
|
24601
|
-
return true;
|
|
24602
|
-
} catch {
|
|
24603
|
-
return false;
|
|
24604
|
-
}
|
|
24605
|
-
}
|
|
24606
|
-
var interpreters = /* @__PURE__ */ new WeakMap();
|
|
24607
|
-
async function interpreterFor(ctx) {
|
|
24608
|
-
const existing = interpreters.get(ctx.vfs);
|
|
24609
|
-
if (existing) return existing;
|
|
24610
|
-
const starting = (async () => {
|
|
24611
|
-
const { loadPyodide } = await importPyodide();
|
|
24612
|
-
const resolved = await resolveIndexUrl();
|
|
24613
|
-
const py = await loadPyodide({
|
|
24614
|
-
...resolved ? { indexURL: resolved } : {},
|
|
24615
|
-
/* Output is rebound per program; these only catch anything printed
|
|
24616
|
-
* while the interpreter is still starting. */
|
|
24617
|
-
stdout: () => {
|
|
24618
|
-
},
|
|
24619
|
-
stderr: () => {
|
|
24620
|
-
}
|
|
24621
|
-
});
|
|
24622
|
-
mountContainerDirs(py, ctx);
|
|
24623
|
-
installRunner(py);
|
|
24624
|
-
installSyscallBridge(py);
|
|
24625
|
-
return py;
|
|
24626
|
-
})();
|
|
24627
|
-
interpreters.set(ctx.vfs, starting);
|
|
24628
|
-
try {
|
|
24629
|
-
return await starting;
|
|
24630
|
-
} catch (error) {
|
|
24631
|
-
interpreters.delete(ctx.vfs);
|
|
24632
|
-
throw error;
|
|
24633
|
-
}
|
|
24634
|
-
}
|
|
24635
|
-
function installRunner(py) {
|
|
24636
|
-
py.__sbxRun = py.runPython(`
|
|
24637
|
-
from pyodide.code import eval_code_async
|
|
24638
|
-
|
|
24639
|
-
async def __sbx_run(source, scope):
|
|
24640
|
-
try:
|
|
24641
|
-
await eval_code_async(source, globals=scope)
|
|
24642
|
-
return 0
|
|
24643
|
-
except SystemExit as exit:
|
|
24644
|
-
code = exit.code
|
|
24645
|
-
if code is None:
|
|
24646
|
-
return 0
|
|
24647
|
-
return code if isinstance(code, int) else 1
|
|
24648
|
-
|
|
24649
|
-
__sbx_run
|
|
24650
|
-
`);
|
|
24651
|
-
}
|
|
24652
|
-
function mountContainerDirs(py, ctx) {
|
|
24653
|
-
const mounted = py.__sbxMounted ??= /* @__PURE__ */ new Set();
|
|
24654
|
-
let entries;
|
|
24655
|
-
try {
|
|
24656
|
-
entries = ctx.vfs.readdirWithTypes("/", ctx.cred);
|
|
24657
|
-
} catch {
|
|
24658
|
-
return;
|
|
24659
|
-
}
|
|
24660
|
-
for (const entry of entries) {
|
|
24661
|
-
if (entry.kind !== "directory") continue;
|
|
24662
|
-
if (RESERVED_FOR_INTERPRETER.has(entry.name)) continue;
|
|
24663
|
-
if (mounted.has(entry.name)) continue;
|
|
24664
|
-
try {
|
|
24665
|
-
mountContainerFs(py.FS, {
|
|
24666
|
-
vfs: ctx.vfs,
|
|
24667
|
-
cred: ctx.cred,
|
|
24668
|
-
mountAt: `/${entry.name}`
|
|
24669
|
-
});
|
|
24670
|
-
mounted.add(entry.name);
|
|
24671
|
-
} catch {
|
|
24672
|
-
}
|
|
24673
|
-
}
|
|
24674
|
-
}
|
|
24675
|
-
function bootstrap(py, ctx, argv, scriptDir) {
|
|
24676
|
-
const paths = [
|
|
24677
|
-
...scriptDir ? [scriptDir] : [""],
|
|
24678
|
-
"/workspace",
|
|
24679
|
-
"/usr/lib/python3",
|
|
24680
|
-
"/usr/lib/python3/site-packages",
|
|
24681
|
-
"/usr/local/lib/python3/site-packages"
|
|
24682
|
-
];
|
|
24683
|
-
py.runPython(`
|
|
24684
|
-
import sys, os, importlib
|
|
24685
|
-
sys.argv[:] = ${JSON.stringify(argv)}
|
|
24686
|
-
for __p in reversed(${JSON.stringify(paths)}):
|
|
24687
|
-
if __p and __p not in sys.path:
|
|
24688
|
-
sys.path.insert(0, __p)
|
|
24689
|
-
elif __p == "" and "" not in sys.path:
|
|
24690
|
-
sys.path.insert(0, "")
|
|
24691
|
-
try:
|
|
24692
|
-
del __p
|
|
24693
|
-
except NameError:
|
|
24694
|
-
pass
|
|
24695
|
-
os.environ.clear()
|
|
24696
|
-
os.environ.update(${JSON.stringify(ctx.env)})
|
|
24697
|
-
importlib.invalidate_caches()
|
|
24698
|
-
`);
|
|
24699
|
-
try {
|
|
24700
|
-
py.FS.chdir(ctx.cwd);
|
|
24701
|
-
} catch {
|
|
24702
|
-
}
|
|
24703
|
-
}
|
|
24704
|
-
function reportError(ctx, error) {
|
|
24705
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
24706
|
-
const systemExit = /SystemExit:?\s*(-?\d+)?/.exec(message);
|
|
24707
|
-
if (systemExit) {
|
|
24708
|
-
return systemExit[1] !== void 0 ? Number(systemExit[1]) & 255 : 0;
|
|
24709
|
-
}
|
|
24710
|
-
if (/KeyboardInterrupt/.test(message)) {
|
|
24711
|
-
ctx.stderr.write("KeyboardInterrupt\n");
|
|
24712
|
-
return 130;
|
|
24713
|
-
}
|
|
24714
|
-
const text2 = message.replace(/^PythonError:\s*/, "");
|
|
24715
|
-
ctx.stderr.write(text2.endsWith("\n") ? text2 : `${text2}
|
|
24716
|
-
`);
|
|
24717
|
-
return 1;
|
|
24718
|
-
}
|
|
24719
|
-
async function runCPythonProgram(ctx, source, argv, scriptDir = null) {
|
|
24720
|
-
let py;
|
|
24721
|
-
try {
|
|
24722
|
-
py = await interpreterFor(ctx);
|
|
24723
|
-
} catch (error) {
|
|
24724
|
-
ctx.stderr.write(
|
|
24725
|
-
`python3: CPython is unavailable in this host (${error instanceof Error ? error.message : String(error)})
|
|
24726
|
-
`
|
|
24727
|
-
);
|
|
24728
|
-
return { exitCode: 127 };
|
|
24729
|
-
}
|
|
24730
|
-
mountContainerDirs(py, ctx);
|
|
24731
|
-
try {
|
|
24732
|
-
await py.loadPackagesFromImports(source, {
|
|
24733
|
-
messageCallback: () => {
|
|
24734
|
-
},
|
|
24735
|
-
errorCallback: () => {
|
|
24736
|
-
}
|
|
24737
|
-
});
|
|
24738
|
-
} catch {
|
|
24739
|
-
}
|
|
24740
|
-
const stdinHost = createStdinHost(ctx);
|
|
24741
|
-
await stdinHost.prime();
|
|
24742
|
-
bindProgram(py, { ctx, stdin: stdinHost });
|
|
24743
|
-
try {
|
|
24744
|
-
bootstrap(py, ctx, argv, scriptDir);
|
|
24745
|
-
} catch (error) {
|
|
24746
|
-
return { exitCode: reportError(ctx, error) };
|
|
24747
|
-
}
|
|
24748
|
-
const globals = py.globals.get("dict")();
|
|
24749
|
-
try {
|
|
24750
|
-
globals.set("__name__", "__main__");
|
|
24751
|
-
const exitCode = await py.__sbxRun(source, globals);
|
|
24752
|
-
return { exitCode: Number(exitCode) & 255 };
|
|
24753
|
-
} catch (error) {
|
|
24754
|
-
return { exitCode: reportError(ctx, error) };
|
|
24755
|
-
} finally {
|
|
24756
|
-
try {
|
|
24757
|
-
globals.destroy();
|
|
24758
|
-
} catch {
|
|
24759
|
-
}
|
|
24760
|
-
}
|
|
24761
|
-
}
|
|
24762
|
-
async function cpythonVersion(ctx) {
|
|
24763
|
-
try {
|
|
24764
|
-
const py = await interpreterFor(ctx);
|
|
24765
|
-
return String(py.runPython("import sys; sys.version.split()[0]"));
|
|
24766
|
-
} catch {
|
|
24767
|
-
return null;
|
|
24768
|
-
}
|
|
24769
|
-
}
|
|
24770
|
-
async function runCPythonRepl(ctx) {
|
|
24771
|
-
let py;
|
|
24772
|
-
try {
|
|
24773
|
-
py = await interpreterFor(ctx);
|
|
24774
|
-
} catch (error) {
|
|
24775
|
-
ctx.stderr.write(`python3: Pyodide is unavailable (${error instanceof Error ? error.message : String(error)})
|
|
24776
|
-
`);
|
|
24777
|
-
return 127;
|
|
24778
|
-
}
|
|
24779
|
-
mountContainerDirs(py, ctx);
|
|
24780
|
-
bootstrap(py, ctx, [""], null);
|
|
24781
|
-
bindProgram(py, { ctx, stdin: createStdinHost(ctx) });
|
|
24782
|
-
ctx.line(`Python ${String(py.runPython("import sys; sys.version.split()[0]"))} (Pyodide)`);
|
|
24783
|
-
ctx.line('Type "help()" for more information.');
|
|
24784
|
-
let source = "";
|
|
24785
|
-
for (; ; ) {
|
|
24786
|
-
ctx.write(source ? "... " : ">>> ");
|
|
24787
|
-
const line = await ctx.stdin.readLine();
|
|
24788
|
-
if (line === null) {
|
|
24789
|
-
ctx.line("");
|
|
24790
|
-
break;
|
|
24791
|
-
}
|
|
24792
|
-
if (!source && ["exit()", "quit()"].includes(line.trim())) break;
|
|
24793
|
-
source += `${source ? "\n" : ""}${line}`;
|
|
24794
|
-
if (/[:\\]\s*$/.test(line) || source.includes("\n") && line.trim() !== "" && /^\s+/.test(line)) continue;
|
|
24795
|
-
try {
|
|
24796
|
-
await py.runPythonAsync(source);
|
|
24797
|
-
} catch (error) {
|
|
24798
|
-
reportError(ctx, error);
|
|
24799
|
-
}
|
|
24800
|
-
source = "";
|
|
24801
|
-
}
|
|
24802
|
-
return 0;
|
|
24803
|
-
}
|
|
24804
|
-
var PIP_PY = `
|
|
24805
|
-
import json, re
|
|
24806
|
-
|
|
24807
|
-
import micropip
|
|
24808
|
-
|
|
24809
|
-
_MISSING = re.compile(r"Can't find a pure Python 3 wheel for '([^']+)'")
|
|
24810
|
-
_EXTRAS = re.compile(r"^\\s*([A-Za-z0-9._-]+)\\s*\\[([^\\]]+)\\](.*)$")
|
|
24811
|
-
|
|
24812
|
-
|
|
24813
|
-
def _name_of(spec):
|
|
24814
|
-
return re.split(r"[\\[<>=!~;\\s]", spec.strip(), 1)[0]
|
|
24815
|
-
|
|
24816
|
-
|
|
24817
|
-
def _installed_version(name):
|
|
24818
|
-
try:
|
|
24819
|
-
found = micropip.list()[name.replace("_", "-").lower()]
|
|
24820
|
-
return getattr(found, "version", None)
|
|
24821
|
-
except Exception:
|
|
24822
|
-
return None
|
|
24823
|
-
|
|
24824
|
-
|
|
24825
|
-
async def _install_one(spec):
|
|
24826
|
-
"""Install one requirement, retrying without extras if they are impossible."""
|
|
24827
|
-
attempt = spec
|
|
24828
|
-
dropped = None
|
|
24829
|
-
while True:
|
|
24830
|
-
try:
|
|
24831
|
-
await micropip.install(attempt)
|
|
24832
|
-
except ValueError as error:
|
|
24833
|
-
message = str(error)
|
|
24834
|
-
missing = _MISSING.findall(message)
|
|
24835
|
-
extras = _EXTRAS.match(attempt)
|
|
24836
|
-
if missing and extras and dropped is None:
|
|
24837
|
-
dropped = [e.strip() for e in extras.group(2).split(",")]
|
|
24838
|
-
attempt = extras.group(1) + extras.group(3)
|
|
24839
|
-
continue
|
|
24840
|
-
return {
|
|
24841
|
-
"spec": spec, "ok": False, "missing": missing,
|
|
24842
|
-
"reason": message.strip().split("\\n")[0],
|
|
24843
|
-
}
|
|
24844
|
-
except Exception as error:
|
|
24845
|
-
return {"spec": spec, "ok": False, "missing": [], "reason": str(error).strip().split("\\n")[0]}
|
|
24846
|
-
|
|
24847
|
-
name = _name_of(attempt)
|
|
24848
|
-
return {
|
|
24849
|
-
"spec": spec, "ok": True, "name": name,
|
|
24850
|
-
"version": _installed_version(name), "dropped": dropped,
|
|
24851
|
-
}
|
|
24852
|
-
|
|
24853
|
-
|
|
24854
|
-
async def __sbx_pip(specs_json):
|
|
24855
|
-
results = []
|
|
24856
|
-
for spec in json.loads(specs_json):
|
|
24857
|
-
try:
|
|
24858
|
-
results.append(await _install_one(spec))
|
|
24859
|
-
except Exception as error:
|
|
24860
|
-
results.append({"spec": spec, "ok": False, "missing": [], "reason": str(error)})
|
|
24861
|
-
return json.dumps(results)
|
|
24862
|
-
|
|
24863
|
-
__sbx_pip
|
|
24864
|
-
`;
|
|
24865
|
-
var micropip = defineCommand({
|
|
24866
|
-
name: "micropip",
|
|
24867
|
-
path: "/usr/bin/micropip",
|
|
24868
|
-
aliases: ["pip", "pip3"],
|
|
24869
|
-
summary: "install Python packages into the running interpreter",
|
|
24870
|
-
usage: "micropip install <package>...",
|
|
24871
|
-
async run(ctx) {
|
|
24872
|
-
if (usingOwnedPython()) {
|
|
24873
|
-
ctx.warn(
|
|
24874
|
-
"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)."
|
|
24875
|
-
);
|
|
24876
|
-
return 1;
|
|
24877
|
-
}
|
|
24878
|
-
const [action, ...args] = ctx.args;
|
|
24879
|
-
if (action === "--version" || action === "-V") {
|
|
24880
|
-
ctx.line("pip (micropip, Pyodide)");
|
|
24881
|
-
return 0;
|
|
24882
|
-
}
|
|
24883
|
-
if (action !== "install") {
|
|
24884
|
-
ctx.line("usage: pip install [-r requirements.txt] <package>...");
|
|
24885
|
-
return action === void 0 ? 1 : 0;
|
|
24886
|
-
}
|
|
24887
|
-
const packages = args.filter((arg) => !arg.startsWith("-"));
|
|
24888
|
-
const requirementIndex = args.findIndex((arg) => arg === "-r" || arg === "--requirement");
|
|
24889
|
-
if (requirementIndex >= 0) {
|
|
24890
|
-
const file3 = args[requirementIndex + 1];
|
|
24891
|
-
if (!file3) {
|
|
24892
|
-
ctx.stderr.write("pip: option -r requires a file\n");
|
|
24893
|
-
return 2;
|
|
23755
|
+
if (attr.timestamp !== void 0) vfs.utimes(path, attr.timestamp, attr.timestamp, cred);
|
|
23756
|
+
if (attr.size !== void 0) {
|
|
23757
|
+
vfs.truncate(path, attr.size, cred);
|
|
23758
|
+
opts.onWrite?.(path);
|
|
23759
|
+
}
|
|
23760
|
+
} catch (e) {
|
|
23761
|
+
translate(e);
|
|
24894
23762
|
}
|
|
23763
|
+
},
|
|
23764
|
+
lookup(parent, name) {
|
|
23765
|
+
const path = join(backend.realPath(parent), name);
|
|
23766
|
+
let mode;
|
|
24895
23767
|
try {
|
|
24896
|
-
|
|
24897
|
-
packages.push(...ctx.vfs.readText(ctx.path(file3), ctx.cred).split(/\r?\n/).map((line) => line.replace(/#.*$/, "").trim()).filter(Boolean));
|
|
23768
|
+
mode = vfs.lstat(path).mode;
|
|
24898
23769
|
} catch {
|
|
24899
|
-
|
|
24900
|
-
`);
|
|
24901
|
-
return 1;
|
|
23770
|
+
throw new FS.ErrnoError(EM_ERRNO.ENOENT);
|
|
24902
23771
|
}
|
|
24903
|
-
|
|
24904
|
-
|
|
24905
|
-
|
|
24906
|
-
|
|
24907
|
-
|
|
24908
|
-
|
|
24909
|
-
|
|
24910
|
-
|
|
24911
|
-
|
|
24912
|
-
|
|
24913
|
-
|
|
24914
|
-
let py;
|
|
24915
|
-
try {
|
|
24916
|
-
py = await interpreterFor(ctx);
|
|
24917
|
-
} catch {
|
|
24918
|
-
ctx.stderr.write("pip: Pyodide is unavailable in this host\n");
|
|
24919
|
-
return 127;
|
|
24920
|
-
}
|
|
24921
|
-
let outcomes;
|
|
24922
|
-
try {
|
|
24923
|
-
await py.loadPackage("micropip");
|
|
24924
|
-
for (const name of packages) ctx.line(`Collecting ${name}`);
|
|
24925
|
-
const install2 = py.runPython(PIP_PY);
|
|
24926
|
-
outcomes = JSON.parse(String(await install2(JSON.stringify(packages))));
|
|
24927
|
-
} catch (error) {
|
|
24928
|
-
ctx.stderr.write(
|
|
24929
|
-
`pip: installation failed: ${error instanceof Error ? error.message : String(error)}
|
|
24930
|
-
`
|
|
24931
|
-
);
|
|
24932
|
-
return 1;
|
|
24933
|
-
}
|
|
24934
|
-
const installed2 = [];
|
|
24935
|
-
for (const outcome of outcomes) {
|
|
24936
|
-
if (outcome.ok) {
|
|
24937
|
-
installed2.push(
|
|
24938
|
-
outcome.version ? `${outcome.name}-${outcome.version}` : String(outcome.name)
|
|
24939
|
-
);
|
|
24940
|
-
if (outcome.dropped?.length) {
|
|
24941
|
-
ctx.stderr.write(
|
|
24942
|
-
` WARNING: ${outcome.name}: the ${outcome.dropped.map((extra) => `'${extra}'`).join(", ")} extra needs native code with no WebAssembly build; installed ${outcome.name} without it
|
|
24943
|
-
`
|
|
24944
|
-
);
|
|
23772
|
+
return backend.createNode(parent, name, mode, 0);
|
|
23773
|
+
},
|
|
23774
|
+
mknod(parent, name, mode, dev) {
|
|
23775
|
+
const path = join(backend.realPath(parent), name);
|
|
23776
|
+
try {
|
|
23777
|
+
if (FS.isDir(mode)) {
|
|
23778
|
+
vfs.mkdir(path, { cred, mode: mode & 4095 });
|
|
23779
|
+
} else if (FS.isFile(mode)) {
|
|
23780
|
+
vfs.writeFile(path, new Uint8Array(0), { cred, mode: mode & 4095 });
|
|
23781
|
+
} else {
|
|
23782
|
+
return throwErrno("ENOTSUP");
|
|
24945
23783
|
}
|
|
24946
|
-
|
|
23784
|
+
opts.onWrite?.(path);
|
|
23785
|
+
} catch (e) {
|
|
23786
|
+
translate(e);
|
|
24947
23787
|
}
|
|
24948
|
-
|
|
24949
|
-
|
|
24950
|
-
|
|
24951
|
-
|
|
24952
|
-
|
|
24953
|
-
);
|
|
24954
|
-
}
|
|
24955
|
-
if (installed2.length > 0) ctx.line(`Successfully installed ${installed2.join(" ")}`);
|
|
24956
|
-
return outcomes.every((outcome) => outcome.ok) ? 0 : 1;
|
|
24957
|
-
}
|
|
24958
|
-
});
|
|
24959
|
-
|
|
24960
|
-
// src/python/python.ts
|
|
24961
|
-
var PYTHON_VERSION = "3.13";
|
|
24962
|
-
function configurePython(options = {}) {
|
|
24963
|
-
configureCPython({ indexURL: options.indexURL, moduleURL: options.pyodideURL });
|
|
24964
|
-
setPythonBackend({
|
|
24965
|
-
...options.backend !== void 0 ? { backend: options.backend } : {},
|
|
24966
|
-
...options.manifest !== void 0 ? { manifest: options.manifest } : {},
|
|
24967
|
-
...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {},
|
|
24968
|
-
...options.wheelIndex !== void 0 ? { wheelIndex: options.wheelIndex } : {},
|
|
24969
|
-
...options.buildFromSource !== void 0 ? { buildFromSource: options.buildFromSource } : {}
|
|
24970
|
-
});
|
|
24971
|
-
}
|
|
24972
|
-
var isPythonAvailable = isCPythonAvailable;
|
|
24973
|
-
var python = defineCommand({
|
|
24974
|
-
name: "python3",
|
|
24975
|
-
path: "/usr/bin/python3",
|
|
24976
|
-
aliases: ["python"],
|
|
24977
|
-
summary: "run Python using CPython (Pyodide)",
|
|
24978
|
-
usage: "python3 [-c command | -m module | script.py] [arguments]",
|
|
24979
|
-
manual: `Python is CPython compiled to WebAssembly by Pyodide. Scripts use
|
|
24980
|
-
the container's virtual filesystem, modules in /workspace are importable, and
|
|
24981
|
-
compatible packages can be installed with pip (micropip).`,
|
|
24982
|
-
async run(ctx) {
|
|
24983
|
-
if (usingOwnedPython()) {
|
|
23788
|
+
return backend.createNode(parent, name, mode, dev);
|
|
23789
|
+
},
|
|
23790
|
+
rename(oldNode, newDir, newName) {
|
|
23791
|
+
const from = backend.realPath(oldNode);
|
|
23792
|
+
const to = join(backend.realPath(newDir), newName);
|
|
24984
23793
|
try {
|
|
24985
|
-
|
|
24986
|
-
|
|
24987
|
-
|
|
23794
|
+
vfs.rename(from, to, cred);
|
|
23795
|
+
opts.onWrite?.(to);
|
|
23796
|
+
} catch (e) {
|
|
23797
|
+
translate(e);
|
|
24988
23798
|
}
|
|
24989
|
-
|
|
24990
|
-
|
|
24991
|
-
|
|
24992
|
-
|
|
24993
|
-
|
|
24994
|
-
|
|
24995
|
-
|
|
24996
|
-
|
|
24997
|
-
|
|
24998
|
-
|
|
24999
|
-
return 0;
|
|
23799
|
+
oldNode.name = newName;
|
|
23800
|
+
oldNode.parent = newDir;
|
|
23801
|
+
},
|
|
23802
|
+
unlink(parent, name) {
|
|
23803
|
+
const path = join(backend.realPath(parent), name);
|
|
23804
|
+
try {
|
|
23805
|
+
vfs.unlink(path, cred);
|
|
23806
|
+
opts.onWrite?.(path);
|
|
23807
|
+
} catch (e) {
|
|
23808
|
+
translate(e);
|
|
25000
23809
|
}
|
|
25001
|
-
|
|
25002
|
-
|
|
25003
|
-
|
|
23810
|
+
},
|
|
23811
|
+
rmdir(parent, name) {
|
|
23812
|
+
const path = join(backend.realPath(parent), name);
|
|
23813
|
+
try {
|
|
23814
|
+
vfs.rmdir(path, cred);
|
|
23815
|
+
opts.onWrite?.(path);
|
|
23816
|
+
} catch (e) {
|
|
23817
|
+
translate(e);
|
|
25004
23818
|
}
|
|
25005
|
-
|
|
25006
|
-
|
|
25007
|
-
|
|
25008
|
-
|
|
23819
|
+
},
|
|
23820
|
+
readdir(node2) {
|
|
23821
|
+
const path = backend.realPath(node2);
|
|
23822
|
+
try {
|
|
23823
|
+
return [".", "..", ...vfs.readdir(path, cred)];
|
|
23824
|
+
} catch (e) {
|
|
23825
|
+
return translate(e);
|
|
25009
23826
|
}
|
|
25010
|
-
|
|
25011
|
-
|
|
25012
|
-
|
|
25013
|
-
|
|
23827
|
+
},
|
|
23828
|
+
symlink(parent, newName, oldPath) {
|
|
23829
|
+
const path = join(backend.realPath(parent), newName);
|
|
23830
|
+
try {
|
|
23831
|
+
vfs.symlink(oldPath, path, cred);
|
|
23832
|
+
opts.onWrite?.(path);
|
|
23833
|
+
} catch (e) {
|
|
23834
|
+
translate(e);
|
|
25014
23835
|
}
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
|
|
25018
|
-
|
|
23836
|
+
},
|
|
23837
|
+
readlink(node2) {
|
|
23838
|
+
const path = backend.realPath(node2);
|
|
23839
|
+
try {
|
|
23840
|
+
return vfs.readlink(path, cred);
|
|
23841
|
+
} catch (e) {
|
|
23842
|
+
return translate(e);
|
|
25019
23843
|
}
|
|
25020
|
-
if (["-i", "-u", "-B", "-E", "-s", "-S", "-O"].includes(arg)) continue;
|
|
25021
|
-
if (arg.startsWith("-")) continue;
|
|
25022
|
-
script = arg;
|
|
25023
|
-
i++;
|
|
25024
|
-
break;
|
|
25025
|
-
}
|
|
25026
|
-
const rest = argv.slice(i);
|
|
25027
|
-
if (command !== void 0) {
|
|
25028
|
-
return (await runCPythonProgram(ctx, withTopLevelAwait(command), ["-c", ...rest], null)).exitCode;
|
|
25029
23844
|
}
|
|
25030
|
-
|
|
25031
|
-
|
|
25032
|
-
|
|
25033
|
-
|
|
25034
|
-
|
|
25035
|
-
|
|
25036
|
-
|
|
25037
|
-
|
|
25038
|
-
|
|
25039
|
-
|
|
25040
|
-
|
|
25041
|
-
}
|
|
25042
|
-
|
|
25043
|
-
if (
|
|
25044
|
-
const
|
|
25045
|
-
|
|
25046
|
-
|
|
25047
|
-
|
|
25048
|
-
|
|
25049
|
-
|
|
23845
|
+
};
|
|
23846
|
+
const streamOps = {
|
|
23847
|
+
open(stream) {
|
|
23848
|
+
const path = backend.realPath(stream.node);
|
|
23849
|
+
if (FS.isDir(stream.node.mode)) return;
|
|
23850
|
+
try {
|
|
23851
|
+
stream.sbxBuffer = vfs.lexists(path) ? vfs.readFile(path, cred) : new Uint8Array(0);
|
|
23852
|
+
stream.sbxDirty = false;
|
|
23853
|
+
} catch (e) {
|
|
23854
|
+
translate(e);
|
|
23855
|
+
}
|
|
23856
|
+
},
|
|
23857
|
+
close(stream) {
|
|
23858
|
+
if (!stream.sbxDirty) return;
|
|
23859
|
+
const path = backend.realPath(stream.node);
|
|
23860
|
+
try {
|
|
23861
|
+
vfs.writeFile(path, stream.sbxBuffer, { cred });
|
|
23862
|
+
opts.onWrite?.(path);
|
|
23863
|
+
} catch (e) {
|
|
23864
|
+
translate(e);
|
|
23865
|
+
}
|
|
23866
|
+
stream.sbxDirty = false;
|
|
23867
|
+
},
|
|
23868
|
+
read(stream, buffer, offset, length, position) {
|
|
23869
|
+
const source = stream.sbxBuffer ?? new Uint8Array(0);
|
|
23870
|
+
if (position >= source.length) return 0;
|
|
23871
|
+
const size = Math.min(length, source.length - position);
|
|
23872
|
+
buffer.set(source.subarray(position, position + size), offset);
|
|
23873
|
+
return size;
|
|
23874
|
+
},
|
|
23875
|
+
write(stream, buffer, offset, length, position) {
|
|
23876
|
+
const current = stream.sbxBuffer ?? new Uint8Array(0);
|
|
23877
|
+
const end = Math.max(current.length, position + length);
|
|
23878
|
+
const next = new Uint8Array(end);
|
|
23879
|
+
next.set(current);
|
|
23880
|
+
next.set(buffer.subarray(offset, offset + length), position);
|
|
23881
|
+
stream.sbxBuffer = next;
|
|
23882
|
+
stream.sbxDirty = true;
|
|
23883
|
+
stream.node.size = end;
|
|
23884
|
+
const path = backend.realPath(stream.node);
|
|
23885
|
+
try {
|
|
23886
|
+
vfs.writeFile(path, next, { cred });
|
|
23887
|
+
opts.onWrite?.(path);
|
|
23888
|
+
stream.sbxDirty = false;
|
|
23889
|
+
} catch (e) {
|
|
23890
|
+
translate(e);
|
|
23891
|
+
}
|
|
23892
|
+
return length;
|
|
23893
|
+
},
|
|
23894
|
+
llseek(stream, offset, whence) {
|
|
23895
|
+
let position = offset;
|
|
23896
|
+
if (whence === 1) position += stream.position;
|
|
23897
|
+
else if (whence === 2) position += (stream.sbxBuffer ?? new Uint8Array(0)).length;
|
|
23898
|
+
if (position < 0) throw new FS.ErrnoError(EM_ERRNO.EINVAL);
|
|
23899
|
+
return position;
|
|
25050
23900
|
}
|
|
25051
|
-
|
|
25052
|
-
|
|
23901
|
+
};
|
|
23902
|
+
if (opts.mountAt) {
|
|
25053
23903
|
try {
|
|
25054
|
-
|
|
23904
|
+
FS.mkdir(mountRoot);
|
|
25055
23905
|
} catch {
|
|
25056
|
-
ctx.stderr.write(`${ctx.name}: can't open file '${abs}': [Errno 2] No such file or directory
|
|
25057
|
-
`);
|
|
25058
|
-
return 2;
|
|
25059
23906
|
}
|
|
25060
|
-
|
|
23907
|
+
FS.mount(backend, {}, mountRoot);
|
|
23908
|
+
return;
|
|
25061
23909
|
}
|
|
25062
|
-
|
|
25063
|
-
|
|
25064
|
-
|
|
25065
|
-
|
|
25066
|
-
|
|
25067
|
-
try:
|
|
25068
|
-
_sbx_code = compile(_sbx_src, "<string>", "exec")
|
|
25069
|
-
except SyntaxError:
|
|
25070
|
-
_sbx_code = compile(_sbx_src, "<string>", "exec", _sbx_ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
|
|
25071
|
-
_sbx_async = bool(_sbx_code.co_flags & _sbx_inspect.CO_COROUTINE)
|
|
25072
|
-
_sbx_globals = globals()
|
|
25073
|
-
for _sbx_name in ("_sbx_ast", "_sbx_inspect", "_sbx_src", "_sbx_name"):
|
|
25074
|
-
_sbx_globals.pop(_sbx_name, None)
|
|
25075
|
-
if _sbx_async:
|
|
25076
|
-
import asyncio as _sbx_asyncio
|
|
25077
|
-
_sbx_asyncio.run(eval(_sbx_code, _sbx_globals))
|
|
25078
|
-
else:
|
|
25079
|
-
exec(_sbx_code, _sbx_globals)
|
|
25080
|
-
`;
|
|
25081
|
-
}
|
|
25082
|
-
function withTopLevelAwaitArgs(args) {
|
|
25083
|
-
for (let i = 0; i < args.length; i++) {
|
|
25084
|
-
const arg = args[i];
|
|
25085
|
-
if (arg === "-c") {
|
|
25086
|
-
if (i + 1 >= args.length) return args;
|
|
25087
|
-
const rewritten = withTopLevelAwait(args[i + 1]);
|
|
25088
|
-
if (rewritten === args[i + 1]) return args;
|
|
25089
|
-
const copy = [...args];
|
|
25090
|
-
copy[i + 1] = rewritten;
|
|
25091
|
-
return copy;
|
|
25092
|
-
}
|
|
25093
|
-
if (arg === "-m" || !arg.startsWith("-") || arg === "-") return args;
|
|
23910
|
+
FS.root = null;
|
|
23911
|
+
FS.mount(backend, {}, "/");
|
|
23912
|
+
try {
|
|
23913
|
+
FS.chdir("/");
|
|
23914
|
+
} catch {
|
|
25094
23915
|
}
|
|
25095
|
-
return args;
|
|
25096
|
-
}
|
|
25097
|
-
function printHelp2(ctx) {
|
|
25098
|
-
ctx.line("usage: python3 [option] ... [-c cmd | -m mod | file | -] [arg] ...");
|
|
25099
|
-
ctx.line("-c cmd : program passed in as string");
|
|
25100
|
-
ctx.line("-m mod : run library module as a script");
|
|
25101
|
-
ctx.line("-V : print the Python version and exit");
|
|
25102
|
-
}
|
|
25103
|
-
function pythonCommands() {
|
|
25104
|
-
return usingOwnedPython() ? [python, pipCommand] : [python, micropip];
|
|
25105
23916
|
}
|
|
25106
23917
|
|
|
25107
23918
|
// src/tools/ffmpeg.ts
|
|
@@ -25795,8 +24606,8 @@ function renameShadowedExports(source) {
|
|
|
25795
24606
|
for (const [key, value] of Object.entries(node2)) {
|
|
25796
24607
|
if (key === "type" || key === "start" || key === "end" || skip.includes(key)) continue;
|
|
25797
24608
|
if (Array.isArray(value)) {
|
|
25798
|
-
for (const item of value) if (
|
|
25799
|
-
} else if (
|
|
24609
|
+
for (const item of value) if (isNode3(item)) visit(item, childScope);
|
|
24610
|
+
} else if (isNode3(value)) {
|
|
25800
24611
|
visit(value, childScope);
|
|
25801
24612
|
}
|
|
25802
24613
|
}
|
|
@@ -25820,9 +24631,9 @@ function blockNames2(body) {
|
|
|
25820
24631
|
for (const declarator of node2.declarations) {
|
|
25821
24632
|
collectPattern2(declarator.id, names);
|
|
25822
24633
|
}
|
|
25823
|
-
} else if (node2.type === "ClassDeclaration" &&
|
|
24634
|
+
} else if (node2.type === "ClassDeclaration" && isNode3(node2.id)) {
|
|
25824
24635
|
names.add(node2.id.name);
|
|
25825
|
-
} else if (node2.type === "FunctionDeclaration" &&
|
|
24636
|
+
} else if (node2.type === "FunctionDeclaration" && isNode3(node2.id)) {
|
|
25826
24637
|
names.add(node2.id.name);
|
|
25827
24638
|
}
|
|
25828
24639
|
}
|
|
@@ -25833,10 +24644,10 @@ function collectVars2(nodes, names) {
|
|
|
25833
24644
|
for (const item of nodes) collectVars2(item, names);
|
|
25834
24645
|
return;
|
|
25835
24646
|
}
|
|
25836
|
-
if (!
|
|
24647
|
+
if (!isNode3(nodes)) return;
|
|
25837
24648
|
const node2 = nodes;
|
|
25838
24649
|
if (node2.type === "FunctionDeclaration" || node2.type === "FunctionExpression" || node2.type === "ArrowFunctionExpression") {
|
|
25839
|
-
if (
|
|
24650
|
+
if (isNode3(node2.id)) names.add(node2.id.name);
|
|
25840
24651
|
return;
|
|
25841
24652
|
}
|
|
25842
24653
|
if (node2.type === "VariableDeclaration" && node2.kind === "var") {
|
|
@@ -25850,7 +24661,7 @@ function collectVars2(nodes, names) {
|
|
|
25850
24661
|
}
|
|
25851
24662
|
}
|
|
25852
24663
|
function collectPattern2(node2, names) {
|
|
25853
|
-
if (!
|
|
24664
|
+
if (!isNode3(node2)) return;
|
|
25854
24665
|
switch (node2.type) {
|
|
25855
24666
|
case "Identifier":
|
|
25856
24667
|
names.add(node2.name);
|
|
@@ -25874,7 +24685,7 @@ function collectPattern2(node2, names) {
|
|
|
25874
24685
|
}
|
|
25875
24686
|
}
|
|
25876
24687
|
function isExports(value) {
|
|
25877
|
-
return
|
|
24688
|
+
return isNode3(value) && value.type === "Identifier" && value.name === NAME;
|
|
25878
24689
|
}
|
|
25879
24690
|
function freshName(source) {
|
|
25880
24691
|
let name = "__sandboxedjs_exports";
|
|
@@ -25882,7 +24693,7 @@ function freshName(source) {
|
|
|
25882
24693
|
while (source.includes(name)) name = `__sandboxedjs_exports${++suffix}`;
|
|
25883
24694
|
return name;
|
|
25884
24695
|
}
|
|
25885
|
-
function
|
|
24696
|
+
function isNode3(value) {
|
|
25886
24697
|
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
25887
24698
|
}
|
|
25888
24699
|
var NOTHING2 = [];
|
|
@@ -26413,7 +25224,7 @@ var apt = defineCommand({
|
|
|
26413
25224
|
const provided = {
|
|
26414
25225
|
nodejs: { version: NODE_VERSION.replace(/^v/, ""), description: "Node.js event-based server-side JavaScript engine" },
|
|
26415
25226
|
npm: { version: NPM_VERSION, description: "package manager for Node.js" },
|
|
26416
|
-
python3: { version: PYTHON_VERSION, description: "CPython interpreter
|
|
25227
|
+
python3: { version: PYTHON_VERSION, description: "CPython interpreter" },
|
|
26417
25228
|
"python3-pip": { version: "24.0", description: "Python package installer" },
|
|
26418
25229
|
coreutils: { version: "9.4", description: "GNU core utilities" },
|
|
26419
25230
|
grep: { version: "3.11", description: "GNU grep, egrep and fgrep" },
|
|
@@ -26517,7 +25328,7 @@ var dpkg = defineCommand({
|
|
|
26517
25328
|
ctx.line("||/ Name Version Architecture Description");
|
|
26518
25329
|
ctx.line("+++-==============-============-============-=================================");
|
|
26519
25330
|
ctx.line(`ii nodejs ${NODE_VERSION.replace(/^v/, "").padEnd(12)} amd64 Node.js JavaScript runtime`);
|
|
26520
|
-
ctx.line(`ii python3 ${PYTHON_VERSION.padEnd(12)} wasm32 CPython
|
|
25331
|
+
ctx.line(`ii python3 ${PYTHON_VERSION.padEnd(12)} wasm32 CPython interpreter`);
|
|
26521
25332
|
return 0;
|
|
26522
25333
|
}
|
|
26523
25334
|
ctx.line("dpkg 1.22.6 (amd64)");
|
|
@@ -27525,6 +26336,9 @@ function render(value, depth, seen, options) {
|
|
|
27525
26336
|
if (value instanceof Date) return Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString();
|
|
27526
26337
|
if (value instanceof RegExp) return String(value);
|
|
27527
26338
|
if (Buffer2.isBuffer(value)) return renderBuffer(value);
|
|
26339
|
+
if (value instanceof DataView) {
|
|
26340
|
+
return `DataView { byteLength: ${value.byteLength}, byteOffset: ${value.byteOffset} }`;
|
|
26341
|
+
}
|
|
27528
26342
|
if (ArrayBuffer.isView(value)) return renderTypedArray(value);
|
|
27529
26343
|
seen.add(object);
|
|
27530
26344
|
try {
|
|
@@ -27586,7 +26400,13 @@ function renderBuffer(value) {
|
|
|
27586
26400
|
}
|
|
27587
26401
|
function renderTypedArray(value) {
|
|
27588
26402
|
const name = value.constructor?.name ?? "TypedArray";
|
|
27589
|
-
const items = [
|
|
26403
|
+
const items = [];
|
|
26404
|
+
const indexed = value;
|
|
26405
|
+
for (let i = 0; i < Math.min(value.length, MAX_ARRAY); i++) {
|
|
26406
|
+
const item = indexed[i];
|
|
26407
|
+
items.push(typeof item === "bigint" ? `${item}n` : String(item));
|
|
26408
|
+
}
|
|
26409
|
+
if (value.length > MAX_ARRAY) items.push(`... ${value.length - MAX_ARRAY} more items`);
|
|
27590
26410
|
return `${name}(${value.length}) [ ${items.join(", ")} ]`;
|
|
27591
26411
|
}
|
|
27592
26412
|
function prefixed(name, size, parts) {
|
|
@@ -27842,6 +26662,7 @@ var legacy = {
|
|
|
27842
26662
|
};
|
|
27843
26663
|
inspect.custom = customInspect;
|
|
27844
26664
|
var utilModule = {
|
|
26665
|
+
parseEnv,
|
|
27845
26666
|
format,
|
|
27846
26667
|
formatWithOptions,
|
|
27847
26668
|
inspect,
|
|
@@ -30446,6 +29267,14 @@ function createCoreModules(options) {
|
|
|
30446
29267
|
processObject.stdin = stdin;
|
|
30447
29268
|
Reflect.deleteProperty(processObject, "browser");
|
|
30448
29269
|
const fs = createFsModule(volume, () => cwd, options.stdinPath, defer);
|
|
29270
|
+
processObject.loadEnvFile = (path2 = ".env") => {
|
|
29271
|
+
const parsed = parseEnv(fs.readFileSync(path2, "utf8"));
|
|
29272
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
29273
|
+
if (!Object.prototype.hasOwnProperty.call(processObject.env, key)) {
|
|
29274
|
+
Object.defineProperty(processObject.env, key, { value, writable: true, enumerable: true, configurable: true });
|
|
29275
|
+
}
|
|
29276
|
+
}
|
|
29277
|
+
};
|
|
30449
29278
|
const path = createPathModule(() => cwd);
|
|
30450
29279
|
const consoleObject = new Console(stdoutWrite, stderrWrite);
|
|
30451
29280
|
const os = createOsModule();
|
|
@@ -35429,6 +34258,6 @@ function createFrontendPlaywright(options = {}) {
|
|
|
35429
34258
|
// src/index.ts
|
|
35430
34259
|
var src_default = createContainer;
|
|
35431
34260
|
|
|
35432
|
-
export { ArithError, BinaryExecutionRegistry, BufferSink, CallbackSink, CleanPackageInstaller, CommandRegistry, CommonJsEngine, Container, ContainerFs, ERRNO, FileInput, FileOutput, FrontendAutomationUnsupported, FrontendLocator, FrontendPage, IncompleteInputError, KERNEL_NAME, KERNEL_RELEASE, Kernel, LocalRuntimePod, MANIFEST_FORMAT, MANIFEST_SCHEMA_VERSION, MemoryVolume, 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, VirtualHttpRouter, VirtualHttpServer, VirtualIncomingMessage, VirtualServerResponse, WASM_ALIASES, WasiError, WasiExit, WasiHost, WorkerRuntimePod, allCommands, applyChmod, binaryDigest, braceExpand, buildRootfs, builtinNames, captureStdio,
|
|
34261
|
+
export { ArithError, BinaryExecutionRegistry, BufferSink, CallbackSink, CleanPackageInstaller, CommandRegistry, CommonJsEngine, Container, ContainerFs, ERRNO, FileInput, FileOutput, FrontendAutomationUnsupported, FrontendLocator, FrontendPage, IncompleteInputError, KERNEL_NAME, KERNEL_RELEASE, Kernel, LocalRuntimePod, MANIFEST_FORMAT, MANIFEST_SCHEMA_VERSION, MemoryVolume, 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, VirtualHttpRouter, VirtualHttpServer, VirtualIncomingMessage, VirtualServerResponse, WASM_ALIASES, WasiError, WasiExit, WasiHost, WorkerRuntimePod, allCommands, applyChmod, binaryDigest, braceExpand, buildRootfs, builtinNames, captureStdio, configurePython, containerTarget, createChildProcessModule, createContainer, createContext, createCoreModules, createFrontendPlaywright, createGitCommand, createOriginalX64Backends, createPreview, createSqlCommand, createTranslationBackend, createWasmCompatibilityBackend, src_default as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, extractNpmTarball, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, inspectElf, installUserland, installWasmCommands, isBuiltinName, isElfBinary, isPythonAvailable, isSysError, isWasmBinary, looksLikeEsm, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path_exports as posixPath, renderInto, resetPidCounter, runWasi, shellQuote, startRuntimeWorker, strerror, syncChannelSupported, transformEsm, unameInfo, validateManifest, wasi as wasiCommand };
|
|
35433
34262
|
//# sourceMappingURL=index.js.map
|
|
35434
34263
|
//# sourceMappingURL=index.js.map
|