sandboxedjs 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -14
- package/dist/index.cjs +59 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -15
- package/dist/index.d.ts +50 -15
- package/dist/index.js +58 -11
- package/dist/index.js.map +1 -1
- package/dist/integrations/server.cjs +24 -0
- package/dist/integrations/server.cjs.map +1 -0
- package/dist/integrations/server.d.cts +2 -0
- package/dist/integrations/server.d.ts +2 -0
- package/dist/integrations/server.js +3 -0
- package/dist/integrations/server.js.map +1 -0
- package/dist/integrations/vite.cjs +22 -0
- package/dist/integrations/vite.cjs.map +1 -0
- package/dist/integrations/vite.d.cts +1 -0
- package/dist/integrations/vite.d.ts +1 -0
- package/dist/integrations/vite.js +3 -0
- package/dist/integrations/vite.js.map +1 -0
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -579,33 +579,87 @@ resolve their implementation at call time (`node:zlib`/`node:crypto` on Node,
|
|
|
579
579
|
`CompressionStream`/`crypto.subtle` plus a JS MD5 in a browser), so nothing pulls a `node:`
|
|
580
580
|
builtin in when the module loads and a bundler will not fail on it.
|
|
581
581
|
|
|
582
|
-
**
|
|
583
|
-
`worker_threads`
|
|
584
|
-
|
|
582
|
+
**One call, either side.** `createContainer()` picks its own host: the headless
|
|
583
|
+
`worker_threads` engine on Node, Nodepod's browser build in a browser. You do not import Nodepod,
|
|
584
|
+
and you do not pass a pod:
|
|
585
585
|
|
|
586
586
|
```ts
|
|
587
|
-
import { Nodepod } from "@scelar/nodepod";
|
|
588
587
|
import { createContainer } from "sandboxedjs";
|
|
589
588
|
|
|
590
|
-
const
|
|
591
|
-
const box = await createContainer({ pod });
|
|
589
|
+
const box = await createContainer({ files: { "/app/index.js": "console.log(1)" } });
|
|
592
590
|
|
|
593
591
|
await box.exec("ls -la /"); // shell + coreutils
|
|
594
|
-
await box.exec("node app.js");
|
|
592
|
+
await box.exec("node app/index.js");
|
|
595
593
|
```
|
|
596
594
|
|
|
597
|
-
**
|
|
595
|
+
**Serve the service worker.** In a browser, a container's preview iframe reaches an HTTP server
|
|
596
|
+
running inside the page through a service worker, and a browser will not register one from
|
|
597
|
+
`node_modules` — it has to come from your own origin at `/__sw__.js`. One line:
|
|
598
|
+
|
|
599
|
+
```ts
|
|
600
|
+
// vite.config.ts
|
|
601
|
+
import sandboxedjs from "sandboxedjs/vite";
|
|
602
|
+
export default defineConfig({ plugins: [sandboxedjs()] });
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
For anything else, `sandboxedjs/server` exports `serveSW()` (Fetch-style hosts) and
|
|
606
|
+
`serveSWNode()` (Express, Fastify, `node:http`). Skip it with
|
|
607
|
+
`createContainer({ browser: { serviceWorker: false } })`, which also disables preview iframes.
|
|
608
|
+
|
|
609
|
+
You can still boot the pod yourself and pass it as `pod` when you need control over Nodepod's
|
|
610
|
+
own options.
|
|
611
|
+
|
|
612
|
+
**Verified in a browser.** Booted under Vite, the following run:
|
|
613
|
+
|
|
614
|
+
```
|
|
615
|
+
$ uname -a Linux sandbox 5.10.0 (sandboxedjs@nodepod) … x86_64 GNU/Linux
|
|
616
|
+
$ ls -la /app the mounted files
|
|
617
|
+
$ grep beta … beta
|
|
618
|
+
$ node /app/hello.js node runtime says hi
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Shell, coreutils, the virtual filesystem and the Node runtime all work. Boot costs about ten
|
|
622
|
+
seconds, nearly all of it Nodepod starting.
|
|
623
|
+
|
|
624
|
+
**What does not work in a browser:**
|
|
598
625
|
|
|
599
626
|
- `copyIn()` / `copyOut()` — they read and write the host filesystem, which does not exist.
|
|
600
|
-
- `expose()` — it opens a real `node:http` listener.
|
|
601
|
-
|
|
627
|
+
- `expose()` — it opens a real `node:http` listener. Use the service worker and a preview iframe
|
|
628
|
+
to reach an in-container server instead of a host port.
|
|
629
|
+
- `python3` — see below.
|
|
602
630
|
- The `sandboxedjs` CLI, obviously.
|
|
603
631
|
|
|
604
|
-
Those
|
|
632
|
+
Those use dynamic imports, so they only fail if you call them.
|
|
633
|
+
|
|
634
|
+
### Python in a browser
|
|
635
|
+
|
|
636
|
+
MicroPython does not currently start in a browser. Two separate things are involved.
|
|
637
|
+
|
|
638
|
+
The first is ours and is fixed: Emscripten resolves `micropython.wasm` against the loader's own
|
|
639
|
+
directory, which is right in Node and wrong once a bundler has rewritten the loader into a hashed
|
|
640
|
+
chunk — the guess lands on a path the dev server answers with `index.html`, and instantiation
|
|
641
|
+
dies on `expected magic word 00 61 73 6d, found 3c 21 64 6f` (the first bytes of `<!doctype`).
|
|
642
|
+
Pass the real URL and that goes away:
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
import wasm from "@micropython/micropython-webassembly-pyscript/micropython.wasm?url";
|
|
646
|
+
|
|
647
|
+
await createContainer({ pod, python: { wasmUrl: wasm } });
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
`?url` is Vite's spelling; every bundler has its own. `configurePython({ wasmUrl })` does the
|
|
651
|
+
same thing outside a container.
|
|
652
|
+
|
|
653
|
+
The second is upstream and is not fixed. With the correct binary loaded, instantiation fails on
|
|
654
|
+
|
|
655
|
+
```
|
|
656
|
+
LinkError: Import #45 "env" "__syscall_poll_nonblocking": function import requires a callable
|
|
657
|
+
```
|
|
605
658
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
a
|
|
659
|
+
The symbol appears nowhere in the shipped `micropython.mjs`, so the published glue and `.wasm`
|
|
660
|
+
disagree about their import table. Node is unaffected — the Python tests pass there — and nothing
|
|
661
|
+
in this package can supply a symbol the loader never declares. Treat `python3` as Node-only until
|
|
662
|
+
the upstream package ships a matching pair.
|
|
609
663
|
|
|
610
664
|
## Uploading files
|
|
611
665
|
|
package/dist/index.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var nodepod = require('@scelar/nodepod');
|
|
6
6
|
var acorn = require('acorn');
|
|
7
7
|
|
|
8
8
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -8553,6 +8553,15 @@ async function nodeBuiltin(name) {
|
|
|
8553
8553
|
specifier
|
|
8554
8554
|
);
|
|
8555
8555
|
}
|
|
8556
|
+
async function nodeOnlyModule(specifier) {
|
|
8557
|
+
const parts = specifier.split("/");
|
|
8558
|
+
const runtimeSpecifier = parts.join("/");
|
|
8559
|
+
return await import(
|
|
8560
|
+
/* @vite-ignore */
|
|
8561
|
+
/* webpackIgnore: true */
|
|
8562
|
+
runtimeSpecifier
|
|
8563
|
+
);
|
|
8564
|
+
}
|
|
8556
8565
|
var zlibPromise = null;
|
|
8557
8566
|
function nodeZlib() {
|
|
8558
8567
|
zlibPromise ??= nodeBuiltin("zlib");
|
|
@@ -8755,7 +8764,7 @@ function installEsbuildRuntime() {
|
|
|
8755
8764
|
async function install() {
|
|
8756
8765
|
const workerPath = await buildPatchedWorker();
|
|
8757
8766
|
if (!workerPath) return;
|
|
8758
|
-
const { createNodeHost, setRuntimeHost } = await
|
|
8767
|
+
const { createNodeHost, setRuntimeHost } = await nodeOnlyModule("@scelar/nodepod/headless");
|
|
8759
8768
|
setRuntimeHost(createNodeHost({ workerPath }));
|
|
8760
8769
|
}
|
|
8761
8770
|
async function buildPatchedWorker() {
|
|
@@ -22144,6 +22153,10 @@ init_path();
|
|
|
22144
22153
|
var PYTHON_VERSION = "3.4.0";
|
|
22145
22154
|
var MICROPYTHON_BANNER = "MicroPython v1.28.0 on 2026-04-06; SandboxedJS with Emscripten";
|
|
22146
22155
|
var loaderPromise = null;
|
|
22156
|
+
var wasmUrl;
|
|
22157
|
+
function configurePython(options = {}) {
|
|
22158
|
+
if (options.wasmUrl !== void 0) wasmUrl = options.wasmUrl;
|
|
22159
|
+
}
|
|
22147
22160
|
async function getLoader() {
|
|
22148
22161
|
if (!loaderPromise) {
|
|
22149
22162
|
loaderPromise = import('@micropython/micropython-webassembly-pyscript/micropython.mjs').then(
|
|
@@ -22166,6 +22179,7 @@ async function createInterpreter(ctx, opts) {
|
|
|
22166
22179
|
stdout: opts.stdout,
|
|
22167
22180
|
stderr: opts.stderr,
|
|
22168
22181
|
...opts.stdin ? { stdin: opts.stdin } : {},
|
|
22182
|
+
...wasmUrl ? { url: wasmUrl } : {},
|
|
22169
22183
|
linebuffer: false,
|
|
22170
22184
|
heapsize: opts.heapsize ?? 64 * 1024 * 1024
|
|
22171
22185
|
});
|
|
@@ -22890,7 +22904,7 @@ function splitPackageSpec(spec) {
|
|
|
22890
22904
|
return { name: spec.slice(0, at), version: spec.slice(at + 1) };
|
|
22891
22905
|
}
|
|
22892
22906
|
async function installPackages(ctx, specs, opts) {
|
|
22893
|
-
const installer = opts.cwd === "/" ? ctx.kernel.pod.packages : new
|
|
22907
|
+
const installer = opts.cwd === "/" ? ctx.kernel.pod.packages : new nodepod.DependencyInstaller(ctx.vfs.volume, { cwd: opts.cwd });
|
|
22894
22908
|
const onProgress = (message) => {
|
|
22895
22909
|
if (!opts.quiet) ctx.stderr.write(message.endsWith("\n") ? message : message + "\n");
|
|
22896
22910
|
};
|
|
@@ -23182,6 +23196,12 @@ function packageBinaries(ctx, root, packageName) {
|
|
|
23182
23196
|
}
|
|
23183
23197
|
return [];
|
|
23184
23198
|
}
|
|
23199
|
+
var CLI_MOVED_TO = {
|
|
23200
|
+
tailwindcss: "@tailwindcss/cli",
|
|
23201
|
+
postcss: "postcss-cli",
|
|
23202
|
+
autoprefixer: "postcss-cli",
|
|
23203
|
+
sass: "sass-embedded"
|
|
23204
|
+
};
|
|
23185
23205
|
function findLocalBin(ctx, name) {
|
|
23186
23206
|
let dir3 = ctx.cwd;
|
|
23187
23207
|
for (let i = 0; i < 64; i++) {
|
|
@@ -23288,6 +23308,14 @@ unless the container was created with network: { allowOutbound: true }.`,
|
|
|
23288
23308
|
const chosen = binaries.includes(command) ? command : binaries[0];
|
|
23289
23309
|
if (chosen === void 0) {
|
|
23290
23310
|
ctx.warn(`could not determine executable to run: ${packageName} provides no binary`);
|
|
23311
|
+
const replacement = CLI_MOVED_TO[packageName];
|
|
23312
|
+
if (replacement !== void 0) {
|
|
23313
|
+
ctx.warn(`'${packageName}' ships no CLI; its command lives in '${replacement}'.`);
|
|
23314
|
+
ctx.warn(`Try: npx ${replacement} ${rest.join(" ")}`.trimEnd());
|
|
23315
|
+
} else {
|
|
23316
|
+
ctx.warn(`'${packageName}' is installed but declares no "bin" entry.`);
|
|
23317
|
+
ctx.warn("Check which package provides the command, or use `npx -p <pkg> <command>`.");
|
|
23318
|
+
}
|
|
23291
23319
|
return 127;
|
|
23292
23320
|
}
|
|
23293
23321
|
command = chosen;
|
|
@@ -23864,14 +23892,9 @@ var Container = class _Container {
|
|
|
23864
23892
|
}
|
|
23865
23893
|
// ── boot ──────────────────────────────────────────────────────────────────
|
|
23866
23894
|
static async create(opts = {}) {
|
|
23867
|
-
if (
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
serviceWorker: false,
|
|
23871
|
-
env: opts.env ?? {},
|
|
23872
|
-
workdir: opts.cwd ?? "/",
|
|
23873
|
-
...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
|
|
23874
|
-
});
|
|
23895
|
+
if (opts.python) configurePython(opts.python);
|
|
23896
|
+
if (!opts.pod && isNodeRuntime()) await installEsbuildRuntime();
|
|
23897
|
+
const pod = opts.pod ?? (isNodeRuntime() ? await bootHeadlessPod(opts) : await bootBrowserPod(opts));
|
|
23875
23898
|
const kernel = new Kernel({
|
|
23876
23899
|
pod,
|
|
23877
23900
|
hostname: opts.hostname ?? "sandbox",
|
|
@@ -24293,6 +24316,30 @@ function normalizeHeaders(headers) {
|
|
|
24293
24316
|
async function createContainer(opts = {}) {
|
|
24294
24317
|
return Container.create(opts);
|
|
24295
24318
|
}
|
|
24319
|
+
function isNodeRuntime() {
|
|
24320
|
+
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
24321
|
+
}
|
|
24322
|
+
async function bootBrowserPod(opts) {
|
|
24323
|
+
const { Nodepod } = await import('@scelar/nodepod');
|
|
24324
|
+
return Nodepod.boot({
|
|
24325
|
+
env: opts.env ?? {},
|
|
24326
|
+
workdir: opts.cwd ?? "/",
|
|
24327
|
+
...opts.browser ?? {},
|
|
24328
|
+
...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
|
|
24329
|
+
});
|
|
24330
|
+
}
|
|
24331
|
+
async function bootHeadlessPod(opts) {
|
|
24332
|
+
const { Nodepod } = await nodeOnlyModule(
|
|
24333
|
+
"@scelar/nodepod/headless"
|
|
24334
|
+
);
|
|
24335
|
+
return Nodepod.boot({
|
|
24336
|
+
headless: true,
|
|
24337
|
+
serviceWorker: false,
|
|
24338
|
+
env: opts.env ?? {},
|
|
24339
|
+
workdir: opts.cwd ?? "/",
|
|
24340
|
+
...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
|
|
24341
|
+
});
|
|
24342
|
+
}
|
|
24296
24343
|
|
|
24297
24344
|
// src/container/terminal.ts
|
|
24298
24345
|
init_path();
|
|
@@ -24771,6 +24818,7 @@ exports.braceExpand = braceExpand;
|
|
|
24771
24818
|
exports.buildRootfs = buildRootfs;
|
|
24772
24819
|
exports.builtinNames = builtinNames;
|
|
24773
24820
|
exports.captureStdio = captureStdio;
|
|
24821
|
+
exports.configurePython = configurePython;
|
|
24774
24822
|
exports.createContainer = createContainer;
|
|
24775
24823
|
exports.createContext = createContext;
|
|
24776
24824
|
exports.default = src_default;
|