sandboxedjs 0.1.50 → 0.1.52

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.js CHANGED
@@ -1,6 +1,6 @@
1
- import { ungzip, gzip as gzip$1, deflate as deflate$1, inflate as inflate$1, deflateRaw, inflateRaw } from 'pako';
2
- import { Buffer as Buffer$1 } from 'buffer/index.js';
1
+ import { inflateRaw, ungzip, gzip as gzip$1, deflate as deflate$1, inflate as inflate$1, deflateRaw } from 'pako';
3
2
  import { sha256, sha224 } from '@noble/hashes/sha256';
3
+ import { Buffer as Buffer$1 } from 'buffer/index.js';
4
4
  import { sha512, sha384 } from '@noble/hashes/sha512';
5
5
  import { sha1 } from '@noble/hashes/sha1';
6
6
  import { valid, maxSatisfying } from 'semver';
@@ -3331,7 +3331,7 @@ async function builtinRead({ shell, argv, io }) {
3331
3331
  if (timeoutSeconds !== void 0 && Number.isFinite(timeoutSeconds)) {
3332
3332
  line = await Promise.race([
3333
3333
  readOnce(),
3334
- new Promise((resolve2) => setTimeout(() => resolve2(null), timeoutSeconds * 1e3))
3334
+ new Promise((resolve3) => setTimeout(() => resolve3(null), timeoutSeconds * 1e3))
3335
3335
  ]);
3336
3336
  } else {
3337
3337
  line = await readOnce();
@@ -3590,10 +3590,10 @@ function builtinType({ shell, argv, io }) {
3590
3590
  }
3591
3591
  async function builtinCommand({ shell, argv, io }) {
3592
3592
  const args = argv.slice(1);
3593
- const describe3 = args.includes("-v") || args.includes("-V");
3593
+ const describe2 = args.includes("-v") || args.includes("-V");
3594
3594
  const verbose = args.includes("-V");
3595
3595
  const rest = args.filter((a) => a !== "-v" && a !== "-V" && a !== "-p");
3596
- if (describe3) {
3596
+ if (describe2) {
3597
3597
  let status = 0;
3598
3598
  for (const name of rest) {
3599
3599
  if (isBuiltinName(name)) {
@@ -4057,6 +4057,94 @@ var init_builtins = __esm({
4057
4057
  }
4058
4058
  });
4059
4059
 
4060
+ // src/pkg/zip.ts
4061
+ var zip_exports = {};
4062
+ __export(zip_exports, {
4063
+ ZipError: () => ZipError,
4064
+ readZip: () => readZip
4065
+ });
4066
+ function readZip(archive) {
4067
+ const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
4068
+ const end = findEndOfCentralDirectory(archive);
4069
+ let entryCount = view.getUint16(end + 10, true);
4070
+ let directoryOffset = view.getUint32(end + 16, true);
4071
+ if (entryCount === 65535 || directoryOffset === 4294967295) {
4072
+ const locator = findSignature(archive, ZIP64_END_LOCATOR, end);
4073
+ if (locator < 0) throw new ZipError("zip64 archive without an end locator");
4074
+ const zip64End = Number(view.getBigUint64(locator + 8, true));
4075
+ if (view.getUint32(zip64End, true) !== ZIP64_END_OF_CENTRAL_DIRECTORY) {
4076
+ throw new ZipError("zip64 end record not found where the locator says");
4077
+ }
4078
+ entryCount = Number(view.getBigUint64(zip64End + 32, true));
4079
+ directoryOffset = Number(view.getBigUint64(zip64End + 48, true));
4080
+ }
4081
+ const entries = [];
4082
+ let at = directoryOffset;
4083
+ for (let i = 0; i < entryCount; i += 1) {
4084
+ if (view.getUint32(at, true) !== CENTRAL_FILE_HEADER) {
4085
+ throw new ZipError(`central directory entry ${i} has a bad signature`);
4086
+ }
4087
+ const flags = view.getUint16(at + 8, true);
4088
+ if (flags & 1) throw new ZipError("encrypted archives are not supported");
4089
+ const method = view.getUint16(at + 10, true);
4090
+ const compressedSize = view.getUint32(at + 20, true);
4091
+ const uncompressedSize = view.getUint32(at + 24, true);
4092
+ const nameLength = view.getUint16(at + 28, true);
4093
+ const extraLength = view.getUint16(at + 30, true);
4094
+ const commentLength = view.getUint16(at + 32, true);
4095
+ const externalAttributes = view.getUint32(at + 38, true);
4096
+ const localOffset = view.getUint32(at + 42, true);
4097
+ const name = new TextDecoder().decode(archive.subarray(at + 46, at + 46 + nameLength));
4098
+ entries.push({
4099
+ name,
4100
+ /* The high 16 bits are the Unix mode when the archive was made on Unix.
4101
+ * pip relies on it for console scripts, which have to stay executable. */
4102
+ mode: externalAttributes >>> 16,
4103
+ isDirectory: name.endsWith("/"),
4104
+ data: () => extract(archive, view, localOffset, method, compressedSize, uncompressedSize, name)
4105
+ });
4106
+ at += 46 + nameLength + extraLength + commentLength;
4107
+ }
4108
+ return entries;
4109
+ }
4110
+ function extract(archive, view, localOffset, method, compressedSize, uncompressedSize, name) {
4111
+ const nameLength = view.getUint16(localOffset + 26, true);
4112
+ const extraLength = view.getUint16(localOffset + 28, true);
4113
+ const start2 = localOffset + 30 + nameLength + extraLength;
4114
+ const body = archive.subarray(start2, start2 + compressedSize);
4115
+ if (method === 0) return body.slice();
4116
+ if (method !== 8) throw new ZipError(`${name}: unsupported compression method ${method}`);
4117
+ const inflated = inflateRaw(body);
4118
+ if (inflated.length !== uncompressedSize) {
4119
+ throw new ZipError(`${name}: inflated to ${inflated.length} bytes, the directory says ${uncompressedSize}`);
4120
+ }
4121
+ return inflated;
4122
+ }
4123
+ function findEndOfCentralDirectory(archive, view) {
4124
+ const at = findSignature(archive, END_OF_CENTRAL_DIRECTORY, archive.length);
4125
+ if (at < 0) throw new ZipError("not a zip archive: no end-of-central-directory record");
4126
+ return at;
4127
+ }
4128
+ function findSignature(archive, signature, before) {
4129
+ const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
4130
+ for (let at = Math.min(before, archive.length) - 4; at >= 0; at -= 1) {
4131
+ if (view.getUint32(at, true) === signature) return at;
4132
+ }
4133
+ return -1;
4134
+ }
4135
+ var END_OF_CENTRAL_DIRECTORY, CENTRAL_FILE_HEADER, ZIP64_END_LOCATOR, ZIP64_END_OF_CENTRAL_DIRECTORY, ZipError;
4136
+ var init_zip = __esm({
4137
+ "src/pkg/zip.ts"() {
4138
+ END_OF_CENTRAL_DIRECTORY = 101010256;
4139
+ CENTRAL_FILE_HEADER = 33639248;
4140
+ ZIP64_END_LOCATOR = 117853008;
4141
+ ZIP64_END_OF_CENTRAL_DIRECTORY = 101075792;
4142
+ ZipError = class extends Error {
4143
+ code = "ERR_ZIP";
4144
+ };
4145
+ }
4146
+ });
4147
+
4060
4148
  // src/fs/vfs.ts
4061
4149
  init_errno();
4062
4150
  init_path();
@@ -4842,7 +4930,7 @@ var Pipe = class _Pipe {
4842
4930
  }
4843
4931
  waitForData() {
4844
4932
  if (this.buffered > 0 || this.writerClosed || this.readerClosed) return Promise.resolve();
4845
- return new Promise((resolve2) => this.wakers.push(resolve2));
4933
+ return new Promise((resolve3) => this.wakers.push(resolve3));
4846
4934
  }
4847
4935
  async read(size = Infinity) {
4848
4936
  await this.waitForData();
@@ -5201,7 +5289,7 @@ var Process = class {
5201
5289
  }
5202
5290
  wait() {
5203
5291
  if (this.exitCode !== null) return Promise.resolve(this.exitCode);
5204
- return new Promise((resolve2) => this.exitWaiters.push(resolve2));
5292
+ return new Promise((resolve3) => this.exitWaiters.push(resolve3));
5205
5293
  }
5206
5294
  };
5207
5295
  var ProcessTable = class {
@@ -6968,7 +7056,7 @@ var NetworkStack = class {
6968
7056
  try {
6969
7057
  const res = await Promise.race([
6970
7058
  this.pod.request(port, { path: "/", method: "GET" }),
6971
- new Promise((resolve2) => setTimeout(() => resolve2(null), timeoutMs))
7059
+ new Promise((resolve3) => setTimeout(() => resolve3(null), timeoutMs))
6972
7060
  ]);
6973
7061
  if (res === null) return false;
6974
7062
  return res.statusCode !== 503;
@@ -10432,11 +10520,11 @@ var sleepCmd = defineCommand({
10432
10520
  const multiplier = { "": 1e3, s: 1e3, m: 6e4, h: 36e5, d: 864e5 }[m[2] ?? ""];
10433
10521
  totalMs += value * multiplier;
10434
10522
  }
10435
- await new Promise((resolve2) => {
10436
- const timer = setTimeout(resolve2, totalMs);
10523
+ await new Promise((resolve3) => {
10524
+ const timer = setTimeout(resolve3, totalMs);
10437
10525
  ctx.signal.addEventListener("abort", () => {
10438
10526
  clearTimeout(timer);
10439
- resolve2();
10527
+ resolve3();
10440
10528
  });
10441
10529
  });
10442
10530
  return ctx.signal.aborted ? 130 : 0;
@@ -17374,13 +17462,13 @@ async function execute(ctx, invocation) {
17374
17462
  })();
17375
17463
  }
17376
17464
  let onAbort;
17377
- const aborted = new Promise((resolve2) => {
17465
+ const aborted = new Promise((resolve3) => {
17378
17466
  onAbort = () => {
17379
17467
  try {
17380
17468
  proc.kill();
17381
17469
  } catch {
17382
17470
  }
17383
- resolve2({ exitCode: 137 });
17471
+ resolve3({ exitCode: 137 });
17384
17472
  };
17385
17473
  if (ctx.signal.aborted) onAbort();
17386
17474
  else ctx.signal.addEventListener("abort", onAbort, { once: true });
@@ -17789,10 +17877,10 @@ function validateManifest(value) {
17789
17877
  var bundledManifest = {
17790
17878
  format: "sandboxedjs-python-runtime",
17791
17879
  schemaVersion: 1,
17792
- runtimeId: "sbx-cpython-3.13.5-threaded-fixed",
17880
+ runtimeId: "sbx-cpython-3.13.5-dynamic",
17793
17881
  engine: "cpython-wasm",
17794
17882
  pythonVersion: "3.13.5",
17795
- profile: "threaded-fixed",
17883
+ profile: "dynamic",
17796
17884
  hostAbi: { name: "sbx_host_v1", version: 1 },
17797
17885
  artifacts: {
17798
17886
  moduleUrl: new URL(
@@ -17802,7 +17890,7 @@ var bundledManifest = {
17802
17890
  },
17803
17891
  capabilities: {
17804
17892
  threads: true,
17805
- nativeExtensions: "fixed",
17893
+ nativeExtensions: "dynamic",
17806
17894
  networking: "none",
17807
17895
  processes: "none",
17808
17896
  persistence: "memory"
@@ -17810,6 +17898,7 @@ var bundledManifest = {
17810
17898
  };
17811
17899
  var config = { backend: "sbx-cpython-wasm", manifest: bundledManifest };
17812
17900
  function setPythonBackend(options) {
17901
+ if (options.wheelIndex !== void 0) config.wheelIndex = options.wheelIndex;
17813
17902
  if (options.manifest !== void 0) config.manifest = validateManifest(options.manifest);
17814
17903
  if (options.workerUrl !== void 0) config.workerUrl = options.workerUrl;
17815
17904
  if (options.backend !== void 0) {
@@ -18117,10 +18206,10 @@ var PipeBuffer = class {
18117
18206
  wake() {
18118
18207
  const waiting = this.waiters;
18119
18208
  this.waiters = [];
18120
- for (const resolve2 of waiting) resolve2();
18209
+ for (const resolve3 of waiting) resolve3();
18121
18210
  }
18122
18211
  whenReady() {
18123
- return new Promise((resolve2) => this.waiters.push(resolve2));
18212
+ return new Promise((resolve3) => this.waiters.push(resolve3));
18124
18213
  }
18125
18214
  };
18126
18215
  var PipeReadEnd = class {
@@ -18274,7 +18363,7 @@ var StreamDescription = class {
18274
18363
  return true;
18275
18364
  }
18276
18365
  whenReady() {
18277
- return new Promise((resolve2) => this.waiters.push(resolve2));
18366
+ return new Promise((resolve3) => this.waiters.push(resolve3));
18278
18367
  }
18279
18368
  close() {
18280
18369
  this.wake();
@@ -18282,7 +18371,7 @@ var StreamDescription = class {
18282
18371
  wake() {
18283
18372
  const waiting = this.waiters;
18284
18373
  this.waiters = [];
18285
- for (const resolve2 of waiting) resolve2();
18374
+ for (const resolve3 of waiting) resolve3();
18286
18375
  }
18287
18376
  };
18288
18377
  function toPosixError(error) {
@@ -18633,7 +18722,7 @@ function createHostAbiServer(proc) {
18633
18722
  }
18634
18723
  /* ------------------------------------------------------------ files */
18635
18724
  case Op.openat: {
18636
- const path = resolve2(r.string());
18725
+ const path = resolve3(r.string());
18637
18726
  const flags = r.u32();
18638
18727
  const mode = r.u32();
18639
18728
  const description = files.open(path, flags, mode);
@@ -18681,7 +18770,7 @@ function createHostAbiServer(proc) {
18681
18770
  return { status: 0, payload: encodeStat(proc.table.get(r.i32()).stat()) };
18682
18771
  }
18683
18772
  case Op.statat: {
18684
- const path = resolve2(r.string());
18773
+ const path = resolve3(r.string());
18685
18774
  const followLinks = r.u32() !== 0;
18686
18775
  try {
18687
18776
  const st = followLinks ? proc.vfs.stat(path, { cred: proc.cred }) : proc.vfs.lstat(path);
@@ -18711,11 +18800,11 @@ function createHostAbiServer(proc) {
18711
18800
  return { status: 0 };
18712
18801
  }
18713
18802
  case Op.renameat: {
18714
- files.rename(resolve2(r.string()), resolve2(r.string()));
18803
+ files.rename(resolve3(r.string()), resolve3(r.string()));
18715
18804
  return { status: 0 };
18716
18805
  }
18717
18806
  case Op.unlinkat: {
18718
- const path = resolve2(r.string());
18807
+ const path = resolve3(r.string());
18719
18808
  const removeDirectory = r.u32() !== 0;
18720
18809
  if (removeDirectory) {
18721
18810
  try {
@@ -18729,7 +18818,7 @@ function createHostAbiServer(proc) {
18729
18818
  return { status: 0 };
18730
18819
  }
18731
18820
  case Op.mkdirat: {
18732
- const path = resolve2(r.string());
18821
+ const path = resolve3(r.string());
18733
18822
  const mode = r.u32();
18734
18823
  try {
18735
18824
  proc.vfs.mkdir(path, { mode, cred: proc.cred });
@@ -18740,7 +18829,7 @@ function createHostAbiServer(proc) {
18740
18829
  }
18741
18830
  case Op.readlinkat: {
18742
18831
  try {
18743
- const target = proc.vfs.readlink(resolve2(r.string()), proc.cred);
18832
+ const target = proc.vfs.readlink(resolve3(r.string()), proc.cred);
18744
18833
  return { status: 0, payload: new Writer().string(target).finish() };
18745
18834
  } catch (error) {
18746
18835
  throw toPosixError(error);
@@ -18749,7 +18838,7 @@ function createHostAbiServer(proc) {
18749
18838
  case Op.symlinkat: {
18750
18839
  const target = r.string();
18751
18840
  try {
18752
- proc.vfs.symlink(target, resolve2(r.string()), proc.cred);
18841
+ proc.vfs.symlink(target, resolve3(r.string()), proc.cred);
18753
18842
  } catch (error) {
18754
18843
  throw toPosixError(error);
18755
18844
  }
@@ -18824,7 +18913,7 @@ function createHostAbiServer(proc) {
18824
18913
  case Op.getcwd:
18825
18914
  return { status: 0, payload: new Writer().string(proc.cwd).finish() };
18826
18915
  case Op.chdir: {
18827
- const path = resolve2(r.string());
18916
+ const path = resolve3(r.string());
18828
18917
  let st;
18829
18918
  try {
18830
18919
  st = proc.vfs.stat(path, { cred: proc.cred });
@@ -18853,7 +18942,7 @@ function createHostAbiServer(proc) {
18853
18942
  function implemented(capability) {
18854
18943
  return ["files", "descriptors", "pipes", "readiness", "time", "identity", "entropy"].includes(capability);
18855
18944
  }
18856
- function resolve2(path) {
18945
+ function resolve3(path) {
18857
18946
  if (path.startsWith("/")) return proc.vfs.resolvePath(path);
18858
18947
  return proc.vfs.resolvePath(`${proc.cwd.replace(/\/$/, "")}/${path}`);
18859
18948
  }
@@ -18915,11 +19004,11 @@ function createHostAbiServer(proc) {
18915
19004
  }
18916
19005
  function aborted() {
18917
19006
  if (proc.signal.aborted) return Promise.resolve();
18918
- return new Promise((resolve3) => proc.signal.addEventListener("abort", () => resolve3(), { once: true }));
19007
+ return new Promise((resolve4) => proc.signal.addEventListener("abort", () => resolve4(), { once: true }));
18919
19008
  }
18920
19009
  }
18921
19010
  function sleep(ms) {
18922
- return new Promise((resolve2) => setTimeout(resolve2, Math.max(0, ms)));
19011
+ return new Promise((resolve3) => setTimeout(resolve3, Math.max(0, ms)));
18923
19012
  }
18924
19013
  function encodeStat(st) {
18925
19014
  return new Writer().u64(st.ino).u32(st.mode).u64(st.size).u32(st.uid).u32(st.gid).u32(st.nlink).i64(Math.round(st.atimeMs * 1e6)).i64(Math.round(st.mtimeMs * 1e6)).i64(Math.round(st.ctimeMs * 1e6)).finish();
@@ -19039,12 +19128,12 @@ async function startRuntimeWorker(options = {}) {
19039
19128
  const url = options.url ?? defaultWorkerUrl();
19040
19129
  const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
19041
19130
  try {
19042
- await new Promise((resolve2, reject) => {
19131
+ await new Promise((resolve3, reject) => {
19043
19132
  const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
19044
19133
  worker.onMessage((message) => {
19045
19134
  if (message?.type === "sandboxedjs:ready") {
19046
19135
  clearTimeout(timer);
19047
- resolve2();
19136
+ resolve3();
19048
19137
  }
19049
19138
  });
19050
19139
  worker.onError((error) => {
@@ -19090,7 +19179,7 @@ async function startNodeWorker(url, workerData) {
19090
19179
  /* webpackIgnore: true */
19091
19180
  specifier
19092
19181
  );
19093
- const worker = new NodeWorker(url, { workerData });
19182
+ const worker = new NodeWorker(url, { workerData, execArgv: [] });
19094
19183
  return {
19095
19184
  postMessage: (message) => worker.postMessage(message),
19096
19185
  onMessage: (listener) => worker.on("message", listener),
@@ -19138,8 +19227,8 @@ async function startPythonProcess(options) {
19138
19227
  };
19139
19228
  let blame = () => {
19140
19229
  };
19141
- const finished = new Promise((resolve2, reject) => {
19142
- settle = resolve2;
19230
+ const finished = new Promise((resolve3, reject) => {
19231
+ settle = resolve3;
19143
19232
  blame = reject;
19144
19233
  });
19145
19234
  let done = false;
@@ -19204,85 +19293,11 @@ async function startPythonProcess(options) {
19204
19293
  }
19205
19294
  };
19206
19295
  }
19207
- var END_OF_CENTRAL_DIRECTORY = 101010256;
19208
- var CENTRAL_FILE_HEADER = 33639248;
19209
- var ZIP64_END_LOCATOR = 117853008;
19210
- var ZIP64_END_OF_CENTRAL_DIRECTORY = 101075792;
19211
- var ZipError = class extends Error {
19212
- code = "ERR_ZIP";
19213
- };
19214
- function readZip(archive) {
19215
- const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
19216
- const end = findEndOfCentralDirectory(archive);
19217
- let entryCount = view.getUint16(end + 10, true);
19218
- let directoryOffset = view.getUint32(end + 16, true);
19219
- if (entryCount === 65535 || directoryOffset === 4294967295) {
19220
- const locator = findSignature(archive, ZIP64_END_LOCATOR, end);
19221
- if (locator < 0) throw new ZipError("zip64 archive without an end locator");
19222
- const zip64End = Number(view.getBigUint64(locator + 8, true));
19223
- if (view.getUint32(zip64End, true) !== ZIP64_END_OF_CENTRAL_DIRECTORY) {
19224
- throw new ZipError("zip64 end record not found where the locator says");
19225
- }
19226
- entryCount = Number(view.getBigUint64(zip64End + 32, true));
19227
- directoryOffset = Number(view.getBigUint64(zip64End + 48, true));
19228
- }
19229
- const entries = [];
19230
- let at = directoryOffset;
19231
- for (let i = 0; i < entryCount; i += 1) {
19232
- if (view.getUint32(at, true) !== CENTRAL_FILE_HEADER) {
19233
- throw new ZipError(`central directory entry ${i} has a bad signature`);
19234
- }
19235
- const flags = view.getUint16(at + 8, true);
19236
- if (flags & 1) throw new ZipError("encrypted archives are not supported");
19237
- const method = view.getUint16(at + 10, true);
19238
- const compressedSize = view.getUint32(at + 20, true);
19239
- const uncompressedSize = view.getUint32(at + 24, true);
19240
- const nameLength = view.getUint16(at + 28, true);
19241
- const extraLength = view.getUint16(at + 30, true);
19242
- const commentLength = view.getUint16(at + 32, true);
19243
- const externalAttributes = view.getUint32(at + 38, true);
19244
- const localOffset = view.getUint32(at + 42, true);
19245
- const name = new TextDecoder().decode(archive.subarray(at + 46, at + 46 + nameLength));
19246
- entries.push({
19247
- name,
19248
- /* The high 16 bits are the Unix mode when the archive was made on Unix.
19249
- * pip relies on it for console scripts, which have to stay executable. */
19250
- mode: externalAttributes >>> 16,
19251
- isDirectory: name.endsWith("/"),
19252
- data: () => extract(archive, view, localOffset, method, compressedSize, uncompressedSize, name)
19253
- });
19254
- at += 46 + nameLength + extraLength + commentLength;
19255
- }
19256
- return entries;
19257
- }
19258
- function extract(archive, view, localOffset, method, compressedSize, uncompressedSize, name) {
19259
- const nameLength = view.getUint16(localOffset + 26, true);
19260
- const extraLength = view.getUint16(localOffset + 28, true);
19261
- const start2 = localOffset + 30 + nameLength + extraLength;
19262
- const body = archive.subarray(start2, start2 + compressedSize);
19263
- if (method === 0) return body.slice();
19264
- if (method !== 8) throw new ZipError(`${name}: unsupported compression method ${method}`);
19265
- const inflated = inflateRaw(body);
19266
- if (inflated.length !== uncompressedSize) {
19267
- throw new ZipError(`${name}: inflated to ${inflated.length} bytes, the directory says ${uncompressedSize}`);
19268
- }
19269
- return inflated;
19270
- }
19271
- function findEndOfCentralDirectory(archive, view) {
19272
- const at = findSignature(archive, END_OF_CENTRAL_DIRECTORY, archive.length);
19273
- if (at < 0) throw new ZipError("not a zip archive: no end-of-central-directory record");
19274
- return at;
19275
- }
19276
- function findSignature(archive, signature, before) {
19277
- const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
19278
- for (let at = Math.min(before, archive.length) - 4; at >= 0; at -= 1) {
19279
- if (view.getUint32(at, true) === signature) return at;
19280
- }
19281
- return -1;
19282
- }
19296
+
19297
+ // src/runtime/python/install.ts
19298
+ init_zip();
19283
19299
 
19284
19300
  // src/runtime/python/pypi.ts
19285
- var ACCEPTED_TAGS = ["py3-none-any", "py2.py3-none-any", "py3-none-emscripten"];
19286
19301
  function parseRequirement(text2) {
19287
19302
  const cleaned = text2.replace(/#.*$/, "").trim();
19288
19303
  if (!cleaned) return null;
@@ -19356,56 +19371,280 @@ function compareRelease(left, right) {
19356
19371
  }
19357
19372
  return 0;
19358
19373
  }
19359
- function isPreRelease(version) {
19360
- return /(a|b|rc|dev|post)\d*$/i.test(version.replace(/^\d+(\.\d+)*/, ""));
19361
- }
19362
- async function resolvePackageCandidates(client, requirement, environment) {
19363
- const index = await client.json(`https://pypi.org/pypi/${requirement.name}/json`);
19364
- const candidates = Object.keys(index.releases).filter((version) => requirement.specifiers.every((s) => compareVersions(version, s.version, s.op))).sort((a, b) => compareRelease(releaseOf(a), releaseOf(b)));
19365
- const stable = candidates.filter((version) => !isPreRelease(version));
19366
- const ordered = (stable.length > 0 ? stable : candidates).reverse();
19367
- let sawSourceOnly = false;
19368
- const resolved = [];
19369
- for (const version of ordered) {
19370
- const files = (index.releases[version] ?? []).filter((file3) => !file3.yanked);
19371
- const wheel = pickWheel(files);
19372
- if (wheel) {
19373
- resolved.push({
19374
- name: requirement.name,
19374
+ function splitOnce(text2, separator) {
19375
+ const at = text2.indexOf(separator);
19376
+ return at < 0 ? [text2] : [text2.slice(0, at), text2.slice(at + 1)];
19377
+ }
19378
+
19379
+ // src/runtime/python/extension-abi.ts
19380
+ var EXTENSION_ABI = {
19381
+ "abiId": "sbxabi1-c2637d04695ad927",
19382
+ "wheelTag": "cp313-cp313-emscripten_5_0_6_wasm32",
19383
+ "wheel": {
19384
+ "acceptedPurePythonTags": [
19385
+ "py3-none-any",
19386
+ "py2.py3-none-any",
19387
+ "cp313-none-any"
19388
+ ]
19389
+ }};
19390
+ var WHEEL_TAG = EXTENSION_ABI.wheelTag;
19391
+ var PURE_PYTHON_TAGS = EXTENSION_ABI.wheel.acceptedPurePythonTags;
19392
+
19393
+ // src/runtime/python/resolver.ts
19394
+ var ResolutionError = class extends Error {
19395
+ constructor(message, explanation = []) {
19396
+ super(explanation.length > 0 ? `${message}
19397
+ ${explanation.join("\n ")}` : message);
19398
+ this.explanation = explanation;
19399
+ this.name = "ResolutionError";
19400
+ }
19401
+ explanation;
19402
+ };
19403
+ function classifyFile(file3, allowSourceBuilds) {
19404
+ if (file3.packagetype === "sdist") return allowSourceBuilds ? "sdist" : null;
19405
+ if (file3.packagetype !== "bdist_wheel") return null;
19406
+ const stem = file3.filename.replace(/\.whl$/, "");
19407
+ const parts = stem.split("-");
19408
+ const tags = parts.slice(-3).join("-");
19409
+ if (tags === WHEEL_TAG) return "sbx-wasm";
19410
+ if (PURE_PYTHON_TAGS.includes(tags)) return "pure";
19411
+ const [pythonTags = "", abiTags = "", platformTags = ""] = parts.slice(-3);
19412
+ for (const python2 of pythonTags.split(".")) {
19413
+ for (const abi of abiTags.split(".")) {
19414
+ for (const platform of platformTags.split(".")) {
19415
+ const tag2 = `${python2}-${abi}-${platform}`;
19416
+ if (tag2 === WHEEL_TAG) return "sbx-wasm";
19417
+ if (PURE_PYTHON_TAGS.includes(tag2)) return "pure";
19418
+ }
19419
+ }
19420
+ }
19421
+ return null;
19422
+ }
19423
+ function metadataUrlFor(file3) {
19424
+ const declared = file3["core-metadata"] ?? file3.core_metadata;
19425
+ return declared ? `${file3.url}.metadata` : null;
19426
+ }
19427
+ var KIND_RANK = { pure: 0, "sbx-wasm": 1, sdist: 2 };
19428
+ async function resolve2(options) {
19429
+ const {
19430
+ client,
19431
+ environment,
19432
+ signal,
19433
+ deadlineMs = 18e4,
19434
+ allowSourceBuilds = false,
19435
+ progress
19436
+ } = options;
19437
+ const started = Date.now();
19438
+ const checkBudget = () => {
19439
+ if (signal?.aborted) throw new ResolutionError("dependency resolution was cancelled");
19440
+ if (Date.now() - started > deadlineMs) {
19441
+ throw new ResolutionError(
19442
+ `dependency resolution exceeded its ${Math.round(deadlineMs / 1e3)}s budget. The graph explored so far is consistent but incomplete; constrain the requirements or raise the deadline.`
19443
+ );
19444
+ }
19445
+ };
19446
+ const releases = /* @__PURE__ */ new Map();
19447
+ const dependencies = /* @__PURE__ */ new Map();
19448
+ const nogoods = /* @__PURE__ */ new Map();
19449
+ const candidatesFor = (name) => {
19450
+ let pending = releases.get(name);
19451
+ if (!pending) {
19452
+ pending = fetchCandidates(client, name, allowSourceBuilds, options.index ?? null);
19453
+ releases.set(name, pending);
19454
+ }
19455
+ return pending;
19456
+ };
19457
+ const requiresFor = (candidate) => {
19458
+ const key = `${candidate.name}==${candidate.version}`;
19459
+ let pending = dependencies.get(key);
19460
+ if (!pending) {
19461
+ pending = candidate.indexedRequires ? Promise.resolve(candidate.indexedRequires) : fetchRequires(client, candidate);
19462
+ dependencies.set(key, pending);
19463
+ }
19464
+ return pending;
19465
+ };
19466
+ const roots = [];
19467
+ for (const text2 of options.requirements) {
19468
+ const requirement = parseRequirement(text2);
19469
+ if (requirement) roots.push({ requirement, extras: requirement.extras, origin: "<root>" });
19470
+ }
19471
+ const skipped = [];
19472
+ const signatureOf = (name, state) => {
19473
+ const clauses = (state.constraints.get(name) ?? []).flatMap((entry) => entry.spec.map((s) => `${s.op}${s.version}`)).sort();
19474
+ return `${name}|${[...new Set(clauses)].join(",")}`;
19475
+ };
19476
+ const search = async (queue, state) => {
19477
+ checkBudget();
19478
+ if (queue.length === 0) return state;
19479
+ const [edge, ...rest] = queue;
19480
+ const { requirement, extras, origin } = edge;
19481
+ const name = requirement.name;
19482
+ if (!markerApplies(requirement.marker, environment, extras)) {
19483
+ if (requirement.marker) skipped.push({ name, marker: requirement.marker });
19484
+ return search(rest, state);
19485
+ }
19486
+ const previous = state.constraints.get(name) ?? [];
19487
+ const constraints = new Map(state.constraints);
19488
+ constraints.set(name, [...previous, { spec: requirement.specifiers, origin }]);
19489
+ const next = { chosen: state.chosen, constraints };
19490
+ const already = next.chosen.get(name);
19491
+ if (already) {
19492
+ const satisfied = requirement.specifiers.every((s) => compareVersions(already.version, s.version, s.op));
19493
+ if (!satisfied) {
19494
+ throw new ResolutionError(
19495
+ `cannot satisfy ${name}`,
19496
+ explain(name, next.constraints, already.version)
19497
+ );
19498
+ }
19499
+ return search(rest, next);
19500
+ }
19501
+ const signature = signatureOf(name, next);
19502
+ const learned = nogoods.get(signature);
19503
+ if (learned) throw learned;
19504
+ progress?.collecting(name);
19505
+ const all = await candidatesFor(name);
19506
+ checkBudget();
19507
+ const clauses = (next.constraints.get(name) ?? []).flatMap((entry) => entry.spec);
19508
+ const viable = all.filter((candidate) => clauses.every((s) => compareVersions(candidate.version, s.version, s.op))).sort((a, b) => compareVersions(a.version, b.version, ">") ? -1 : compareVersions(b.version, a.version, ">") ? 1 : KIND_RANK[a.kind] - KIND_RANK[b.kind]);
19509
+ if (viable.length === 0) {
19510
+ const error2 = new ResolutionError(
19511
+ all.length === 0 ? `no distribution of ${name} is usable by this runtime (it needs a pure-Python wheel or one tagged ${WHEEL_TAG})` : `no version of ${name} satisfies every requirement`,
19512
+ explain(name, next.constraints, null, all)
19513
+ );
19514
+ nogoods.set(signature, error2);
19515
+ throw error2;
19516
+ }
19517
+ let failure2 = null;
19518
+ const seenVersions = /* @__PURE__ */ new Set();
19519
+ for (const candidate of viable) {
19520
+ if (seenVersions.has(candidate.version)) continue;
19521
+ seenVersions.add(candidate.version);
19522
+ checkBudget();
19523
+ progress?.examining?.(candidate.name, candidate.version);
19524
+ let requires;
19525
+ try {
19526
+ requires = await requiresFor(candidate);
19527
+ } catch (error2) {
19528
+ failure2 = new ResolutionError(
19529
+ `could not read the dependencies of ${name} ${candidate.version}: ${error2.message}`
19530
+ );
19531
+ continue;
19532
+ }
19533
+ const edges = [];
19534
+ for (const line of requires) {
19535
+ const dependency = parseRequirement(line);
19536
+ if (!dependency) continue;
19537
+ if (!markerApplies(dependency.marker, environment, extras)) continue;
19538
+ edges.push({
19539
+ requirement: dependency,
19540
+ extras: dependency.extras,
19541
+ origin: `${name}==${candidate.version}`
19542
+ });
19543
+ }
19544
+ const chosen = new Map(next.chosen);
19545
+ chosen.set(name, { ...candidate, requires });
19546
+ try {
19547
+ return await search([...edges, ...rest], { chosen, constraints: next.constraints });
19548
+ } catch (error2) {
19549
+ if (!(error2 instanceof ResolutionError)) throw error2;
19550
+ if (/cancelled|budget/.test(error2.message)) throw error2;
19551
+ failure2 = error2;
19552
+ }
19553
+ }
19554
+ const error = failure2 ?? new ResolutionError(`could not resolve ${name}`);
19555
+ nogoods.set(signature, error);
19556
+ throw error;
19557
+ };
19558
+ const solved = await search(roots, { chosen: /* @__PURE__ */ new Map(), constraints: /* @__PURE__ */ new Map() });
19559
+ const distributions = [...solved.chosen.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
19560
+ return { distributions, skipped };
19561
+ }
19562
+ function explain(name, constraints, chosenVersion, available = []) {
19563
+ const lines = [];
19564
+ for (const entry of constraints.get(name) ?? []) {
19565
+ if (entry.spec.length === 0) continue;
19566
+ const text2 = entry.spec.map((s) => `${s.op}${s.version}`).join(", ");
19567
+ lines.push(`${entry.origin} requires ${name} ${text2}`);
19568
+ }
19569
+ if (chosenVersion) lines.push(`but ${name} ${chosenVersion} is already selected`);
19570
+ if (available.length > 0) {
19571
+ const versions = [...new Set(available.map((c) => c.version))].slice(0, 8);
19572
+ lines.push(`usable versions: ${versions.join(", ")}${available.length > 8 ? ", ..." : ""}`);
19573
+ }
19574
+ return lines;
19575
+ }
19576
+ async function fetchCandidates(client, name, allowSourceBuilds, prebuilt) {
19577
+ const candidates = [];
19578
+ for (const wheel of prebuilt?.wheels ?? []) {
19579
+ if (normalize2(wheel.name) !== normalize2(name)) continue;
19580
+ if (wheel.abiId !== EXTENSION_ABI.abiId) {
19581
+ throw new ResolutionError(
19582
+ `${wheel.filename} in the wheel index was built for ABI ${wheel.abiId}, but this runtime implements ${EXTENSION_ABI.abiId}; rebuild it`
19583
+ );
19584
+ }
19585
+ candidates.push({
19586
+ name: normalize2(wheel.name),
19587
+ version: wheel.version,
19588
+ kind: "sbx-wasm",
19589
+ url: `${prebuilt.baseUrl.replace(/\/$/, "")}/${wheel.filename}`,
19590
+ filename: wheel.filename,
19591
+ sha256: wheel.sha256,
19592
+ metadataUrl: null,
19593
+ indexedRequires: wheel.requires
19594
+ });
19595
+ }
19596
+ let index;
19597
+ try {
19598
+ index = await client.json(`https://pypi.org/pypi/${name}/json`, { timeoutMs: 3e4 });
19599
+ } catch (error) {
19600
+ if (candidates.length > 0) return candidates;
19601
+ throw error;
19602
+ }
19603
+ for (const [version, files] of Object.entries(index.releases ?? {})) {
19604
+ if (isPreRelease(version)) continue;
19605
+ for (const file3 of files) {
19606
+ if (file3.yanked) continue;
19607
+ const kind = classifyFile(file3, allowSourceBuilds);
19608
+ if (!kind) continue;
19609
+ candidates.push({
19610
+ name: normalize2(name),
19375
19611
  version,
19376
- url: wheel.url,
19377
- filename: wheel.filename,
19378
- sha256: wheel.digests?.sha256 ?? "",
19379
- requires: []
19612
+ kind,
19613
+ url: file3.url,
19614
+ filename: file3.filename,
19615
+ sha256: file3.digests?.sha256 ?? "",
19616
+ metadataUrl: metadataUrlFor(file3)
19380
19617
  });
19381
- continue;
19382
19618
  }
19383
- if (files.some((file3) => file3.packagetype === "sdist")) sawSourceOnly = true;
19384
19619
  }
19385
- if (resolved.length > 0) return resolved;
19386
- if (ordered.length === 0) {
19620
+ return candidates;
19621
+ }
19622
+ function isPreRelease(version) {
19623
+ return /(a|b|rc|dev)\d*$/i.test(version.replace(/^\d+(\.\d+)*/, ""));
19624
+ }
19625
+ async function fetchRequires(client, candidate) {
19626
+ if (candidate.metadataUrl) {
19627
+ const text2 = await client.text(candidate.metadataUrl, { timeoutMs: 3e4 });
19628
+ return requiresFromMetadata(text2);
19629
+ }
19630
+ if (candidate.kind === "sdist") {
19387
19631
  throw new Error(
19388
- `no version of ${requirement.name} matches ${describe(requirement)}`
19632
+ `${candidate.name} ${candidate.version} is a source distribution and publishes no metadata sidecar, so its dependencies cannot be read without building it`
19389
19633
  );
19390
19634
  }
19391
- throw new Error(
19392
- sawSourceOnly ? `${requirement.name} ${ordered[0]} publishes only a source distribution, and this runtime cannot build one yet (source builds are a later milestone)` : `${requirement.name} ${ordered[0]} has no wheel this runtime can use; it needs one tagged ` + ACCEPTED_TAGS.join(" or ")
19393
- );
19635
+ const { readZip: readZip2 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
19636
+ const entries = readZip2(await client.bytes(candidate.url, { timeoutMs: 12e4 }));
19637
+ const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
19638
+ if (!metadata) return [];
19639
+ return requiresFromMetadata(new TextDecoder().decode(metadata.data()));
19394
19640
  }
19395
- function pickWheel(files) {
19396
- const wheels = files.filter((file3) => file3.packagetype === "bdist_wheel");
19397
- for (const tag2 of ACCEPTED_TAGS) {
19398
- const match2 = wheels.find((file3) => file3.filename.endsWith(`-${tag2}.whl`));
19399
- if (match2) return match2;
19641
+ function requiresFromMetadata(text2) {
19642
+ const result = [];
19643
+ for (const line of text2.split(/\r?\n/)) {
19644
+ if (line === "") break;
19645
+ if (line.startsWith("Requires-Dist:")) result.push(line.slice("Requires-Dist:".length).trim());
19400
19646
  }
19401
- return null;
19402
- }
19403
- function describe(requirement) {
19404
- return requirement.specifiers.length === 0 ? "any version" : requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ");
19405
- }
19406
- function splitOnce(text2, separator) {
19407
- const at = text2.indexOf(separator);
19408
- return at < 0 ? [text2] : [text2.slice(0, at), text2.slice(at + 1)];
19647
+ return result;
19409
19648
  }
19410
19649
 
19411
19650
  // src/runtime/python/install.ts
@@ -19425,123 +19664,45 @@ function markerEnvironment(pythonVersion) {
19425
19664
  }
19426
19665
  async function installRequirements(options) {
19427
19666
  const environment = markerEnvironment(options.pythonVersion);
19428
- const report = { installed: [], skipped: [] };
19429
- const queue = [];
19430
- for (const text2 of options.requirements) {
19431
- const requirement = parseRequirement(text2);
19432
- if (requirement) queue.push({ requirement, extras: requirement.extras });
19433
- }
19434
- const archives = /* @__PURE__ */ new Map();
19435
- const unavailable2 = /* @__PURE__ */ new Set();
19436
- const candidateCache = /* @__PURE__ */ new Map();
19437
- const solve = async (pending, state) => {
19438
- if (pending.length === 0) return state;
19439
- const [first, ...tail2] = pending;
19440
- let { requirement, extras } = first;
19441
- const rest = [];
19442
- const mergedSpecifiers = [...requirement.specifiers];
19443
- const mergedExtras = new Set(extras);
19444
- for (const item of tail2) {
19445
- if (item.requirement.name === requirement.name && markerApplies(item.requirement.marker, environment, item.extras)) {
19446
- mergedSpecifiers.push(...item.requirement.specifiers);
19447
- for (const extra of item.extras) mergedExtras.add(extra);
19448
- } else {
19449
- rest.push(item);
19450
- }
19451
- }
19452
- requirement = { ...requirement, specifiers: mergedSpecifiers };
19453
- extras = [...mergedExtras];
19454
- if (!markerApplies(requirement.marker, environment, extras)) {
19455
- return solve(rest, {
19456
- ...state,
19457
- skipped: [...state.skipped, { name: requirement.name, marker: requirement.marker ?? "" }]
19458
- });
19459
- }
19460
- const already = state.chosen.get(requirement.name);
19461
- if (already) {
19462
- const satisfied = requirement.specifiers.every(
19463
- (s) => compareVersions(already.version, s.version, s.op)
19667
+ const solved = await resolve2({
19668
+ client: options.client,
19669
+ requirements: options.requirements,
19670
+ environment,
19671
+ index: options.index ?? null,
19672
+ ...options.deadlineMs === void 0 ? {} : { deadlineMs: options.deadlineMs },
19673
+ ...options.signal === void 0 ? {} : { signal: options.signal },
19674
+ allowSourceBuilds: options.allowSourceBuilds ?? false,
19675
+ progress: { collecting: (name) => options.progress.collecting(name) }
19676
+ });
19677
+ const staged = [];
19678
+ const installed2 = [];
19679
+ for (const distribution of solved.distributions) {
19680
+ if (distribution.kind === "sdist") {
19681
+ throw new ResolutionError(
19682
+ `${distribution.name} ${distribution.version} resolved to a source distribution, and no local builder is configured to build it for this runtime`
19464
19683
  );
19465
- if (!satisfied) {
19466
- throw new Error(
19467
- `${requirement.name} ${already.version} is already selected but another dependency needs ` + requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ")
19468
- );
19469
- }
19470
- return solve(rest, state);
19471
- }
19472
- options.progress.collecting(requirement.name);
19473
- if (unavailable2.has(requirement.name)) {
19474
- throw new Error(`${requirement.name} has no wheel this runtime can use`);
19475
- }
19476
- const candidateKey = `${requirement.name}:${requirement.specifiers.map((s) => `${s.op}${s.version}`).join(",")}`;
19477
- let candidatePromise = candidateCache.get(candidateKey);
19478
- if (!candidatePromise) {
19479
- candidatePromise = resolvePackageCandidates(options.client, requirement);
19480
- candidateCache.set(candidateKey, candidatePromise);
19481
- }
19482
- let candidates;
19483
- try {
19484
- candidates = await candidatePromise;
19485
- } catch (error) {
19486
- const anyKey = `${requirement.name}:`;
19487
- let anyPromise = candidateCache.get(anyKey);
19488
- if (!anyPromise) {
19489
- anyPromise = resolvePackageCandidates(
19490
- options.client,
19491
- { ...requirement, specifiers: [] });
19492
- candidateCache.set(anyKey, anyPromise);
19493
- }
19494
- try {
19495
- await anyPromise;
19496
- } catch {
19497
- unavailable2.add(requirement.name);
19498
- }
19499
- throw error;
19500
- }
19501
- let failure2;
19502
- for (const resolved of candidates) {
19503
- try {
19504
- let entries = archives.get(resolved.url);
19505
- if (!entries) {
19506
- options.progress.downloading(resolved.name, resolved.version);
19507
- entries = readZip(await options.client.bytes(resolved.url));
19508
- archives.set(resolved.url, entries);
19509
- }
19510
- const dependencies = [];
19511
- const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
19512
- if (metadata) {
19513
- for (const line of new TextDecoder().decode(metadata.data()).split(/\r?\n/)) {
19514
- if (!line.startsWith("Requires-Dist:")) continue;
19515
- const dependency = parseRequirement(line.slice("Requires-Dist:".length));
19516
- if (dependency && markerApplies(dependency.marker, environment, extras)) {
19517
- dependencies.push({ requirement: dependency, extras: dependency.extras });
19518
- }
19519
- }
19520
- }
19521
- const chosen = new Map(state.chosen);
19522
- chosen.set(requirement.name, resolved);
19523
- return await solve([...dependencies, ...rest], {
19524
- chosen,
19525
- staged: [...state.staged, ...stageWheel(entries, resolved)],
19526
- installed: [...state.installed, { name: resolved.name, version: resolved.version }],
19527
- skipped: state.skipped
19528
- });
19529
- } catch (error) {
19530
- failure2 = error;
19531
- }
19532
19684
  }
19533
- throw failure2 instanceof Error ? failure2 : new Error(`could not resolve ${requirement.name}`);
19534
- };
19535
- const solved = await solve(queue, {
19536
- chosen: /* @__PURE__ */ new Map(),
19537
- staged: [],
19538
- installed: [],
19539
- skipped: []
19540
- });
19541
- report.installed = solved.installed;
19542
- report.skipped = solved.skipped;
19543
- commit(options.vfs, options.cred, solved.staged);
19544
- return report;
19685
+ options.progress.downloading(distribution.name, distribution.version);
19686
+ const bytes2 = await options.client.bytes(distribution.url, { timeoutMs: 12e4 });
19687
+ verifyDigest(distribution, bytes2);
19688
+ staged.push(...stageWheel(readZip(bytes2)));
19689
+ installed2.push({ name: distribution.name, version: distribution.version });
19690
+ }
19691
+ commit(options.vfs, options.cred, staged);
19692
+ return { installed: installed2, skipped: solved.skipped };
19693
+ }
19694
+ function verifyDigest(distribution, bytes2) {
19695
+ if (!distribution.sha256) {
19696
+ throw new Error(
19697
+ `${distribution.filename} was published without a sha256 digest, so it cannot be verified`
19698
+ );
19699
+ }
19700
+ const actual = [...sha256(bytes2)].map((b) => b.toString(16).padStart(2, "0")).join("");
19701
+ if (actual !== distribution.sha256) {
19702
+ throw new Error(
19703
+ `${distribution.filename} failed verification: the index published sha256 ${distribution.sha256} but the download hashes to ${actual}`
19704
+ );
19705
+ }
19545
19706
  }
19546
19707
  function stageWheel(entries, resolved) {
19547
19708
  const staged = [];
@@ -19764,34 +19925,52 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
19764
19925
  "outbound network access is disabled for this container (enable with network: { allowOutbound: true })"
19765
19926
  );
19766
19927
  }
19767
- const jsonCache = /* @__PURE__ */ new Map();
19768
- const bytesCache = /* @__PURE__ */ new Map();
19769
- const client = {
19770
- async json(url) {
19771
- let pending = jsonCache.get(url);
19772
- if (!pending) {
19773
- pending = fetch(url).then((response) => {
19774
- if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19775
- return response.json();
19776
- });
19777
- jsonCache.set(url, pending);
19778
- }
19779
- return pending;
19780
- },
19781
- async bytes(url) {
19782
- let pending = bytesCache.get(url);
19783
- if (!pending) {
19784
- pending = fetch(url).then(async (response) => {
19785
- if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19786
- return new Uint8Array(await response.arrayBuffer());
19787
- });
19788
- bytesCache.set(url, pending);
19928
+ const cache = /* @__PURE__ */ new Map();
19929
+ const request = async (url, timeoutMs, read) => {
19930
+ const controller = new AbortController();
19931
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
19932
+ try {
19933
+ const response = await fetch(url, { signal: controller.signal });
19934
+ if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19935
+ return await read(response);
19936
+ } catch (error) {
19937
+ if (controller.signal.aborted) {
19938
+ throw new Error(`${url} did not answer within ${Math.round(timeoutMs / 1e3)}s`);
19789
19939
  }
19790
- return pending;
19940
+ throw error;
19941
+ } finally {
19942
+ clearTimeout(timer);
19791
19943
  }
19792
19944
  };
19945
+ const once = (url, produce) => {
19946
+ let pending = cache.get(url);
19947
+ if (!pending) {
19948
+ pending = produce();
19949
+ cache.set(url, pending);
19950
+ }
19951
+ return pending;
19952
+ };
19953
+ const client = {
19954
+ json: (url, o) => once(`json:${url}`, () => request(url, o?.timeoutMs ?? 3e4, (r) => r.json())),
19955
+ text: (url, o) => once(`text:${url}`, () => request(url, o?.timeoutMs ?? 3e4, (r) => r.text())),
19956
+ bytes: (url, o) => once(`bytes:${url}`, () => request(url, o?.timeoutMs ?? 12e4, async (r) => new Uint8Array(await r.arrayBuffer())))
19957
+ };
19958
+ let index = null;
19959
+ const configured = pythonBackend().wheelIndex ?? null;
19960
+ if (typeof configured === "string") {
19961
+ try {
19962
+ const url = configured.replace(/\/$/, "");
19963
+ const loaded = await client.json(`${url}/index.json`);
19964
+ index = { baseUrl: url, wheels: loaded.wheels ?? [] };
19965
+ } catch (error) {
19966
+ return ctx.fail(`could not read the wheel index at ${configured}: ${error.message}`);
19967
+ }
19968
+ } else if (configured) {
19969
+ index = configured;
19970
+ }
19793
19971
  try {
19794
19972
  const report = await installRequirements({
19973
+ index,
19795
19974
  client,
19796
19975
  vfs: ctx.vfs,
19797
19976
  cred: ctx.cred,
@@ -21236,7 +21415,8 @@ function configurePython(options = {}) {
21236
21415
  setPythonBackend({
21237
21416
  ...options.backend !== void 0 ? { backend: options.backend } : {},
21238
21417
  ...options.manifest !== void 0 ? { manifest: options.manifest } : {},
21239
- ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {}
21418
+ ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {},
21419
+ ...options.wheelIndex !== void 0 ? { wheelIndex: options.wheelIndex } : {}
21240
21420
  });
21241
21421
  }
21242
21422
  var isPythonAvailable = isCPythonAvailable;
@@ -25166,10 +25346,10 @@ function promisify(original) {
25166
25346
  const override = original[promisifyCustom];
25167
25347
  if (typeof override === "function") return override;
25168
25348
  const wrapped = function(...args) {
25169
- return new Promise((resolve2, reject) => {
25349
+ return new Promise((resolve3, reject) => {
25170
25350
  original.call(this, ...args, (error, ...values) => {
25171
25351
  if (error) reject(error);
25172
- else resolve2(values.length > 1 ? values : values[0]);
25352
+ else resolve3(values.length > 1 ? values : values[0]);
25173
25353
  });
25174
25354
  });
25175
25355
  };
@@ -25367,14 +25547,14 @@ var AssertionError = class extends Error {
25367
25547
  code = "ERR_ASSERTION";
25368
25548
  constructor(options) {
25369
25549
  const generated = options.message === void 0;
25370
- super(options.message ?? describe2(options.actual, options.expected, options.operator));
25550
+ super(options.message ?? describe(options.actual, options.expected, options.operator));
25371
25551
  this.actual = options.actual;
25372
25552
  this.expected = options.expected;
25373
25553
  this.operator = options.operator;
25374
25554
  this.generatedMessage = generated;
25375
25555
  }
25376
25556
  };
25377
- function describe2(actual, expected, operator) {
25557
+ function describe(actual, expected, operator) {
25378
25558
  if (operator === "fail") return "Failed";
25379
25559
  const rendered = (value) => inspect(value, { depth: 2 });
25380
25560
  return `${rendered(actual)} ${operator} ${rendered(expected)}`;
@@ -25748,8 +25928,8 @@ var VirtualServerResponse = class extends streamModule4.Writable {
25748
25928
  constructor(request) {
25749
25929
  super();
25750
25930
  this.req = request;
25751
- this.completed = new Promise((resolve2) => {
25752
- this.resolve = resolve2;
25931
+ this.completed = new Promise((resolve3) => {
25932
+ this.resolve = resolve3;
25753
25933
  });
25754
25934
  }
25755
25935
  _write(chunk, encoding, callback) {
@@ -26926,7 +27106,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
26926
27106
  this.pending.push(callback);
26927
27107
  return;
26928
27108
  }
26929
- return new Promise((resolve2) => this.pending.push(resolve2));
27109
+ return new Promise((resolve3) => this.pending.push(resolve3));
26930
27110
  }
26931
27111
  prompt() {
26932
27112
  this.output?.write?.(this.promptText);
@@ -27083,14 +27263,14 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
27083
27263
  yield line;
27084
27264
  continue;
27085
27265
  }
27086
- const next = await new Promise((resolve2) => {
27266
+ const next = await new Promise((resolve3) => {
27087
27267
  const onLine = (value) => {
27088
27268
  cleanup();
27089
- resolve2(value);
27269
+ resolve3(value);
27090
27270
  };
27091
27271
  const onClose = () => {
27092
27272
  cleanup();
27093
- resolve2(null);
27273
+ resolve3(null);
27094
27274
  };
27095
27275
  const cleanup = () => {
27096
27276
  this.off("line", onLine);
@@ -27467,13 +27647,13 @@ var ASYNC_FS_METHODS = [
27467
27647
  "exists"
27468
27648
  ];
27469
27649
  function createPathModule(cwd) {
27470
- const resolve2 = (...segments2) => pathModule.resolve(cwd(), ...segments2);
27650
+ const resolve3 = (...segments2) => pathModule.resolve(cwd(), ...segments2);
27471
27651
  const path = {
27472
27652
  ...pathModule,
27473
- resolve: resolve2,
27653
+ resolve: resolve3,
27474
27654
  /* `relative` resolves both operands, and the implementation would do so
27475
27655
  * against the host cwd, so they are resolved here first. */
27476
- relative: (from, to) => pathModule.relative(resolve2(from), resolve2(to))
27656
+ relative: (from, to) => pathModule.relative(resolve3(from), resolve3(to))
27477
27657
  };
27478
27658
  path.posix = path;
27479
27659
  path.win32 = pathModule.win32 ?? path;
@@ -28137,8 +28317,8 @@ function createTrackedTimers() {
28137
28317
  }
28138
28318
  function createTimerPromises(timers) {
28139
28319
  return {
28140
- setTimeout: (delay, value) => new Promise((resolve2) => timers.setTimeout(resolve2, delay, value)),
28141
- setImmediate: (value) => new Promise((resolve2) => timers.setImmediate(resolve2, value))
28320
+ setTimeout: (delay, value) => new Promise((resolve3) => timers.setTimeout(resolve3, delay, value)),
28321
+ setImmediate: (value) => new Promise((resolve3) => timers.setImmediate(resolve3, value))
28142
28322
  };
28143
28323
  }
28144
28324
  function createAsyncHooksModule() {
@@ -28217,12 +28397,12 @@ function createAsyncHooksModule() {
28217
28397
  }
28218
28398
  function createStreamPromises() {
28219
28399
  return {
28220
- pipeline: (...streams) => new Promise((resolve2, reject) => {
28221
- const callback = (error) => error ? reject(error) : resolve2();
28400
+ pipeline: (...streams) => new Promise((resolve3, reject) => {
28401
+ const callback = (error) => error ? reject(error) : resolve3();
28222
28402
  streamModule4.pipeline(...streams, callback);
28223
28403
  }),
28224
- finished: (stream) => new Promise((resolve2, reject) => {
28225
- streamModule4.finished(stream, (error) => error ? reject(error) : resolve2());
28404
+ finished: (stream) => new Promise((resolve3, reject) => {
28405
+ streamModule4.finished(stream, (error) => error ? reject(error) : resolve3());
28226
28406
  })
28227
28407
  };
28228
28408
  }
@@ -29286,8 +29466,8 @@ var LocalProcess = class extends EventEmitter4 {
29286
29466
  this.task = task;
29287
29467
  super.on("error", () => {
29288
29468
  });
29289
- this.completion = new Promise((resolve2) => {
29290
- this.resolveCompletion = resolve2;
29469
+ this.completion = new Promise((resolve3) => {
29470
+ this.resolveCompletion = resolve3;
29291
29471
  });
29292
29472
  setTimeout(() => void this.start(), 0);
29293
29473
  }
@@ -29304,8 +29484,8 @@ var LocalProcess = class extends EventEmitter4 {
29304
29484
  pendingInput = [];
29305
29485
  resolveKilled;
29306
29486
  cleanup;
29307
- killedPromise = new Promise((resolve2) => {
29308
- this.resolveKilled = resolve2;
29487
+ killedPromise = new Promise((resolve3) => {
29488
+ this.resolveKilled = resolve3;
29309
29489
  });
29310
29490
  on(event, listener) {
29311
29491
  return super.on(event, listener);
@@ -29735,7 +29915,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
29735
29915
  const busy = this.router.referencedPorts(owner).length > 0 || pendingHandles() > 0 || pendingRequests() > 0 || readingStdin();
29736
29916
  idleTurns = busy ? 0 : idleTurns + 1;
29737
29917
  if (idleTurns >= DRAIN_TURNS) return;
29738
- await new Promise((resolve2) => setTimeout(resolve2, busy ? 5 : 0));
29918
+ await new Promise((resolve3) => setTimeout(resolve3, busy ? 5 : 0));
29739
29919
  }
29740
29920
  }
29741
29921
  async request(_port, _init = {}) {
@@ -29921,8 +30101,8 @@ var WorkerProcess = class extends EventEmitter4 {
29921
30101
  this.release = release;
29922
30102
  super.on("error", () => {
29923
30103
  });
29924
- this.completion = new Promise((resolve2) => {
29925
- this.resolveCompletion = resolve2;
30104
+ this.completion = new Promise((resolve3) => {
30105
+ this.resolveCompletion = resolve3;
29926
30106
  });
29927
30107
  }
29928
30108
  worker;
@@ -30164,7 +30344,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30164
30344
  }
30165
30345
  /** Run a child to completion and collect it, for the guest's `spawnSync`. */
30166
30346
  runChildToCompletion(request, streamTo, owned) {
30167
- return new Promise((resolve2) => {
30347
+ return new Promise((resolve3) => {
30168
30348
  let handle;
30169
30349
  try {
30170
30350
  handle = this.processManager.spawn({
@@ -30176,7 +30356,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30176
30356
  });
30177
30357
  } catch (error) {
30178
30358
  const failure2 = error;
30179
- resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure2.code ? { code: failure2.code } : {}, message: failure2.message } });
30359
+ resolve3({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure2.code ? { code: failure2.code } : {}, message: failure2.message } });
30180
30360
  return;
30181
30361
  }
30182
30362
  owned.add(handle);
@@ -30194,7 +30374,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30194
30374
  stderr += text2;
30195
30375
  live?.error(text2);
30196
30376
  });
30197
- handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
30377
+ handle.on("exit", (code) => resolve3({ status: code, stdout, stderr, signal: null }));
30198
30378
  handle.exec();
30199
30379
  if (request.input !== void 0) {
30200
30380
  handle.sendStdin?.(request.input);
@@ -30233,7 +30413,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30233
30413
  for await (const chunk of request) chunks.push(chunk);
30234
30414
  const body = concat5(chunks);
30235
30415
  const id = this.nextRequestId++;
30236
- const answered = new Promise((resolve2) => this.waiting.set(id, resolve2));
30416
+ const answered = new Promise((resolve3) => this.waiting.set(id, resolve3));
30237
30417
  worker.postMessage({
30238
30418
  type: "http-request",
30239
30419
  id,
@@ -30280,10 +30460,10 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30280
30460
  });
30281
30461
  }
30282
30462
  settleProxied(id, response) {
30283
- const resolve2 = this.waiting.get(id);
30284
- if (!resolve2) return;
30463
+ const resolve3 = this.waiting.get(id);
30464
+ if (!resolve3) return;
30285
30465
  this.waiting.delete(id);
30286
- resolve2(response);
30466
+ resolve3(response);
30287
30467
  }
30288
30468
  closeProxies(owner) {
30289
30469
  for (const [port, server] of [...this.proxies]) {
@@ -30495,7 +30675,7 @@ var Container = class _Container {
30495
30675
  try {
30496
30676
  exitCode = timeoutMs === void 0 ? await shell.execute(command, io) : await Promise.race([
30497
30677
  shell.execute(command, io),
30498
- new Promise((resolve2) => setTimeout(() => resolve2(137), timeoutMs + 500))
30678
+ new Promise((resolve3) => setTimeout(() => resolve3(137), timeoutMs + 500))
30499
30679
  ]);
30500
30680
  } finally {
30501
30681
  if (timer) clearTimeout(timer);
@@ -30758,16 +30938,16 @@ var Container = class _Container {
30758
30938
  });
30759
30939
  const hostPort = opts.hostPort ?? 0;
30760
30940
  const hostname = opts.hostname ?? "127.0.0.1";
30761
- await new Promise((resolve2, reject) => {
30941
+ await new Promise((resolve3, reject) => {
30762
30942
  server.once("error", reject);
30763
- server.listen(hostPort, hostname, () => resolve2());
30943
+ server.listen(hostPort, hostname, () => resolve3());
30764
30944
  });
30765
30945
  const address = server.address();
30766
30946
  const actualPort = typeof address === "object" && address ? address.port : hostPort;
30767
30947
  return {
30768
30948
  url: `http://${hostname}:${actualPort}`,
30769
30949
  port: actualPort,
30770
- close: () => new Promise((resolve2) => server.close(() => resolve2()))
30950
+ close: () => new Promise((resolve3) => server.close(() => resolve3()))
30771
30951
  };
30772
30952
  }
30773
30953
  // ── persistence ───────────────────────────────────────────────────────────
@@ -31331,18 +31511,18 @@ async function createPreview(box, options = {}) {
31331
31511
  const worker = registration.active ?? registration.waiting ?? registration.installing;
31332
31512
  if (!worker) return null;
31333
31513
  if (worker.state !== "activated") {
31334
- const activated = await new Promise((resolve2) => {
31514
+ const activated = await new Promise((resolve3) => {
31335
31515
  const check = () => {
31336
31516
  if (worker.state === "activated") {
31337
31517
  worker.removeEventListener("statechange", check);
31338
- resolve2(true);
31518
+ resolve3(true);
31339
31519
  } else if (worker.state === "redundant") {
31340
31520
  worker.removeEventListener("statechange", check);
31341
- resolve2(false);
31521
+ resolve3(false);
31342
31522
  }
31343
31523
  };
31344
31524
  worker.addEventListener("statechange", check);
31345
- setTimeout(() => resolve2(worker.state === "activated"), 1e4);
31525
+ setTimeout(() => resolve3(worker.state === "activated"), 1e4);
31346
31526
  check();
31347
31527
  });
31348
31528
  if (!activated) return null;