sandboxedjs 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1435,6 +1435,21 @@ interface ContainerOptions {
1435
1435
  onStderr?: (chunk: string) => void;
1436
1436
  /** Invoked when an in-container HTTP server starts listening. */
1437
1437
  onServerReady?: (port: number, url: string) => void;
1438
+ /**
1439
+ * Supply the Nodepod instance instead of letting the container boot one.
1440
+ *
1441
+ * The default path imports `@scelar/nodepod/headless`, which installs a
1442
+ * `worker_threads` host and is the right choice on Node. In a browser you
1443
+ * boot Nodepod's browser build yourself (it needs a service worker) and hand
1444
+ * the instance over:
1445
+ *
1446
+ * ```ts
1447
+ * import { Nodepod } from "@scelar/nodepod";
1448
+ * const pod = await Nodepod.boot({ ... });
1449
+ * const box = await createContainer({ pod });
1450
+ * ```
1451
+ */
1452
+ pod?: Nodepod;
1438
1453
  }
1439
1454
  interface ExecOptions {
1440
1455
  cwd?: string;
package/dist/index.d.ts CHANGED
@@ -1435,6 +1435,21 @@ interface ContainerOptions {
1435
1435
  onStderr?: (chunk: string) => void;
1436
1436
  /** Invoked when an in-container HTTP server starts listening. */
1437
1437
  onServerReady?: (port: number, url: string) => void;
1438
+ /**
1439
+ * Supply the Nodepod instance instead of letting the container boot one.
1440
+ *
1441
+ * The default path imports `@scelar/nodepod/headless`, which installs a
1442
+ * `worker_threads` host and is the right choice on Node. In a browser you
1443
+ * boot Nodepod's browser build yourself (it needs a service worker) and hand
1444
+ * the instance over:
1445
+ *
1446
+ * ```ts
1447
+ * import { Nodepod } from "@scelar/nodepod";
1448
+ * const pod = await Nodepod.boot({ ... });
1449
+ * const box = await createContainer({ pod });
1450
+ * ```
1451
+ */
1452
+ pod?: Nodepod;
1438
1453
  }
1439
1454
  interface ExecOptions {
1440
1455
  cwd?: string;
package/dist/index.js CHANGED
@@ -1,6 +1,4 @@
1
1
  import { Nodepod, DependencyInstaller } from '@scelar/nodepod/headless';
2
- import { gzipSync, gunzipSync, inflateRawSync, inflateSync, deflateSync } from 'zlib';
3
- import { createHash, randomUUID } from 'crypto';
4
2
 
5
3
  var __defProp = Object.defineProperty;
6
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -15133,6 +15131,206 @@ var commands5 = [grep, find, xargs, diff, cmp];
15133
15131
  // src/bin/archive.ts
15134
15132
  init_mode();
15135
15133
  init_path();
15134
+
15135
+ // src/util/binary.ts
15136
+ var isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
15137
+ async function nodeBuiltin(name) {
15138
+ const specifier = `node:${name}`;
15139
+ return await import(
15140
+ /* @vite-ignore */
15141
+ /* webpackIgnore: true */
15142
+ specifier
15143
+ );
15144
+ }
15145
+ var zlibPromise = null;
15146
+ function nodeZlib() {
15147
+ zlibPromise ??= nodeBuiltin("zlib");
15148
+ return zlibPromise;
15149
+ }
15150
+ async function throughStream(data, stream) {
15151
+ const source = new Blob([data]).stream();
15152
+ const piped = source.pipeThrough(stream);
15153
+ return new Uint8Array(await new Response(piped).arrayBuffer());
15154
+ }
15155
+ async function gzip(data) {
15156
+ if (isNode) return new Uint8Array((await nodeZlib()).gzipSync(data));
15157
+ return throughStream(data, new CompressionStream("gzip"));
15158
+ }
15159
+ async function gunzip(data) {
15160
+ if (isNode) return new Uint8Array((await nodeZlib()).gunzipSync(data));
15161
+ return throughStream(data, new DecompressionStream("gzip"));
15162
+ }
15163
+ async function deflate(data) {
15164
+ if (isNode) return new Uint8Array((await nodeZlib()).deflateSync(data));
15165
+ return throughStream(data, new CompressionStream("deflate"));
15166
+ }
15167
+ async function inflate(data) {
15168
+ if (isNode) return new Uint8Array((await nodeZlib()).inflateSync(data));
15169
+ return throughStream(data, new DecompressionStream("deflate"));
15170
+ }
15171
+ async function inflateRaw(data) {
15172
+ if (isNode) return new Uint8Array((await nodeZlib()).inflateRawSync(data));
15173
+ return throughStream(data, new DecompressionStream("deflate-raw"));
15174
+ }
15175
+ var cryptoPromise = null;
15176
+ function nodeCrypto() {
15177
+ cryptoPromise ??= nodeBuiltin("crypto");
15178
+ return cryptoPromise;
15179
+ }
15180
+ var SUBTLE_NAMES = {
15181
+ sha1: "SHA-1",
15182
+ sha256: "SHA-256",
15183
+ sha384: "SHA-384",
15184
+ sha512: "SHA-512"
15185
+ };
15186
+ var UnsupportedAlgorithmError = class extends Error {
15187
+ constructor(algorithm) {
15188
+ super(`${algorithm} is not available in this environment`);
15189
+ this.name = "UnsupportedAlgorithmError";
15190
+ }
15191
+ };
15192
+ async function digestHex(algorithm, data) {
15193
+ if (isNode) {
15194
+ const { createHash } = await nodeCrypto();
15195
+ return createHash(algorithm).update(data).digest("hex");
15196
+ }
15197
+ if (algorithm === "md5") return md5Hex(data);
15198
+ const name = SUBTLE_NAMES[algorithm];
15199
+ if (!name) throw new UnsupportedAlgorithmError(algorithm);
15200
+ const buffer = await crypto.subtle.digest(name, data);
15201
+ return toHex(new Uint8Array(buffer));
15202
+ }
15203
+ async function randomUuid() {
15204
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
15205
+ const { randomUUID } = await nodeCrypto();
15206
+ return randomUUID();
15207
+ }
15208
+ function toHex(bytes) {
15209
+ let out = "";
15210
+ for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
15211
+ return out;
15212
+ }
15213
+ function md5Hex(input) {
15214
+ const S = [
15215
+ 7,
15216
+ 12,
15217
+ 17,
15218
+ 22,
15219
+ 7,
15220
+ 12,
15221
+ 17,
15222
+ 22,
15223
+ 7,
15224
+ 12,
15225
+ 17,
15226
+ 22,
15227
+ 7,
15228
+ 12,
15229
+ 17,
15230
+ 22,
15231
+ 5,
15232
+ 9,
15233
+ 14,
15234
+ 20,
15235
+ 5,
15236
+ 9,
15237
+ 14,
15238
+ 20,
15239
+ 5,
15240
+ 9,
15241
+ 14,
15242
+ 20,
15243
+ 5,
15244
+ 9,
15245
+ 14,
15246
+ 20,
15247
+ 4,
15248
+ 11,
15249
+ 16,
15250
+ 23,
15251
+ 4,
15252
+ 11,
15253
+ 16,
15254
+ 23,
15255
+ 4,
15256
+ 11,
15257
+ 16,
15258
+ 23,
15259
+ 4,
15260
+ 11,
15261
+ 16,
15262
+ 23,
15263
+ 6,
15264
+ 10,
15265
+ 15,
15266
+ 21,
15267
+ 6,
15268
+ 10,
15269
+ 15,
15270
+ 21,
15271
+ 6,
15272
+ 10,
15273
+ 15,
15274
+ 21,
15275
+ 6,
15276
+ 10,
15277
+ 15,
15278
+ 21
15279
+ ];
15280
+ const K = new Uint32Array(64);
15281
+ for (let i = 0; i < 64; i++) K[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32);
15282
+ const bitLength = input.length * 8;
15283
+ const padded = new Uint8Array(((input.length + 8 >> 6) + 1) * 64);
15284
+ padded.set(input);
15285
+ padded[input.length] = 128;
15286
+ const view = new DataView(padded.buffer);
15287
+ view.setUint32(padded.length - 8, bitLength >>> 0, true);
15288
+ view.setUint32(padded.length - 4, Math.floor(bitLength / 2 ** 32), true);
15289
+ let a0 = 1732584193;
15290
+ let b0 = 4023233417;
15291
+ let c0 = 2562383102;
15292
+ let d0 = 271733878;
15293
+ for (let chunk = 0; chunk < padded.length; chunk += 64) {
15294
+ const M = new Uint32Array(16);
15295
+ for (let i = 0; i < 16; i++) M[i] = view.getUint32(chunk + i * 4, true);
15296
+ let [a, b, c, d] = [a0, b0, c0, d0];
15297
+ for (let i = 0; i < 64; i++) {
15298
+ let f;
15299
+ let g;
15300
+ if (i < 16) {
15301
+ f = b & c | ~b & d;
15302
+ g = i;
15303
+ } else if (i < 32) {
15304
+ f = d & b | ~d & c;
15305
+ g = (5 * i + 1) % 16;
15306
+ } else if (i < 48) {
15307
+ f = b ^ c ^ d;
15308
+ g = (3 * i + 5) % 16;
15309
+ } else {
15310
+ f = c ^ (b | ~d);
15311
+ g = 7 * i % 16;
15312
+ }
15313
+ const tmp = d;
15314
+ d = c;
15315
+ c = b;
15316
+ const sum = a + f + K[i] + M[g] >>> 0;
15317
+ b = b + (sum << S[i] | sum >>> 32 - S[i]) >>> 0;
15318
+ a = tmp;
15319
+ }
15320
+ a0 = a0 + a >>> 0;
15321
+ b0 = b0 + b >>> 0;
15322
+ c0 = c0 + c >>> 0;
15323
+ d0 = d0 + d >>> 0;
15324
+ }
15325
+ const out = new Uint8Array(16);
15326
+ new DataView(out.buffer).setUint32(0, a0, true);
15327
+ new DataView(out.buffer).setUint32(4, b0, true);
15328
+ new DataView(out.buffer).setUint32(8, c0, true);
15329
+ new DataView(out.buffer).setUint32(12, d0, true);
15330
+ return toHex(out);
15331
+ }
15332
+
15333
+ // src/bin/archive.ts
15136
15334
  var BLOCK = 512;
15137
15335
  var encoder4 = new TextEncoder();
15138
15336
  var decoder6 = new TextDecoder();
@@ -15290,7 +15488,7 @@ var tar = defineCommand({
15290
15488
  };
15291
15489
  for (const operand of args.positional) add(resolve(baseDir, operand), operand);
15292
15490
  let bytes2 = createTar(entries2);
15293
- if (compressed) bytes2 = new Uint8Array(gzipSync(bytes2));
15491
+ if (compressed) bytes2 = await gzip(bytes2);
15294
15492
  if (archive === void 0 || archive === "-") ctx.write(bytes2);
15295
15493
  else ctx.vfs.writeFile(ctx.path(archive), bytes2, { cred: ctx.cred, mode: 420 });
15296
15494
  return 0;
@@ -15304,7 +15502,7 @@ var tar = defineCommand({
15304
15502
  return ctx.reportError(e, archive);
15305
15503
  }
15306
15504
  }
15307
- if (bytes[0] === 31 && bytes[1] === 139) bytes = new Uint8Array(gunzipSync(bytes));
15505
+ if (bytes[0] === 31 && bytes[1] === 139) bytes = await gunzip(bytes);
15308
15506
  const entries = readTar(bytes);
15309
15507
  const strip = args.num("strip-components", 0);
15310
15508
  if (args.has("list")) {
@@ -15350,7 +15548,7 @@ var tar = defineCommand({
15350
15548
  return 2;
15351
15549
  }
15352
15550
  });
15353
- var gzip = defineCommand({
15551
+ var gzip2 = defineCommand({
15354
15552
  name: "gzip",
15355
15553
  path: "/bin/gzip",
15356
15554
  summary: "compress files",
@@ -15373,28 +15571,28 @@ var gzip = defineCommand({
15373
15571
  toStdout,
15374
15572
  keep: args.has("keep") || ctx.name === "zcat",
15375
15573
  suffix: ".gz",
15376
- compress: (data) => new Uint8Array(gzipSync(data)),
15377
- expand: (data) => new Uint8Array(gunzipSync(data))
15574
+ compress: (data) => gzip(data),
15575
+ expand: (data) => gunzip(data)
15378
15576
  });
15379
15577
  }
15380
15578
  });
15381
- var gunzip = defineCommand({
15579
+ var gunzip2 = defineCommand({
15382
15580
  name: "gunzip",
15383
15581
  path: "/bin/gunzip",
15384
15582
  summary: "decompress files",
15385
- run: (ctx) => gzip.run(ctx)
15583
+ run: (ctx) => gzip2.run(ctx)
15386
15584
  });
15387
15585
  var zcat = defineCommand({
15388
15586
  name: "zcat",
15389
15587
  path: "/bin/zcat",
15390
15588
  summary: "decompress files to standard output",
15391
- run: (ctx) => gzip.run(ctx)
15589
+ run: (ctx) => gzip2.run(ctx)
15392
15590
  });
15393
15591
  async function runCompressor(ctx, operands, opts) {
15394
15592
  if (operands.length === 0) {
15395
15593
  const data = await ctx.stdin.readAll();
15396
15594
  try {
15397
- ctx.write(opts.decompress ? opts.expand(data) : opts.compress(data));
15595
+ ctx.write(opts.decompress ? await opts.expand(data) : await opts.compress(data));
15398
15596
  return 0;
15399
15597
  } catch (e) {
15400
15598
  return ctx.fail(e instanceof Error ? e.message : String(e));
@@ -15411,7 +15609,7 @@ async function runCompressor(ctx, operands, opts) {
15411
15609
  continue;
15412
15610
  }
15413
15611
  try {
15414
- const result = opts.decompress ? opts.expand(data) : opts.compress(data);
15612
+ const result = opts.decompress ? await opts.expand(data) : await opts.compress(data);
15415
15613
  if (opts.toStdout) {
15416
15614
  ctx.write(result);
15417
15615
  continue;
@@ -15437,12 +15635,14 @@ var zlibCompress = defineCommand({
15437
15635
  toStdout: args.has("stdout"),
15438
15636
  keep: false,
15439
15637
  suffix: ".Z",
15440
- compress: (data) => new Uint8Array(deflateSync(data)),
15441
- expand: (data) => new Uint8Array(inflateSync(data))
15638
+ compress: (data) => deflate(data),
15639
+ expand: (data) => inflate(data)
15442
15640
  });
15443
15641
  }
15444
15642
  });
15445
- var commands6 = [tar, gzip, gunzip, zcat, zlibCompress];
15643
+ var commands6 = [tar, gzip2, gunzip2, zcat, zlibCompress];
15644
+
15645
+ // src/bin/hash.ts
15446
15646
  function makeSum(name, algorithm, path) {
15447
15647
  return defineCommand({
15448
15648
  name,
@@ -15461,7 +15661,7 @@ function makeSum(name, algorithm, path) {
15461
15661
  if (args.has("check")) return checkSums(ctx, algorithm, args.positional, args.has("quiet") || args.has("status"));
15462
15662
  const { sources, ok } = await readInputs(ctx, args.positional, { stdinName: "-" });
15463
15663
  for (const source of sources) {
15464
- const digest = createHash(algorithm).update(source.bytes).digest("hex");
15664
+ const digest = await digestHex(algorithm, source.bytes);
15465
15665
  if (args.has("tag")) ctx.line(`${algorithm.toUpperCase()} (${source.name}) = ${digest}`);
15466
15666
  else ctx.line(`${digest} ${source.name}`);
15467
15667
  }
@@ -15482,7 +15682,7 @@ async function checkSums(ctx, algorithm, files, quiet) {
15482
15682
  const [, expected, name] = m;
15483
15683
  try {
15484
15684
  const bytes = ctx.vfs.readFile(ctx.path(name), ctx.cred);
15485
- const actual = createHash(algorithm).update(bytes).digest("hex");
15685
+ const actual = await digestHex(algorithm, bytes);
15486
15686
  if (actual === expected.toLowerCase()) {
15487
15687
  if (!quiet) ctx.line(`${name}: OK`);
15488
15688
  } else {
@@ -15613,8 +15813,8 @@ var uuidgen = defineCommand({
15613
15813
  name: "uuidgen",
15614
15814
  path: "/usr/bin/uuidgen",
15615
15815
  summary: "create a new UUID value",
15616
- run(ctx) {
15617
- ctx.line(randomUUID());
15816
+ async run(ctx) {
15817
+ ctx.line(await randomUuid());
15618
15818
  return 0;
15619
15819
  }
15620
15820
  });
@@ -17688,7 +17888,7 @@ var pip = defineCommand({
17688
17888
  }
17689
17889
  const data = new Uint8Array(await (await fetch(wheel.url)).arrayBuffer());
17690
17890
  ctx.line(` Downloading ${wheel.filename} (${Math.round(data.length / 1024)} kB)`);
17691
- installWheel(ctx, siteDir, data, name, chosen);
17891
+ await installWheel(ctx, siteDir, data, name, chosen);
17692
17892
  ctx.line(`Successfully installed ${name}-${chosen}`);
17693
17893
  } catch (e) {
17694
17894
  ctx.warn(`ERROR: Could not install ${spec}: ${e instanceof Error ? e.message : String(e)}`);
@@ -17698,8 +17898,8 @@ var pip = defineCommand({
17698
17898
  return status;
17699
17899
  }
17700
17900
  });
17701
- function installWheel(ctx, siteDir, data, name, version) {
17702
- const entries = readZip(data);
17901
+ async function installWheel(ctx, siteDir, data, name, version) {
17902
+ const entries = await readZip(data);
17703
17903
  for (const entry of entries) {
17704
17904
  if (entry.name.endsWith("/")) continue;
17705
17905
  const target = join(siteDir, entry.name);
@@ -17713,7 +17913,7 @@ function installWheel(ctx, siteDir, data, name, version) {
17713
17913
  Version: ${version}
17714
17914
  `, { cred: ctx.cred });
17715
17915
  }
17716
- function readZip(data) {
17916
+ async function readZip(data) {
17717
17917
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
17718
17918
  const entries = [];
17719
17919
  let eocd = -1;
@@ -17739,7 +17939,7 @@ function readZip(data) {
17739
17939
  const localExtraLength = view.getUint16(localOffset + 28, true);
17740
17940
  const dataStart = localOffset + 30 + localNameLength + localExtraLength;
17741
17941
  const raw = data.subarray(dataStart, dataStart + compressedSize);
17742
- entries.push({ name, data: method === 0 ? raw.slice() : new Uint8Array(inflateRawSync(raw)) });
17942
+ entries.push({ name, data: method === 0 ? raw.slice() : await inflateRaw(raw) });
17743
17943
  offset += 46 + nameLength + extraLength + commentLength;
17744
17944
  }
17745
17945
  return entries;
@@ -17770,9 +17970,17 @@ function findProjectRoot(ctx) {
17770
17970
  function writeManifest(ctx, path, manifest) {
17771
17971
  ctx.vfs.writeFile(path, JSON.stringify(manifest, null, 2) + "\n", { cred: ctx.cred, mode: 420 });
17772
17972
  }
17973
+ function splitPackageSpec(spec) {
17974
+ const at = spec.lastIndexOf("@");
17975
+ if (at <= 0) return { name: spec };
17976
+ return { name: spec.slice(0, at), version: spec.slice(at + 1) };
17977
+ }
17773
17978
  async function installPackages(ctx, specs, opts) {
17774
17979
  const installer = opts.cwd === "/" ? ctx.kernel.pod.packages : new DependencyInstaller(ctx.vfs.volume, { cwd: opts.cwd });
17775
- const onProgress = (message) => ctx.stderr.write(message.endsWith("\n") ? message : message + "\n");
17980
+ const onProgress = (message) => {
17981
+ if (!opts.quiet) ctx.stderr.write(message.endsWith("\n") ? message : message + "\n");
17982
+ };
17983
+ const note = (text) => opts.quiet ? ctx.stderr.write(text + "\n") : ctx.line(text);
17776
17984
  if (!ctx.kernel.net.options.allowOutbound) {
17777
17985
  ctx.warn("npm error code ENOTFOUND");
17778
17986
  ctx.warn("npm error network request to https://registry.npmjs.org failed");
@@ -17782,7 +17990,7 @@ async function installPackages(ctx, specs, opts) {
17782
17990
  }
17783
17991
  try {
17784
17992
  if (specs.length === 0) {
17785
- ctx.line(`npm install (project at ${opts.cwd})`);
17993
+ note(`npm install (project at ${opts.cwd})`);
17786
17994
  await installer.installFromManifest(join(opts.cwd, "package.json"), {
17787
17995
  withDevDeps: true,
17788
17996
  onProgress,
@@ -17790,11 +17998,8 @@ async function installPackages(ctx, specs, opts) {
17790
17998
  });
17791
17999
  } else {
17792
18000
  for (const spec of specs) {
17793
- const at = spec.lastIndexOf("@");
17794
- const scoped = spec.startsWith("@");
17795
- const name = at > 0 && !(scoped && at === 0) ? spec.slice(0, at) : spec;
17796
- const version = at > 0 && !(scoped && at === 0) ? spec.slice(at + 1) : void 0;
17797
- ctx.line(`added ${name}${version ? `@${version}` : ""}`);
18001
+ const { name, version } = splitPackageSpec(spec);
18002
+ note(`added ${name}${version ? `@${version}` : ""}`);
17798
18003
  await installer.install(name, version, {
17799
18004
  onProgress,
17800
18005
  persist: opts.save !== false,
@@ -18038,33 +18243,127 @@ function printNpmHelp(ctx) {
18038
18243
  ctx.line("");
18039
18244
  ctx.line(`npm@${NPM_VERSION} /usr/lib/node_modules/npm`);
18040
18245
  }
18246
+ function packageBinaries(ctx, root, packageName) {
18247
+ const manifestPath = join(root, "node_modules", packageName, "package.json");
18248
+ try {
18249
+ const manifest = JSON.parse(ctx.vfs.readText(manifestPath, ctx.cred));
18250
+ if (typeof manifest.bin === "string") return [basename(packageName)];
18251
+ if (manifest.bin && typeof manifest.bin === "object") return Object.keys(manifest.bin);
18252
+ } catch {
18253
+ }
18254
+ return [];
18255
+ }
18256
+ function findLocalBin(ctx, name) {
18257
+ let dir3 = ctx.cwd;
18258
+ for (let i = 0; i < 64; i++) {
18259
+ const candidate = join(dir3, "node_modules", ".bin", name);
18260
+ if (ctx.vfs.lexists(candidate)) return candidate;
18261
+ const parent = dirname(dir3);
18262
+ if (parent === dir3) break;
18263
+ dir3 = parent;
18264
+ }
18265
+ return null;
18266
+ }
18041
18267
  var npx = defineCommand({
18042
18268
  name: "npx",
18043
18269
  path: "/usr/bin/npx",
18044
18270
  summary: "run a command from a local or remote npm package",
18271
+ usage: "npx [-y] [-p package] <command> [args]",
18272
+ manual: `Runs a package binary, installing the package first if it is not
18273
+ already present. Installation needs outbound network access, which is off
18274
+ unless the container was created with network: { allowOutbound: true }.`,
18045
18275
  async run(ctx) {
18046
- const args = parseArgs(ctx.args, [{ short: "y", long: "yes" }, { long: "no-install" }, { short: "p", long: "package", arg: true }], {
18047
- stopAtFirstPositional: true
18048
- });
18049
- if (args.positional.length === 0) return ctx.fail("npx: a command is required", 1);
18276
+ const args = parseArgs(
18277
+ ctx.args,
18278
+ [
18279
+ { short: "y", long: "yes" },
18280
+ { long: "no-install" },
18281
+ { short: "p", long: "package", arg: true, multiple: true },
18282
+ { short: "q", long: "quiet" },
18283
+ { short: "c", long: "call", arg: true },
18284
+ { long: "version" },
18285
+ { long: "help" }
18286
+ ],
18287
+ { stopAtFirstPositional: true, allowUnknown: true }
18288
+ );
18289
+ if (args.has("version")) {
18290
+ ctx.line(NPM_VERSION);
18291
+ return 0;
18292
+ }
18293
+ if (args.has("help")) {
18294
+ ctx.line("Usage: npx [options] <command>[@version] [command-arg]...");
18295
+ ctx.line("");
18296
+ ctx.line("Options:");
18297
+ ctx.line(" -y, --yes skip the install confirmation (always implied here)");
18298
+ ctx.line(" -p, --package <pkg> package providing the command (repeatable)");
18299
+ ctx.line(" --no-install fail instead of installing a missing package");
18300
+ ctx.line(" -q, --quiet suppress install progress");
18301
+ return 0;
18302
+ }
18303
+ const call = args.str("call");
18304
+ const operands = call ? call.split(/\s+/).filter(Boolean) : args.positional;
18305
+ if (operands.length === 0) {
18306
+ ctx.warn("a command is required");
18307
+ ctx.warn("usage: npx [options] <command>[@version] [command-arg]...");
18308
+ return 1;
18309
+ }
18050
18310
  const root = findProjectRoot(ctx);
18051
- const [name, ...rest] = args.positional;
18052
- const binPath = join(root, "node_modules", ".bin", name);
18053
- if (!ctx.vfs.lexists(binPath) && ctx.kernel.net.options.allowOutbound) {
18054
- ctx.stderr.write(`npx: installing ${name}...
18311
+ const binDir = join(root, "node_modules", ".bin");
18312
+ const [spec, ...rest] = operands;
18313
+ const { name: specName, version } = splitPackageSpec(spec);
18314
+ const packages = args.list("package");
18315
+ const packageSpec = packages[0] ?? spec;
18316
+ const packageName = packages[0] ? splitPackageSpec(packages[0]).name : specName;
18317
+ let command = packages.length > 0 ? spec : basename(specName);
18318
+ const run = async (binary) => {
18319
+ const env2 = {
18320
+ ...ctx.env,
18321
+ PATH: `${binDir}:${ctx.env.PATH ?? ""}`,
18322
+ npm_config_user_agent: `npm/${NPM_VERSION} node/${NODE_VERSION} linux x64`
18323
+ };
18324
+ return await ctx.kernel.spawn([binary, ...rest], {
18325
+ cwd: ctx.cwd,
18326
+ env: env2,
18327
+ cred: ctx.cred,
18328
+ ppid: ctx.proc.pid,
18329
+ stdin: ctx.stdin,
18330
+ stdout: ctx.stdout,
18331
+ stderr: ctx.stderr
18332
+ }).wait();
18333
+ };
18334
+ if (!version) {
18335
+ if (findLocalBin(ctx, command)) return run(command);
18336
+ if (ctx.kernel.which(command, ctx.cwd, ctx.env, ctx.cred)) return run(command);
18337
+ }
18338
+ if (args.has("no-install")) {
18339
+ ctx.warn(`command not found: ${command}`);
18340
+ return 127;
18341
+ }
18342
+ if (!ctx.kernel.net.options.allowOutbound) {
18343
+ ctx.warn(`could not determine executable to run: ${command}`);
18344
+ ctx.warn(`'${packageName}' is not installed, and installing it needs network access.`);
18345
+ ctx.warn("Outbound network access is disabled for this container.");
18346
+ ctx.warn("Enable it with createContainer({ network: { allowOutbound: true } }).");
18347
+ return 127;
18348
+ }
18349
+ ctx.stderr.write(`npx: installing ${packageSpec}...
18055
18350
  `);
18056
- await installPackages(ctx, [name], { cwd: root, save: false });
18351
+ const installed = await installPackages(ctx, [packageSpec], {
18352
+ cwd: root,
18353
+ save: false,
18354
+ quiet: true
18355
+ });
18356
+ if (installed !== 0) return installed;
18357
+ if (!findLocalBin(ctx, command)) {
18358
+ const binaries = packageBinaries(ctx, root, packageName);
18359
+ const chosen = binaries.includes(command) ? command : binaries[0];
18360
+ if (chosen === void 0) {
18361
+ ctx.warn(`could not determine executable to run: ${packageName} provides no binary`);
18362
+ return 127;
18363
+ }
18364
+ command = chosen;
18057
18365
  }
18058
- const env2 = { ...ctx.env, PATH: `${join(root, "node_modules", ".bin")}:${ctx.env.PATH ?? ""}` };
18059
- return await ctx.kernel.spawn([name, ...rest], {
18060
- cwd: ctx.cwd,
18061
- env: env2,
18062
- cred: ctx.cred,
18063
- ppid: ctx.proc.pid,
18064
- stdin: ctx.stdin,
18065
- stdout: ctx.stdout,
18066
- stderr: ctx.stderr
18067
- }).wait();
18366
+ return run(command);
18068
18367
  }
18069
18368
  });
18070
18369
  function makeNpmAlias(name, path) {
@@ -18483,7 +18782,7 @@ var Container = class _Container {
18483
18782
  }
18484
18783
  // ── boot ──────────────────────────────────────────────────────────────────
18485
18784
  static async create(opts = {}) {
18486
- const pod = await Nodepod.boot({
18785
+ const pod = opts.pod ?? await Nodepod.boot({
18487
18786
  headless: true,
18488
18787
  serviceWorker: false,
18489
18788
  env: opts.env ?? {},
@@ -18534,8 +18833,8 @@ var Container = class _Container {
18534
18833
  /** Copy a directory tree from the host filesystem into the container. */
18535
18834
  async copyIn(hostPath, containerPath) {
18536
18835
  this.assertActive();
18537
- const { readdir, readFile, stat: stat2 } = await import('fs/promises');
18538
- const nodePath = await import('path');
18836
+ const { readdir, readFile, stat: stat2 } = await nodeBuiltin("fs/promises");
18837
+ const nodePath = await nodeBuiltin("path");
18539
18838
  const walk = async (src, dest) => {
18540
18839
  const st = await stat2(src);
18541
18840
  if (st.isDirectory()) {
@@ -18562,8 +18861,8 @@ var Container = class _Container {
18562
18861
  /** Copy a file or directory out of the container onto the host. */
18563
18862
  async copyOut(containerPath, hostPath) {
18564
18863
  this.assertActive();
18565
- const { mkdir: mkdir2, writeFile } = await import('fs/promises');
18566
- const nodePath = await import('path');
18864
+ const { mkdir: mkdir2, writeFile } = await nodeBuiltin("fs/promises");
18865
+ const nodePath = await nodeBuiltin("path");
18567
18866
  const src = resolve(this.defaults.cwd, containerPath);
18568
18867
  const walk = async (from, to) => {
18569
18868
  const st = this.kernel.vfs.lstat(from);
@@ -18787,7 +19086,7 @@ var Container = class _Container {
18787
19086
  */
18788
19087
  async expose(port, opts = {}) {
18789
19088
  this.assertActive();
18790
- const http = await import('http');
19089
+ const http = await nodeBuiltin("http");
18791
19090
  const container = this;
18792
19091
  const server = http.createServer((req, res) => {
18793
19092
  const chunks = [];