@volter/tabnode 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6350,6 +6350,14 @@ const asyncHooksShim$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
6350
6350
  executionAsyncResource,
6351
6351
  triggerAsyncId
6352
6352
  }, Symbol.toStringTag, { value: "Module" }));
6353
+ function describedIdentity(identity) {
6354
+ const { pid, ppid, argv, cwd, startedAt } = identity;
6355
+ const described = { pid, ppid };
6356
+ if (Array.isArray(argv) && argv.length <= 4096 && argv.every((arg) => typeof arg === "string")) described.argv = Object.freeze([...argv]);
6357
+ if (typeof cwd === "string" && cwd.startsWith("/")) described.cwd = cwd;
6358
+ if (typeof startedAt === "number" && Number.isFinite(startedAt)) described.startedAt = startedAt;
6359
+ return Object.freeze(described);
6360
+ }
6353
6361
  function createProcessRegistryOwner() {
6354
6362
  let nextPid2 = 1e3 + Math.floor(Math.random() * 3e4);
6355
6363
  const live = /* @__PURE__ */ new Map();
@@ -6406,7 +6414,7 @@ function createProcessRegistryOwner() {
6406
6414
  if (identity.ppid !== 0 && !Array.from(runs2.values()).some((parent) => parent.pid === identity.ppid)) {
6407
6415
  throw new Error("Parent process is not owned by this realm.");
6408
6416
  }
6409
- const entry = Object.freeze({ pid: identity.pid, ppid: identity.ppid });
6417
+ const entry = describedIdentity(identity);
6410
6418
  runs2.set(token, entry);
6411
6419
  live.set(entry.pid, entry);
6412
6420
  receivers.set(entry.pid, receiver);
@@ -6448,6 +6456,10 @@ function createProcessRegistryOwner() {
6448
6456
  };
6449
6457
  scopes2.set(scope, { allocated: allocated2, runs: runs2, active: active2 });
6450
6458
  return scope;
6459
+ },
6460
+ /** Every live process of the container, in pid order: what `/proc` lists. */
6461
+ table() {
6462
+ return [...live.values()].sort((a, b) => a.pid - b.pid);
6451
6463
  }
6452
6464
  };
6453
6465
  }
@@ -6485,6 +6497,18 @@ function __stopOwnedProcess(token) {
6485
6497
  var _a2;
6486
6498
  (_a2 = runs.get(token)) == null ? void 0 : _a2.stopTimers();
6487
6499
  }
6500
+ function __signalOwnedProcess(token, signal) {
6501
+ const run = runs.get(token);
6502
+ if (!run) return false;
6503
+ const guest = run.process;
6504
+ try {
6505
+ enterRun(token, () => guest.kill(guest.pid, signal));
6506
+ } catch (error) {
6507
+ if (error instanceof Error && error.message.startsWith("Process exited with code")) return true;
6508
+ return false;
6509
+ }
6510
+ return true;
6511
+ }
6488
6512
  const processRegistryOwner = createProcessRegistryOwner();
6489
6513
  let processRegistry = processRegistryOwner.createScope();
6490
6514
  let registryInstalled = false;
@@ -6494,6 +6518,10 @@ function createProcessRegistryScope() {
6494
6518
  if (registryInstalled) throw new Error("Only the container owner can create process registry scopes.");
6495
6519
  return processRegistryOwner.createScope();
6496
6520
  }
6521
+ function ownerProcessTable() {
6522
+ if (registryInstalled) throw new Error("This realm is a process registry client, not the container owner.");
6523
+ return processRegistryOwner.table();
6524
+ }
6497
6525
  function ownerProcessRegistryScope() {
6498
6526
  if (registryInstalled) throw new Error("This realm is a process registry client, not the container owner.");
6499
6527
  return processRegistry;
@@ -6513,9 +6541,9 @@ function mintPid() {
6513
6541
  registryUsed = true;
6514
6542
  return processRegistry.allocate();
6515
6543
  }
6516
- function setRunPid(token, pid, ppid) {
6544
+ function setRunPid(token, pid, ppid, started2) {
6517
6545
  registryUsed = true;
6518
- processRegistry.publish(token, { pid, ppid });
6546
+ processRegistry.publish(token, { pid, ppid, ...started2, startedAt: Date.now() });
6519
6547
  pidsOfRuns.set(token, { pid, ppid });
6520
6548
  }
6521
6549
  function runPid(token) {
@@ -9804,7 +9832,7 @@ function createUtilBinding(require2) {
9804
9832
  const bufferModule = lazyModule("buffer");
9805
9833
  const Buffer$9 = lazyExport("buffer", "Buffer");
9806
9834
  function createNodeError$1(code2, syscall, path2) {
9807
- const known = ["EEXIST", "EINVAL", "EISDIR", "ENOENT", "ENOTDIR", "ENOTEMPTY", "ELOOP"];
9835
+ const known = ["EEXIST", "EINVAL", "EISDIR", "ENOENT", "ENOTDIR", "ENOTEMPTY", "ELOOP", "EROFS"];
9808
9836
  if (known.includes(code2)) return createNodeError(code2, syscall, path2);
9809
9837
  return Object.assign(new Error(`${code2}: ${syscall} '${path2}'`), { code: code2, syscall, path: path2, errno: -9 });
9810
9838
  }
@@ -17762,10 +17790,17 @@ var implementation$1 = function bind2(that) {
17762
17790
  var implementation = implementation$1;
17763
17791
  var functionBind = Function.prototype.bind || implementation;
17764
17792
  var functionCall = Function.prototype.call;
17765
- var functionApply = Function.prototype.apply;
17793
+ var functionApply;
17794
+ var hasRequiredFunctionApply;
17795
+ function requireFunctionApply() {
17796
+ if (hasRequiredFunctionApply) return functionApply;
17797
+ hasRequiredFunctionApply = 1;
17798
+ functionApply = Function.prototype.apply;
17799
+ return functionApply;
17800
+ }
17766
17801
  var reflectApply = typeof Reflect !== "undefined" && Reflect && Reflect.apply;
17767
17802
  var bind$2 = functionBind;
17768
- var $apply$1 = functionApply;
17803
+ var $apply$1 = requireFunctionApply();
17769
17804
  var $call$2 = functionCall;
17770
17805
  var $reflectApply = reflectApply;
17771
17806
  var actualApply = $reflectApply || bind$2.call($call$2, $apply$1);
@@ -17885,7 +17920,7 @@ var hasSymbols = requireHasSymbols()();
17885
17920
  var getProto = requireGetProto();
17886
17921
  var $ObjectGPO = requireObject_getPrototypeOf();
17887
17922
  var $ReflectGPO = requireReflect_getPrototypeOf();
17888
- var $apply = functionApply;
17923
+ var $apply = requireFunctionApply();
17889
17924
  var $call = functionCall;
17890
17925
  var needsEval = {};
17891
17926
  var TypedArray = typeof Uint8Array === "undefined" || !getProto ? undefined$1 : getProto(Uint8Array);
@@ -18526,7 +18561,7 @@ function requireApplyBind() {
18526
18561
  if (hasRequiredApplyBind) return applyBind;
18527
18562
  hasRequiredApplyBind = 1;
18528
18563
  var bind3 = functionBind;
18529
- var $apply2 = functionApply;
18564
+ var $apply2 = requireFunctionApply();
18530
18565
  var actualApply$1 = actualApply;
18531
18566
  applyBind = function applyBind2() {
18532
18567
  return actualApply$1(bind3, $apply2, arguments);
@@ -27737,14 +27772,16 @@ function createNodeError(code2, syscall, path2, message) {
27737
27772
  ENOTDIR: -20,
27738
27773
  EISDIR: -21,
27739
27774
  EEXIST: -17,
27740
- ENOTEMPTY: -39
27775
+ ENOTEMPTY: -39,
27776
+ EROFS: -30
27741
27777
  };
27742
27778
  const messages2 = {
27743
27779
  ENOENT: "no such file or directory",
27744
27780
  ENOTDIR: "not a directory",
27745
27781
  EISDIR: "is a directory",
27746
27782
  EEXIST: "file already exists",
27747
- ENOTEMPTY: "directory not empty"
27783
+ ENOTEMPTY: "directory not empty",
27784
+ EROFS: "read-only file system"
27748
27785
  };
27749
27786
  const err2 = new Error(
27750
27787
  `${code2}: ${messages2[code2]}, ${syscall} '${path2}'`
@@ -27803,6 +27840,7 @@ class VirtualFS {
27803
27840
  return { files };
27804
27841
  }
27805
27842
  serializeNode(path2, node, files) {
27843
+ if (node.mount) return;
27806
27844
  if (node.type === "file") {
27807
27845
  let content = "";
27808
27846
  if (node.content && node.content.length > 0) {
@@ -27858,6 +27896,7 @@ class VirtualFS {
27858
27896
  */
27859
27897
  writeFileSyncInternal(path2, data2, emitEvent) {
27860
27898
  const normalized = this.normalizePath(path2);
27899
+ this.assertWritable(normalized, "open");
27861
27900
  const parentPath = this.getParentPath(normalized);
27862
27901
  const basename2 = this.getBasename(normalized);
27863
27902
  if (!basename2) {
@@ -27933,11 +27972,12 @@ class VirtualFS {
27933
27972
  if (depth > 40) throw createNodeError("ELOOP", "stat", path2);
27934
27973
  const segments = this.getPathSegments(path2);
27935
27974
  let current3 = this.root;
27975
+ let mounted;
27936
27976
  for (let index2 = 0; index2 < segments.length; index2 += 1) {
27937
- if (current3.type !== "directory" || !current3.children) {
27977
+ if (current3.type !== "directory" || !mounted && !current3.children) {
27938
27978
  return void 0;
27939
27979
  }
27940
- const child = current3.children.get(segments[index2]);
27980
+ const child = mounted ? mounted.tree.node("/" + segments.slice(mounted.at + 1, index2 + 1).join("/")) : current3.children.get(segments[index2]);
27941
27981
  if (!child) {
27942
27982
  return void 0;
27943
27983
  }
@@ -27947,16 +27987,53 @@ class VirtualFS {
27947
27987
  const target = this.__substrateLinkTarget(linkPath, child.target);
27948
27988
  return this.__substrateNode(rest ? target + "/" + rest : target, follow, depth + 1);
27949
27989
  }
27950
- current3 = child;
27990
+ if (!mounted && child.mount) {
27991
+ mounted = { tree: child.mount, at: index2 };
27992
+ current3 = child.mount.node("/") ?? child;
27993
+ } else {
27994
+ current3 = child;
27995
+ }
27951
27996
  }
27952
27997
  return current3;
27953
27998
  }
27999
+ /**
28000
+ * Mount a read-only tree at a directory, as Linux mounts `/proc`: reads
28001
+ * below it are the tree's answers, writes fail with `EROFS`, and a snapshot
28002
+ * leaves it out. Answers the unmount.
28003
+ */
28004
+ mount(path2, tree2) {
28005
+ var _a2;
28006
+ const normalized = this.normalizePath(path2);
28007
+ const basename2 = this.getBasename(normalized);
28008
+ if (!basename2) throw createNodeError("EINVAL", "mount", path2);
28009
+ const parent = this.ensureDirectory(this.getParentPath(normalized));
28010
+ const existing = parent.children.get(basename2);
28011
+ if (existing && (existing.type !== "directory" || existing.mount || ((_a2 = existing.children) == null ? void 0 : _a2.size))) {
28012
+ throw createNodeError("EEXIST", "mount", path2);
28013
+ }
28014
+ const node = { type: "directory", children: /* @__PURE__ */ new Map(), mtime: Date.now(), mode: 365, mount: tree2 };
28015
+ parent.children.set(basename2, node);
28016
+ return () => {
28017
+ if (parent.children.get(basename2) === node) parent.children.delete(basename2);
28018
+ };
28019
+ }
28020
+ /** A write at or below a mount point is refused, as a read-only filesystem refuses it. */
28021
+ assertWritable(path2, syscall) {
28022
+ var _a2;
28023
+ let current3 = this.root;
28024
+ for (const segment of this.getPathSegments(path2)) {
28025
+ current3 = (_a2 = current3 == null ? void 0 : current3.children) == null ? void 0 : _a2.get(segment);
28026
+ if (!current3) return;
28027
+ if (current3.mount) throw createNodeError("EROFS", syscall, path2);
28028
+ }
28029
+ }
27954
28030
  /** A link's target as an absolute path: absolute as written, or relative to the link's directory. */
27955
28031
  __substrateLinkTarget(linkPath, target) {
27956
28032
  return this.normalizePath(target.startsWith("/") ? target : this.getParentPath(linkPath) + "/" + target);
27957
28033
  }
27958
28034
  symlinkSync(target, path2) {
27959
28035
  const normalized = this.normalizePath(path2);
28036
+ this.assertWritable(normalized, "symlink");
27960
28037
  const parentPath = this.getParentPath(normalized);
27961
28038
  const basename2 = this.getBasename(normalized);
27962
28039
  const parent = this.getNode(parentPath);
@@ -28017,11 +28094,13 @@ class VirtualFS {
28017
28094
  * Get stats for path
28018
28095
  */
28019
28096
  chmodSync(path2, mode) {
28097
+ this.assertWritable(path2, "chmod");
28020
28098
  const node = this.getNode(path2);
28021
28099
  if (!node) throw createNodeError("ENOENT", "chmod", path2);
28022
28100
  node.mode = mode & 4095;
28023
28101
  }
28024
28102
  utimesSync(path2, atime, mtime) {
28103
+ this.assertWritable(path2, "utime");
28025
28104
  const node = this.getNode(path2);
28026
28105
  if (!node) throw createNodeError("ENOENT", "utimes", path2);
28027
28106
  node.atime = atime instanceof Date ? atime.getTime() : atime * 1e3;
@@ -28127,7 +28206,10 @@ class VirtualFS {
28127
28206
  * Create directory, optionally with recursive parent creation
28128
28207
  */
28129
28208
  mkdirSync(path2, options) {
28209
+ var _a2;
28130
28210
  const normalized = this.normalizePath(path2);
28211
+ if ((options == null ? void 0 : options.recursive) && ((_a2 = this.getNode(normalized)) == null ? void 0 : _a2.type) === "directory") return;
28212
+ this.assertWritable(normalized, "mkdir");
28131
28213
  if (options == null ? void 0 : options.recursive) {
28132
28214
  this.ensureDirectory(normalized);
28133
28215
  return;
@@ -28172,6 +28254,7 @@ class VirtualFS {
28172
28254
  */
28173
28255
  unlinkSync(path2) {
28174
28256
  const normalized = this.normalizePath(path2);
28257
+ this.assertWritable(normalized, "unlink");
28175
28258
  const parentPath = this.getParentPath(normalized);
28176
28259
  const basename2 = this.getBasename(normalized);
28177
28260
  const parent = this.getNode(parentPath);
@@ -28194,6 +28277,7 @@ class VirtualFS {
28194
28277
  */
28195
28278
  rmdirSync(path2) {
28196
28279
  const normalized = this.normalizePath(path2);
28280
+ this.assertWritable(normalized, "rmdir");
28197
28281
  const parentPath = this.getParentPath(normalized);
28198
28282
  const basename2 = this.getBasename(normalized);
28199
28283
  if (!basename2) {
@@ -28222,6 +28306,8 @@ class VirtualFS {
28222
28306
  renameSync(oldPath, newPath) {
28223
28307
  const normalizedOld = this.normalizePath(oldPath);
28224
28308
  const normalizedNew = this.normalizePath(newPath);
28309
+ this.assertWritable(normalizedOld, "rename");
28310
+ this.assertWritable(normalizedNew, "rename");
28225
28311
  const oldParentPath = this.getParentPath(normalizedOld);
28226
28312
  const oldBasename = this.getBasename(normalizedOld);
28227
28313
  const newParentPath = this.getParentPath(normalizedNew);
@@ -28320,10 +28406,12 @@ class VirtualFS {
28320
28406
  for (let hops = 0; hops <= 40; hops += 1) {
28321
28407
  const segments = resolved.split("/").filter(Boolean);
28322
28408
  let current3 = this.root;
28409
+ let mounted;
28323
28410
  let linked = false;
28324
28411
  for (let index2 = 0; index2 < segments.length; index2 += 1) {
28325
- const child = current3 && current3.children ? current3.children.get(segments[index2]) : void 0;
28412
+ const child = mounted ? mounted.tree.node("/" + segments.slice(mounted.at + 1, index2 + 1).join("/")) : current3 && current3.children ? current3.children.get(segments[index2]) : void 0;
28326
28413
  if (!child) throw createNodeError("ENOENT", "realpath", path2);
28414
+ if (!mounted && child.mount) mounted = { tree: child.mount, at: index2 };
28327
28415
  if (child.type === "symlink") {
28328
28416
  const linkPath = "/" + segments.slice(0, index2 + 1).join("/");
28329
28417
  const rest = segments.slice(index2 + 1).join("/");
@@ -29379,7 +29467,7 @@ const _runStreams = /* @__PURE__ */ new Map();
29379
29467
  async function runHostedNode(host2, launch) {
29380
29468
  var _a2;
29381
29469
  const { token, streams } = launch;
29382
- if (!runPid(token)) setRunPid(token, mintPid(), 0);
29470
+ if (!runPid(token)) setRunPid(token, mintPid(), 0, { argv: ["node", ...launch.argv], cwd: launch.cwd });
29383
29471
  let input;
29384
29472
  try {
29385
29473
  (_a2 = streams == null ? void 0 : streams.signal) == null ? void 0 : _a2.throwIfAborted();
@@ -29638,7 +29726,7 @@ function initChildProcess(vfs2) {
29638
29726
  ...runPid(runToken) ? { pid: runPid(runToken).pid, ppid: runPid(runToken).ppid } : {}
29639
29727
  });
29640
29728
  const proc = runtime.getProcess();
29641
- if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid);
29729
+ if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid, { argv: ["node", ...args], cwd: ctx.cwd });
29642
29730
  const releaseRun = runToken === null ? null : __recordRun(runToken, {
29643
29731
  process: proc,
29644
29732
  stdout: appendStdout,
@@ -30384,7 +30472,12 @@ function startChildRun(request) {
30384
30472
  var _a2;
30385
30473
  const token = `child-${__nextChildRun++}`;
30386
30474
  const pid = mintPid();
30387
- setRunPid(token, pid, ((_a2 = runPid(nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken)) == null ? void 0 : _a2.pid) ?? 0);
30475
+ setRunPid(
30476
+ token,
30477
+ pid,
30478
+ ((_a2 = runPid(nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken)) == null ? void 0 : _a2.pid) ?? 0,
30479
+ { argv: request.args.length ? request.args : [request.file], ...request.cwd ? { cwd: request.cwd } : {} }
30480
+ );
30388
30481
  const controller = new AbortController();
30389
30482
  const pendingStdin = [];
30390
30483
  const admittedNode = nodeProcessHostInstalled() && engineProgramFor(request.file, request.cwd) === "node";
@@ -33667,7 +33760,7 @@ function transformDynamicImportsRegex(code2) {
33667
33760
  return code2.replace(new RegExp("(?<![.$\\w#])import\\s*\\(", "g"), "__dynamicImport(");
33668
33761
  }
33669
33762
  function __substrateRewriteDynamicImportsInScript(code2) {
33670
- if (!new RegExp("(?<![.$\\w#])import\\s*\\(").test(code2)) return code2;
33763
+ if (!new RegExp("(?<![.$\\w#])(?:import|eval)\\s*\\(").test(code2)) return code2;
33671
33764
  let ast;
33672
33765
  try {
33673
33766
  ast = parse3(code2, { ecmaVersion: "latest", sourceType: "script", allowReturnOutsideFunction: true, allowHashBang: true, allowAwaitOutsideFunction: true });
@@ -33676,14 +33769,30 @@ function __substrateRewriteDynamicImportsInScript(code2) {
33676
33769
  }
33677
33770
  const replacements = [];
33678
33771
  walkAst(ast, (node) => {
33679
- if (node.type === "ImportExpression") replacements.push([node.start, node.start + 6]);
33772
+ if (node.type === "ImportExpression") replacements.push([node.start, node.start + 6, "__dynamicImport"]);
33773
+ collectDirectEval(node, replacements);
33680
33774
  });
33681
33775
  if (replacements.length === 0) return code2;
33682
- replacements.sort((a, b) => b[0] - a[0]);
33776
+ return applyReplacements(code2, replacements);
33777
+ }
33778
+ function collectDirectEval(node, replacements) {
33779
+ var _a2, _b2;
33780
+ if (node.type !== "CallExpression" || node.optional || ((_a2 = node.callee) == null ? void 0 : _a2.type) !== "Identifier" || node.callee.name !== "eval") return;
33781
+ const source = (_b2 = node.arguments) == null ? void 0 : _b2[0];
33782
+ if (!source || source.type === "SpreadElement") return;
33783
+ replacements.push([source.start, source.start, "__substrateEvalSource("]);
33784
+ replacements.push([source.end, source.end, ")"]);
33785
+ }
33786
+ function applyReplacements(code2, replacements) {
33787
+ replacements.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
33683
33788
  let out = code2;
33684
- for (const [start, end] of replacements) out = out.slice(0, start) + "__dynamicImport" + out.slice(end);
33789
+ for (const [start, end, text] of replacements) out = out.slice(0, start) + text + out.slice(end);
33685
33790
  return out;
33686
33791
  }
33792
+ Object.defineProperty(globalThis, "__substrateEvalSource", {
33793
+ configurable: true,
33794
+ value: (source) => typeof source === "string" ? __substrateRewriteDynamicImportsInScript(source) : source
33795
+ });
33687
33796
  function transformEsmToCjs(code2, filename) {
33688
33797
  const maybeEsm = __substrateHasEsmSyntax(code2);
33689
33798
  if (!maybeEsm) return __substrateRewriteDynamicImportsInScript(code2);
@@ -33707,17 +33816,14 @@ function transformEsmToCjsAst(code2, filename) {
33707
33816
  if (node.type === "ImportExpression") {
33708
33817
  deepReplacements.push([node.start, node.start + 6, "__dynamicImport"]);
33709
33818
  }
33819
+ collectDirectEval(node, deepReplacements);
33710
33820
  });
33711
33821
  const hasImportDecl = ast.body.some((n) => n.type === "ImportDeclaration");
33712
33822
  const hasExportDecl = ast.body.some((n) => {
33713
33823
  var _a2;
33714
33824
  return (_a2 = n.type) == null ? void 0 : _a2.startsWith("Export");
33715
33825
  });
33716
- let transformed = code2;
33717
- deepReplacements.sort((a, b) => b[0] - a[0]);
33718
- for (const [start, end, replacement] of deepReplacements) {
33719
- transformed = transformed.slice(0, start) + replacement + transformed.slice(end);
33720
- }
33826
+ let transformed = applyReplacements(code2, deepReplacements);
33721
33827
  if (hasImportDecl || hasExportDecl) {
33722
33828
  transformed = transformEsmToCjsSimple(transformed);
33723
33829
  if (hasExportDecl) {
@@ -35562,7 +35668,7 @@ class WorkerRuntime {
35562
35668
  this.worker = new Worker(
35563
35669
  new URL(
35564
35670
  /* @vite-ignore */
35565
- "/assets/runtime-worker-DBtzjAkn.js",
35671
+ "/assets/runtime-worker-Bt4uLPDa.js",
35566
35672
  import.meta.url
35567
35673
  ),
35568
35674
  { type: "module" }
@@ -37930,7 +38036,11 @@ function resetServerBridge() {
37930
38036
  }
37931
38037
  const scopes = /* @__PURE__ */ new WeakMap();
37932
38038
  class NativeStreamScope {
37933
- constructor(limits, emit2) {
38039
+ /**
38040
+ * `ownerPid` names the process whose worker this scope serves: a listener
38041
+ * it opens is that process's socket, as the container's /proc shows it.
38042
+ */
38043
+ constructor(limits, emit2, ownerPid) {
37934
38044
  __publicField(this, "entries", /* @__PURE__ */ new Map());
37935
38045
  __publicField(this, "descriptors", /* @__PURE__ */ new Map());
37936
38046
  __publicField(this, "writes", /* @__PURE__ */ new Map());
@@ -37940,6 +38050,7 @@ class NativeStreamScope {
37940
38050
  __publicField(this, "disposed", false);
37941
38051
  __publicField(this, "limits");
37942
38052
  this.emit = emit2;
38053
+ this.ownerPid = ownerPid;
37943
38054
  if ([limits.maxHandles, limits.maxReadChunkBytes, limits.maxQueuedBytes, limits.maxQueuedHandles, limits.maxPendingWriteBytes].some((value) => !Number.isSafeInteger(value) || value < 1) || limits.maxReadChunkBytes > 2147483647 || limits.maxReadChunkBytes > limits.maxQueuedBytes || limits.maxReadChunkBytes > limits.maxPendingWriteBytes) throw new Error("Invalid native stream resource limits.");
37944
38055
  this.limits = Object.freeze({ ...limits });
37945
38056
  }
@@ -38224,6 +38335,10 @@ class NativeStreamScope {
38224
38335
  if (failures.length) throw new AggregateError(failures, "Native stream scope cleanup failed.");
38225
38336
  }
38226
38337
  }
38338
+ function nativeStreamOwnerPid(handle) {
38339
+ var _a2, _b2;
38340
+ return (_b2 = (_a2 = scopes.get(handle)) == null ? void 0 : _a2.ownerPid) == null ? void 0 : _b2.call(_a2);
38341
+ }
38227
38342
  let __nextRunName = 1;
38228
38343
  function createContainer(options) {
38229
38344
  const vfs2 = options && options.vfs || new VirtualFS();
@@ -38278,6 +38393,17 @@ function createContainer(options) {
38278
38393
  },
38279
38394
  /** The ports the servers the named run opened are listening on. */
38280
38395
  processPorts: (token) => __ownedServerPorts(token),
38396
+ portPid: (port) => {
38397
+ var _a2;
38398
+ const listener = listenerOnPort(port);
38399
+ if (!listener) return void 0;
38400
+ return ((_a2 = runPid(ownerOf(listener))) == null ? void 0 : _a2.pid) ?? nativeStreamOwnerPid(listener);
38401
+ },
38402
+ /**
38403
+ * Deliver a signal to the named run as another process's `kill(pid)` does:
38404
+ * the guest's listeners for it run, else its default action ends the run.
38405
+ */
38406
+ signalProcess: (token, signal) => __signalOwnedProcess(token, signal),
38281
38407
  /** End the named run: its timers stop and its servers are released. */
38282
38408
  stopProcess: (token) => {
38283
38409
  const known = __runFor(token) !== void 0 || __ownedServerPorts(token).length > 0;
@@ -38350,6 +38476,7 @@ export {
38350
38476
  nodeProcessHostInstalled,
38351
38477
  index as npm,
38352
38478
  ownerProcessRegistryScope,
38479
+ ownerProcessTable,
38353
38480
  path,
38354
38481
  perfHooksShim as perf_hooks,
38355
38482
  processByPid,
@@ -94,6 +94,7 @@ export interface NativeStreamTransport {
94
94
  */
95
95
  export declare class NativeStreamScope {
96
96
  private readonly emit;
97
+ readonly ownerPid?: (() => number | undefined) | undefined;
97
98
  private readonly entries;
98
99
  private readonly descriptors;
99
100
  private readonly writes;
@@ -102,7 +103,11 @@ export declare class NativeStreamScope {
102
103
  private pendingWriteBytes;
103
104
  private disposed;
104
105
  private readonly limits;
105
- constructor(limits: NativeStreamLimits, emit: (event: NativeStreamEvent) => void);
106
+ /**
107
+ * `ownerPid` names the process whose worker this scope serves: a listener
108
+ * it opens is that process's socket, as the container's /proc shows it.
109
+ */
110
+ constructor(limits: NativeStreamLimits, emit: (event: NativeStreamEvent) => void, ownerPid?: (() => number | undefined) | undefined);
106
111
  private entry;
107
112
  private publish;
108
113
  private claim;
@@ -119,3 +124,5 @@ export declare class NativeStreamScope {
119
124
  private close;
120
125
  dispose(): void;
121
126
  }
127
+ /** The process a handle opened for another worker's process belongs to, where a scope holds it. */
128
+ export declare function nativeStreamOwnerPid(handle: object): number | undefined;
@@ -2,6 +2,12 @@
2
2
  export interface ProcessIdentity {
3
3
  readonly pid: number;
4
4
  readonly ppid: number;
5
+ /** What the process was started as, where its starter said: `/proc/<pid>/cmdline`. */
6
+ readonly argv?: readonly string[];
7
+ /** The directory it was started in: `/proc/<pid>/cwd`. */
8
+ readonly cwd?: string;
9
+ /** When it started, in milliseconds since the epoch. */
10
+ readonly startedAt?: number;
5
11
  }
6
12
  /** A run already admitted by the container owner into this realm's scope. */
7
13
  export interface InitialProcessRegistration {
@@ -36,4 +42,5 @@ export interface ProcessRegistryScope extends ProcessRegistry {
36
42
  */
37
43
  export declare function createProcessRegistryOwner(): {
38
44
  createScope(): ProcessRegistryScope;
45
+ table(): ProcessIdentity[];
39
46
  };
@@ -15,7 +15,7 @@
15
15
  * command gives its process has it removed.
16
16
  */
17
17
  import type { Process } from './shims/process';
18
- import { type ProcessRegistry, type ProcessRegistryScope, type InitialProcessRegistration } from './process-registry';
18
+ import { type ProcessIdentity, type ProcessRegistry, type ProcessRegistryScope, type InitialProcessRegistration } from './process-registry';
19
19
  /** Whatever the embedding runtime uses to name one guest process. */
20
20
  export type ProcessToken = string;
21
21
  /** The environment variable a run's name travels in, from `exec` to `node`. */
@@ -64,8 +64,18 @@ export declare function __currentProcessToken(): ProcessToken | null;
64
64
  * half, `__releaseOwnedServers`, which calls this for the timers.
65
65
  */
66
66
  export declare function __stopOwnedProcess(token: ProcessToken): void;
67
+ /**
68
+ * Another process's `kill(pid, signal)`, delivered to a named run's guest.
69
+ * On a machine the target's handlers run, or its default action ends it; the
70
+ * guest's own `process.kill(process.pid, signal)` is exactly that, so the
71
+ * signal is raised through it, as the run. False when the run is gone or the
72
+ * signal is not one Node names.
73
+ */
74
+ export declare function __signalOwnedProcess(token: ProcessToken, signal: string): boolean;
67
75
  /** Each child realm gets its own registration authority in this container. */
68
76
  export declare function createProcessRegistryScope(): ProcessRegistryScope;
77
+ /** Trusted container owner only: every live process of the container, from every realm. */
78
+ export declare function ownerProcessTable(): ProcessIdentity[];
69
79
  /** Trusted container owner only; worker clients cannot expose their authority. */
70
80
  export declare function ownerProcessRegistryScope(): ProcessRegistryScope;
71
81
  /** Startup-only host seam; never swap identity authorities under live runs. */
@@ -73,7 +83,10 @@ export declare function installProcessRegistry(registry: ProcessRegistry, initia
73
83
  /** The next process number this engine hands out. */
74
84
  export declare function mintPid(): number;
75
85
  /** Record the numbers a named run was started with, for the run to read back. */
76
- export declare function setRunPid(token: ProcessToken, pid: number, ppid: number): void;
86
+ export declare function setRunPid(token: ProcessToken, pid: number, ppid: number, started?: {
87
+ argv?: readonly string[];
88
+ cwd?: string;
89
+ }): void;
77
90
  /** The numbers a named run was started with, where one was recorded. */
78
91
  export declare function runPid(token: ProcessToken | null | undefined): {
79
92
  pid: number;
@@ -37,6 +37,22 @@ export interface FSNode {
37
37
  * apart). Absent until something sets it, which is the write time.
38
38
  */
39
39
  atime?: number;
40
+ /**
41
+ * A tree mounted here: the paths below this node are the tree's, answered
42
+ * when they are read, and never this filesystem's to write. Linux's
43
+ * `/proc` is one.
44
+ */
45
+ mount?: MountedTree;
46
+ }
47
+ /**
48
+ * A read-only tree another party answers, mounted at a directory: `node`
49
+ * returns what is at a path relative to the mount point (`/` is the mount
50
+ * point itself), computed when it is asked, or undefined where nothing is.
51
+ * A directory's `children` names what a listing shows; a link's `target` is
52
+ * followed as any link is.
53
+ */
54
+ export interface MountedTree {
55
+ node(path: string): FSNode | undefined;
40
56
  }
41
57
  type VFSChangeListener = (path: string, content: string) => void;
42
58
  type VFSDeleteListener = (path: string) => void;
@@ -83,7 +99,7 @@ export interface NodeError extends Error {
83
99
  syscall: string;
84
100
  path?: string;
85
101
  }
86
- export declare function createNodeError(code: 'ENOENT' | 'ENOTDIR' | 'EISDIR' | 'EEXIST' | 'ENOTEMPTY' | 'ELOOP' | 'EINVAL', syscall: string, path: string, message?: string): NodeError;
102
+ export declare function createNodeError(code: 'ENOENT' | 'ENOTDIR' | 'EISDIR' | 'EEXIST' | 'ENOTEMPTY' | 'ELOOP' | 'EINVAL' | 'EROFS', syscall: string, path: string, message?: string): NodeError;
87
103
  export declare class VirtualFS {
88
104
  private root;
89
105
  private encoder;
@@ -140,6 +156,14 @@ export declare class VirtualFS {
140
156
  getNode(path: string): FSNode | undefined;
141
157
  /** The node at a path, following every link on the way and, when `follow`, the last one too. */
142
158
  __substrateNode(path: string, follow: boolean, depth: number): FSNode | undefined;
159
+ /**
160
+ * Mount a read-only tree at a directory, as Linux mounts `/proc`: reads
161
+ * below it are the tree's answers, writes fail with `EROFS`, and a snapshot
162
+ * leaves it out. Answers the unmount.
163
+ */
164
+ mount(path: string, tree: MountedTree): () => void;
165
+ /** A write at or below a mount point is refused, as a read-only filesystem refuses it. */
166
+ private assertWritable;
143
167
  /** A link's target as an absolute path: absolute as written, or relative to the link's directory. */
144
168
  __substrateLinkTarget(linkPath: string, target: string): string;
145
169
  symlinkSync(target: string, path: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/tabnode",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Node's own library, in a browser tab.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  // hands the host's own globals back. See `host-globals.ts`.
14
14
  export { restoreHostGlobals, guestRealmInstalled } from './host-globals';
15
15
  export { VirtualFS } from './virtual-fs';
16
- export type { FSNode, Stats, FSWatcher, WatchListener, WatchEventType } from './virtual-fs';
16
+ export type { FSNode, MountedTree, Stats, FSWatcher, WatchListener, WatchEventType } from './virtual-fs';
17
17
  export { Runtime, execute } from './runtime';
18
18
  export type { Module, RuntimeOptions, RequireFunction } from './runtime';
19
19
  export { createRuntime, WorkerRuntime } from './create-runtime';
@@ -54,11 +54,13 @@ import { PackageManager } from './npm';
54
54
  import { ServerBridge, getServerBridge } from './server-bridge';
55
55
  import { runCommand, registerRunStreams, releaseRunStreams, sendStdin } from './shims/child_process';
56
56
  import { Server as NetServer, __releaseOwnedHandles, type Socket as NetSocket } from './node-lib/net-module';
57
- import { __adoptHandle, type OwnedHandle } from './node-lib/binding/handles';
57
+ import { __adoptHandle, ownerOf, type OwnedHandle } from './node-lib/binding/handles';
58
+ import { listenerOnPort } from './node-lib/binding/tcp_wrap';
59
+ import { nativeStreamOwnerPid } from './native-stream-owner';
58
60
 
59
- import { __currentProcessToken, __runFor, __stopOwnedProcess, runPid, processByPid } from './process-tokens';
61
+ import { __currentProcessToken, __runFor, __signalOwnedProcess, __stopOwnedProcess, runPid, processByPid } from './process-tokens';
60
62
  export { runPid, processByPid };
61
- export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope } from './process-tokens';
63
+ export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope, ownerProcessTable } from './process-tokens';
62
64
  export { installNodeProcessHost, nodeProcessHostInstalled } from './node-process-host';
63
65
  export type { NodeProcessLaunch, NodeProcessHost } from './node-process-host';
64
66
  export type { ProcessIdentity, ProcessRegistry, ProcessRegistryScope, InitialProcessRegistration } from './process-registry';
@@ -123,6 +125,10 @@ export function createContainer(options?: ContainerOptions): {
123
125
  run: (command: string, options?: RunOptions) => Promise<RunResult>;
124
126
  pendingTimers: (token: string) => number;
125
127
  processPorts: (token: string) => number[];
128
+ /** The pid of the process listening on a port of this engine, where a guest process is: `/proc`'s socket owner. */
129
+ portPid: (port: number) => number | undefined;
130
+ /** Deliver a signal to the named run as another process's `kill(pid)` does; false when the run is gone. */
131
+ signalProcess: (token: string, signal: string) => boolean;
126
132
  stopProcess: (token: string) => boolean;
127
133
  /**
128
134
  * Input for one run's guest, by its process token; without a token, the most
@@ -208,6 +214,17 @@ export function createContainer(options?: ContainerOptions): {
208
214
  pendingTimers: (token: string): number => __runFor(token)?.pendingTimers() ?? 0,
209
215
  /** The ports the servers the named run opened are listening on. */
210
216
  processPorts: (token: string): number[] => __ownedServerPorts(token),
217
+ portPid: (port: number): number | undefined => {
218
+ const listener = listenerOnPort(port);
219
+ if (!listener) return undefined;
220
+ // a guest's own listener, or one this engine holds for another worker's process
221
+ return runPid(ownerOf(listener as unknown as OwnedHandle))?.pid ?? nativeStreamOwnerPid(listener);
222
+ },
223
+ /**
224
+ * Deliver a signal to the named run as another process's `kill(pid)` does:
225
+ * the guest's listeners for it run, else its default action ends the run.
226
+ */
227
+ signalProcess: (token: string, signal: string): boolean => __signalOwnedProcess(token, signal),
211
228
  /** End the named run: its timers stop and its servers are released. */
212
229
  stopProcess: (token: string): boolean => {
213
230
  const known = __runFor(token) !== undefined || __ownedServerPorts(token).length > 0;
@@ -56,7 +56,11 @@ export class NativeStreamScope {
56
56
  private disposed = false;
57
57
  private readonly limits: Readonly<NativeStreamLimits>;
58
58
 
59
- constructor(limits: NativeStreamLimits, private readonly emit: (event: NativeStreamEvent) => void) {
59
+ /**
60
+ * `ownerPid` names the process whose worker this scope serves: a listener
61
+ * it opens is that process's socket, as the container's /proc shows it.
62
+ */
63
+ constructor(limits: NativeStreamLimits, private readonly emit: (event: NativeStreamEvent) => void, readonly ownerPid?: () => number | undefined) {
60
64
  if ([limits.maxHandles, limits.maxReadChunkBytes, limits.maxQueuedBytes, limits.maxQueuedHandles, limits.maxPendingWriteBytes].some(value => !Number.isSafeInteger(value) || value < 1)
61
65
  || limits.maxReadChunkBytes > 0x7fffffff
62
66
  || limits.maxReadChunkBytes > limits.maxQueuedBytes
@@ -315,3 +319,8 @@ export class NativeStreamScope {
315
319
  if (failures.length) throw new AggregateError(failures, 'Native stream scope cleanup failed.');
316
320
  }
317
321
  }
322
+
323
+ /** The process a handle opened for another worker's process belongs to, where a scope holds it. */
324
+ export function nativeStreamOwnerPid(handle: object): number | undefined {
325
+ return scopes.get(handle as NativeHandle)?.ownerPid?.();
326
+ }