@ai-ecoverse/slicc-kernel 1.12.0 → 1.14.0

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/node.js CHANGED
@@ -252,7 +252,7 @@ function serveTransport(peer, transport) {
252
252
  }
253
253
 
254
254
  // src/client/protocol.ts
255
- var PROTOCOL = [1, 1];
255
+ var PROTOCOL = [1, 2];
256
256
  var FS_METHODS = [
257
257
  "readFile",
258
258
  "writeFile",
@@ -341,6 +341,11 @@ async function fsCall(fs, method, args) {
341
341
  return { result: await fs.exists(path(0)) };
342
342
  }
343
343
  }
344
+ function signalOf(host) {
345
+ return host.signal ?? ((name2) => {
346
+ throw fsError("EINVAL", `this kernel names no signal ${name2}`);
347
+ });
348
+ }
344
349
  function serveClient(port, host) {
345
350
  let state = "new";
346
351
  const terminals = /* @__PURE__ */ new Map();
@@ -428,6 +433,9 @@ function serveClient(port, host) {
428
433
  return { result: result.status };
429
434
  }
430
435
  async function handle(req) {
436
+ if (host.scope === "transport" && req.op !== "detach") {
437
+ throw fsError("EPERM", `${req.op}: this client may only use the network transport`);
438
+ }
431
439
  const l = await host.launcher();
432
440
  switch (req.op) {
433
441
  case "spawn":
@@ -437,11 +445,11 @@ function serveClient(port, host) {
437
445
  case "terminal": {
438
446
  const session = terminals.get(req.terminal);
439
447
  if (!session) throw new Error(`no terminal ${req.terminal}`);
440
- act(session, req, host.signal);
448
+ act(session, req, signalOf(host));
441
449
  return { result: true };
442
450
  }
443
451
  case "kill":
444
- if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
452
+ if (!l.kill(req.pid, signalOf(host)(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
445
453
  return { result: true };
446
454
  case "ps":
447
455
  return {
@@ -465,6 +473,13 @@ function serveClient(port, host) {
465
473
  watches.get(req.watch)?.();
466
474
  watches.delete(req.watch);
467
475
  return { result: true };
476
+ case "mount":
477
+ return { result: await l.mount(req.spec) };
478
+ case "umount":
479
+ l.umount(req.target);
480
+ return { result: true };
481
+ case "mounts":
482
+ return { result: l.mounts.list() };
468
483
  case "detach":
469
484
  return { result: true };
470
485
  default:
@@ -1153,9 +1168,28 @@ async function packages(fs, modules) {
1153
1168
  }
1154
1169
  return dirs;
1155
1170
  }
1171
+ async function pnpmGlobalRoots(fs, home) {
1172
+ const roots = [];
1173
+ const global = `${home}/global`;
1174
+ for (const version of (await fs.readdir(global).catch(() => [])).sort()) {
1175
+ for (const group of (await fs.readdir(`${global}/${version}`).catch(() => [])).sort()) {
1176
+ const dir = `${global}/${version}/${group}`;
1177
+ const st = await fs.lstat(dir).catch(() => void 0);
1178
+ if (st?.isDirectory && await fs.exists(`${dir}/node_modules`))
1179
+ roots.push(`${dir}/node_modules`);
1180
+ }
1181
+ }
1182
+ return roots;
1183
+ }
1184
+ async function packagesIn(fs, roots) {
1185
+ const dirs = [];
1186
+ for (const root of typeof roots === "string" ? [roots] : roots)
1187
+ dirs.push(...await packages(fs, root));
1188
+ return dirs;
1189
+ }
1156
1190
  async function scanCommands(fs, modules) {
1157
1191
  const found = /* @__PURE__ */ new Map();
1158
- for (const pkg of await packages(fs, modules)) {
1192
+ for (const pkg of await packagesIn(fs, modules)) {
1159
1193
  let manifest;
1160
1194
  try {
1161
1195
  manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
@@ -1172,6 +1206,24 @@ async function scanCommands(fs, modules) {
1172
1206
  }
1173
1207
  return found;
1174
1208
  }
1209
+ async function scanFilesystems(fs, modules) {
1210
+ const found = /* @__PURE__ */ new Map();
1211
+ for (const pkg of await packagesIn(fs, modules)) {
1212
+ let manifest;
1213
+ try {
1214
+ manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
1215
+ } catch {
1216
+ continue;
1217
+ }
1218
+ const declared = manifest.slicc?.filesystems;
1219
+ if (!declared || typeof declared !== "object") continue;
1220
+ for (const [type, entry] of Object.entries(declared)) {
1221
+ const module = inside(pkg, entry?.module);
1222
+ if (NAME.test(type) && module && !found.has(type)) found.set(type, module);
1223
+ }
1224
+ }
1225
+ return found;
1226
+ }
1175
1227
 
1176
1228
  // src/fs/commands.ts
1177
1229
  var S_IFDIR2 = 16384;
@@ -1934,7 +1986,8 @@ function statJson(s) {
1934
1986
  mtimeMs: s.mtime.getTime(),
1935
1987
  atimeMs: s.atime.getTime(),
1936
1988
  ctimeMs: s.ctime.getTime(),
1937
- ino: s.ino
1989
+ ino: s.ino,
1990
+ ...s.dev !== void 0 ? { dev: s.dev } : {}
1938
1991
  };
1939
1992
  }
1940
1993
  async function readdirStat(fs, dir) {
@@ -1995,8 +2048,6 @@ async function run(fs, path, req, cwd) {
1995
2048
  case "utimes":
1996
2049
  await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
1997
2050
  return done;
1998
- case "statfs":
1999
- return json(await storageUsage());
2000
2051
  default:
2001
2052
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
2002
2053
  }
@@ -2006,7 +2057,13 @@ async function dispatchSyncFs(req) {
2006
2057
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
2007
2058
  const { fs, cwd } = entry;
2008
2059
  try {
2009
- return await run(fs, fs.resolvePath(cwd, req.path), req, cwd);
2060
+ const path = fs.resolvePath(cwd, req.path);
2061
+ if (req.op === "statfs") return json(await entry.statfs?.(path) ?? await storageUsage());
2062
+ if (req.op === "hold") {
2063
+ entry.hold?.(path, req.mode === 1);
2064
+ return done;
2065
+ }
2066
+ return await run(fs, path, req, cwd);
2010
2067
  } catch (err) {
2011
2068
  return toErrno(err);
2012
2069
  }
@@ -2070,7 +2127,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
2070
2127
  const { header: header2, window } = views;
2071
2128
  const pending = /* @__PURE__ */ new Map();
2072
2129
  let disposed = false;
2073
- const { dispatch } = opts;
2130
+ const { dispatch: dispatch2 } = opts;
2074
2131
  function drop(id) {
2075
2132
  const entry = pending.get(id);
2076
2133
  if (!entry) return;
@@ -2125,7 +2182,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
2125
2182
  const req = { ...body, token };
2126
2183
  let dispatched;
2127
2184
  try {
2128
- dispatched = dispatch(req);
2185
+ dispatched = dispatch2(req);
2129
2186
  } catch (err) {
2130
2187
  settle(id, {
2131
2188
  ok: false,
@@ -4747,6 +4804,10 @@ var VfsNodes = class {
4747
4804
  node.opens++;
4748
4805
  return node;
4749
4806
  }
4807
+ holds(prefix) {
4808
+ for (const path of this.byPath.keys()) if (within2(path, prefix)) return true;
4809
+ return false;
4810
+ }
4750
4811
  closed(node) {
4751
4812
  node.opens--;
4752
4813
  if (node.opens === 0 && this.byPath.get(node.path) === node) this.byPath.delete(node.path);
@@ -4924,6 +4985,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4924
4985
  "proc-fork",
4925
4986
  "proc-kill",
4926
4987
  "proc-list",
4988
+ "mount-list",
4927
4989
  "proc-exec",
4928
4990
  "proc-setpgid",
4929
4991
  "proc-getpgid",
@@ -4967,6 +5029,7 @@ var WasmProcess = class {
4967
5029
  this.options = options;
4968
5030
  this.children = new ChildTable(fds, options.spawner, options.forker);
4969
5031
  this.nodes = new VfsNodes(options.fs);
5032
+ options.openFiles?.add(this.nodes);
4970
5033
  this.children.onChildState = () => this.signal(SIG.CHLD);
4971
5034
  this.children.onReap = options.onReap;
4972
5035
  }
@@ -5378,6 +5441,8 @@ var WasmProcess = class {
5378
5441
  kind: "json",
5379
5442
  json: this.options.processes?.() ?? { boot: 0, processes: [] }
5380
5443
  };
5444
+ case "mount-list":
5445
+ return { ok: true, kind: "json", json: this.options.mounts?.() ?? [] };
5381
5446
  case "proc-alarm":
5382
5447
  this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
5383
5448
  return { ok: true, kind: "void" };
@@ -5435,6 +5500,7 @@ var WasmProcess = class {
5435
5500
  this.asyncOps.close();
5436
5501
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
5437
5502
  await this.fds.closeAll();
5503
+ this.options.openFiles?.delete(this.nodes);
5438
5504
  await this.options.http?.closeAll();
5439
5505
  }
5440
5506
  };
@@ -5482,6 +5548,8 @@ function spawnWasmProcess(opts) {
5482
5548
  fs: opts.fs,
5483
5549
  kill: opts.kill,
5484
5550
  processes: opts.processes,
5551
+ openFiles: opts.openFiles,
5552
+ mounts: opts.mounts,
5485
5553
  jobs: opts.jobs,
5486
5554
  ptys: opts.ptys,
5487
5555
  net: opts.net,
@@ -5494,13 +5562,24 @@ function spawnWasmProcess(opts) {
5494
5562
  raise: (sig) => signal(sig),
5495
5563
  ...opts.program.abi === "wasi" ? { pendingBits: () => Atomics.load(header2, SAB_I_SIGNALS) } : {}
5496
5564
  });
5497
- const token = mintSyncFsToken({ fs: opts.fs, cwd: opts.cwd });
5565
+ const holds = /* @__PURE__ */ new Map();
5566
+ opts.held?.add(holds);
5567
+ const token = mintSyncFsToken({
5568
+ fs: opts.fs,
5569
+ cwd: opts.cwd,
5570
+ ...opts.statfs ? { statfs: opts.statfs } : {},
5571
+ hold: (path, on) => {
5572
+ const count = (holds.get(path) ?? 0) + (on ? 1 : -1);
5573
+ if (count > 0) holds.set(path, count);
5574
+ else holds.delete(path);
5575
+ }
5576
+ });
5498
5577
  const worker = opts.createWorker();
5499
- const dispatch = async (req) => {
5578
+ const dispatch2 = async (req) => {
5500
5579
  if (isWasmSyscall(req)) return process.syscall(req);
5501
5580
  return dispatchSyncFs(req);
5502
5581
  };
5503
- const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
5582
+ const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
5504
5583
  let settle;
5505
5584
  const exited = new Promise((resolve) => settle = resolve);
5506
5585
  let done4 = false;
@@ -5514,6 +5593,7 @@ function spawnWasmProcess(opts) {
5514
5593
  for (const tid of [...threads.keys()]) endThread(tid);
5515
5594
  responder.dispose();
5516
5595
  revokeSyncFsToken(token);
5596
+ opts.held?.delete(holds);
5517
5597
  worker.terminate();
5518
5598
  void process.exit().then(
5519
5599
  () => settle(code),
@@ -5553,7 +5633,7 @@ function spawnWasmProcess(opts) {
5553
5633
  }
5554
5634
  const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
5555
5635
  const tw = opts.createWorker();
5556
- const tresponder = attachSyncSabResponder(tw, tsab, token, { dispatch });
5636
+ const tresponder = attachSyncSabResponder(tw, tsab, token, { dispatch: dispatch2 });
5557
5637
  const onThreadMessage = (event) => {
5558
5638
  const data = event.data;
5559
5639
  if (data?.type === WASM_THREAD_EXIT) endThread(thread.tid);
@@ -5719,6 +5799,815 @@ var JobTable = class {
5719
5799
  }
5720
5800
  };
5721
5801
 
5802
+ // src/mount/protocol.ts
5803
+ var DRIVER_PROTOCOL = [1, 0];
5804
+ function driverVersionError(theirs) {
5805
+ const major = Array.isArray(theirs) ? theirs[0] : void 0;
5806
+ if (major === DRIVER_PROTOCOL[0]) return void 0;
5807
+ const named = typeof major === "number" ? `${major}.x` : "without a version";
5808
+ return `slicc-kernel driver protocol ${named} is not supported: this side speaks ${DRIVER_PROTOCOL.join(".")}`;
5809
+ }
5810
+
5811
+ // src/mount/driver.ts
5812
+ function fsError2(code, message2 = code) {
5813
+ return Object.assign(new Error(message2), { code });
5814
+ }
5815
+ async function dispatch(h, req) {
5816
+ switch (req.op) {
5817
+ case "getattr":
5818
+ return h.getattr(req.path);
5819
+ case "readdir":
5820
+ return h.readdir(req.path);
5821
+ case "open":
5822
+ return h.open(req.path, {
5823
+ write: req.write,
5824
+ create: req.create,
5825
+ truncate: req.truncate,
5826
+ exclusive: req.exclusive
5827
+ });
5828
+ case "read":
5829
+ return h.read(req.fh, req.offset, req.size);
5830
+ case "write":
5831
+ return h.write(req.fh, req.offset, req.bytes);
5832
+ case "release":
5833
+ return h.release(req.fh);
5834
+ case "mkdir":
5835
+ return h.mkdir(req.path);
5836
+ case "rmdir":
5837
+ return h.rmdir(req.path);
5838
+ case "unlink":
5839
+ return h.unlink(req.path);
5840
+ case "rename":
5841
+ return h.rename(req.from, req.to);
5842
+ case "symlink":
5843
+ if (!h.symlink) throw fsError2("EPERM", "this file system has no symlinks");
5844
+ return h.symlink(req.target, req.path);
5845
+ case "readlink":
5846
+ if (!h.readlink) throw fsError2("EINVAL", `${req.path} is not a symlink`);
5847
+ return h.readlink(req.path);
5848
+ case "setattr":
5849
+ return h.setattr?.(req.path, {
5850
+ ...req.mode !== void 0 ? { mode: req.mode } : {},
5851
+ ...req.mtime !== void 0 ? { mtime: req.mtime } : {}
5852
+ });
5853
+ case "statfs":
5854
+ return h.statfs?.() ?? null;
5855
+ default:
5856
+ throw fsError2("ENOSYS", `unknown driver op ${req.op}`);
5857
+ }
5858
+ }
5859
+ function serveFilesystem(port, handlers, capabilities = {}) {
5860
+ let open = false;
5861
+ const reply = (message2, transfer = []) => port.postMessage(message2, transfer);
5862
+ async function hello(message2) {
5863
+ const refused = driverVersionError(message2.protocol);
5864
+ if (refused) {
5865
+ reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused, errno: "EPROTO" } });
5866
+ return;
5867
+ }
5868
+ try {
5869
+ const refined = await handlers.mount?.(message2.mount);
5870
+ open = true;
5871
+ reply({
5872
+ hello: { protocol: DRIVER_PROTOCOL, capabilities: { ...capabilities, ...refined } }
5873
+ });
5874
+ } catch (err) {
5875
+ const code = err?.code;
5876
+ reply({
5877
+ hello: {
5878
+ protocol: DRIVER_PROTOCOL,
5879
+ error: err instanceof Error ? err.message : String(err),
5880
+ errno: typeof code === "string" ? code : "EIO"
5881
+ }
5882
+ });
5883
+ }
5884
+ }
5885
+ port.addEventListener("message", (event) => {
5886
+ const data = event.data;
5887
+ if (data.hello) {
5888
+ void hello(data.hello);
5889
+ return;
5890
+ }
5891
+ if (!open) return;
5892
+ dispatch(handlers, data).then(
5893
+ (result) => {
5894
+ if (result instanceof Uint8Array) {
5895
+ const bytes = result.slice();
5896
+ reply({ id: data.id, result: bytes }, [bytes.buffer]);
5897
+ } else reply({ id: data.id, result: result ?? null });
5898
+ },
5899
+ (err) => {
5900
+ const code = err?.code;
5901
+ reply({
5902
+ id: data.id,
5903
+ errno: typeof code === "string" ? code : "EIO",
5904
+ message: err instanceof Error ? err.message : String(err)
5905
+ });
5906
+ }
5907
+ );
5908
+ });
5909
+ port.start?.();
5910
+ return { invalidate: (paths) => reply({ invalidate: paths }) };
5911
+ }
5912
+
5913
+ // src/mount/connection.ts
5914
+ var DRIVER_TIMEOUT_MS = 3e4;
5915
+ function unref(timer) {
5916
+ timer.unref?.();
5917
+ return timer;
5918
+ }
5919
+ function errnoError(code, message2) {
5920
+ return Object.assign(new Error(`${code}: ${message2}`), { code });
5921
+ }
5922
+ var DriverConnection = class _DriverConnection {
5923
+ capabilities;
5924
+ failure;
5925
+ port;
5926
+ waiting;
5927
+ timeoutMs;
5928
+ nextId = 0;
5929
+ onFail;
5930
+ constructor(port, capabilities, waiting, timeoutMs, onFail) {
5931
+ this.port = port;
5932
+ this.capabilities = capabilities;
5933
+ this.waiting = waiting;
5934
+ this.timeoutMs = timeoutMs;
5935
+ this.onFail = onFail;
5936
+ }
5937
+ static async open(port, mount, options = {}) {
5938
+ const timeoutMs = options.timeoutMs ?? DRIVER_TIMEOUT_MS;
5939
+ const waiting = /* @__PURE__ */ new Map();
5940
+ const greeted = Promise.withResolvers();
5941
+ port.addEventListener("message", (event) => {
5942
+ const data = event.data;
5943
+ if (data.hello) return greeted.resolve(data.hello);
5944
+ if (data.invalidate !== void 0) return options.onInvalidate?.(data.invalidate);
5945
+ const call = waiting.get(data.id);
5946
+ if (!call) return;
5947
+ waiting.delete(data.id);
5948
+ clearTimeout(call.timer);
5949
+ if (data.errno) call.reject(errnoError(data.errno, data.message ?? data.errno));
5950
+ else call.resolve(data.result);
5951
+ });
5952
+ port.start?.();
5953
+ port.postMessage({ hello: { protocol: DRIVER_PROTOCOL, mount } });
5954
+ const timer = unref(
5955
+ setTimeout(
5956
+ () => greeted.reject(errnoError("EIO", `the driver did not answer within ${timeoutMs} ms`)),
5957
+ timeoutMs
5958
+ )
5959
+ );
5960
+ let hello;
5961
+ try {
5962
+ hello = await greeted.promise;
5963
+ } finally {
5964
+ clearTimeout(timer);
5965
+ }
5966
+ const refused = driverVersionError(hello.protocol);
5967
+ if (refused) throw errnoError("EPROTO", refused);
5968
+ if (hello.error) throw errnoError(hello.errno ?? "EIO", hello.error);
5969
+ return new _DriverConnection(port, hello.capabilities ?? {}, waiting, timeoutMs, options.onFail);
5970
+ }
5971
+ call(call, transfer = []) {
5972
+ if (this.failure) return Promise.reject(this.failure);
5973
+ const id = ++this.nextId;
5974
+ return new Promise((resolve, reject) => {
5975
+ const timer = unref(
5976
+ setTimeout(() => {
5977
+ this.waiting.delete(id);
5978
+ const error = errnoError(
5979
+ "EIO",
5980
+ `the driver did not answer ${call.op} within ${this.timeoutMs} ms`
5981
+ );
5982
+ reject(error);
5983
+ this.fail(error);
5984
+ this.onFail?.(error);
5985
+ }, this.timeoutMs)
5986
+ );
5987
+ this.waiting.set(id, { resolve, reject, timer });
5988
+ this.port.postMessage({ ...call, id }, transfer);
5989
+ });
5990
+ }
5991
+ fail(error) {
5992
+ if (this.failure) return;
5993
+ this.failure = error;
5994
+ for (const call of this.waiting.values()) {
5995
+ clearTimeout(call.timer);
5996
+ call.reject(error);
5997
+ }
5998
+ this.waiting.clear();
5999
+ }
6000
+ close() {
6001
+ this.fail(errnoError("EIO", "the file system is unmounted"));
6002
+ this.port.close?.();
6003
+ }
6004
+ };
6005
+
6006
+ // src/mount/mount-fs.ts
6007
+ var MAX_LINKS4 = 40;
6008
+ var DEFAULT_IO = 8 * 1024 * 1024;
6009
+ var UNITS = { "": 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3, t: 1024 ** 4 };
6010
+ function parseSize(text) {
6011
+ const match = /^(\d+(?:\.\d+)?)\s*([kmgt]?)i?b?$/i.exec(text.trim());
6012
+ if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
6013
+ return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
6014
+ }
6015
+ var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
6016
+ function heldUnder(held, target) {
6017
+ for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
6018
+ return false;
6019
+ }
6020
+ var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6021
+ function statOf(attr, path, dev) {
6022
+ const mtime = new Date(attr.mtime);
6023
+ const fallback = attr.kind === "directory" ? 16877 : attr.kind === "symlink" ? 41471 : 33188;
6024
+ return {
6025
+ isFile: attr.kind === "file",
6026
+ isDirectory: attr.kind === "directory",
6027
+ isSymbolicLink: attr.kind === "symlink",
6028
+ size: attr.size,
6029
+ mode: attr.mode ?? fallback,
6030
+ mtime,
6031
+ atime: mtime,
6032
+ ctime: mtime,
6033
+ ino: attr.ino ?? inodeOf(path),
6034
+ dev
6035
+ };
6036
+ }
6037
+ function concat4(chunks2, total) {
6038
+ if (chunks2.length === 1 && chunks2[0]?.length === total) return chunks2[0];
6039
+ const out = new Uint8Array(total);
6040
+ let at = 0;
6041
+ for (const chunk2 of chunks2) {
6042
+ out.set(chunk2, at);
6043
+ at += chunk2.length;
6044
+ }
6045
+ return out;
6046
+ }
6047
+ var MountTable = class {
6048
+ mounts = /* @__PURE__ */ new Map();
6049
+ devices = 0;
6050
+ pending = /* @__PURE__ */ new Set();
6051
+ deps;
6052
+ constructor(deps) {
6053
+ this.deps = deps;
6054
+ }
6055
+ list() {
6056
+ return [...this.mounts.values()].map(({ type, source, target, options, state, error }) => ({
6057
+ type,
6058
+ source,
6059
+ target,
6060
+ options: { ...options },
6061
+ state,
6062
+ ...error ? { error } : {}
6063
+ }));
6064
+ }
6065
+ locate(path) {
6066
+ let best;
6067
+ for (const mount of this.mounts.values()) {
6068
+ if (within3(path, mount.target) && (!best || mount.target.length > best.target.length))
6069
+ best = mount;
6070
+ }
6071
+ if (!best) return void 0;
6072
+ const rel = best.target === "/" ? path : path.slice(best.target.length);
6073
+ return { mount: best, rel: rel || "/" };
6074
+ }
6075
+ async mount(spec, fs) {
6076
+ const target = normalizePath(spec.target);
6077
+ if (this.mounts.has(target) || this.pending.has(target)) {
6078
+ throw errnoError("EBUSY", `${target} is already mounted`);
6079
+ }
6080
+ this.pending.add(target);
6081
+ try {
6082
+ return await this.attach(spec, target, fs);
6083
+ } finally {
6084
+ this.pending.delete(target);
6085
+ }
6086
+ }
6087
+ async attach(spec, target, fs) {
6088
+ const st = await fs.stat(target);
6089
+ if (!st.isDirectory) throw errnoError("ENOTDIR", target);
6090
+ const options = { ...spec.options };
6091
+ const opened2 = await this.deps.open(spec.type, { ...spec, target, options });
6092
+ let mounted;
6093
+ const started = Promise.withResolvers();
6094
+ opened2.onCrash?.((error) => {
6095
+ if (mounted) this.crashed(mounted, error);
6096
+ else
6097
+ started.reject(
6098
+ errnoError("EIO", `the ${spec.type} driver failed to start: ${error.message}`)
6099
+ );
6100
+ });
6101
+ let conn;
6102
+ try {
6103
+ conn = await Promise.race([
6104
+ DriverConnection.open(
6105
+ opened2.port,
6106
+ { source: spec.source, options },
6107
+ {
6108
+ ...this.deps.timeoutMs ? { timeoutMs: this.deps.timeoutMs } : {},
6109
+ onInvalidate: (paths) => this.invalidated(target, paths),
6110
+ onFail: (error) => {
6111
+ if (mounted) this.crashed(mounted, error);
6112
+ }
6113
+ }
6114
+ ),
6115
+ started.promise
6116
+ ]);
6117
+ } catch (err) {
6118
+ opened2.dispose();
6119
+ throw err;
6120
+ }
6121
+ const caps = conn.capabilities;
6122
+ const mount = {
6123
+ type: spec.type,
6124
+ source: spec.source,
6125
+ target,
6126
+ options,
6127
+ state: "ok",
6128
+ dev: 256 + ++this.devices,
6129
+ conn,
6130
+ caps,
6131
+ maxFile: options.maxfile !== void 0 ? parseSize(options.maxfile) : caps.maxFile ?? 0,
6132
+ attrs: /* @__PURE__ */ new Map(),
6133
+ lists: /* @__PURE__ */ new Map(),
6134
+ dispose: () => opened2.dispose()
6135
+ };
6136
+ mounted = mount;
6137
+ this.mounts.set(target, mount);
6138
+ return this.list().find((m) => m.target === target);
6139
+ }
6140
+ umount(target) {
6141
+ const at = normalizePath(target);
6142
+ const mount = this.mounts.get(at);
6143
+ if (!mount) throw errnoError("EINVAL", `${at} is not mounted`);
6144
+ if (this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
6145
+ this.mounts.delete(at);
6146
+ mount.conn.close();
6147
+ mount.dispose();
6148
+ }
6149
+ crashed(mount, error) {
6150
+ mount.state = "failed";
6151
+ mount.attrs.clear();
6152
+ mount.lists.clear();
6153
+ mount.error = error.message;
6154
+ mount.conn.fail(
6155
+ errnoError("EIO", `the ${mount.type} driver for ${mount.target} failed: ${error.message}`)
6156
+ );
6157
+ }
6158
+ invalidated(target, paths) {
6159
+ const mount = this.mounts.get(target);
6160
+ if (!mount) return;
6161
+ if (paths === true) {
6162
+ mount.attrs.clear();
6163
+ mount.lists.clear();
6164
+ this.deps.changed?.(target);
6165
+ return;
6166
+ }
6167
+ const full = paths.map((p) => normalizePath(`${target}/${p}`));
6168
+ for (const path of full) this.forget(mount, path);
6169
+ this.deps.changed?.(...full);
6170
+ }
6171
+ forget(mount, path) {
6172
+ for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
6173
+ mount.lists.delete(path);
6174
+ mount.lists.delete(parentOf4(path));
6175
+ for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
6176
+ }
6177
+ async getattr(mount, path, rel) {
6178
+ if (mount.conn.failure) throw mount.conn.failure;
6179
+ const cached = mount.attrs.get(path);
6180
+ if (cached && cached.expires > Date.now()) return cached.value;
6181
+ const attr = await mount.conn.call({ op: "getattr", path: rel });
6182
+ const ttl = mount.caps.attrTtl ?? 1e3;
6183
+ if (ttl > 0) mount.attrs.set(path, { value: attr, expires: Date.now() + ttl });
6184
+ return attr;
6185
+ }
6186
+ async readdir(mount, path, rel) {
6187
+ if (mount.conn.failure) throw mount.conn.failure;
6188
+ const cached = mount.lists.get(path);
6189
+ if (cached && cached.expires > Date.now()) return cached.value;
6190
+ const entries = await mount.conn.call({ op: "readdir", path: rel });
6191
+ const ttl = mount.caps.entryTtl ?? mount.caps.attrTtl ?? 1e3;
6192
+ if (ttl > 0) mount.lists.set(path, { value: entries, expires: Date.now() + ttl });
6193
+ return entries;
6194
+ }
6195
+ async statfs(path) {
6196
+ const found = this.locate(normalizePath(path));
6197
+ if (!found) return void 0;
6198
+ const st = await found.mount.conn.call({ op: "statfs" });
6199
+ if (!st) return { quota: 0, usage: 0 };
6200
+ return { quota: st.blocks * st.bsize, usage: (st.blocks - st.bfree) * st.bsize };
6201
+ }
6202
+ wrap(base) {
6203
+ return new MountFs(this, base);
6204
+ }
6205
+ };
6206
+ var MountFs = class {
6207
+ table;
6208
+ base;
6209
+ constructor(table, base) {
6210
+ this.table = table;
6211
+ this.base = base;
6212
+ }
6213
+ at(path) {
6214
+ return this.table.locate(normalizePath(path));
6215
+ }
6216
+ writable(mount, path) {
6217
+ if (mount.caps.readonly) throw errnoError("EROFS", path);
6218
+ }
6219
+ async follow(path, hops = 0, create = false) {
6220
+ const found = this.at(path);
6221
+ if (!found) return path;
6222
+ const real = normalizePath(path);
6223
+ let attr;
6224
+ try {
6225
+ attr = await this.table.getattr(found.mount, real, found.rel);
6226
+ } catch (err) {
6227
+ if (create && err.code === "ENOENT") return real;
6228
+ throw err;
6229
+ }
6230
+ if (attr.kind !== "symlink") return real;
6231
+ if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
6232
+ const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
6233
+ return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
6234
+ }
6235
+ async open(mount, rel, write) {
6236
+ return await mount.conn.call({
6237
+ op: "open",
6238
+ path: rel,
6239
+ write,
6240
+ create: write,
6241
+ truncate: write,
6242
+ exclusive: false
6243
+ });
6244
+ }
6245
+ resolvePath(base, path) {
6246
+ return this.base.resolvePath(base, path);
6247
+ }
6248
+ async readFile(path) {
6249
+ return new TextDecoder().decode(await this.readFileBuffer(path));
6250
+ }
6251
+ async readFileBuffer(path) {
6252
+ const real = await this.follow(path);
6253
+ const found = this.at(real);
6254
+ if (!found) return this.base.readFileBuffer(real);
6255
+ const { mount, rel } = found;
6256
+ const attr = await this.table.getattr(mount, real, rel);
6257
+ if (attr.kind === "directory") throw errnoError("EISDIR", real);
6258
+ if (mount.maxFile > 0 && attr.size > mount.maxFile) {
6259
+ throw errnoError("EFBIG", `${real} is larger than this mount's maxfile (${mount.maxFile})`);
6260
+ }
6261
+ const fh = await this.open(mount, rel, false);
6262
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6263
+ const chunks2 = [];
6264
+ let total = 0;
6265
+ try {
6266
+ for (; ; ) {
6267
+ const bytes = await mount.conn.call({
6268
+ op: "read",
6269
+ fh,
6270
+ offset: total,
6271
+ size: chunk2
6272
+ });
6273
+ if (bytes.length === 0) break;
6274
+ chunks2.push(bytes);
6275
+ total += bytes.length;
6276
+ if (bytes.length < chunk2 && total >= attr.size) break;
6277
+ }
6278
+ } finally {
6279
+ await mount.conn.call({ op: "release", fh });
6280
+ }
6281
+ return concat4(chunks2, total);
6282
+ }
6283
+ async writeFile(path, content) {
6284
+ const real = await this.follow(path, 0, true);
6285
+ const found = this.at(real);
6286
+ if (!found) return this.base.writeFile(real, content);
6287
+ const { mount, rel } = found;
6288
+ this.writable(mount, real);
6289
+ const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
6290
+ if (mount.maxFile > 0 && bytes.length > mount.maxFile) {
6291
+ throw errnoError(
6292
+ "EFBIG",
6293
+ `${real} would be larger than this mount's maxfile (${mount.maxFile})`
6294
+ );
6295
+ }
6296
+ this.table.forget(mount, real);
6297
+ const fh = await this.open(mount, rel, true);
6298
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6299
+ try {
6300
+ for (let offset = 0; offset < bytes.length; offset += chunk2) {
6301
+ await mount.conn.call({
6302
+ op: "write",
6303
+ fh,
6304
+ offset,
6305
+ bytes: bytes.slice(offset, offset + chunk2)
6306
+ });
6307
+ }
6308
+ } finally {
6309
+ await mount.conn.call({ op: "release", fh });
6310
+ this.table.forget(mount, real);
6311
+ }
6312
+ }
6313
+ async exists(path) {
6314
+ if (!this.at(path)) return this.base.exists(path);
6315
+ try {
6316
+ await this.stat(path);
6317
+ return true;
6318
+ } catch (err) {
6319
+ const code = err.code;
6320
+ if (code === "ENOENT" || code === "ENOTDIR") return false;
6321
+ throw err;
6322
+ }
6323
+ }
6324
+ async lstat(path) {
6325
+ const found = this.at(path);
6326
+ if (!found) return this.base.lstat(path);
6327
+ const real = normalizePath(path);
6328
+ return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount.dev);
6329
+ }
6330
+ async stat(path) {
6331
+ const real = await this.follow(path);
6332
+ return this.at(real) ? this.lstat(real) : this.base.stat(real);
6333
+ }
6334
+ async entries(path) {
6335
+ const real = await this.follow(path);
6336
+ const found = this.at(real);
6337
+ if (!found) throw errnoError("ENOTDIR", real);
6338
+ return {
6339
+ real,
6340
+ mount: found.mount,
6341
+ list: await this.table.readdir(found.mount, real, found.rel)
6342
+ };
6343
+ }
6344
+ async readdir(path) {
6345
+ if (!this.at(path)) return this.base.readdir(path);
6346
+ return (await this.entries(path)).list.map((e) => e.name);
6347
+ }
6348
+ async readdirStat(path) {
6349
+ if (!this.at(path)) return this.base.readdirStat(path);
6350
+ const { real, mount, list } = await this.entries(path);
6351
+ return Promise.all(
6352
+ list.map(async (entry) => {
6353
+ const child = normalizePath(`${real}/${entry.name}`);
6354
+ if (entry.attr && mount.caps.listingStats) {
6355
+ return [entry.name, statOf(entry.attr, child, mount.dev)];
6356
+ }
6357
+ return [entry.name, await this.lstat(child).catch(() => null)];
6358
+ })
6359
+ );
6360
+ }
6361
+ async mkdir(path, options = {}) {
6362
+ const found = this.at(path);
6363
+ if (!found) return this.base.mkdir(path, options);
6364
+ const { mount, rel } = found;
6365
+ const real = normalizePath(path);
6366
+ this.writable(mount, real);
6367
+ if (!options.recursive) {
6368
+ if (rel === "/") throw errnoError("EEXIST", real);
6369
+ this.table.forget(mount, real);
6370
+ await mount.conn.call({ op: "mkdir", path: rel });
6371
+ return;
6372
+ }
6373
+ const parts = rel.split("/").filter(Boolean);
6374
+ for (let n = 1; n <= parts.length; n++) {
6375
+ const sub = `/${parts.slice(0, n).join("/")}`;
6376
+ const full = normalizePath(`${mount.target}${sub}`);
6377
+ const exists = await this.table.getattr(mount, full, sub).catch(() => void 0);
6378
+ if (exists?.kind === "directory") continue;
6379
+ if (exists) throw errnoError("ENOTDIR", full);
6380
+ this.table.forget(mount, full);
6381
+ await mount.conn.call({ op: "mkdir", path: sub });
6382
+ }
6383
+ }
6384
+ async rm(path, options = {}) {
6385
+ const found = this.at(path);
6386
+ if (!found) return this.base.rm(path, options);
6387
+ const { mount, rel } = found;
6388
+ const real = normalizePath(path);
6389
+ if (rel === "/") throw errnoError("EBUSY", `${real} is a mount point`);
6390
+ this.writable(mount, real);
6391
+ let attr;
6392
+ try {
6393
+ attr = await this.table.getattr(mount, real, rel);
6394
+ } catch (err) {
6395
+ if (options.force && err.code === "ENOENT") return;
6396
+ throw err;
6397
+ }
6398
+ this.table.forget(mount, real);
6399
+ if (attr.kind === "directory" && options.recursive) {
6400
+ for (const entry of await this.table.readdir(mount, real, rel)) {
6401
+ await this.rm(`${real}/${entry.name}`, { recursive: true, force: options.force ?? false });
6402
+ }
6403
+ this.table.forget(mount, real);
6404
+ }
6405
+ await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path: rel });
6406
+ this.table.forget(mount, real);
6407
+ }
6408
+ async rename(from, to) {
6409
+ const a = this.at(from);
6410
+ const b = this.at(to);
6411
+ if (!a && !b) return this.base.rename(from, to);
6412
+ if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
6413
+ this.writable(a.mount, normalizePath(to));
6414
+ this.table.forget(a.mount, normalizePath(from));
6415
+ this.table.forget(a.mount, normalizePath(to));
6416
+ try {
6417
+ await a.mount.conn.call({ op: "rename", from: a.rel, to: b.rel });
6418
+ } finally {
6419
+ this.table.forget(a.mount, normalizePath(from));
6420
+ this.table.forget(a.mount, normalizePath(to));
6421
+ }
6422
+ }
6423
+ async symlink(target, path) {
6424
+ const found = this.at(path);
6425
+ if (!found) return this.base.symlink(target, path);
6426
+ this.writable(found.mount, normalizePath(path));
6427
+ if (!found.mount.caps.symlinks)
6428
+ throw errnoError("EPERM", `${path}: this file system has no symlinks`);
6429
+ this.table.forget(found.mount, normalizePath(path));
6430
+ await found.mount.conn.call({ op: "symlink", target, path: found.rel });
6431
+ }
6432
+ async readlink(path) {
6433
+ const found = this.at(path);
6434
+ if (!found) return this.base.readlink(path);
6435
+ return await found.mount.conn.call({ op: "readlink", path: found.rel });
6436
+ }
6437
+ async chmod(path, mode) {
6438
+ const found = this.at(path);
6439
+ if (!found) return this.base.chmod(path, mode);
6440
+ this.writable(found.mount, normalizePath(path));
6441
+ if (!found.mount.caps.chmod) return;
6442
+ this.table.forget(found.mount, normalizePath(path));
6443
+ await found.mount.conn.call({ op: "setattr", path: found.rel, mode });
6444
+ }
6445
+ async utimes(path, atime, mtime) {
6446
+ const found = this.at(path);
6447
+ if (!found) return this.base.utimes(path, atime, mtime);
6448
+ this.writable(found.mount, normalizePath(path));
6449
+ this.table.forget(found.mount, normalizePath(path));
6450
+ await found.mount.conn.call({ op: "setattr", path: found.rel, mtime: mtime.getTime() });
6451
+ }
6452
+ };
6453
+
6454
+ // src/mount/tmpfs.ts
6455
+ function tmpfs() {
6456
+ let inodes = 0;
6457
+ const make = (kind, mode) => ({
6458
+ kind,
6459
+ ino: ++inodes,
6460
+ mode,
6461
+ mtime: Date.now(),
6462
+ data: new Uint8Array(0),
6463
+ target: "",
6464
+ children: /* @__PURE__ */ new Map()
6465
+ });
6466
+ const root = make("directory", 16877);
6467
+ const handles = /* @__PURE__ */ new Map();
6468
+ let nextFh = 0;
6469
+ const parts = (path) => path.split("/").filter(Boolean);
6470
+ function find(path) {
6471
+ let node = root;
6472
+ for (const name2 of parts(path)) {
6473
+ if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
6474
+ const next = node.children.get(name2);
6475
+ if (!next) throw fsError2("ENOENT", path);
6476
+ node = next;
6477
+ }
6478
+ return node;
6479
+ }
6480
+ function parent(path) {
6481
+ const names = parts(path);
6482
+ const name2 = names.pop();
6483
+ if (name2 === void 0) throw fsError2("EBUSY", "the root of a mount");
6484
+ const dir = find(`/${names.join("/")}`);
6485
+ if (dir.kind !== "directory") throw fsError2("ENOTDIR", path);
6486
+ return { dir, name: name2 };
6487
+ }
6488
+ const attr = (node) => ({
6489
+ kind: node.kind,
6490
+ size: node.kind === "file" ? node.data.length : node.kind === "symlink" ? node.target.length : 4096,
6491
+ mtime: node.mtime,
6492
+ mode: node.mode,
6493
+ ino: node.ino
6494
+ });
6495
+ const handle = (fh) => {
6496
+ const found = handles.get(fh);
6497
+ if (!found) throw fsError2("EBADF", `handle ${fh}`);
6498
+ return found;
6499
+ };
6500
+ function opened2(path, flags) {
6501
+ const { dir, name: name2 } = parent(path);
6502
+ const existing = dir.children.get(name2);
6503
+ if (existing && flags.exclusive) throw fsError2("EEXIST", path);
6504
+ if (existing?.kind === "directory" && flags.write) throw fsError2("EISDIR", path);
6505
+ if (existing) return existing;
6506
+ if (!flags.create) throw fsError2("ENOENT", path);
6507
+ const node = make("file", 33188);
6508
+ dir.children.set(name2, node);
6509
+ dir.mtime = Date.now();
6510
+ return node;
6511
+ }
6512
+ return {
6513
+ async getattr(path) {
6514
+ return attr(find(path));
6515
+ },
6516
+ async readdir(path) {
6517
+ const dir = find(path);
6518
+ if (dir.kind !== "directory") throw fsError2("ENOTDIR", path);
6519
+ return [...dir.children].map(
6520
+ ([name2, node]) => ({ name: name2, kind: node.kind, attr: attr(node) })
6521
+ );
6522
+ },
6523
+ async open(path, flags) {
6524
+ const node = opened2(path, flags);
6525
+ const data = flags.truncate ? new Uint8Array(0) : node.data.slice();
6526
+ handles.set(++nextFh, { node, data, length: data.length, dirty: flags.truncate });
6527
+ return nextFh;
6528
+ },
6529
+ async read(fh, offset, size) {
6530
+ const h = handle(fh);
6531
+ return h.data.slice(offset, Math.min(h.length, offset + size));
6532
+ },
6533
+ async write(fh, offset, bytes) {
6534
+ const h = handle(fh);
6535
+ const end = offset + bytes.length;
6536
+ if (end > h.data.length) {
6537
+ const grown = new Uint8Array(Math.max(end, h.data.length * 2));
6538
+ grown.set(h.data.subarray(0, h.length));
6539
+ h.data = grown;
6540
+ }
6541
+ h.data.set(bytes, offset);
6542
+ h.length = Math.max(h.length, end);
6543
+ h.dirty = true;
6544
+ },
6545
+ async release(fh) {
6546
+ const h = handle(fh);
6547
+ handles.delete(fh);
6548
+ if (!h.dirty) return;
6549
+ h.node.data = h.data.slice(0, h.length);
6550
+ h.node.mtime = Date.now();
6551
+ },
6552
+ async mkdir(path) {
6553
+ const { dir, name: name2 } = parent(path);
6554
+ if (dir.children.has(name2)) throw fsError2("EEXIST", path);
6555
+ dir.children.set(name2, make("directory", 16877));
6556
+ },
6557
+ async rmdir(path) {
6558
+ const { dir, name: name2 } = parent(path);
6559
+ const node = dir.children.get(name2);
6560
+ if (!node) throw fsError2("ENOENT", path);
6561
+ if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
6562
+ if (node.children.size > 0) throw fsError2("ENOTEMPTY", path);
6563
+ dir.children.delete(name2);
6564
+ },
6565
+ async unlink(path) {
6566
+ const { dir, name: name2 } = parent(path);
6567
+ const node = dir.children.get(name2);
6568
+ if (!node) throw fsError2("ENOENT", path);
6569
+ if (node.kind === "directory") throw fsError2("EISDIR", path);
6570
+ dir.children.delete(name2);
6571
+ },
6572
+ async rename(from, to) {
6573
+ const a = `/${parts(from).join("/")}`;
6574
+ const b = `/${parts(to).join("/")}`;
6575
+ if (b !== a && b.startsWith(`${a}/`)) throw fsError2("EINVAL", `${to} is inside ${from}`);
6576
+ const source = parent(from);
6577
+ const node = source.dir.children.get(source.name);
6578
+ if (!node) throw fsError2("ENOENT", from);
6579
+ const target = parent(to);
6580
+ const replaced = target.dir.children.get(target.name);
6581
+ if (replaced === node) return;
6582
+ if (replaced?.kind === "directory" && (node.kind !== "directory" || replaced.children.size > 0)) {
6583
+ throw fsError2(node.kind === "directory" ? "ENOTEMPTY" : "EISDIR", to);
6584
+ }
6585
+ source.dir.children.delete(source.name);
6586
+ target.dir.children.set(target.name, node);
6587
+ },
6588
+ async symlink(target, path) {
6589
+ const { dir, name: name2 } = parent(path);
6590
+ if (dir.children.has(name2)) throw fsError2("EEXIST", path);
6591
+ const node = make("symlink", 41471);
6592
+ node.target = target;
6593
+ dir.children.set(name2, node);
6594
+ },
6595
+ async readlink(path) {
6596
+ const node = find(path);
6597
+ if (node.kind !== "symlink") throw fsError2("EINVAL", path);
6598
+ return node.target;
6599
+ },
6600
+ async setattr(path, change) {
6601
+ const node = find(path);
6602
+ if (change.mode !== void 0) node.mode = node.mode & ~4095 | change.mode & 4095;
6603
+ if (change.mtime !== void 0) node.mtime = change.mtime;
6604
+ },
6605
+ async statfs() {
6606
+ return { bsize: 4096, blocks: 262144, bfree: 262144 };
6607
+ }
6608
+ };
6609
+ }
6610
+
5722
6611
  // src/process/wasi/wasi-module.ts
5723
6612
  function importedMemory(bytes) {
5724
6613
  let at = 8;
@@ -5879,6 +6768,7 @@ function foreignImports(bytes) {
5879
6768
  }
5880
6769
 
5881
6770
  // src/launcher.ts
6771
+ var PNPM_HOME = "/home/.local/share/pnpm";
5882
6772
  var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
5883
6773
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
5884
6774
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -5940,7 +6830,7 @@ function childHandle(handle) {
5940
6830
  onState: (listener) => handle.onState(listener)
5941
6831
  };
5942
6832
  }
5943
- function concat4(chunks2) {
6833
+ function concat5(chunks2) {
5944
6834
  const out = new Uint8Array(chunks2.reduce((n, chunk2) => n + chunk2.length, 0));
5945
6835
  let offset = 0;
5946
6836
  for (const chunk2 of chunks2) {
@@ -5968,12 +6858,24 @@ var Launcher = class {
5968
6858
  ca;
5969
6859
  transport;
5970
6860
  watchers = new FsWatchers();
6861
+ pnpmHome;
6862
+ mounts;
6863
+ createDriverWorker;
6864
+ openFiles = /* @__PURE__ */ new Set();
6865
+ held = /* @__PURE__ */ new Set();
5971
6866
  nextPid = 1e3;
5972
6867
  boot = Date.now();
5973
6868
  terminals = 0;
5974
6869
  constructor(options) {
5975
- this.base = this.watchers.wrap(options.fs);
6870
+ this.mounts = new MountTable({
6871
+ open: (type, spec) => this.driver(type, spec),
6872
+ busy: (target) => [...this.openFiles].some((nodes) => nodes.holds(target)) || heldUnder(this.held, target),
6873
+ changed: this.watchers.changed.bind(this.watchers)
6874
+ });
6875
+ this.base = this.watchers.wrap(this.mounts.wrap(options.fs));
5976
6876
  this.createWorker = options.createWorker;
6877
+ this.createDriverWorker = options.createDriverWorker;
6878
+ this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
5977
6879
  this.modulesDir = options.modules ?? "/node_modules";
5978
6880
  this.env = { ...networkEnv(), ...options.env };
5979
6881
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
@@ -5984,11 +6886,17 @@ var Launcher = class {
5984
6886
  ca: this.ca
5985
6887
  });
5986
6888
  this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
6889
+ this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
6890
+ this.catalog = void 0;
6891
+ });
5987
6892
  }
5988
6893
  commands() {
5989
- this.catalog ??= scanCommands(this.base, this.modulesDir);
6894
+ this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
5990
6895
  return this.catalog;
5991
6896
  }
6897
+ async roots() {
6898
+ return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
6899
+ }
5992
6900
  async resolve(file, argv0, cwd) {
5993
6901
  const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
5994
6902
  if (name2 !== void 0) {
@@ -6142,6 +7050,10 @@ var Launcher = class {
6142
7050
  forker: this.forker(pid, req),
6143
7051
  kill: (target, sig) => this.kill(target, sig),
6144
7052
  processes: () => ({ boot: this.boot, processes: this.list() }),
7053
+ openFiles: this.openFiles,
7054
+ held: this.held,
7055
+ statfs: (path) => this.mounts.statfs(path),
7056
+ mounts: () => this.mounts.list(),
6145
7057
  jobs: this.jobs,
6146
7058
  ptys: this.ptys,
6147
7059
  net: this.net,
@@ -6226,12 +7138,66 @@ var Launcher = class {
6226
7138
  if (sig !== 0) handle.signal(sig);
6227
7139
  return true;
6228
7140
  }
7141
+ async driver(type, _spec) {
7142
+ if (type === "tmpfs") {
7143
+ const { port1, port2 } = new MessageChannel();
7144
+ serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });
7145
+ return {
7146
+ port: port1,
7147
+ dispose: () => {
7148
+ port1.close();
7149
+ port2.close();
7150
+ }
7151
+ };
7152
+ }
7153
+ const module = (await scanFilesystems(this.base, await this.roots())).get(type);
7154
+ if (!module) throw fsError("ENODEV", `unknown file system type ${type}`);
7155
+ if (!this.createDriverWorker)
7156
+ throw fsError("ENODEV", `this kernel cannot start ${type} drivers`);
7157
+ const code = await this.base.readFile(module);
7158
+ const worker = this.createDriverWorker();
7159
+ const driver = new MessageChannel();
7160
+ const client = new MessageChannel();
7161
+ const served = serveClient(client.port1, {
7162
+ launcher: async () => this,
7163
+ scope: "transport"
7164
+ });
7165
+ worker.postMessage({ code, driver: driver.port2, client: client.port2 }, [
7166
+ driver.port2,
7167
+ client.port2
7168
+ ]);
7169
+ return {
7170
+ port: driver.port1,
7171
+ dispose: () => {
7172
+ worker.terminate();
7173
+ served.detach();
7174
+ driver.port1.close();
7175
+ },
7176
+ onCrash: (listener) => worker.addEventListener("error", (event) => {
7177
+ event.preventDefault?.();
7178
+ listener(new Error(String(event.message)));
7179
+ })
7180
+ };
7181
+ }
7182
+ mount(spec) {
7183
+ return this.mounts.mount(spec, this.fs);
7184
+ }
7185
+ umount(target) {
7186
+ this.mounts.umount(target);
7187
+ }
6229
7188
  async prepare() {
6230
7189
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
6231
7190
  await writeCaFile(this.base, this.ca).catch(() => void 0);
6232
7191
  }
6233
7192
  environment(cwd, extra) {
6234
- return { PATH: "/usr/bin:/bin", HOME: "/home", ...this.env, PWD: cwd, ...extra };
7193
+ return {
7194
+ PATH: `/usr/bin:/bin:${this.pnpmHome}/bin`,
7195
+ HOME: "/home",
7196
+ PNPM_HOME: this.pnpmHome,
7197
+ ...this.env,
7198
+ PWD: cwd,
7199
+ ...extra
7200
+ };
6235
7201
  }
6236
7202
  async starting(argv, dir) {
6237
7203
  this.catalog = void 0;
@@ -6293,7 +7259,7 @@ var Launcher = class {
6293
7259
  `));
6294
7260
  if (!planned) {
6295
7261
  report(`${file}: command not found`);
6296
- return { status: NOT_FOUND, stdout: concat4(out), stderr: concat4(err) };
7262
+ return { status: NOT_FOUND, stdout: concat5(out), stderr: concat5(err) };
6297
7263
  }
6298
7264
  const fds = new FdTable();
6299
7265
  fds.installAt(0, options.stdin ? bytesSource(options.stdin) : nullFile());
@@ -6302,7 +7268,7 @@ var Launcher = class {
6302
7268
  const handle = await this.launch(planned, { env, cwd, fds, report });
6303
7269
  options.onStarted?.(handle.pid);
6304
7270
  const status = await handle.exited;
6305
- return { status, stdout: concat4(out), stderr: concat4(err) };
7271
+ return { status, stdout: concat5(out), stderr: concat5(err) };
6306
7272
  }
6307
7273
  };
6308
7274
 
@@ -6598,7 +7564,7 @@ async function holdLock(locks) {
6598
7564
  await granted.promise;
6599
7565
  return { name: name2, release: () => held.resolve() };
6600
7566
  }
6601
- function concat5(chunks2) {
7567
+ function concat6(chunks2) {
6602
7568
  const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
6603
7569
  let at = 0;
6604
7570
  for (const chunk2 of chunks2) {
@@ -6720,7 +7686,7 @@ async function runOn(channel, argv, opts) {
6720
7686
  onStderr: tee(2, onStderr)
6721
7687
  });
6722
7688
  const status = await child.exited;
6723
- const text = (fd) => new TextDecoder().decode(concat5(chunks2[fd]));
7689
+ const text = (fd) => new TextDecoder().decode(concat6(chunks2[fd]));
6724
7690
  return { pid: child.pid, status, stdout: text(1), stderr: text(2) };
6725
7691
  }
6726
7692
  async function terminalOn(channel, argv, opts) {
@@ -6799,6 +7765,17 @@ function fsOn(channel) {
6799
7765
  }
6800
7766
  };
6801
7767
  }
7768
+ function since(channel, minor, what, run2) {
7769
+ if (channel.protocol[1] < minor) {
7770
+ return Promise.reject(
7771
+ new KernelCallError(
7772
+ `this kernel speaks protocol ${channel.protocol.join(".")}, which has no ${what}`,
7773
+ "ENOSYS"
7774
+ )
7775
+ );
7776
+ }
7777
+ return run2();
7778
+ }
6802
7779
  async function greet(channel, lock, timeoutMs) {
6803
7780
  channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
6804
7781
  const timer = setTimeout(
@@ -6841,6 +7818,9 @@ async function attachKernel(port, options = {}) {
6841
7818
  run: (argv, opts = {}) => runOn(channel, argv, opts),
6842
7819
  openTerminal: (argv, opts = {}) => terminalOn(channel, argv, opts),
6843
7820
  ps: () => channel.call({ op: "ps" }),
7821
+ mount: (spec) => since(channel, 2, "mount", () => channel.call({ op: "mount", spec })),
7822
+ umount: async (target) => void await since(channel, 2, "umount", () => channel.call({ op: "umount", target })),
7823
+ mounts: () => since(channel, 2, "mounts", () => channel.call({ op: "mounts" })),
6844
7824
  kill: async (pid, signal = "SIGTERM") => void await channel.call({ op: "kill", pid, signal }),
6845
7825
  fetch: (req) => {
6846
7826
  const { signal, ...rest } = req;
@@ -6919,6 +7899,11 @@ async function createNodeKernel(options = {}) {
6919
7899
  const launcher = new Launcher({
6920
7900
  fs,
6921
7901
  createWorker: () => nodeWorker(file, live, terminated),
7902
+ createDriverWorker: () => nodeWorker(
7903
+ options.driverWorker ?? new URL("./node-driver-worker.js", import.meta.url),
7904
+ live,
7905
+ terminated
7906
+ ),
6922
7907
  ...options.modules ? { modules: options.modules } : {},
6923
7908
  ...options.env ? { env: options.env } : {},
6924
7909
  ...transport ? { transport } : {}
@@ -6982,6 +7967,9 @@ async function createNodeKernel(options = {}) {
6982
7967
  await fs.writeFile(path, data);
6983
7968
  },
6984
7969
  readFile: async (path) => fs.readFileBuffer(path),
7970
+ mount: (spec) => guard(() => launcher.mount(spec)),
7971
+ umount: (target) => guard(async () => launcher.umount(target)),
7972
+ mounts: async () => launcher.mounts.list(),
6985
7973
  async connect() {
6986
7974
  if (terminated) throw new Error(TERMINATED);
6987
7975
  const { port1, port2 } = new MessageChannel2();