@ai-ecoverse/slicc-kernel 1.54.8 → 1.55.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
@@ -1693,6 +1693,7 @@ var LAST_UID = 6e4;
1693
1693
  var USERS_GROUP = "users";
1694
1694
  var HOME_DIR = /^\/home\/[^/]+$/;
1695
1695
  var SYSTEM_PNPM_HOME = "/usr/local/share/pnpm";
1696
+ var PNPM_CONFIG = "minimumReleaseAge: 1440\n";
1696
1697
  var isId = (text) => text !== void 0 && /^\d+$/.test(text);
1697
1698
  function lines(text) {
1698
1699
  return text.split("\n").filter((line, i, all) => line !== "" || i < all.length - 1);
@@ -1748,6 +1749,7 @@ function userEnv(account, system) {
1748
1749
  USER: account.name,
1749
1750
  LOGNAME: account.name,
1750
1751
  PNPM_HOME: pnpm,
1752
+ NPM_CONFIG_PREFIX: account.uid === 0 ? "/usr/local" : `${account.home}/.local`,
1751
1753
  PATH: `${pnpm}/bin:/usr/local/bin:/usr/bin:/bin`
1752
1754
  };
1753
1755
  }
@@ -1772,6 +1774,18 @@ var UserDb = class {
1772
1774
  group: parseGroup(await this.read(GROUP, DEFAULT_GROUP))
1773
1775
  };
1774
1776
  }
1777
+ async provision(row) {
1778
+ const pnpm = `${row.home}/.local/share/pnpm`;
1779
+ if (!await this.fs.exists(pnpm)) {
1780
+ await this.fs.mkdir(pnpm, { recursive: true });
1781
+ await this.fs.chmod(pnpm, row.name.startsWith("scoop-") ? 488 : 448);
1782
+ }
1783
+ const config = `${row.home}/.config/pnpm/config.yaml`;
1784
+ if (!await this.fs.exists(config)) {
1785
+ await this.fs.mkdir(`${row.home}/.config/pnpm`, { recursive: true });
1786
+ await this.fs.writeFile(config, PNPM_CONFIG);
1787
+ }
1788
+ }
1775
1789
  async commit(passwd, group, then) {
1776
1790
  const before = [await this.read(PASSWD, DEFAULT_PASSWD), await this.read(GROUP, DEFAULT_GROUP)];
1777
1791
  try {
@@ -1845,6 +1859,7 @@ var UserDb = class {
1845
1859
  const groups = parsed(group);
1846
1860
  const found = users.find((r) => r.name === name2);
1847
1861
  if (found) {
1862
+ if (found.uid >= FIRST_UID && found.uid <= LAST_UID) await this.provision(found);
1848
1863
  remember();
1849
1864
  return this.account(found, groups);
1850
1865
  }
@@ -1863,6 +1878,7 @@ var UserDb = class {
1863
1878
  const fresh = !await this.fs.exists(home);
1864
1879
  await this.fs.mkdir(home, { recursive: true });
1865
1880
  try {
1881
+ await this.provision(row);
1866
1882
  await this.fs.mkdir("/etc", { recursive: true });
1867
1883
  await this.commit(formatPasswd(passwd), formatGroup(group));
1868
1884
  } catch (err) {
@@ -2749,7 +2765,7 @@ var OpfsFs = class {
2749
2765
  const parent = await this.locate(parentOf2(path), true);
2750
2766
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
2751
2767
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
2752
- this.touch(parent.path);
2768
+ this.touch(parent.path, `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`);
2753
2769
  }
2754
2770
  async rm(path, options = {}) {
2755
2771
  let found;
@@ -2813,7 +2829,7 @@ var OpfsFs = class {
2813
2829
  const swap = targetStat?.isDirectory === true;
2814
2830
  const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
2815
2831
  this.forget(destination);
2816
- const timed = await this.explicitTimes(source.path);
2832
+ const timed2 = await this.explicitTimes(source.path);
2817
2833
  const moved2 = await this.place(source.handle, dir, staging);
2818
2834
  if (swap) await this.rm(destination, { recursive: true });
2819
2835
  await this.meta.move(source.path, destination, carry);
@@ -2825,7 +2841,7 @@ var OpfsFs = class {
2825
2841
  if (!placed) await dir.removeEntry(staging, { recursive: true });
2826
2842
  copied ||= !placed;
2827
2843
  }
2828
- if (copied) await this.restamp(destination, timed);
2844
+ if (copied) await this.restamp(destination, timed2);
2829
2845
  this.changed(source.path, destination);
2830
2846
  this.touch(parentOf2(source.path), parent.path);
2831
2847
  }
@@ -2834,16 +2850,16 @@ var OpfsFs = class {
2834
2850
  return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
2835
2851
  }
2836
2852
  async explicitTimes(path) {
2837
- const timed = [];
2853
+ const timed2 = [];
2838
2854
  for (const entry of await this.meta.under(path)) {
2839
2855
  if (entry.mtimeFor === void 0 || entry.link !== void 0) continue;
2840
2856
  const current = await this.modified(entry.path).catch(() => void 0);
2841
- if (current === entry.mtimeFor) timed.push(entry.path.slice(path.length));
2857
+ if (current === entry.mtimeFor) timed2.push(entry.path.slice(path.length));
2842
2858
  }
2843
- return timed;
2859
+ return timed2;
2844
2860
  }
2845
- async restamp(path, timed) {
2846
- for (const rest2 of timed) {
2861
+ async restamp(path, timed2) {
2862
+ for (const rest2 of timed2) {
2847
2863
  const mtimeFor = await this.modified(path + rest2);
2848
2864
  await this.meta.update(path + rest2, (entry) => entry && { ...entry, mtimeFor });
2849
2865
  }
@@ -4156,17 +4172,22 @@ async function followLinks(fs, path) {
4156
4172
  }
4157
4173
  return current;
4158
4174
  }
4159
- function withCommandDirs(fs, names) {
4175
+ function withCommandDirs(fs, names, userDir = async () => void 0) {
4160
4176
  async function children2(path) {
4161
4177
  if (path === "/") return ["bin", "usr"];
4162
4178
  if (path === "/usr") return ["bin"];
4163
4179
  if (path === "/bin" || path === "/usr/bin") return [...await names()];
4164
- return void 0;
4180
+ const own = await userDir(path);
4181
+ return own?.size ? [...own] : void 0;
4165
4182
  }
4166
4183
  async function virtual(path) {
4167
4184
  if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
4168
4185
  const name2 = COMMAND.exec(path)?.[1];
4169
- return name2 !== void 0 && (await names()).has(name2) ? synthetic(false, path) : void 0;
4186
+ if (name2 !== void 0) return (await names()).has(name2) ? synthetic(false, path) : void 0;
4187
+ if ((await userDir(path))?.size) return synthetic(true, path);
4188
+ const slash = path.lastIndexOf("/");
4189
+ const own = await userDir(path.slice(0, slash));
4190
+ return own?.has(path.slice(slash + 1)) ? synthetic(false, path) : void 0;
4170
4191
  }
4171
4192
  async function linked(path) {
4172
4193
  return await virtual(path) ?? virtual(await followLinks(fs, path));
@@ -11573,11 +11594,24 @@ var HostfsClient = class {
11573
11594
  get maxIo() {
11574
11595
  return this.capabilities.maxIo ?? HOSTFS_IO;
11575
11596
  }
11576
- async send(path, init, handle = false, retried = false) {
11597
+ retired;
11598
+ stop = new AbortController();
11599
+ get gone() {
11600
+ return this.retired !== void 0;
11601
+ }
11602
+ retire(why) {
11603
+ this.retired ??= why;
11604
+ this.stop.abort();
11605
+ }
11606
+ async send(path, init, handle = false, retried = false, cleanup = false) {
11607
+ if (this.retired && !cleanup) throw fsError2("ENOTCONN", this.retired);
11608
+ const stop = cleanup ? void 0 : this.stop.signal;
11609
+ const signal = init.signal && stop ? AbortSignal.any([init.signal, stop]) : init.signal ?? stop;
11577
11610
  let response;
11578
11611
  try {
11579
11612
  response = await this.fetch(`${this.grant.url}${path}`, {
11580
11613
  ...init,
11614
+ ...signal ? { signal } : {},
11581
11615
  credentials: "omit",
11582
11616
  headers: {
11583
11617
  ...init.headers,
@@ -11614,6 +11648,20 @@ var HostfsClient = class {
11614
11648
  );
11615
11649
  return await response.json();
11616
11650
  }
11651
+ async cleanup(op, body) {
11652
+ const response = await this.send(
11653
+ "/api/hostfs",
11654
+ {
11655
+ method: "POST",
11656
+ headers: { "Content-Type": "application/json" },
11657
+ body: JSON.stringify({ op, ...body })
11658
+ },
11659
+ true,
11660
+ false,
11661
+ true
11662
+ );
11663
+ await response.body?.cancel();
11664
+ }
11617
11665
  async read(fh, offset, size, ifMatch) {
11618
11666
  const response = await this.send(
11619
11667
  "/api/hostfs",
@@ -11681,6 +11729,11 @@ function hostfsHandlers(current) {
11681
11729
  return fresh(remote);
11682
11730
  };
11683
11731
  const call = (op, body = {}) => current().call(op, body);
11732
+ const settle = (client, restore) => client.call("setattr", restore).catch((err) => {
11733
+ if (!client.gone || !client.cleanup) throw err;
11734
+ void client.cleanup("setattr", restore).catch(() => {
11735
+ });
11736
+ });
11684
11737
  async function writable(client, path, flags) {
11685
11738
  const { ifMatch: _, ...asked } = flags;
11686
11739
  const opening = () => client.call("open", { path: rel(path), ...asked });
@@ -11691,9 +11744,9 @@ function hostfsHandlers(current) {
11691
11744
  const mode = (await client.call("stat", { path: rel(path) })).mode ?? 0;
11692
11745
  if (mode & 128) throw err;
11693
11746
  const restore = { path: rel(path), mode };
11694
- await client.call("setattr", { path: rel(path), mode: mode | 128 });
11695
- const opened2 = await opening().catch(async (again) => {
11696
- await client.call("setattr", restore);
11747
+ const opened2 = await client.call("setattr", { path: rel(path), mode: mode | 128 }).then(opening).catch(async (again) => {
11748
+ await settle(client, restore).catch(() => {
11749
+ });
11697
11750
  throw again;
11698
11751
  });
11699
11752
  return { opened: opened2, restore };
@@ -11748,7 +11801,7 @@ function hostfsHandlers(current) {
11748
11801
  try {
11749
11802
  await fresh(remote).client.call("release", { fh: remote.fh }, true);
11750
11803
  } finally {
11751
- if (remote.restore) await current().call("setattr", remote.restore);
11804
+ if (remote.restore) await settle(current(), remote.restore);
11752
11805
  }
11753
11806
  },
11754
11807
  mkdir: async (path) => void await call("mkdir", { path: rel(path) }),
@@ -11878,6 +11931,73 @@ function invalidation(event) {
11878
11931
  return event.paths ?? true;
11879
11932
  }
11880
11933
  var PORT_REQUEST_MS = 6e4;
11934
+ var HOSTFS_CALL_MS = 2e4;
11935
+ var PORT_BEAT_MS = 1e3;
11936
+ var PORT_DEAD_MS = 1e4;
11937
+ var PORT_LINGER_MS = 1e3;
11938
+ function timed(transport, ms) {
11939
+ const inflight = /* @__PURE__ */ new Set();
11940
+ const guard = (name2, run2, late, expire) => new Promise((resolve, reject) => {
11941
+ let expired = false;
11942
+ const running = run2();
11943
+ inflight.add(running);
11944
+ const done4 = () => inflight.delete(running);
11945
+ running.then(done4, done4);
11946
+ const timer = setTimeout(() => {
11947
+ expired = true;
11948
+ const why = `the host folder did not answer ${name2} within ${ms} ms`;
11949
+ transport.retire?.(why);
11950
+ expire?.(running);
11951
+ reject(fsError2("ENOTCONN", why));
11952
+ }, ms);
11953
+ running.then(
11954
+ (value) => {
11955
+ clearTimeout(timer);
11956
+ if (expired) late?.(value);
11957
+ resolve(value);
11958
+ },
11959
+ (err) => {
11960
+ clearTimeout(timer);
11961
+ reject(err);
11962
+ }
11963
+ );
11964
+ });
11965
+ const cleanup = async (op, body, skip) => {
11966
+ await Promise.allSettled([...inflight].filter((work) => work !== skip));
11967
+ await transport.cleanup?.(op, body);
11968
+ };
11969
+ const release = (opened2) => {
11970
+ const fh = opened2?.fh;
11971
+ if (typeof fh === "number") void cleanup("release", { fh }).catch(() => {
11972
+ });
11973
+ };
11974
+ return {
11975
+ get capabilities() {
11976
+ return transport.capabilities;
11977
+ },
11978
+ get token() {
11979
+ return transport.token;
11980
+ },
11981
+ get maxIo() {
11982
+ return transport.maxIo;
11983
+ },
11984
+ get gone() {
11985
+ return transport.gone;
11986
+ },
11987
+ retire: (why) => transport.retire?.(why),
11988
+ ...transport.cleanup ? { cleanup } : {},
11989
+ call: (op, body, handle) => guard(
11990
+ op,
11991
+ () => transport.call(op, body, handle),
11992
+ op === "open" ? release : void 0,
11993
+ op === "release" ? (running) => void cleanup("release", body ?? {}, running).catch(() => {
11994
+ }) : void 0
11995
+ ),
11996
+ read: (fh, offset, size, ifMatch) => guard("read", () => transport.read(fh, offset, size, ifMatch)),
11997
+ write: (fh, offset, bytes) => guard("write", () => transport.write(fh, offset, bytes)),
11998
+ watch: (signal, onEvent, onOpen) => transport.watch(signal, onEvent, onOpen)
11999
+ };
12000
+ }
11881
12001
  var PORT_MS_PER_MIB = 1e3;
11882
12002
  var nextPortToken = 0;
11883
12003
  var PortTransport = class {
@@ -11888,13 +12008,25 @@ var PortTransport = class {
11888
12008
  waiting = /* @__PURE__ */ new Map();
11889
12009
  next = 0;
11890
12010
  dead;
11891
- constructor(grant, requestMs = PORT_REQUEST_MS) {
12011
+ beatMs;
12012
+ deadMs;
12013
+ lingerMs;
12014
+ beat;
12015
+ answers = false;
12016
+ pinged;
12017
+ constructor(grant, liveness = {}) {
11892
12018
  this.port = grant.port;
11893
- this.requestMs = requestMs;
12019
+ this.requestMs = liveness.requestMs ?? PORT_REQUEST_MS;
12020
+ this.beatMs = liveness.beatMs ?? PORT_BEAT_MS;
12021
+ this.deadMs = liveness.deadMs ?? PORT_DEAD_MS;
12022
+ this.lingerMs = liveness.lingerMs ?? PORT_LINGER_MS;
11894
12023
  this.capabilities = grant.capabilities ?? {};
11895
12024
  this.token = grant.token ?? `port-${++nextPortToken}`;
11896
12025
  this.port.addEventListener("message", (event) => {
11897
- this.waiting.get(event.data?.id)?.(event.data);
12026
+ if (event.data?.pong) {
12027
+ this.answers = true;
12028
+ this.pinged = void 0;
12029
+ } else this.waiting.get(event.data?.id)?.(event.data);
11898
12030
  });
11899
12031
  this.port.addEventListener("close", () => this.fail("the folder's port closed"));
11900
12032
  this.port.start();
@@ -11905,8 +12037,51 @@ var PortTransport = class {
11905
12037
  get gone() {
11906
12038
  return this.dead !== void 0;
11907
12039
  }
12040
+ retire(why) {
12041
+ this.fail(why);
12042
+ }
12043
+ ping() {
12044
+ if (this.pinged !== void 0) return;
12045
+ this.pinged = Date.now();
12046
+ try {
12047
+ this.port.postMessage({ id: 0, op: "ping" });
12048
+ } catch {
12049
+ }
12050
+ }
12051
+ watchBeat() {
12052
+ if (this.beat || this.dead) return;
12053
+ this.ping();
12054
+ this.beat = setInterval(() => {
12055
+ if (this.waiting.size === 0) {
12056
+ clearInterval(this.beat);
12057
+ this.beat = void 0;
12058
+ return;
12059
+ }
12060
+ if (this.answers && this.pinged !== void 0 && Date.now() - this.pinged > this.deadMs) {
12061
+ this.fail("the folder's port stopped answering");
12062
+ return;
12063
+ }
12064
+ this.ping();
12065
+ }, this.beatMs);
12066
+ }
12067
+ async cleanup(op, body) {
12068
+ if (this.closed) throw fsError2("ENOTCONN", "the folder's port is closed");
12069
+ this.port.postMessage({ id: 0, op: "cleanup", name: op, body });
12070
+ }
12071
+ closed = false;
11908
12072
  fail(why) {
11909
- if (this.dead === void 0) this.port.close();
12073
+ if (this.dead === void 0) {
12074
+ setTimeout(() => {
12075
+ try {
12076
+ this.port.postMessage({ id: 0, op: "bye" });
12077
+ } catch {
12078
+ }
12079
+ this.closed = true;
12080
+ this.port.close();
12081
+ }, this.lingerMs);
12082
+ }
12083
+ clearInterval(this.beat);
12084
+ this.beat = void 0;
11910
12085
  this.dead ??= why;
11911
12086
  for (const settle of [...this.waiting.values()])
11912
12087
  settle({ id: 0, error: { code: "ENOTCONN", message: why } });
@@ -11923,6 +12098,7 @@ var PortTransport = class {
11923
12098
  },
11924
12099
  this.requestMs + Math.ceil(bytes / 1048576) * PORT_MS_PER_MIB
11925
12100
  );
12101
+ this.watchBeat();
11926
12102
  this.waiting.set(id2, (reply) => {
11927
12103
  clearTimeout(timer);
11928
12104
  this.waiting.delete(id2);
@@ -11969,6 +12145,7 @@ var PortTransport = class {
11969
12145
  }
11970
12146
  resolve();
11971
12147
  };
12148
+ this.watchBeat();
11972
12149
  this.waiting.set(id2, (reply) => {
11973
12150
  if (reply.open) return onOpen();
11974
12151
  if (reply.event) return onEvent(reply.event);
@@ -11992,11 +12169,38 @@ var errorOf = (err) => ({
11992
12169
  });
11993
12170
  function serveHostfsPort(port, transport) {
11994
12171
  const watches = /* @__PURE__ */ new Map();
12172
+ const handles = /* @__PURE__ */ new Set();
12173
+ let stopped = false;
12174
+ const closeHandles = () => {
12175
+ for (const fh of handles) void transport.call("release", { fh }, true).catch(() => {
12176
+ });
12177
+ handles.clear();
12178
+ };
11995
12179
  const send = (reply, transfer = []) => port.postMessage(reply, transfer);
12180
+ const pending = /* @__PURE__ */ new Set();
12181
+ const track = (work) => {
12182
+ pending.add(work);
12183
+ const done4 = () => pending.delete(work);
12184
+ work.then(done4, done4);
12185
+ return work;
12186
+ };
12187
+ const answer = async (name2, body) => {
12188
+ const result = await track(transport.call(name2, body));
12189
+ const opened2 = result?.fh;
12190
+ if (name2 === "open" && typeof opened2 === "number") handles.add(opened2);
12191
+ if (name2 === "release") handles.delete(body?.fh);
12192
+ if (stopped) closeHandles();
12193
+ return result;
12194
+ };
11996
12195
  const listener = async ({
11997
12196
  data
11998
12197
  }) => {
11999
12198
  const { id: id2, op } = data;
12199
+ if (op === "ping") return send({ id: id2, pong: true });
12200
+ if (op === "bye") return stop();
12201
+ if (op === "cleanup")
12202
+ return void Promise.allSettled([...pending]).then(() => answer(data.name, data.body)).catch(() => {
12203
+ });
12000
12204
  if (op === "unwatch") return watches.get(id2)?.abort();
12001
12205
  if (op === "watch") {
12002
12206
  const stop2 = new AbortController();
@@ -12017,32 +12221,35 @@ function serveHostfsPort(port, transport) {
12017
12221
  }
12018
12222
  try {
12019
12223
  if (op === "read") {
12020
- const read = await transport.read(
12021
- data.fh,
12022
- data.offset,
12023
- data.size,
12024
- data.ifMatch
12224
+ const read = await track(
12225
+ transport.read(
12226
+ data.fh,
12227
+ data.offset,
12228
+ data.size,
12229
+ data.ifMatch
12230
+ )
12025
12231
  );
12026
12232
  const bytes = read.slice();
12027
12233
  return send({ id: id2, bytes }, [bytes.buffer]);
12028
12234
  }
12029
12235
  if (op === "write") {
12030
- await transport.write(data.fh, data.offset, data.bytes);
12236
+ await track(
12237
+ transport.write(data.fh, data.offset, data.bytes)
12238
+ );
12031
12239
  return send({ id: id2 });
12032
12240
  }
12033
12241
  if (op !== "call") throw fsError2("EINVAL", `unknown hostfs port op ${op}`);
12034
- send({
12035
- id: id2,
12036
- result: await transport.call(data.name, data.body)
12037
- });
12242
+ send({ id: id2, result: await answer(data.name, data.body) });
12038
12243
  } catch (err) {
12039
12244
  send({ id: id2, error: errorOf(err) });
12040
12245
  }
12041
12246
  };
12042
12247
  const stop = () => {
12248
+ stopped = true;
12043
12249
  port.removeEventListener("message", listener);
12044
12250
  port.removeEventListener("close", stop);
12045
12251
  for (const watch of watches.values()) watch.abort();
12252
+ closeHandles();
12046
12253
  port.close();
12047
12254
  };
12048
12255
  port.addEventListener("message", listener);
@@ -12050,10 +12257,10 @@ function serveHostfsPort(port, transport) {
12050
12257
  port.start();
12051
12258
  return stop;
12052
12259
  }
12053
- function grantConnect(hook, fetch2, requestMs) {
12260
+ function grantConnect(hook, fetch2, liveness = {}) {
12054
12261
  return async (source, options) => {
12055
12262
  const first = await hook(source, options);
12056
- if ("port" in first) return new PortTransport(first, requestMs);
12263
+ if ("port" in first) return new PortTransport(first, liveness);
12057
12264
  const grant = async () => {
12058
12265
  const again = await hook(source, options);
12059
12266
  if ("port" in again)
@@ -12073,7 +12280,10 @@ async function openHostfs(spec, connect, timing = {}) {
12073
12280
  const stop = new AbortController();
12074
12281
  let active;
12075
12282
  const handlers = hostfsHandlers(() => active);
12076
- const ranged2 = () => active?.capabilities.ranges === true ? { ranges: true } : {};
12283
+ const ranged2 = () => ({
12284
+ ...active?.capabilities.ranges === true ? { ranges: true } : {},
12285
+ ...active ? { maxIo: Math.min(active.maxIo, HOSTFS_IO) } : {}
12286
+ });
12077
12287
  const served = serveFilesystem(
12078
12288
  port2,
12079
12289
  { ...slot.handlers, mount: ranged2 },
@@ -12089,7 +12299,7 @@ async function openHostfs(spec, connect, timing = {}) {
12089
12299
  {
12090
12300
  connect: () => connect(spec.source, { readonly: readonly2 }),
12091
12301
  up(client) {
12092
- active = client;
12302
+ active = timed(client, timing.callMs ?? HOSTFS_CALL_MS);
12093
12303
  slot.insert(client, handlers);
12094
12304
  served.invalidate(true);
12095
12305
  },
@@ -12588,8 +12798,11 @@ var Launcher = class {
12588
12798
  createWorker;
12589
12799
  modulesDir;
12590
12800
  env;
12591
- catalog;
12592
- binfmts;
12801
+ catalogs = /* @__PURE__ */ new Map();
12802
+ ownCatalogs = /* @__PURE__ */ new Map();
12803
+ binfmts = /* @__PURE__ */ new Map();
12804
+ scopes;
12805
+ unwatchScopes = () => void 0;
12593
12806
  warned = /* @__PURE__ */ new Set();
12594
12807
  compiled = /* @__PURE__ */ new Map();
12595
12808
  processes = /* @__PURE__ */ new Map();
@@ -12669,7 +12882,11 @@ var Launcher = class {
12669
12882
  ca: this.ca
12670
12883
  });
12671
12884
  enableCdp(this.net, this.cdp);
12672
- const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
12885
+ const fs = withCommandDirs(
12886
+ this.base,
12887
+ async () => new Set((await this.commands()).keys()),
12888
+ (dir) => this.userCommandDir(dir)
12889
+ );
12673
12890
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
12674
12891
  this.nodes.keepsOnDisk = true;
12675
12892
  this.openFiles.add(this.nodes);
@@ -12687,35 +12904,99 @@ var Launcher = class {
12687
12904
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
12688
12905
  this.users = new UserDb(this.fs);
12689
12906
  void this.unlinked.sweep("/");
12690
- this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
12691
- this.catalog = void 0;
12692
- this.binfmts = void 0;
12907
+ this.watchers.watch(
12908
+ [this.modulesDir, this.pnpmHome],
12909
+ { recursive: true },
12910
+ () => this.resetCatalogs()
12911
+ );
12912
+ this.watchers.watch([PASSWD], { recursive: false }, () => {
12913
+ this.scopes = void 0;
12914
+ this.resetCatalogs();
12693
12915
  });
12694
12916
  }
12695
12917
  setRoutes(table) {
12696
12918
  this.routes.set(table);
12697
12919
  }
12698
- commands() {
12699
- this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
12700
- return this.catalog;
12920
+ commands(scope = "") {
12921
+ let found = this.catalogs.get(scope);
12922
+ if (!found) {
12923
+ found = this.roots(scope).then((roots) => scanCommands(this.base, roots));
12924
+ this.catalogs.set(scope, found);
12925
+ }
12926
+ return found;
12701
12927
  }
12702
- interpreters() {
12703
- this.binfmts ??= this.roots().then(
12704
- (roots) => scanBinfmts(this.base, roots, (message3) => {
12705
- if (this.warned.has(message3)) return;
12706
- this.warned.add(message3);
12707
- console.warn(message3);
12708
- })
12709
- );
12710
- return this.binfmts;
12928
+ ownCommands(scope) {
12929
+ let found = this.ownCatalogs.get(scope);
12930
+ if (!found) {
12931
+ found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.base, roots)).then((commands) => new Set(commands.keys()));
12932
+ this.ownCatalogs.set(scope, found);
12933
+ }
12934
+ return found;
12711
12935
  }
12712
- async roots() {
12713
- return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
12936
+ interpreters(scope = "") {
12937
+ let found = this.binfmts.get(scope);
12938
+ if (!found) {
12939
+ found = this.roots(scope).then(
12940
+ (roots) => scanBinfmts(this.base, roots, (message3) => {
12941
+ if (this.warned.has(message3)) return;
12942
+ this.warned.add(message3);
12943
+ console.warn(message3);
12944
+ })
12945
+ );
12946
+ this.binfmts.set(scope, found);
12947
+ }
12948
+ return found;
12714
12949
  }
12715
- async resolve(file, argv0, cwd) {
12716
- const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
12717
- if (name2 !== void 0) {
12718
- const command = (await this.commands()).get(name2);
12950
+ async roots(scope = "") {
12951
+ const system = [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
12952
+ return scope ? [...await pnpmGlobalRoots(this.base, scope), ...system] : system;
12953
+ }
12954
+ resetCatalogs() {
12955
+ this.catalogs.clear();
12956
+ this.ownCatalogs.clear();
12957
+ this.binfmts.clear();
12958
+ }
12959
+ userScopes() {
12960
+ this.scopes ??= this.users.list().then(
12961
+ (accounts) => {
12962
+ const users = accounts.filter((account) => account.uid !== 0);
12963
+ const homes = users.map((account) => pnpmHomeOf(account, this.pnpmHome));
12964
+ this.unwatchScopes();
12965
+ this.unwatchScopes = this.watchers.watch(
12966
+ homes,
12967
+ { recursive: true },
12968
+ () => this.resetCatalogs()
12969
+ );
12970
+ return {
12971
+ byUid: new Map(users.map((account, i) => [account.uid, homes[i]])),
12972
+ byBin: new Map(homes.map((home) => [`${home}/bin`, home]))
12973
+ };
12974
+ },
12975
+ () => ({ byUid: /* @__PURE__ */ new Map(), byBin: /* @__PURE__ */ new Map() })
12976
+ );
12977
+ return this.scopes;
12978
+ }
12979
+ async userCommandDir(dir) {
12980
+ const scope = (await this.userScopes()).byBin.get(dir);
12981
+ return scope === void 0 ? void 0 : this.ownCommands(scope);
12982
+ }
12983
+ async scopeOf(uid) {
12984
+ return (await this.userScopes()).byUid.get(uid) ?? "";
12985
+ }
12986
+ async commandAt(file, scope) {
12987
+ const system = COMMAND2.exec(file)?.[1];
12988
+ if (system !== void 0) return (await this.commands()).get(system);
12989
+ if (!file.includes("/")) return (await this.commands(scope)).get(file);
12990
+ const slash = file.lastIndexOf("/");
12991
+ const owner = (await this.userScopes()).byBin.get(file.slice(0, slash));
12992
+ if (owner === void 0 || !(await this.ownCommands(owner)).has(file.slice(slash + 1))) {
12993
+ return void 0;
12994
+ }
12995
+ return (await this.commands(owner)).get(file.slice(slash + 1));
12996
+ }
12997
+ async resolve(file, argv0, cwd, scope = "") {
12998
+ if (!file.includes("/") || COMMAND2.test(file)) {
12999
+ const command = await this.commandAt(file, scope);
12719
13000
  return command && !command.script ? targetOf(command) : void 0;
12720
13001
  }
12721
13002
  const path = this.fs.resolvePath(cwd, file);
@@ -12726,6 +13007,8 @@ var Launcher = class {
12726
13007
  const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked)?.argv0Path === true;
12727
13008
  return target && byPath ? { ...target, argv0: path } : target;
12728
13009
  }
13010
+ const own = await this.commandAt(glue, scope);
13011
+ if (own) return own.script ? void 0 : targetOf(own);
12729
13012
  const head = await this.head(glue);
12730
13013
  if (!head) return void 0;
12731
13014
  if (isWasm(head)) {
@@ -12748,45 +13031,45 @@ var Launcher = class {
12748
13031
  return void 0;
12749
13032
  }
12750
13033
  }
12751
- async scriptCommand(file) {
12752
- const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
12753
- if (name2 === void 0) return void 0;
12754
- const command = (await this.commands()).get(name2);
13034
+ async scriptCommand(file, cwd, scope) {
13035
+ const direct = !file.includes("/") || COMMAND2.test(file);
13036
+ const at = direct ? file : await followLinks(this.base, this.fs.resolvePath(cwd, file));
13037
+ const command = await this.commandAt(at, scope);
12755
13038
  return command?.script ? command : void 0;
12756
13039
  }
12757
- async interpreted(file, argv, cwd, depth = 0) {
12758
- const command = await this.scriptCommand(file);
13040
+ async interpreted(file, argv, cwd, scope, depth = 0) {
13041
+ const command = await this.scriptCommand(file, cwd, scope);
12759
13042
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
12760
13043
  const head = await this.head(script);
12761
13044
  if (!head) return void 0;
12762
- const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(), script);
13045
+ const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
12763
13046
  const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
12764
13047
  if (!words) return void 0;
12765
13048
  if (baseName(words[0]) === "env") words.shift();
12766
13049
  const [interp, ...rest2] = words;
12767
13050
  const arg = rest2.join(" ");
12768
13051
  const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
12769
- const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
13052
+ const found = interp ? await this.resolve(interp, interp, cwd, scope) : void 0;
12770
13053
  if (!found) {
12771
- if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp)) {
13054
+ if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp, cwd, scope)) {
12772
13055
  return void 0;
12773
13056
  }
12774
- const inner = await this.interpreted(interp, [interp, ...passed], cwd, depth + 1);
13057
+ const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
12775
13058
  return inner && { ...inner, target: withScriptEnv(inner.target, command) };
12776
13059
  }
12777
13060
  const target = withScriptEnv(found, command);
12778
13061
  return { target, args: [...target.prefix ?? [], ...passed] };
12779
13062
  }
12780
- async plan(file, argv, cwd) {
12781
- const direct = await this.resolve(file, argv[0] ?? file, cwd);
13063
+ async plan(file, argv, cwd, scope) {
13064
+ const direct = await this.resolve(file, argv[0] ?? file, cwd, scope);
12782
13065
  if (direct) return { target: direct, args: [...direct.prefix ?? [], ...argv.slice(1)] };
12783
- return this.interpreted(file, argv, cwd);
13066
+ return this.interpreted(file, argv, cwd, scope);
12784
13067
  }
12785
- async unrunnable(file, cwd) {
13068
+ async unrunnable(file, cwd, scope) {
12786
13069
  const path = this.fs.resolvePath(cwd, file);
12787
13070
  const head = await this.base.readFileBuffer(path).catch(() => null);
12788
13071
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
12789
- return binfmtOf(await this.interpreters(), path) ? "ENOENT" : "ENOEXEC";
13072
+ return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
12790
13073
  }
12791
13074
  module(path) {
12792
13075
  return this.base.stat(path).then((st) => {
@@ -12996,10 +13279,11 @@ var Launcher = class {
12996
13279
  }
12997
13280
  spawner(ppid, report) {
12998
13281
  return async (req, fds) => {
12999
- const planned = await this.plan(req.file, req.argv, req.cwd);
13282
+ const scope = await this.scopeOf(this.credOf(ppid).euid);
13283
+ const planned = await this.plan(req.file, req.argv, req.cwd, scope);
13000
13284
  if (!planned) {
13001
13285
  await fds.closeAll();
13002
- throw new SpawnError(await this.unrunnable(req.file, req.cwd));
13286
+ throw new SpawnError(await this.unrunnable(req.file, req.cwd, scope));
13003
13287
  }
13004
13288
  const exec = req.exec ? { exec: true } : {};
13005
13289
  const deciding = req.exec ? void 0 : this.undecided(ppid);
@@ -13118,11 +13402,7 @@ var Launcher = class {
13118
13402
  throw fsError("ENODEV", "this kernel has no hostfs hook (createKernel({ hostfs }))");
13119
13403
  return openHostfs(
13120
13404
  spec,
13121
- grantConnect(
13122
- hostfs,
13123
- hostfsFetch ?? ((url, init) => fetch(url, init)),
13124
- hostfsTiming?.requestMs
13125
- ),
13405
+ grantConnect(hostfs, hostfsFetch ?? ((url, init) => fetch(url, init)), hostfsTiming),
13126
13406
  hostfsTiming
13127
13407
  );
13128
13408
  }
@@ -13305,18 +13585,18 @@ var Launcher = class {
13305
13585
  ...account.umask !== void 0 ? { umask: account.umask } : {}
13306
13586
  };
13307
13587
  }
13308
- async starting(argv, dir) {
13309
- this.catalog = void 0;
13310
- this.binfmts = void 0;
13588
+ async starting(argv, dir, account) {
13589
+ this.resetCatalogs();
13590
+ const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
13311
13591
  const start2 = dir ?? "/";
13312
13592
  const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
13313
13593
  const [file = ""] = argv;
13314
13594
  await this.base.mkdir(cwd, { recursive: true });
13315
- return { cwd, file, planned: await this.plan(file, argv, cwd) };
13595
+ return { cwd, file, planned: await this.plan(file, argv, cwd, scope) };
13316
13596
  }
13317
13597
  async openTerminal(argv, options) {
13318
13598
  const account = await this.account(options.user);
13319
- const { cwd, file, planned } = await this.starting(argv, options.cwd);
13599
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
13320
13600
  if (!planned) throw new Error(`${file}: command not found`);
13321
13601
  let leader = 0;
13322
13602
  const tty = new KernelTty(
@@ -13359,7 +13639,7 @@ var Launcher = class {
13359
13639
  }
13360
13640
  async run(argv, options = {}) {
13361
13641
  const account = await this.account(options.user);
13362
- const { cwd, file, planned } = await this.starting(argv, options.cwd);
13642
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
13363
13643
  const env = this.environment(cwd, options.env, account);
13364
13644
  const out = [];
13365
13645
  const err = [];