@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.
package/dist/node.js CHANGED
@@ -1938,11 +1938,41 @@ var OpfsFs = class {
1938
1938
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
1939
1939
  return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
1940
1940
  }
1941
+ dirTimes = /* @__PURE__ */ new Map();
1942
+ dirDirty = /* @__PURE__ */ new Set();
1943
+ dirFlush;
1944
+ touch(...dirs) {
1945
+ const now = Date.now();
1946
+ for (const dir of dirs) {
1947
+ this.dirTimes.set(dir, now);
1948
+ this.dirDirty.add(dir);
1949
+ }
1950
+ this.dirFlush ??= Promise.resolve().then(() => this.flushDirTimes()).catch(() => void 0).finally(() => {
1951
+ this.dirFlush = void 0;
1952
+ });
1953
+ }
1954
+ async flushDirTimes() {
1955
+ for (const path of this.dirDirty) {
1956
+ this.dirDirty.delete(path);
1957
+ const ms = this.dirTimes.get(path);
1958
+ if (ms === void 0) continue;
1959
+ await this.meta.update(path, (entry) => ({
1960
+ ...entry,
1961
+ path,
1962
+ mtimeMs: ms,
1963
+ mtimeFor: 0,
1964
+ ctimeMs: ms
1965
+ }));
1966
+ if (this.dirTimes.get(path) === ms) this.dirTimes.delete(path);
1967
+ }
1968
+ }
1941
1969
  async statOf(path, handle, meta) {
1942
1970
  const directory = handle.kind === "directory";
1943
1971
  const file = directory ? void 0 : await handle.getFile();
1944
1972
  const modified = file?.lastModified ?? 0;
1945
- const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
1973
+ const touched = directory ? this.dirTimes.get(path) : void 0;
1974
+ const kept = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
1975
+ const mtime = touched ?? kept;
1946
1976
  return {
1947
1977
  isFile: !directory,
1948
1978
  isDirectory: directory,
@@ -1951,7 +1981,7 @@ var OpfsFs = class {
1951
1981
  mode: this.mode(path, directory, meta),
1952
1982
  mtime: new Date(mtime),
1953
1983
  atime: new Date(meta?.atimeMs ?? mtime),
1954
- ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
1984
+ ctime: new Date(Math.max(touched ?? meta?.ctimeMs ?? mtime, modified)),
1955
1985
  ino: meta?.ino ?? inodeOf(path),
1956
1986
  ...this.ranged && !directory ? { ranged: true } : {}
1957
1987
  };
@@ -2028,6 +2058,19 @@ var OpfsFs = class {
2028
2058
  const handle = await this.existing(path);
2029
2059
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
2030
2060
  }
2061
+ async writeSession(path, truncate) {
2062
+ const handle = (await this.fileFor(path))[0];
2063
+ const writable = await this.retrying(
2064
+ path,
2065
+ () => handle.createWritable({ keepExistingData: !truncate }),
2066
+ true
2067
+ );
2068
+ return {
2069
+ write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
2070
+ truncate: (size) => writable.truncate(size),
2071
+ close: () => writable.close()
2072
+ };
2073
+ }
2031
2074
  async readFile(path) {
2032
2075
  return new TextDecoder().decode(await this.readFileBuffer(path));
2033
2076
  }
@@ -2045,10 +2088,12 @@ var OpfsFs = class {
2045
2088
  const [target, existed] = await this.writeTarget(path);
2046
2089
  const [dir, name2] = await this.parent(target);
2047
2090
  try {
2048
- return [
2049
- await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true })),
2050
- existed
2051
- ];
2091
+ const handle = await this.inDir(
2092
+ dir,
2093
+ (parent) => parent.getFileHandle(name2, { create: true })
2094
+ );
2095
+ if (!existed) this.touch(parentOf2(target));
2096
+ return [handle, existed];
2052
2097
  } catch (err) {
2053
2098
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
2054
2099
  throw code ? fsError(code, path) : translate(err, path);
@@ -2106,6 +2151,7 @@ var OpfsFs = class {
2106
2151
  const parent = await this.locate(parentOf2(path), true);
2107
2152
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
2108
2153
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
2154
+ this.touch(parent.path);
2109
2155
  }
2110
2156
  async rm(path, options = {}) {
2111
2157
  let found;
@@ -2129,6 +2175,7 @@ var OpfsFs = class {
2129
2175
  this.changed(found.path);
2130
2176
  }
2131
2177
  await this.meta.remove([found.path]);
2178
+ this.touch(parentOf2(found.path));
2132
2179
  }
2133
2180
  async rename(from, to) {
2134
2181
  if (from === to) return;
@@ -2160,6 +2207,7 @@ var OpfsFs = class {
2160
2207
  if (source.link) {
2161
2208
  if (target?.handle) await this.rm(destination);
2162
2209
  await this.meta.move(source.path, destination, carry);
2210
+ this.touch(parentOf2(source.path), parent.path);
2163
2211
  return;
2164
2212
  }
2165
2213
  const dir = parent.handle;
@@ -2181,6 +2229,7 @@ var OpfsFs = class {
2181
2229
  }
2182
2230
  if (copied) await this.restamp(destination, timed);
2183
2231
  this.changed(source.path, destination);
2232
+ this.touch(parentOf2(source.path), parent.path);
2184
2233
  }
2185
2234
  async modified(path) {
2186
2235
  const handle = await this.handle(path);
@@ -2244,6 +2293,7 @@ var OpfsFs = class {
2244
2293
  mtimeMs: now,
2245
2294
  ctimeMs: now
2246
2295
  }));
2296
+ this.touch(parent.path);
2247
2297
  }
2248
2298
  async realpath(path, follow) {
2249
2299
  return (await this.locate(path, follow)).path;
@@ -2284,6 +2334,7 @@ var OpfsFs = class {
2284
2334
  }
2285
2335
  const handle = found.handle;
2286
2336
  const file = handle.kind === "file" ? await handle.getFile() : void 0;
2337
+ this.dirTimes.delete(found.path);
2287
2338
  const ctimeMs = Date.now();
2288
2339
  await this.meta.update(found.path, (entry) => ({
2289
2340
  ...entry,
@@ -3198,6 +3249,35 @@ async function scanFilesystems(fs, modules) {
3198
3249
  }
3199
3250
  return found;
3200
3251
  }
3252
+ var SUFFIX = /^\.[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
3253
+ async function scanBinfmts(fs, modules) {
3254
+ const found = /* @__PURE__ */ new Map();
3255
+ for (const pkg of await packagesIn(fs, modules)) {
3256
+ let manifest;
3257
+ try {
3258
+ manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
3259
+ } catch {
3260
+ continue;
3261
+ }
3262
+ const declared = manifest.slicc?.binfmt;
3263
+ if (!declared || typeof declared !== "object") continue;
3264
+ for (const [suffix, command] of Object.entries(declared)) {
3265
+ if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME.test(command)) continue;
3266
+ if (!found.has(suffix)) found.set(suffix, command);
3267
+ }
3268
+ }
3269
+ return found;
3270
+ }
3271
+ function binfmtOf(binfmts, path) {
3272
+ const name2 = path.slice(path.lastIndexOf("/") + 1);
3273
+ let best;
3274
+ for (const suffix of binfmts.keys()) {
3275
+ if (name2.length > suffix.length && name2.endsWith(suffix)) {
3276
+ if (best === void 0 || suffix.length > best.length) best = suffix;
3277
+ }
3278
+ }
3279
+ return best === void 0 ? void 0 : binfmts.get(best);
3280
+ }
3201
3281
 
3202
3282
  // src/fs/commands.ts
3203
3283
  var S_IFDIR2 = 16384;
@@ -3703,18 +3783,36 @@ function parseSize(text) {
3703
3783
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
3704
3784
  }
3705
3785
  var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3786
+ var HeldSet = class extends Set {
3787
+ idle;
3788
+ constructor(idle) {
3789
+ super();
3790
+ this.idle = idle;
3791
+ }
3792
+ add(holds) {
3793
+ holds.onIdle = this.idle;
3794
+ return super.add(holds);
3795
+ }
3796
+ delete(holds) {
3797
+ const removed = super.delete(holds);
3798
+ for (const path of holds.keys()) this.idle(path);
3799
+ return removed;
3800
+ }
3801
+ };
3706
3802
  function heldUnder(held, target) {
3707
3803
  for (const paths of held) for (const path of paths.keys()) if (within2(path, target)) return true;
3708
3804
  return false;
3709
3805
  }
3710
3806
  var HeldPaths = class extends Map {
3711
3807
  revoked = /* @__PURE__ */ new Set();
3808
+ onIdle;
3712
3809
  hold(path, on) {
3713
3810
  const count = (this.get(path) ?? 0) + (on ? 1 : -1);
3714
3811
  if (count > 0) this.set(path, count);
3715
3812
  else {
3716
3813
  this.delete(path);
3717
3814
  this.revoked.delete(path);
3815
+ this.onIdle?.(path);
3718
3816
  }
3719
3817
  }
3720
3818
  revoke(prefix) {
@@ -3782,6 +3880,8 @@ function spliced(bytes, offset, part) {
3782
3880
  }
3783
3881
  var MountTable = class {
3784
3882
  mounts = /* @__PURE__ */ new Map();
3883
+ sessions = /* @__PURE__ */ new Map();
3884
+ committing = /* @__PURE__ */ new Map();
3785
3885
  devices = 0;
3786
3886
  pending = /* @__PURE__ */ new Set();
3787
3887
  notes = /* @__PURE__ */ new Map();
@@ -3906,8 +4006,69 @@ var MountTable = class {
3906
4006
  if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
3907
4007
  this.notes.delete(at);
3908
4008
  this.mounts.delete(at);
3909
- mount.conn.close();
3910
- mount.dispose();
4009
+ const waits = [...this.sessions].filter(([, s]) => s.mount === mount).map(([real]) => this.commit(real));
4010
+ for (const c of this.committing.values()) if (c.mount === mount) waits.push(c.done);
4011
+ const close = () => {
4012
+ mount.conn.close();
4013
+ mount.dispose();
4014
+ };
4015
+ if (waits.length === 0) close();
4016
+ else void Promise.allSettled(waits).then(close);
4017
+ }
4018
+ session(mount, real, rel2) {
4019
+ let session = this.sessions.get(real);
4020
+ if (!session) {
4021
+ const open = () => mount.conn.call({
4022
+ op: "open",
4023
+ path: rel2,
4024
+ write: true,
4025
+ create: true,
4026
+ truncate: false,
4027
+ exclusive: false
4028
+ });
4029
+ const before = this.committing.get(real)?.done ?? Promise.resolve();
4030
+ const fh = before.then(open, open);
4031
+ session = { mount, fh, extents: [], truncated: false };
4032
+ this.sessions.set(real, session);
4033
+ fh.catch(() => this.sessions.delete(real));
4034
+ }
4035
+ return session;
4036
+ }
4037
+ writing(real) {
4038
+ return this.sessions.get(real);
4039
+ }
4040
+ async commit(real) {
4041
+ const session = this.sessions.get(real);
4042
+ if (!session) return this.committing.get(real)?.done;
4043
+ this.sessions.delete(real);
4044
+ const done4 = (async () => {
4045
+ try {
4046
+ await session.mount.conn.call({ op: "release", fh: await session.fh });
4047
+ } finally {
4048
+ this.forget(session.mount, real);
4049
+ }
4050
+ })();
4051
+ const entry = { mount: session.mount, done: done4 };
4052
+ this.committing.set(real, entry);
4053
+ const settle = () => {
4054
+ if (this.committing.get(real) === entry) this.committing.delete(real);
4055
+ };
4056
+ done4.then(settle, settle);
4057
+ return done4;
4058
+ }
4059
+ async commitUnder(prefix) {
4060
+ const at = normalizePath(prefix);
4061
+ await Promise.all(
4062
+ [...this.sessions.keys()].filter((p) => within2(p, at)).map((p) => this.commit(p))
4063
+ );
4064
+ }
4065
+ async commitOverlapping(real, offset, length2) {
4066
+ const session = this.sessions.get(real);
4067
+ if (!session) return;
4068
+ const end = offset + length2;
4069
+ if (session.truncated || session.extents.some(([a, b]) => a < end && offset < b)) {
4070
+ await this.commit(real);
4071
+ }
3911
4072
  }
3912
4073
  crashed(mount, error) {
3913
4074
  mount.state = "failed";
@@ -3966,6 +4127,13 @@ var MountTable = class {
3966
4127
  return new MountFs(this, base);
3967
4128
  }
3968
4129
  };
4130
+ function noteExtent(extents, from, to) {
4131
+ const last = extents.at(-1);
4132
+ if (last && from <= last[1] && to >= last[0]) {
4133
+ last[0] = Math.min(last[0], from);
4134
+ last[1] = Math.max(last[1], to);
4135
+ } else extents.push([from, to]);
4136
+ }
3969
4137
  var MountFs = class {
3970
4138
  table;
3971
4139
  base;
@@ -4025,6 +4193,7 @@ var MountFs = class {
4025
4193
  }
4026
4194
  }
4027
4195
  async readWhole(mount, real, rel2) {
4196
+ await this.table.commit(real);
4028
4197
  const attr = await this.table.getattr(mount, real, rel2);
4029
4198
  if (attr.kind === "directory") throw errnoError("EISDIR", real);
4030
4199
  if (mount.maxFile > 0 && attr.size > mount.maxFile) {
@@ -4074,6 +4243,7 @@ var MountFs = class {
4074
4243
  }
4075
4244
  }
4076
4245
  async writeWhole(mount, real, rel2, bytes) {
4246
+ await this.table.commit(real);
4077
4247
  this.table.forget(mount, real);
4078
4248
  const fh = await this.open(mount, rel2, true);
4079
4249
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
@@ -4096,6 +4266,7 @@ var MountFs = class {
4096
4266
  if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
4097
4267
  throw errnoError("EISDIR", real);
4098
4268
  }
4269
+ await this.table.commitOverlapping(real, offset, length2);
4099
4270
  const fh = await mount.conn.call({
4100
4271
  op: "open",
4101
4272
  path: rel2,
@@ -4149,6 +4320,14 @@ var MountFs = class {
4149
4320
  this.writable(mount, real);
4150
4321
  this.fits(mount, real, offset + bytes.length);
4151
4322
  this.table.forget(mount, real);
4323
+ if (mount.caps.sessions) {
4324
+ const session = this.table.session(mount, real, rel2);
4325
+ const end = offset + bytes.length;
4326
+ await this.writeRuns(mount, await session.fh, offset, bytes, transfer);
4327
+ noteExtent(session.extents, offset, end);
4328
+ this.table.forget(mount, real);
4329
+ return;
4330
+ }
4152
4331
  const fh = await mount.conn.call({
4153
4332
  op: "open",
4154
4333
  path: rel2,
@@ -4157,17 +4336,20 @@ var MountFs = class {
4157
4336
  truncate: false,
4158
4337
  exclusive: false
4159
4338
  });
4160
- const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
4161
4339
  try {
4162
- for (let at = 0; at < bytes.length; at += chunk2) {
4163
- const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
4164
- await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
4165
- }
4340
+ await this.writeRuns(mount, fh, offset, bytes, transfer);
4166
4341
  } finally {
4167
4342
  await mount.conn.call({ op: "release", fh });
4168
4343
  this.table.forget(mount, real);
4169
4344
  }
4170
4345
  }
4346
+ async writeRuns(mount, fh, offset, bytes, transfer) {
4347
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
4348
+ for (let at = 0; at < bytes.length; at += chunk2) {
4349
+ const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
4350
+ await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
4351
+ }
4352
+ }
4171
4353
  async truncate(path, size) {
4172
4354
  const real = await this.follow(path);
4173
4355
  const found = this.at(real);
@@ -4179,6 +4361,8 @@ var MountFs = class {
4179
4361
  this.writable(mount, real);
4180
4362
  this.fits(mount, real, size);
4181
4363
  this.table.forget(mount, real);
4364
+ const session = this.table.writing(real);
4365
+ if (session) session.truncated = true;
4182
4366
  try {
4183
4367
  await mount.conn.call({ op: "setattr", path: rel2, size });
4184
4368
  } finally {
@@ -4200,7 +4384,16 @@ var MountFs = class {
4200
4384
  const found = this.at(path);
4201
4385
  if (!found) return this.base.lstat(path);
4202
4386
  const real = normalizePath(path);
4203
- return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount);
4387
+ if (this.table.writing(real)?.truncated) await this.table.commit(real);
4388
+ return this.statWriting(
4389
+ await this.table.getattr(found.mount, real, found.rel),
4390
+ real,
4391
+ found.mount
4392
+ );
4393
+ }
4394
+ statWriting(attr, real, mount) {
4395
+ const ends = (this.table.writing(real)?.extents ?? []).map(([, end]) => end);
4396
+ return statOf({ ...attr, size: Math.max(attr.size, ...ends) }, real, mount);
4204
4397
  }
4205
4398
  async stat(path) {
4206
4399
  const real = await this.follow(path);
@@ -4227,7 +4420,7 @@ var MountFs = class {
4227
4420
  list.map(async (entry) => {
4228
4421
  const child = normalizePath(`${real}/${entry.name}`);
4229
4422
  if (entry.attr && mount.caps.listingStats) {
4230
- return [entry.name, statOf(entry.attr, child, mount)];
4423
+ return [entry.name, this.statWriting(entry.attr, child, mount)];
4231
4424
  }
4232
4425
  return [entry.name, await this.lstat(child).catch(() => null)];
4233
4426
  })
@@ -4263,6 +4456,7 @@ var MountFs = class {
4263
4456
  const real = normalizePath(path);
4264
4457
  if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
4265
4458
  this.writable(mount, real);
4459
+ await this.table.commitUnder(real);
4266
4460
  let attr;
4267
4461
  try {
4268
4462
  attr = await this.table.getattr(mount, real, rel2);
@@ -4286,6 +4480,8 @@ var MountFs = class {
4286
4480
  if (!a && !b) return this.base.rename(from, to);
4287
4481
  if (!a || !b || a.mount !== b.mount) throw errnoError("EXDEV", `${from} -> ${to}`);
4288
4482
  this.writable(a.mount, normalizePath(to));
4483
+ await this.table.commitUnder(from);
4484
+ await this.table.commitUnder(to);
4289
4485
  this.table.forget(a.mount, normalizePath(from));
4290
4486
  this.table.forget(a.mount, normalizePath(to));
4291
4487
  try {
@@ -7094,6 +7290,7 @@ var VfsNode = class {
7094
7290
  writeBack;
7095
7291
  writeBackCost = 0;
7096
7292
  opens = 0;
7293
+ writers = 0;
7097
7294
  missing = false;
7098
7295
  revoked = false;
7099
7296
  fs;
@@ -7269,8 +7466,10 @@ var VfsNodes = class {
7269
7466
  byPath = /* @__PURE__ */ new Map();
7270
7467
  fs;
7271
7468
  entries = /* @__PURE__ */ new Map();
7272
- constructor(fs) {
7469
+ onClosed;
7470
+ constructor(fs, onClosed) {
7273
7471
  this.fs = fs;
7472
+ this.onClosed = onClosed;
7274
7473
  }
7275
7474
  async onEntry(path, op) {
7276
7475
  const key = await this.entryKey(path);
@@ -7308,16 +7507,17 @@ var VfsNodes = class {
7308
7507
  await this.fs.writeFile(path, new Uint8Array(0));
7309
7508
  });
7310
7509
  }
7311
- open(path, pin) {
7510
+ open(path, pin, writable = false) {
7312
7511
  let node = this.byPath.get(path);
7313
7512
  if (!node) {
7314
7513
  node = new VfsNode(this.fs, path, void 0, false, false, pin);
7315
7514
  this.byPath.set(path, node);
7316
7515
  }
7317
7516
  node.opens++;
7517
+ if (writable) node.writers++;
7318
7518
  return node;
7319
7519
  }
7320
- adopt(path, contents2, dirty) {
7520
+ adopt(path, contents2, dirty, writable = false) {
7321
7521
  let node = this.byPath.get(path);
7322
7522
  if (!node) {
7323
7523
  node = new VfsNode(this.fs, path, contents2, false, dirty);
@@ -7326,15 +7526,27 @@ var VfsNodes = class {
7326
7526
  node.replace(contents2);
7327
7527
  }
7328
7528
  node.opens++;
7529
+ if (writable) node.writers++;
7329
7530
  return node;
7330
7531
  }
7331
7532
  holds(prefix) {
7332
7533
  for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
7333
7534
  return false;
7334
7535
  }
7335
- closed(node) {
7536
+ writes(prefix) {
7537
+ for (const [path, node] of this.byPath)
7538
+ if (node.writers > 0 && within4(path, prefix)) return true;
7539
+ return false;
7540
+ }
7541
+ closed(node, writable = false) {
7336
7542
  node.opens--;
7337
- if (node.opens === 0 && this.byPath.get(node.path) === node) this.byPath.delete(node.path);
7543
+ if (writable) node.writers--;
7544
+ if (node.opens > 0) {
7545
+ if (writable && node.writers === 0) this.onClosed?.(node.path);
7546
+ return;
7547
+ }
7548
+ if (this.byPath.get(node.path) === node) this.byPath.delete(node.path);
7549
+ this.onClosed?.(node.path);
7338
7550
  }
7339
7551
  async flush(path) {
7340
7552
  for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
@@ -7480,7 +7692,7 @@ function vfsFile(fs, opts, nodes) {
7480
7692
  const access = opts.flags & O_ACCMODE;
7481
7693
  const readable = access !== O_WRONLY;
7482
7694
  const writable = access === O_WRONLY || access === O_RDWR;
7483
- 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);
7695
+ 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);
7484
7696
  let openError;
7485
7697
  const atOpen = (op) => {
7486
7698
  void node.serial(op).catch((err) => {
@@ -7543,7 +7755,7 @@ function vfsFile(fs, opts, nodes) {
7543
7755
  takeOpenError();
7544
7756
  await node.flush();
7545
7757
  } finally {
7546
- nodes?.closed(node);
7758
+ nodes?.closed(node, writable);
7547
7759
  }
7548
7760
  })
7549
7761
  });
@@ -8890,7 +9102,11 @@ function mediumSlot(lost, onLost) {
8890
9102
  }
8891
9103
 
8892
9104
  // src/mount/fsa.ts
8893
- var FSA_CAPABILITIES = { listingStats: true };
9105
+ var FSA_CAPABILITIES = {
9106
+ listingStats: true,
9107
+ ranges: true,
9108
+ sessions: true
9109
+ };
8894
9110
  var codeOf2 = (err) => err?.code;
8895
9111
  var denied = (err) => codeOf2(err) === "EACCES" || err?.name === "NotAllowedError";
8896
9112
  function attrOf(st) {
@@ -8930,30 +9146,20 @@ function kernelFsHandlers(fs) {
8930
9146
  if (st && flags.exclusive) throw fsError2("EEXIST", path);
8931
9147
  if (st?.isDirectory && flags.write) throw fsError2("EISDIR", path);
8932
9148
  if (!st && !flags.create) throw fsError2("ENOENT", path);
8933
- const data = st && !flags.truncate ? await fs.readFileBuffer(path) : new Uint8Array(0);
8934
- handles.set(++nextFh, { path, data, length: data.length, dirty: !st || flags.truncate });
9149
+ if (!st || flags.truncate) await fs.writeFile(path, new Uint8Array(0));
9150
+ handles.set(++nextFh, { path });
8935
9151
  return nextFh;
8936
9152
  },
8937
- async read(fh, offset, size) {
8938
- const h = handle(fh);
8939
- return h.data.slice(offset, Math.min(h.length, offset + size));
8940
- },
9153
+ read: async (fh, offset, size) => fs.pread(handle(fh).path, offset, size),
8941
9154
  async write(fh, offset, bytes) {
8942
9155
  const h = handle(fh);
8943
- const end = offset + bytes.length;
8944
- if (end > h.data.length) {
8945
- const grown = new Uint8Array(Math.max(end, h.data.length * 2));
8946
- grown.set(h.data.subarray(0, h.length));
8947
- h.data = grown;
8948
- }
8949
- h.data.set(bytes, offset);
8950
- h.length = Math.max(h.length, end);
8951
- h.dirty = true;
9156
+ h.session ??= fs.writeSession(h.path, false);
9157
+ await (await h.session).write(offset, bytes);
8952
9158
  },
8953
9159
  async release(fh) {
8954
9160
  const h = handle(fh);
8955
9161
  handles.delete(fh);
8956
- if (h.dirty) await fs.writeFile(h.path, h.data.slice(0, h.length));
9162
+ if (h.session !== void 0) await (await h.session).close();
8957
9163
  },
8958
9164
  mkdir: (path) => fs.mkdir(path),
8959
9165
  async rmdir(path) {
@@ -8969,6 +9175,13 @@ function kernelFsHandlers(fs) {
8969
9175
  symlink: (target, path) => fs.symlink(target, path),
8970
9176
  readlink: (path) => fs.readlink(path),
8971
9177
  async setattr(path, change) {
9178
+ if (change.size !== void 0) {
9179
+ const open = [...handles.values()].find(
9180
+ (h) => h.path === path && h.session !== void 0
9181
+ )?.session;
9182
+ if (open !== void 0) await (await open).truncate(change.size);
9183
+ else await fs.truncate?.(path, change.size);
9184
+ }
8972
9185
  if (change.mode !== void 0) await fs.chmod(path, change.mode);
8973
9186
  if (change.mtime !== void 0) {
8974
9187
  await fs.utimes(path, new Date(change.mtime), new Date(change.mtime));
@@ -9693,6 +9906,7 @@ function tmpfs() {
9693
9906
  const { dir, name: name2 } = parent(path);
9694
9907
  if (dir.children.has(name2)) throw fsError2("EEXIST", path);
9695
9908
  dir.children.set(name2, make("directory", 16877));
9909
+ dir.mtime = Date.now();
9696
9910
  },
9697
9911
  async rmdir(path) {
9698
9912
  const { dir, name: name2 } = parent(path);
@@ -9701,6 +9915,7 @@ function tmpfs() {
9701
9915
  if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
9702
9916
  if (node.children.size > 0) throw fsError2("ENOTEMPTY", path);
9703
9917
  dir.children.delete(name2);
9918
+ dir.mtime = Date.now();
9704
9919
  },
9705
9920
  async unlink(path) {
9706
9921
  const { dir, name: name2 } = parent(path);
@@ -9708,6 +9923,7 @@ function tmpfs() {
9708
9923
  if (!node) throw fsError2("ENOENT", path);
9709
9924
  if (node.kind === "directory") throw fsError2("EISDIR", path);
9710
9925
  dir.children.delete(name2);
9926
+ dir.mtime = Date.now();
9711
9927
  },
9712
9928
  async rename(from, to) {
9713
9929
  const a = `/${parts(from).join("/")}`;
@@ -9724,6 +9940,8 @@ function tmpfs() {
9724
9940
  }
9725
9941
  source.dir.children.delete(source.name);
9726
9942
  target.dir.children.set(target.name, node);
9943
+ source.dir.mtime = Date.now();
9944
+ target.dir.mtime = source.dir.mtime;
9727
9945
  },
9728
9946
  async symlink(target, path) {
9729
9947
  const { dir, name: name2 } = parent(path);
@@ -9731,6 +9949,7 @@ function tmpfs() {
9731
9949
  const node = make("symlink", 41471);
9732
9950
  node.target = target;
9733
9951
  dir.children.set(name2, node);
9952
+ dir.mtime = Date.now();
9734
9953
  },
9735
9954
  async readlink(path) {
9736
9955
  const node = find(path);
@@ -9920,6 +10139,7 @@ var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
9920
10139
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
9921
10140
  var WASM_MAGIC = [0, 97, 115, 109];
9922
10141
  var SHEBANG_MAX = 256;
10142
+ var MAX_INTERPRETERS = 4;
9923
10143
  var NOT_FOUND = 127;
9924
10144
  var DECIDE_MS = 100;
9925
10145
  var INIT_PID = 1;
@@ -9971,6 +10191,17 @@ function targetOf(command) {
9971
10191
  ...command.preopenRoot ? { preopenRoot: true } : {}
9972
10192
  };
9973
10193
  }
10194
+ function withScriptEnv(target, command) {
10195
+ if (!command) return target;
10196
+ return {
10197
+ ...target,
10198
+ env: { ...target.env, ...command.env },
10199
+ unset: [
10200
+ ...(target.unset ?? []).filter((key) => !Object.hasOwn(command.env ?? {}, key)),
10201
+ ...command.unset ?? []
10202
+ ]
10203
+ };
10204
+ }
9974
10205
  function childHandle(handle) {
9975
10206
  return {
9976
10207
  pid: handle.pid,
@@ -9995,6 +10226,7 @@ var Launcher = class {
9995
10226
  modulesDir;
9996
10227
  env;
9997
10228
  catalog;
10229
+ binfmts;
9998
10230
  compiled = /* @__PURE__ */ new Map();
9999
10231
  processes = /* @__PURE__ */ new Map();
10000
10232
  zombies = /* @__PURE__ */ new Set();
@@ -10029,7 +10261,7 @@ var Launcher = class {
10029
10261
  deciding = /* @__PURE__ */ new Map();
10030
10262
  identities = { asked: 0, timedOut: 0 };
10031
10263
  nodes;
10032
- held = /* @__PURE__ */ new Set();
10264
+ held = new HeldSet((path) => this.released(path));
10033
10265
  processMounts;
10034
10266
  booting = new AbortController();
10035
10267
  fstab = Promise.resolve([]);
@@ -10071,11 +10303,12 @@ var Launcher = class {
10071
10303
  });
10072
10304
  enableCdp(this.net, this.cdp);
10073
10305
  const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
10074
- this.nodes = new VfsNodes(fs);
10306
+ this.nodes = new VfsNodes(fs, (path) => this.released(path));
10075
10307
  this.openFiles.add(this.nodes);
10076
10308
  this.fs = keepingOpen(fs, this.nodes);
10077
10309
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
10078
10310
  this.catalog = void 0;
10311
+ this.binfmts = void 0;
10079
10312
  });
10080
10313
  }
10081
10314
  setRoutes(table) {
@@ -10085,6 +10318,10 @@ var Launcher = class {
10085
10318
  this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
10086
10319
  return this.catalog;
10087
10320
  }
10321
+ interpreters() {
10322
+ this.binfmts ??= this.roots().then((roots) => scanBinfmts(this.base, roots));
10323
+ return this.binfmts;
10324
+ }
10088
10325
  async roots() {
10089
10326
  return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
10090
10327
  }
@@ -10130,30 +10367,28 @@ var Launcher = class {
10130
10367
  const command = (await this.commands()).get(name2);
10131
10368
  return command?.script ? command : void 0;
10132
10369
  }
10133
- async interpreted(file, argv, cwd) {
10370
+ async interpreted(file, argv, cwd, depth = 0) {
10134
10371
  const command = await this.scriptCommand(file);
10135
10372
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
10136
- const words = shebang(await this.head(script) ?? new Uint8Array());
10373
+ const head = await this.head(script);
10374
+ if (!head) return void 0;
10375
+ const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(), script);
10376
+ const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
10137
10377
  if (!words) return void 0;
10138
10378
  if (baseName(words[0]) === "env") words.shift();
10139
10379
  const [interp, ...rest] = words;
10140
- const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
10141
- if (!found) return void 0;
10142
- const target = command ? {
10143
- ...found,
10144
- env: { ...found.env, ...command.env },
10145
- unset: [...found.unset ?? [], ...command.unset ?? []]
10146
- } : found;
10147
10380
  const arg = rest.join(" ");
10148
- return {
10149
- target,
10150
- args: [
10151
- ...target.prefix ?? [],
10152
- ...arg ? [arg] : [],
10153
- command ? script : file,
10154
- ...argv.slice(1)
10155
- ]
10156
- };
10381
+ const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
10382
+ const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
10383
+ if (!found) {
10384
+ if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp)) {
10385
+ return void 0;
10386
+ }
10387
+ const inner = await this.interpreted(interp, [interp, ...passed], cwd, depth + 1);
10388
+ return inner && { ...inner, target: withScriptEnv(inner.target, command) };
10389
+ }
10390
+ const target = withScriptEnv(found, command);
10391
+ return { target, args: [...target.prefix ?? [], ...passed] };
10157
10392
  }
10158
10393
  async plan(file, argv, cwd) {
10159
10394
  const direct = await this.resolve(file, argv[0] ?? file, cwd);
@@ -10161,8 +10396,10 @@ var Launcher = class {
10161
10396
  return this.interpreted(file, argv, cwd);
10162
10397
  }
10163
10398
  async unrunnable(file, cwd) {
10164
- const head = await this.base.readFileBuffer(this.fs.resolvePath(cwd, file)).catch(() => null);
10165
- return head && !(head[0] === 35 && head[1] === 33) ? "ENOEXEC" : "ENOENT";
10399
+ const path = this.fs.resolvePath(cwd, file);
10400
+ const head = await this.base.readFileBuffer(path).catch(() => null);
10401
+ if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
10402
+ return binfmtOf(await this.interpreters(), path) ? "ENOENT" : "ENOEXEC";
10166
10403
  }
10167
10404
  module(path) {
10168
10405
  return this.base.stat(path).then((st) => {
@@ -10433,6 +10670,11 @@ var Launcher = class {
10433
10670
  umaskOf(pid) {
10434
10671
  return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
10435
10672
  }
10673
+ released(path) {
10674
+ const at = normalizePath(path);
10675
+ const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
10676
+ if (!open) void this.mounts.commit(at).catch(() => void 0);
10677
+ }
10436
10678
  ignoredBy(pid, fork = false) {
10437
10679
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
10438
10680
  }
@@ -10617,6 +10859,7 @@ var Launcher = class {
10617
10859
  }
10618
10860
  async starting(argv, dir) {
10619
10861
  this.catalog = void 0;
10862
+ this.binfmts = void 0;
10620
10863
  const cwd = this.fs.resolvePath("/", dir ?? "/");
10621
10864
  const [file = ""] = argv;
10622
10865
  await this.base.mkdir(cwd, { recursive: true });