sandboxedjs 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -580,8 +580,10 @@ resolve their implementation at call time (`node:zlib`/`node:crypto` on Node,
580
580
  builtin in when the module loads and a bundler will not fail on it.
581
581
 
582
582
  **You supply the pod.** The default boot path uses `@scelar/nodepod/headless`, which installs a
583
- `worker_threads` host. In a browser you boot Nodepod's browser build yourself it needs a
584
- service worker, per Nodepod's own setup docs and hand the instance over:
583
+ `worker_threads` host. That entry is now loaded lazily, behind a runtime-built specifier, so a
584
+ browser bundle never pulls `node:worker_threads` in through it. In a browser you boot Nodepod's
585
+ browser build yourself — it needs a service worker, per Nodepod's own setup docs — and hand the
586
+ instance over:
585
587
 
586
588
  ```ts
587
589
  import { Nodepod } from "@scelar/nodepod";
@@ -594,18 +596,58 @@ await box.exec("ls -la /"); // shell + coreutils
594
596
  await box.exec("node app.js"); // Node, through Nodepod's browser engine
595
597
  ```
596
598
 
597
- **What will not work in a browser:**
599
+ **Verified in a browser.** Booted under Vite with Nodepod's browser build, the following run:
600
+
601
+ ```
602
+ $ uname -a Linux sandbox 5.10.0 (sandboxedjs@nodepod) … x86_64 GNU/Linux
603
+ $ ls -la /app the mounted files
604
+ $ wc -l /app/data.txt 3 /app/data.txt
605
+ $ grep beta … beta
606
+ $ node /app/hello.js node runtime says hi
607
+ ```
608
+
609
+ Shell, coreutils, the virtual filesystem and the Node runtime all work. Boot costs about ten
610
+ seconds, nearly all of it Nodepod starting.
611
+
612
+ **What does not work in a browser:**
598
613
 
599
614
  - `copyIn()` / `copyOut()` — they read and write the host filesystem, which does not exist.
600
615
  - `expose()` — it opens a real `node:http` listener. In a browser you use Nodepod's service
601
616
  worker and preview iframe to reach an in-container server instead of a host port.
617
+ - `python3` — see below.
602
618
  - The `sandboxedjs` CLI, obviously.
603
619
 
604
- Those three use dynamic imports, so they only fail if you call them.
620
+ Those use dynamic imports, so they only fail if you call them.
621
+
622
+ ### Python in a browser
623
+
624
+ MicroPython does not currently start in a browser. Two separate things are involved.
625
+
626
+ The first is ours and is fixed: Emscripten resolves `micropython.wasm` against the loader's own
627
+ directory, which is right in Node and wrong once a bundler has rewritten the loader into a hashed
628
+ chunk — the guess lands on a path the dev server answers with `index.html`, and instantiation
629
+ dies on `expected magic word 00 61 73 6d, found 3c 21 64 6f` (the first bytes of `<!doctype`).
630
+ Pass the real URL and that goes away:
631
+
632
+ ```ts
633
+ import wasm from "@micropython/micropython-webassembly-pyscript/micropython.wasm?url";
634
+
635
+ await createContainer({ pod, python: { wasmUrl: wasm } });
636
+ ```
637
+
638
+ `?url` is Vite's spelling; every bundler has its own. `configurePython({ wasmUrl })` does the
639
+ same thing outside a container.
640
+
641
+ The second is upstream and is not fixed. With the correct binary loaded, instantiation fails on
642
+
643
+ ```
644
+ LinkError: Import #45 "env" "__syscall_poll_nonblocking": function import requires a callable
645
+ ```
605
646
 
606
- **Not yet verified.** I have not run this in a browser end to end. The Node-side blockers are
607
- removed and the seam is there, but treat browser support as "should work, unproven" rather than
608
- a tested claim.
647
+ The symbol appears nowhere in the shipped `micropython.mjs`, so the published glue and `.wasm`
648
+ disagree about their import table. Node is unaffected the Python tests pass there and nothing
649
+ in this package can supply a symbol the loader never declares. Treat `python3` as Node-only until
650
+ the upstream package ships a matching pair.
609
651
 
610
652
  ## Uploading files
611
653
 
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var headless = require('@scelar/nodepod/headless');
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 import('@scelar/nodepod/headless');
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 headless.DependencyInstaller(ctx.vfs.volume, { cwd: opts.cwd });
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 = {}) {
23895
+ if (opts.python) configurePython(opts.python);
23867
23896
  if (!opts.pod) await installEsbuildRuntime();
23868
- const pod = opts.pod ?? await headless.Nodepod.boot({
23869
- headless: true,
23870
- serviceWorker: false,
23871
- env: opts.env ?? {},
23872
- workdir: opts.cwd ?? "/",
23873
- ...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
23874
- });
23897
+ const pod = opts.pod ?? await bootHeadlessPod(opts);
23875
23898
  const kernel = new Kernel({
23876
23899
  pod,
23877
23900
  hostname: opts.hostname ?? "sandbox",
@@ -24293,6 +24316,18 @@ function normalizeHeaders(headers) {
24293
24316
  async function createContainer(opts = {}) {
24294
24317
  return Container.create(opts);
24295
24318
  }
24319
+ async function bootHeadlessPod(opts) {
24320
+ const { Nodepod } = await nodeOnlyModule(
24321
+ "@scelar/nodepod/headless"
24322
+ );
24323
+ return Nodepod.boot({
24324
+ headless: true,
24325
+ serviceWorker: false,
24326
+ env: opts.env ?? {},
24327
+ workdir: opts.cwd ?? "/",
24328
+ ...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
24329
+ });
24330
+ }
24296
24331
 
24297
24332
  // src/container/terminal.ts
24298
24333
  init_path();
@@ -24771,6 +24806,7 @@ exports.braceExpand = braceExpand;
24771
24806
  exports.buildRootfs = buildRootfs;
24772
24807
  exports.builtinNames = builtinNames;
24773
24808
  exports.captureStdio = captureStdio;
24809
+ exports.configurePython = configurePython;
24774
24810
  exports.createContainer = createContainer;
24775
24811
  exports.createContext = createContext;
24776
24812
  exports.default = src_default;