@ai-ecoverse/slicc-kernel 1.35.1 → 1.37.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.
@@ -448,11 +448,41 @@ var OpfsFs = class {
448
448
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
449
449
  return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
450
450
  }
451
+ dirTimes = /* @__PURE__ */ new Map();
452
+ dirDirty = /* @__PURE__ */ new Set();
453
+ dirFlush;
454
+ touch(...dirs) {
455
+ const now = Date.now();
456
+ for (const dir of dirs) {
457
+ this.dirTimes.set(dir, now);
458
+ this.dirDirty.add(dir);
459
+ }
460
+ this.dirFlush ??= Promise.resolve().then(() => this.flushDirTimes()).catch(() => void 0).finally(() => {
461
+ this.dirFlush = void 0;
462
+ });
463
+ }
464
+ async flushDirTimes() {
465
+ for (const path of this.dirDirty) {
466
+ this.dirDirty.delete(path);
467
+ const ms = this.dirTimes.get(path);
468
+ if (ms === void 0) continue;
469
+ await this.meta.update(path, (entry) => ({
470
+ ...entry,
471
+ path,
472
+ mtimeMs: ms,
473
+ mtimeFor: 0,
474
+ ctimeMs: ms
475
+ }));
476
+ if (this.dirTimes.get(path) === ms) this.dirTimes.delete(path);
477
+ }
478
+ }
451
479
  async statOf(path, handle, meta) {
452
480
  const directory = handle.kind === "directory";
453
481
  const file = directory ? void 0 : await handle.getFile();
454
482
  const modified = file?.lastModified ?? 0;
455
- const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
483
+ const touched = directory ? this.dirTimes.get(path) : void 0;
484
+ const kept = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
485
+ const mtime = touched ?? kept;
456
486
  return {
457
487
  isFile: !directory,
458
488
  isDirectory: directory,
@@ -461,7 +491,7 @@ var OpfsFs = class {
461
491
  mode: this.mode(path, directory, meta),
462
492
  mtime: new Date(mtime),
463
493
  atime: new Date(meta?.atimeMs ?? mtime),
464
- ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
494
+ ctime: new Date(Math.max(touched ?? meta?.ctimeMs ?? mtime, modified)),
465
495
  ino: meta?.ino ?? inodeOf(path),
466
496
  ...this.ranged && !directory ? { ranged: true } : {}
467
497
  };
@@ -538,6 +568,19 @@ var OpfsFs = class {
538
568
  const handle = await this.existing(path);
539
569
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
540
570
  }
571
+ async writeSession(path, truncate) {
572
+ const handle = (await this.fileFor(path))[0];
573
+ const writable = await this.retrying(
574
+ path,
575
+ () => handle.createWritable({ keepExistingData: !truncate }),
576
+ true
577
+ );
578
+ return {
579
+ write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
580
+ truncate: (size) => writable.truncate(size),
581
+ close: () => writable.close()
582
+ };
583
+ }
541
584
  async readFile(path) {
542
585
  return new TextDecoder().decode(await this.readFileBuffer(path));
543
586
  }
@@ -555,10 +598,12 @@ var OpfsFs = class {
555
598
  const [target, existed] = await this.writeTarget(path);
556
599
  const [dir, name2] = await this.parent(target);
557
600
  try {
558
- return [
559
- await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true })),
560
- existed
561
- ];
601
+ const handle = await this.inDir(
602
+ dir,
603
+ (parent) => parent.getFileHandle(name2, { create: true })
604
+ );
605
+ if (!existed) this.touch(parentOf2(target));
606
+ return [handle, existed];
562
607
  } catch (err) {
563
608
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
564
609
  throw code ? fsError(code, path) : translate(err, path);
@@ -616,6 +661,7 @@ var OpfsFs = class {
616
661
  const parent = await this.locate(parentOf2(path), true);
617
662
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
618
663
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
664
+ this.touch(parent.path);
619
665
  }
620
666
  async rm(path, options = {}) {
621
667
  let found;
@@ -639,6 +685,7 @@ var OpfsFs = class {
639
685
  this.changed(found.path);
640
686
  }
641
687
  await this.meta.remove([found.path]);
688
+ this.touch(parentOf2(found.path));
642
689
  }
643
690
  async rename(from, to) {
644
691
  if (from === to) return;
@@ -670,6 +717,7 @@ var OpfsFs = class {
670
717
  if (source.link) {
671
718
  if (target?.handle) await this.rm(destination);
672
719
  await this.meta.move(source.path, destination, carry);
720
+ this.touch(parentOf2(source.path), parent.path);
673
721
  return;
674
722
  }
675
723
  const dir = parent.handle;
@@ -691,6 +739,7 @@ var OpfsFs = class {
691
739
  }
692
740
  if (copied) await this.restamp(destination, timed);
693
741
  this.changed(source.path, destination);
742
+ this.touch(parentOf2(source.path), parent.path);
694
743
  }
695
744
  async modified(path) {
696
745
  const handle = await this.handle(path);
@@ -754,6 +803,7 @@ var OpfsFs = class {
754
803
  mtimeMs: now,
755
804
  ctimeMs: now
756
805
  }));
806
+ this.touch(parent.path);
757
807
  }
758
808
  async realpath(path, follow) {
759
809
  return (await this.locate(path, follow)).path;
@@ -794,6 +844,7 @@ var OpfsFs = class {
794
844
  }
795
845
  const handle = found.handle;
796
846
  const file = handle.kind === "file" ? await handle.getFile() : void 0;
847
+ this.dirTimes.delete(found.path);
797
848
  const ctimeMs = Date.now();
798
849
  await this.meta.update(found.path, (entry) => ({
799
850
  ...entry,
@@ -4682,6 +4733,35 @@ async function scanFilesystems(fs, modules) {
4682
4733
  }
4683
4734
  return found;
4684
4735
  }
4736
+ var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
4737
+ async function scanBinfmts(fs, modules) {
4738
+ const found = /* @__PURE__ */ new Map();
4739
+ for (const pkg of await packagesIn(fs, modules)) {
4740
+ let manifest;
4741
+ try {
4742
+ manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
4743
+ } catch {
4744
+ continue;
4745
+ }
4746
+ const declared = manifest.slicc?.binfmt;
4747
+ if (!declared || typeof declared !== "object") continue;
4748
+ for (const [suffix, command] of Object.entries(declared)) {
4749
+ if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME.test(command)) continue;
4750
+ if (!found.has(suffix)) found.set(suffix, command);
4751
+ }
4752
+ }
4753
+ return found;
4754
+ }
4755
+ function binfmtOf(binfmts, path) {
4756
+ const name2 = path.slice(path.lastIndexOf("/") + 1);
4757
+ let best;
4758
+ for (const suffix of binfmts.keys()) {
4759
+ if (name2.length > suffix.length && name2.endsWith(suffix)) {
4760
+ if (best === void 0 || suffix.length > best.length) best = suffix;
4761
+ }
4762
+ }
4763
+ return best === void 0 ? void 0 : binfmts.get(best);
4764
+ }
4685
4765
 
4686
4766
  // src/fs/commands.ts
4687
4767
  var S_IFDIR2 = 16384;
@@ -5187,18 +5267,36 @@ function parseSize(text) {
5187
5267
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
5188
5268
  }
5189
5269
  var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
5270
+ var HeldSet = class extends Set {
5271
+ idle;
5272
+ constructor(idle) {
5273
+ super();
5274
+ this.idle = idle;
5275
+ }
5276
+ add(holds) {
5277
+ holds.onIdle = this.idle;
5278
+ return super.add(holds);
5279
+ }
5280
+ delete(holds) {
5281
+ const removed = super.delete(holds);
5282
+ for (const path of holds.keys()) this.idle(path);
5283
+ return removed;
5284
+ }
5285
+ };
5190
5286
  function heldUnder(held, target) {
5191
5287
  for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
5192
5288
  return false;
5193
5289
  }
5194
5290
  var HeldPaths = class extends Map {
5195
5291
  revoked = /* @__PURE__ */ new Set();
5292
+ onIdle;
5196
5293
  hold(path, on) {
5197
5294
  const count = (this.get(path) ?? 0) + (on ? 1 : -1);
5198
5295
  if (count > 0) this.set(path, count);
5199
5296
  else {
5200
5297
  this.delete(path);
5201
5298
  this.revoked.delete(path);
5299
+ this.onIdle?.(path);
5202
5300
  }
5203
5301
  }
5204
5302
  revoke(prefix) {
@@ -5266,6 +5364,8 @@ function spliced(bytes, offset, part) {
5266
5364
  }
5267
5365
  var MountTable = class {
5268
5366
  mounts = /* @__PURE__ */ new Map();
5367
+ sessions = /* @__PURE__ */ new Map();
5368
+ committing = /* @__PURE__ */ new Map();
5269
5369
  devices = 0;
5270
5370
  pending = /* @__PURE__ */ new Set();
5271
5371
  notes = /* @__PURE__ */ new Map();
@@ -5390,8 +5490,69 @@ var MountTable = class {
5390
5490
  if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
5391
5491
  this.notes.delete(at);
5392
5492
  this.mounts.delete(at);
5393
- mount.conn.close();
5394
- mount.dispose();
5493
+ const waits = [...this.sessions].filter(([, s]) => s.mount === mount).map(([real]) => this.commit(real));
5494
+ for (const c of this.committing.values()) if (c.mount === mount) waits.push(c.done);
5495
+ const close = () => {
5496
+ mount.conn.close();
5497
+ mount.dispose();
5498
+ };
5499
+ if (waits.length === 0) close();
5500
+ else void Promise.allSettled(waits).then(close);
5501
+ }
5502
+ session(mount, real, rel2) {
5503
+ let session = this.sessions.get(real);
5504
+ if (!session) {
5505
+ const open = () => mount.conn.call({
5506
+ op: "open",
5507
+ path: rel2,
5508
+ write: true,
5509
+ create: true,
5510
+ truncate: false,
5511
+ exclusive: false
5512
+ });
5513
+ const before = this.committing.get(real)?.done ?? Promise.resolve();
5514
+ const fh = before.then(open, open);
5515
+ session = { mount, fh, extents: [], truncated: false };
5516
+ this.sessions.set(real, session);
5517
+ fh.catch(() => this.sessions.delete(real));
5518
+ }
5519
+ return session;
5520
+ }
5521
+ writing(real) {
5522
+ return this.sessions.get(real);
5523
+ }
5524
+ async commit(real) {
5525
+ const session = this.sessions.get(real);
5526
+ if (!session) return this.committing.get(real)?.done;
5527
+ this.sessions.delete(real);
5528
+ const done4 = (async () => {
5529
+ try {
5530
+ await session.mount.conn.call({ op: "release", fh: await session.fh });
5531
+ } finally {
5532
+ this.forget(session.mount, real);
5533
+ }
5534
+ })();
5535
+ const entry = { mount: session.mount, done: done4 };
5536
+ this.committing.set(real, entry);
5537
+ const settle3 = () => {
5538
+ if (this.committing.get(real) === entry) this.committing.delete(real);
5539
+ };
5540
+ done4.then(settle3, settle3);
5541
+ return done4;
5542
+ }
5543
+ async commitUnder(prefix) {
5544
+ const at = normalizePath(prefix);
5545
+ await Promise.all(
5546
+ [...this.sessions.keys()].filter((p) => within3(p, at)).map((p) => this.commit(p))
5547
+ );
5548
+ }
5549
+ async commitOverlapping(real, offset, length2) {
5550
+ const session = this.sessions.get(real);
5551
+ if (!session) return;
5552
+ const end = offset + length2;
5553
+ if (session.truncated || session.extents.some(([a, b]) => a < end && offset < b)) {
5554
+ await this.commit(real);
5555
+ }
5395
5556
  }
5396
5557
  crashed(mount, error) {
5397
5558
  mount.state = "failed";
@@ -5450,6 +5611,13 @@ var MountTable = class {
5450
5611
  return new MountFs(this, base);
5451
5612
  }
5452
5613
  };
5614
+ function noteExtent(extents, from, to) {
5615
+ const last = extents.at(-1);
5616
+ if (last && from <= last[1] && to >= last[0]) {
5617
+ last[0] = Math.min(last[0], from);
5618
+ last[1] = Math.max(last[1], to);
5619
+ } else extents.push([from, to]);
5620
+ }
5453
5621
  var MountFs = class {
5454
5622
  table;
5455
5623
  base;
@@ -5509,6 +5677,7 @@ var MountFs = class {
5509
5677
  }
5510
5678
  }
5511
5679
  async readWhole(mount, real, rel2) {
5680
+ await this.table.commit(real);
5512
5681
  const attr = await this.table.getattr(mount, real, rel2);
5513
5682
  if (attr.kind === "directory") throw errnoError("EISDIR", real);
5514
5683
  if (mount.maxFile > 0 && attr.size > mount.maxFile) {
@@ -5558,6 +5727,7 @@ var MountFs = class {
5558
5727
  }
5559
5728
  }
5560
5729
  async writeWhole(mount, real, rel2, bytes) {
5730
+ await this.table.commit(real);
5561
5731
  this.table.forget(mount, real);
5562
5732
  const fh = await this.open(mount, rel2, true);
5563
5733
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
@@ -5580,6 +5750,7 @@ var MountFs = class {
5580
5750
  if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
5581
5751
  throw errnoError("EISDIR", real);
5582
5752
  }
5753
+ await this.table.commitOverlapping(real, offset, length2);
5583
5754
  const fh = await mount.conn.call({
5584
5755
  op: "open",
5585
5756
  path: rel2,
@@ -5633,6 +5804,14 @@ var MountFs = class {
5633
5804
  this.writable(mount, real);
5634
5805
  this.fits(mount, real, offset + bytes.length);
5635
5806
  this.table.forget(mount, real);
5807
+ if (mount.caps.sessions) {
5808
+ const session = this.table.session(mount, real, rel2);
5809
+ const end = offset + bytes.length;
5810
+ await this.writeRuns(mount, await session.fh, offset, bytes, transfer);
5811
+ noteExtent(session.extents, offset, end);
5812
+ this.table.forget(mount, real);
5813
+ return;
5814
+ }
5636
5815
  const fh = await mount.conn.call({
5637
5816
  op: "open",
5638
5817
  path: rel2,
@@ -5641,17 +5820,20 @@ var MountFs = class {
5641
5820
  truncate: false,
5642
5821
  exclusive: false
5643
5822
  });
5644
- const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
5645
5823
  try {
5646
- for (let at = 0; at < bytes.length; at += chunk2) {
5647
- const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
5648
- await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
5649
- }
5824
+ await this.writeRuns(mount, fh, offset, bytes, transfer);
5650
5825
  } finally {
5651
5826
  await mount.conn.call({ op: "release", fh });
5652
5827
  this.table.forget(mount, real);
5653
5828
  }
5654
5829
  }
5830
+ async writeRuns(mount, fh, offset, bytes, transfer) {
5831
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
5832
+ for (let at = 0; at < bytes.length; at += chunk2) {
5833
+ const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
5834
+ await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
5835
+ }
5836
+ }
5655
5837
  async truncate(path, size) {
5656
5838
  const real = await this.follow(path);
5657
5839
  const found = this.at(real);
@@ -5663,6 +5845,8 @@ var MountFs = class {
5663
5845
  this.writable(mount, real);
5664
5846
  this.fits(mount, real, size);
5665
5847
  this.table.forget(mount, real);
5848
+ const session = this.table.writing(real);
5849
+ if (session) session.truncated = true;
5666
5850
  try {
5667
5851
  await mount.conn.call({ op: "setattr", path: rel2, size });
5668
5852
  } finally {
@@ -5684,7 +5868,16 @@ var MountFs = class {
5684
5868
  const found = this.at(path);
5685
5869
  if (!found) return this.base.lstat(path);
5686
5870
  const real = normalizePath(path);
5687
- return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount);
5871
+ if (this.table.writing(real)?.truncated) await this.table.commit(real);
5872
+ return this.statWriting(
5873
+ await this.table.getattr(found.mount, real, found.rel),
5874
+ real,
5875
+ found.mount
5876
+ );
5877
+ }
5878
+ statWriting(attr, real, mount) {
5879
+ const ends = (this.table.writing(real)?.extents ?? []).map(([, end]) => end);
5880
+ return statOf({ ...attr, size: Math.max(attr.size, ...ends) }, real, mount);
5688
5881
  }
5689
5882
  async stat(path) {
5690
5883
  const real = await this.follow(path);
@@ -5711,7 +5904,7 @@ var MountFs = class {
5711
5904
  list.map(async (entry) => {
5712
5905
  const child = normalizePath(`${real}/${entry.name}`);
5713
5906
  if (entry.attr && mount.caps.listingStats) {
5714
- return [entry.name, statOf(entry.attr, child, mount)];
5907
+ return [entry.name, this.statWriting(entry.attr, child, mount)];
5715
5908
  }
5716
5909
  return [entry.name, await this.lstat(child).catch(() => null)];
5717
5910
  })
@@ -5747,6 +5940,7 @@ var MountFs = class {
5747
5940
  const real = normalizePath(path);
5748
5941
  if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
5749
5942
  this.writable(mount, real);
5943
+ await this.table.commitUnder(real);
5750
5944
  let attr;
5751
5945
  try {
5752
5946
  attr = await this.table.getattr(mount, real, rel2);
@@ -5770,6 +5964,8 @@ var MountFs = class {
5770
5964
  if (!a && !b) return this.base.rename(from, to);
5771
5965
  if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
5772
5966
  this.writable(a.mount, normalizePath(to));
5967
+ await this.table.commitUnder(from);
5968
+ await this.table.commitUnder(to);
5773
5969
  this.table.forget(a.mount, normalizePath(from));
5774
5970
  this.table.forget(a.mount, normalizePath(to));
5775
5971
  try {
@@ -7376,6 +7572,7 @@ var VfsNode = class {
7376
7572
  writeBack;
7377
7573
  writeBackCost = 0;
7378
7574
  opens = 0;
7575
+ writers = 0;
7379
7576
  missing = false;
7380
7577
  revoked = false;
7381
7578
  fs;
@@ -7551,8 +7748,10 @@ var VfsNodes = class {
7551
7748
  byPath = /* @__PURE__ */ new Map();
7552
7749
  fs;
7553
7750
  entries = /* @__PURE__ */ new Map();
7554
- constructor(fs) {
7751
+ onClosed;
7752
+ constructor(fs, onClosed) {
7555
7753
  this.fs = fs;
7754
+ this.onClosed = onClosed;
7556
7755
  }
7557
7756
  async onEntry(path, op) {
7558
7757
  const key = await this.entryKey(path);
@@ -7590,16 +7789,17 @@ var VfsNodes = class {
7590
7789
  await this.fs.writeFile(path, new Uint8Array(0));
7591
7790
  });
7592
7791
  }
7593
- open(path, pin) {
7792
+ open(path, pin, writable = false) {
7594
7793
  let node = this.byPath.get(path);
7595
7794
  if (!node) {
7596
7795
  node = new VfsNode(this.fs, path, void 0, false, false, pin);
7597
7796
  this.byPath.set(path, node);
7598
7797
  }
7599
7798
  node.opens++;
7799
+ if (writable) node.writers++;
7600
7800
  return node;
7601
7801
  }
7602
- adopt(path, contents2, dirty) {
7802
+ adopt(path, contents2, dirty, writable = false) {
7603
7803
  let node = this.byPath.get(path);
7604
7804
  if (!node) {
7605
7805
  node = new VfsNode(this.fs, path, contents2, false, dirty);
@@ -7608,15 +7808,27 @@ var VfsNodes = class {
7608
7808
  node.replace(contents2);
7609
7809
  }
7610
7810
  node.opens++;
7811
+ if (writable) node.writers++;
7611
7812
  return node;
7612
7813
  }
7613
7814
  holds(prefix) {
7614
7815
  for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
7615
7816
  return false;
7616
7817
  }
7617
- closed(node) {
7818
+ writes(prefix) {
7819
+ for (const [path, node] of this.byPath)
7820
+ if (node.writers > 0 && within4(path, prefix)) return true;
7821
+ return false;
7822
+ }
7823
+ closed(node, writable = false) {
7618
7824
  node.opens--;
7619
- if (node.opens === 0 && this.byPath.get(node.path) === node) this.byPath.delete(node.path);
7825
+ if (writable) node.writers--;
7826
+ if (node.opens > 0) {
7827
+ if (writable && node.writers === 0) this.onClosed?.(node.path);
7828
+ return;
7829
+ }
7830
+ if (this.byPath.get(node.path) === node) this.byPath.delete(node.path);
7831
+ this.onClosed?.(node.path);
7620
7832
  }
7621
7833
  async flush(path) {
7622
7834
  for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
@@ -7762,7 +7974,7 @@ function vfsFile(fs, opts, nodes) {
7762
7974
  const access = opts.flags & O_ACCMODE;
7763
7975
  const readable = access !== O_WRONLY;
7764
7976
  const writable = access === O_WRONLY || access === O_RDWR;
7765
- const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true) : nodes.open(opts.path, opts.pin);
7977
+ const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable) : nodes.open(opts.path, opts.pin, writable);
7766
7978
  let openError;
7767
7979
  const atOpen = (op) => {
7768
7980
  void node.serial(op).catch((err) => {
@@ -7825,7 +8037,7 @@ function vfsFile(fs, opts, nodes) {
7825
8037
  takeOpenError();
7826
8038
  await node.flush();
7827
8039
  } finally {
7828
- nodes?.closed(node);
8040
+ nodes?.closed(node, writable);
7829
8041
  }
7830
8042
  })
7831
8043
  });
@@ -9172,7 +9384,11 @@ function mediumSlot(lost, onLost) {
9172
9384
  }
9173
9385
 
9174
9386
  // src/mount/fsa.ts
9175
- var FSA_CAPABILITIES = { listingStats: true };
9387
+ var FSA_CAPABILITIES = {
9388
+ listingStats: true,
9389
+ ranges: true,
9390
+ sessions: true
9391
+ };
9176
9392
  var codeOf2 = (err) => err?.code;
9177
9393
  var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
9178
9394
  function attrOf(st) {
@@ -9212,30 +9428,20 @@ function kernelFsHandlers(fs) {
9212
9428
  if (st && flags.exclusive) throw fsError2("EEXIST", path);
9213
9429
  if (st?.isDirectory && flags.write) throw fsError2("EISDIR", path);
9214
9430
  if (!st && !flags.create) throw fsError2("ENOENT", path);
9215
- const data = st && !flags.truncate ? await fs.readFileBuffer(path) : new Uint8Array(0);
9216
- handles.set(++nextFh, { path, data, length: data.length, dirty: !st || flags.truncate });
9431
+ if (!st || flags.truncate) await fs.writeFile(path, new Uint8Array(0));
9432
+ handles.set(++nextFh, { path });
9217
9433
  return nextFh;
9218
9434
  },
9219
- async read(fh, offset, size) {
9220
- const h = handle(fh);
9221
- return h.data.slice(offset, Math.min(h.length, offset + size));
9222
- },
9435
+ read: async (fh, offset, size) => fs.pread(handle(fh).path, offset, size),
9223
9436
  async write(fh, offset, bytes) {
9224
9437
  const h = handle(fh);
9225
- const end = offset + bytes.length;
9226
- if (end > h.data.length) {
9227
- const grown = new Uint8Array(Math.max(end, h.data.length * 2));
9228
- grown.set(h.data.subarray(0, h.length));
9229
- h.data = grown;
9230
- }
9231
- h.data.set(bytes, offset);
9232
- h.length = Math.max(h.length, end);
9233
- h.dirty = true;
9438
+ h.session ??= fs.writeSession(h.path, false);
9439
+ await (await h.session).write(offset, bytes);
9234
9440
  },
9235
9441
  async release(fh) {
9236
9442
  const h = handle(fh);
9237
9443
  handles.delete(fh);
9238
- if (h.dirty) await fs.writeFile(h.path, h.data.slice(0, h.length));
9444
+ if (h.session !== void 0) await (await h.session).close();
9239
9445
  },
9240
9446
  mkdir: (path) => fs.mkdir(path),
9241
9447
  async rmdir(path) {
@@ -9251,6 +9457,13 @@ function kernelFsHandlers(fs) {
9251
9457
  symlink: (target, path) => fs.symlink(target, path),
9252
9458
  readlink: (path) => fs.readlink(path),
9253
9459
  async setattr(path, change) {
9460
+ if (change.size !== void 0) {
9461
+ const open = [...handles.values()].find(
9462
+ (h) => h.path === path && h.session !== void 0
9463
+ )?.session;
9464
+ if (open !== void 0) await (await open).truncate(change.size);
9465
+ else await fs.truncate?.(path, change.size);
9466
+ }
9254
9467
  if (change.mode !== void 0) await fs.chmod(path, change.mode);
9255
9468
  if (change.mtime !== void 0) {
9256
9469
  await fs.utimes(path, new Date(change.mtime), new Date(change.mtime));
@@ -9966,6 +10179,7 @@ function tmpfs() {
9966
10179
  const { dir, name: name2 } = parent(path);
9967
10180
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
9968
10181
  dir.children.set(name2, make("directory", 16877));
10182
+ dir.mtime = Date.now();
9969
10183
  },
9970
10184
  async rmdir(path) {
9971
10185
  const { dir, name: name2 } = parent(path);
@@ -9974,6 +10188,7 @@ function tmpfs() {
9974
10188
  if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
9975
10189
  if (node.children.size > 0) throw fsError2("ENOTEMPTY", path);
9976
10190
  dir.children.delete(name2);
10191
+ dir.mtime = Date.now();
9977
10192
  },
9978
10193
  async unlink(path) {
9979
10194
  const { dir, name: name2 } = parent(path);
@@ -9981,6 +10196,7 @@ function tmpfs() {
9981
10196
  if (!node) throw fsError2("ENOENT", path);
9982
10197
  if (node.kind === "directory") throw fsError2("EISDIR", path);
9983
10198
  dir.children.delete(name2);
10199
+ dir.mtime = Date.now();
9984
10200
  },
9985
10201
  async rename(from, to) {
9986
10202
  const a = `/${parts(from).join("/")}`;
@@ -9997,6 +10213,8 @@ function tmpfs() {
9997
10213
  }
9998
10214
  source.dir.children.delete(source.name);
9999
10215
  target.dir.children.set(target.name, node);
10216
+ source.dir.mtime = Date.now();
10217
+ target.dir.mtime = source.dir.mtime;
10000
10218
  },
10001
10219
  async symlink(target, path) {
10002
10220
  const { dir, name: name2 } = parent(path);
@@ -10004,6 +10222,7 @@ function tmpfs() {
10004
10222
  const node = make("symlink", 41471);
10005
10223
  node.target = target;
10006
10224
  dir.children.set(name2, node);
10225
+ dir.mtime = Date.now();
10007
10226
  },
10008
10227
  async readlink(path) {
10009
10228
  const node = find(path);
@@ -10193,6 +10412,7 @@ var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
10193
10412
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
10194
10413
  var WASM_MAGIC = [0, 97, 115, 109];
10195
10414
  var SHEBANG_MAX = 256;
10415
+ var MAX_INTERPRETERS = 4;
10196
10416
  var NOT_FOUND = 127;
10197
10417
  var DECIDE_MS = 100;
10198
10418
  var INIT_PID = 1;
@@ -10244,6 +10464,17 @@ function targetOf(command) {
10244
10464
  ...command.preopenRoot ? { preopenRoot: true } : {}
10245
10465
  };
10246
10466
  }
10467
+ function withScriptEnv(target, command) {
10468
+ if (!command) return target;
10469
+ return {
10470
+ ...target,
10471
+ env: { ...target.env, ...command.env },
10472
+ unset: [
10473
+ ...(target.unset ?? []).filter((key) => !Object.hasOwn(command.env ?? {}, key)),
10474
+ ...command.unset ?? []
10475
+ ]
10476
+ };
10477
+ }
10247
10478
  function childHandle(handle) {
10248
10479
  return {
10249
10480
  pid: handle.pid,
@@ -10268,6 +10499,7 @@ var Launcher = class {
10268
10499
  modulesDir;
10269
10500
  env;
10270
10501
  catalog;
10502
+ binfmts;
10271
10503
  compiled = /* @__PURE__ */ new Map();
10272
10504
  processes = /* @__PURE__ */ new Map();
10273
10505
  zombies = /* @__PURE__ */ new Set();
@@ -10302,7 +10534,7 @@ var Launcher = class {
10302
10534
  deciding = /* @__PURE__ */ new Map();
10303
10535
  identities = { asked: 0, timedOut: 0 };
10304
10536
  nodes;
10305
- held = /* @__PURE__ */ new Set();
10537
+ held = new HeldSet((path) => this.released(path));
10306
10538
  processMounts;
10307
10539
  booting = new AbortController();
10308
10540
  fstab = Promise.resolve([]);
@@ -10344,11 +10576,12 @@ var Launcher = class {
10344
10576
  });
10345
10577
  enableCdp(this.net, this.cdp);
10346
10578
  const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
10347
- this.nodes = new VfsNodes(fs);
10579
+ this.nodes = new VfsNodes(fs, (path) => this.released(path));
10348
10580
  this.openFiles.add(this.nodes);
10349
10581
  this.fs = keepingOpen(fs, this.nodes);
10350
10582
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
10351
10583
  this.catalog = void 0;
10584
+ this.binfmts = void 0;
10352
10585
  });
10353
10586
  }
10354
10587
  setRoutes(table) {
@@ -10358,6 +10591,10 @@ var Launcher = class {
10358
10591
  this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
10359
10592
  return this.catalog;
10360
10593
  }
10594
+ interpreters() {
10595
+ this.binfmts ??= this.roots().then((roots) => scanBinfmts(this.base, roots));
10596
+ return this.binfmts;
10597
+ }
10361
10598
  async roots() {
10362
10599
  return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
10363
10600
  }
@@ -10403,30 +10640,28 @@ var Launcher = class {
10403
10640
  const command = (await this.commands()).get(name2);
10404
10641
  return command?.script ? command : void 0;
10405
10642
  }
10406
- async interpreted(file, argv, cwd) {
10643
+ async interpreted(file, argv, cwd, depth = 0) {
10407
10644
  const command = await this.scriptCommand(file);
10408
10645
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
10409
- const words = shebang(await this.head(script) ?? new Uint8Array());
10646
+ const head = await this.head(script);
10647
+ if (!head) return void 0;
10648
+ const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(), script);
10649
+ const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
10410
10650
  if (!words) return void 0;
10411
10651
  if (baseName(words[0]) === "env") words.shift();
10412
10652
  const [interp, ...rest] = words;
10413
- const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
10414
- if (!found) return void 0;
10415
- const target = command ? {
10416
- ...found,
10417
- env: { ...found.env, ...command.env },
10418
- unset: [...found.unset ?? [], ...command.unset ?? []]
10419
- } : found;
10420
10653
  const arg = rest.join(" ");
10421
- return {
10422
- target,
10423
- args: [
10424
- ...target.prefix ?? [],
10425
- ...arg ? [arg] : [],
10426
- command ? script : file,
10427
- ...argv.slice(1)
10428
- ]
10429
- };
10654
+ const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
10655
+ const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
10656
+ if (!found) {
10657
+ if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp)) {
10658
+ return void 0;
10659
+ }
10660
+ const inner = await this.interpreted(interp, [interp, ...passed], cwd, depth + 1);
10661
+ return inner && { ...inner, target: withScriptEnv(inner.target, command) };
10662
+ }
10663
+ const target = withScriptEnv(found, command);
10664
+ return { target, args: [...target.prefix ?? [], ...passed] };
10430
10665
  }
10431
10666
  async plan(file, argv, cwd) {
10432
10667
  const direct = await this.resolve(file, argv[0] ?? file, cwd);
@@ -10434,8 +10669,10 @@ var Launcher = class {
10434
10669
  return this.interpreted(file, argv, cwd);
10435
10670
  }
10436
10671
  async unrunnable(file, cwd) {
10437
- const head = await this.base.readFileBuffer(this.fs.resolvePath(cwd, file)).catch(() => null);
10438
- return head && !(head[0] === 35 && head[1] === 33) ? "ENOEXEC" : "ENOENT";
10672
+ const path = this.fs.resolvePath(cwd, file);
10673
+ const head = await this.base.readFileBuffer(path).catch(() => null);
10674
+ if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
10675
+ return binfmtOf(await this.interpreters(), path) ? "ENOENT" : "ENOEXEC";
10439
10676
  }
10440
10677
  module(path) {
10441
10678
  return this.base.stat(path).then((st) => {
@@ -10706,6 +10943,11 @@ var Launcher = class {
10706
10943
  umaskOf(pid) {
10707
10944
  return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
10708
10945
  }
10946
+ released(path) {
10947
+ const at = normalizePath(path);
10948
+ const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
10949
+ if (!open) void this.mounts.commit(at).catch(() => void 0);
10950
+ }
10709
10951
  ignoredBy(pid, fork = false) {
10710
10952
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
10711
10953
  }
@@ -10890,6 +11132,7 @@ var Launcher = class {
10890
11132
  }
10891
11133
  async starting(argv, dir) {
10892
11134
  this.catalog = void 0;
11135
+ this.binfmts = void 0;
10893
11136
  const cwd = this.fs.resolvePath("/", dir ?? "/");
10894
11137
  const [file = ""] = argv;
10895
11138
  await this.base.mkdir(cwd, { recursive: true });