@volter/tabnode 0.5.3 → 0.5.5
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/assets/{runtime-worker-CfmZOVKz.js → runtime-worker-iInsmIAS.js} +106 -22
- package/dist/index.d.ts +6 -2
- package/dist/index.mjs +135 -24
- package/dist/process-registry.d.ts +7 -0
- package/dist/process-tokens.d.ts +15 -2
- package/dist/stack-overrides.d.ts +1 -1
- package/dist/virtual-fs.d.ts +25 -1
- package/package.json +1 -1
- package/src/index.ts +18 -4
- package/src/node-lib/binding/fs.ts +1 -1
- package/src/process-registry.ts +22 -2
- package/src/process-tokens.ts +30 -3
- package/src/runtime.ts +18 -6
- package/src/shims/child_process.ts +4 -3
- package/src/stack-overrides.ts +11 -2
- package/src/virtual-fs.ts +77 -5
|
@@ -6641,6 +6641,14 @@ var asyncHooksShim$1 = /* @__PURE__ */ Object.freeze({
|
|
|
6641
6641
|
executionAsyncResource,
|
|
6642
6642
|
triggerAsyncId
|
|
6643
6643
|
});
|
|
6644
|
+
function describedIdentity(identity) {
|
|
6645
|
+
const { pid, ppid, argv, cwd, startedAt } = identity;
|
|
6646
|
+
const described = { pid, ppid };
|
|
6647
|
+
if (Array.isArray(argv) && argv.length <= 4096 && argv.every((arg) => typeof arg === "string")) described.argv = Object.freeze([...argv]);
|
|
6648
|
+
if (typeof cwd === "string" && cwd.startsWith("/")) described.cwd = cwd;
|
|
6649
|
+
if (typeof startedAt === "number" && Number.isFinite(startedAt)) described.startedAt = startedAt;
|
|
6650
|
+
return Object.freeze(described);
|
|
6651
|
+
}
|
|
6644
6652
|
function createProcessRegistryOwner() {
|
|
6645
6653
|
let nextPid2 = 1e3 + Math.floor(Math.random() * 3e4);
|
|
6646
6654
|
const live = /* @__PURE__ */ new Map();
|
|
@@ -6697,7 +6705,7 @@ function createProcessRegistryOwner() {
|
|
|
6697
6705
|
if (identity.ppid !== 0 && !Array.from(runs2.values()).some((parent) => parent.pid === identity.ppid)) {
|
|
6698
6706
|
throw new Error("Parent process is not owned by this realm.");
|
|
6699
6707
|
}
|
|
6700
|
-
const entry =
|
|
6708
|
+
const entry = describedIdentity(identity);
|
|
6701
6709
|
runs2.set(token, entry);
|
|
6702
6710
|
live.set(entry.pid, entry);
|
|
6703
6711
|
receivers.set(entry.pid, receiver);
|
|
@@ -6739,6 +6747,10 @@ function createProcessRegistryOwner() {
|
|
|
6739
6747
|
};
|
|
6740
6748
|
scopes.set(scope, { allocated, runs: runs2, active: active2 });
|
|
6741
6749
|
return scope;
|
|
6750
|
+
},
|
|
6751
|
+
/** Every live process of the container, in pid order: what `/proc` lists. */
|
|
6752
|
+
table() {
|
|
6753
|
+
return [...live.values()].sort((a, b) => a.pid - b.pid);
|
|
6742
6754
|
}
|
|
6743
6755
|
};
|
|
6744
6756
|
}
|
|
@@ -6779,8 +6791,8 @@ const pidsOfRuns = /* @__PURE__ */ new Map();
|
|
|
6779
6791
|
function mintPid() {
|
|
6780
6792
|
return processRegistry.allocate();
|
|
6781
6793
|
}
|
|
6782
|
-
function setRunPid(token, pid, ppid) {
|
|
6783
|
-
processRegistry.publish(token, { pid, ppid });
|
|
6794
|
+
function setRunPid(token, pid, ppid, started) {
|
|
6795
|
+
processRegistry.publish(token, { pid, ppid, ...started, startedAt: Date.now() });
|
|
6784
6796
|
pidsOfRuns.set(token, { pid, ppid });
|
|
6785
6797
|
}
|
|
6786
6798
|
function runPid(token) {
|
|
@@ -9657,7 +9669,7 @@ function createUtilBinding(require2) {
|
|
|
9657
9669
|
const bufferModule = lazyModule("buffer");
|
|
9658
9670
|
const Buffer$9 = lazyExport("buffer", "Buffer");
|
|
9659
9671
|
function createNodeError$1(code2, syscall, path2) {
|
|
9660
|
-
const known = ["EEXIST", "EINVAL", "EISDIR", "ENOENT", "ENOTDIR", "ENOTEMPTY", "ELOOP"];
|
|
9672
|
+
const known = ["EEXIST", "EINVAL", "EISDIR", "ENOENT", "ENOTDIR", "ENOTEMPTY", "ELOOP", "EROFS"];
|
|
9661
9673
|
if (known.includes(code2)) return createNodeError(code2, syscall, path2);
|
|
9662
9674
|
return Object.assign(new Error(`${code2}: ${syscall} '${path2}'`), { code: code2, syscall, path: path2, errno: -9 });
|
|
9663
9675
|
}
|
|
@@ -27130,6 +27142,10 @@ const stackOverrides = /* @__PURE__ */ new Map();
|
|
|
27130
27142
|
const stackOverrideMap = {
|
|
27131
27143
|
set(error, override) {
|
|
27132
27144
|
stackOverrides.set(error, override);
|
|
27145
|
+
const nativeSetTimeout = globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout;
|
|
27146
|
+
nativeSetTimeout.call(globalThis, () => {
|
|
27147
|
+
if (stackOverrides.get(error) === override) stackOverrides.delete(error);
|
|
27148
|
+
}, 0);
|
|
27133
27149
|
return this;
|
|
27134
27150
|
},
|
|
27135
27151
|
get: (error) => stackOverrides.get(error),
|
|
@@ -27567,14 +27583,16 @@ function createNodeError(code2, syscall, path2, message) {
|
|
|
27567
27583
|
ENOTDIR: -20,
|
|
27568
27584
|
EISDIR: -21,
|
|
27569
27585
|
EEXIST: -17,
|
|
27570
|
-
ENOTEMPTY: -39
|
|
27586
|
+
ENOTEMPTY: -39,
|
|
27587
|
+
EROFS: -30
|
|
27571
27588
|
};
|
|
27572
27589
|
const messages2 = {
|
|
27573
27590
|
ENOENT: "no such file or directory",
|
|
27574
27591
|
ENOTDIR: "not a directory",
|
|
27575
27592
|
EISDIR: "is a directory",
|
|
27576
27593
|
EEXIST: "file already exists",
|
|
27577
|
-
ENOTEMPTY: "directory not empty"
|
|
27594
|
+
ENOTEMPTY: "directory not empty",
|
|
27595
|
+
EROFS: "read-only file system"
|
|
27578
27596
|
};
|
|
27579
27597
|
const err2 = new Error(
|
|
27580
27598
|
`${code2}: ${messages2[code2]}, ${syscall} '${path2}'`
|
|
@@ -27633,6 +27651,7 @@ class VirtualFS {
|
|
|
27633
27651
|
return { files };
|
|
27634
27652
|
}
|
|
27635
27653
|
serializeNode(path2, node, files) {
|
|
27654
|
+
if (node.mount) return;
|
|
27636
27655
|
if (node.type === "file") {
|
|
27637
27656
|
let content = "";
|
|
27638
27657
|
if (node.content && node.content.length > 0) {
|
|
@@ -27688,6 +27707,7 @@ class VirtualFS {
|
|
|
27688
27707
|
*/
|
|
27689
27708
|
writeFileSyncInternal(path2, data2, emitEvent) {
|
|
27690
27709
|
const normalized = this.normalizePath(path2);
|
|
27710
|
+
this.assertWritable(normalized, "open");
|
|
27691
27711
|
const parentPath = this.getParentPath(normalized);
|
|
27692
27712
|
const basename = this.getBasename(normalized);
|
|
27693
27713
|
if (!basename) {
|
|
@@ -27763,11 +27783,12 @@ class VirtualFS {
|
|
|
27763
27783
|
if (depth > 40) throw createNodeError("ELOOP", "stat", path2);
|
|
27764
27784
|
const segments = this.getPathSegments(path2);
|
|
27765
27785
|
let current3 = this.root;
|
|
27786
|
+
let mounted;
|
|
27766
27787
|
for (let index2 = 0; index2 < segments.length; index2 += 1) {
|
|
27767
|
-
if (current3.type !== "directory" || !current3.children) {
|
|
27788
|
+
if (current3.type !== "directory" || !mounted && !current3.children) {
|
|
27768
27789
|
return void 0;
|
|
27769
27790
|
}
|
|
27770
|
-
const child = current3.children.get(segments[index2]);
|
|
27791
|
+
const child = mounted ? mounted.tree.node("/" + segments.slice(mounted.at + 1, index2 + 1).join("/")) : current3.children.get(segments[index2]);
|
|
27771
27792
|
if (!child) {
|
|
27772
27793
|
return void 0;
|
|
27773
27794
|
}
|
|
@@ -27777,16 +27798,53 @@ class VirtualFS {
|
|
|
27777
27798
|
const target = this.__substrateLinkTarget(linkPath, child.target);
|
|
27778
27799
|
return this.__substrateNode(rest ? target + "/" + rest : target, follow, depth + 1);
|
|
27779
27800
|
}
|
|
27780
|
-
|
|
27801
|
+
if (!mounted && child.mount) {
|
|
27802
|
+
mounted = { tree: child.mount, at: index2 };
|
|
27803
|
+
current3 = child.mount.node("/") ?? child;
|
|
27804
|
+
} else {
|
|
27805
|
+
current3 = child;
|
|
27806
|
+
}
|
|
27781
27807
|
}
|
|
27782
27808
|
return current3;
|
|
27783
27809
|
}
|
|
27810
|
+
/**
|
|
27811
|
+
* Mount a read-only tree at a directory, as Linux mounts `/proc`: reads
|
|
27812
|
+
* below it are the tree's answers, writes fail with `EROFS`, and a snapshot
|
|
27813
|
+
* leaves it out. Answers the unmount.
|
|
27814
|
+
*/
|
|
27815
|
+
mount(path2, tree2) {
|
|
27816
|
+
var _a3;
|
|
27817
|
+
const normalized = this.normalizePath(path2);
|
|
27818
|
+
const basename = this.getBasename(normalized);
|
|
27819
|
+
if (!basename) throw createNodeError("EINVAL", "mount", path2);
|
|
27820
|
+
const parent = this.ensureDirectory(this.getParentPath(normalized));
|
|
27821
|
+
const existing = parent.children.get(basename);
|
|
27822
|
+
if (existing && (existing.type !== "directory" || existing.mount || ((_a3 = existing.children) == null ? void 0 : _a3.size))) {
|
|
27823
|
+
throw createNodeError("EEXIST", "mount", path2);
|
|
27824
|
+
}
|
|
27825
|
+
const node = { type: "directory", children: /* @__PURE__ */ new Map(), mtime: Date.now(), mode: 365, mount: tree2 };
|
|
27826
|
+
parent.children.set(basename, node);
|
|
27827
|
+
return () => {
|
|
27828
|
+
if (parent.children.get(basename) === node) parent.children.delete(basename);
|
|
27829
|
+
};
|
|
27830
|
+
}
|
|
27831
|
+
/** A write at or below a mount point is refused, as a read-only filesystem refuses it. */
|
|
27832
|
+
assertWritable(path2, syscall) {
|
|
27833
|
+
var _a3;
|
|
27834
|
+
let current3 = this.root;
|
|
27835
|
+
for (const segment of this.getPathSegments(path2)) {
|
|
27836
|
+
current3 = (_a3 = current3 == null ? void 0 : current3.children) == null ? void 0 : _a3.get(segment);
|
|
27837
|
+
if (!current3) return;
|
|
27838
|
+
if (current3.mount) throw createNodeError("EROFS", syscall, path2);
|
|
27839
|
+
}
|
|
27840
|
+
}
|
|
27784
27841
|
/** A link's target as an absolute path: absolute as written, or relative to the link's directory. */
|
|
27785
27842
|
__substrateLinkTarget(linkPath, target) {
|
|
27786
27843
|
return this.normalizePath(target.startsWith("/") ? target : this.getParentPath(linkPath) + "/" + target);
|
|
27787
27844
|
}
|
|
27788
27845
|
symlinkSync(target, path2) {
|
|
27789
27846
|
const normalized = this.normalizePath(path2);
|
|
27847
|
+
this.assertWritable(normalized, "symlink");
|
|
27790
27848
|
const parentPath = this.getParentPath(normalized);
|
|
27791
27849
|
const basename = this.getBasename(normalized);
|
|
27792
27850
|
const parent = this.getNode(parentPath);
|
|
@@ -27847,11 +27905,13 @@ class VirtualFS {
|
|
|
27847
27905
|
* Get stats for path
|
|
27848
27906
|
*/
|
|
27849
27907
|
chmodSync(path2, mode) {
|
|
27908
|
+
this.assertWritable(path2, "chmod");
|
|
27850
27909
|
const node = this.getNode(path2);
|
|
27851
27910
|
if (!node) throw createNodeError("ENOENT", "chmod", path2);
|
|
27852
27911
|
node.mode = mode & 4095;
|
|
27853
27912
|
}
|
|
27854
27913
|
utimesSync(path2, atime, mtime) {
|
|
27914
|
+
this.assertWritable(path2, "utime");
|
|
27855
27915
|
const node = this.getNode(path2);
|
|
27856
27916
|
if (!node) throw createNodeError("ENOENT", "utimes", path2);
|
|
27857
27917
|
node.atime = atime instanceof Date ? atime.getTime() : atime * 1e3;
|
|
@@ -27957,7 +28017,10 @@ class VirtualFS {
|
|
|
27957
28017
|
* Create directory, optionally with recursive parent creation
|
|
27958
28018
|
*/
|
|
27959
28019
|
mkdirSync(path2, options) {
|
|
28020
|
+
var _a3;
|
|
27960
28021
|
const normalized = this.normalizePath(path2);
|
|
28022
|
+
if ((options == null ? void 0 : options.recursive) && ((_a3 = this.getNode(normalized)) == null ? void 0 : _a3.type) === "directory") return;
|
|
28023
|
+
this.assertWritable(normalized, "mkdir");
|
|
27961
28024
|
if (options == null ? void 0 : options.recursive) {
|
|
27962
28025
|
this.ensureDirectory(normalized);
|
|
27963
28026
|
return;
|
|
@@ -28002,6 +28065,7 @@ class VirtualFS {
|
|
|
28002
28065
|
*/
|
|
28003
28066
|
unlinkSync(path2) {
|
|
28004
28067
|
const normalized = this.normalizePath(path2);
|
|
28068
|
+
this.assertWritable(normalized, "unlink");
|
|
28005
28069
|
const parentPath = this.getParentPath(normalized);
|
|
28006
28070
|
const basename = this.getBasename(normalized);
|
|
28007
28071
|
const parent = this.getNode(parentPath);
|
|
@@ -28024,6 +28088,7 @@ class VirtualFS {
|
|
|
28024
28088
|
*/
|
|
28025
28089
|
rmdirSync(path2) {
|
|
28026
28090
|
const normalized = this.normalizePath(path2);
|
|
28091
|
+
this.assertWritable(normalized, "rmdir");
|
|
28027
28092
|
const parentPath = this.getParentPath(normalized);
|
|
28028
28093
|
const basename = this.getBasename(normalized);
|
|
28029
28094
|
if (!basename) {
|
|
@@ -28052,6 +28117,8 @@ class VirtualFS {
|
|
|
28052
28117
|
renameSync(oldPath, newPath) {
|
|
28053
28118
|
const normalizedOld = this.normalizePath(oldPath);
|
|
28054
28119
|
const normalizedNew = this.normalizePath(newPath);
|
|
28120
|
+
this.assertWritable(normalizedOld, "rename");
|
|
28121
|
+
this.assertWritable(normalizedNew, "rename");
|
|
28055
28122
|
const oldParentPath = this.getParentPath(normalizedOld);
|
|
28056
28123
|
const oldBasename = this.getBasename(normalizedOld);
|
|
28057
28124
|
const newParentPath = this.getParentPath(normalizedNew);
|
|
@@ -28150,10 +28217,12 @@ class VirtualFS {
|
|
|
28150
28217
|
for (let hops = 0; hops <= 40; hops += 1) {
|
|
28151
28218
|
const segments = resolved.split("/").filter(Boolean);
|
|
28152
28219
|
let current3 = this.root;
|
|
28220
|
+
let mounted;
|
|
28153
28221
|
let linked = false;
|
|
28154
28222
|
for (let index2 = 0; index2 < segments.length; index2 += 1) {
|
|
28155
|
-
const child = current3 && current3.children ? current3.children.get(segments[index2]) : void 0;
|
|
28223
|
+
const child = mounted ? mounted.tree.node("/" + segments.slice(mounted.at + 1, index2 + 1).join("/")) : current3 && current3.children ? current3.children.get(segments[index2]) : void 0;
|
|
28156
28224
|
if (!child) throw createNodeError("ENOENT", "realpath", path2);
|
|
28225
|
+
if (!mounted && child.mount) mounted = { tree: child.mount, at: index2 };
|
|
28157
28226
|
if (child.type === "symlink") {
|
|
28158
28227
|
const linkPath = "/" + segments.slice(0, index2 + 1).join("/");
|
|
28159
28228
|
const rest = segments.slice(index2 + 1).join("/");
|
|
@@ -69749,7 +69818,7 @@ const _runStreams = /* @__PURE__ */ new Map();
|
|
|
69749
69818
|
async function runHostedNode(host, launch) {
|
|
69750
69819
|
var _a3;
|
|
69751
69820
|
const { token, streams } = launch;
|
|
69752
|
-
if (!runPid(token)) setRunPid(token, mintPid(), 0);
|
|
69821
|
+
if (!runPid(token)) setRunPid(token, mintPid(), 0, { argv: ["node", ...launch.argv], cwd: launch.cwd });
|
|
69753
69822
|
let input;
|
|
69754
69823
|
try {
|
|
69755
69824
|
(_a3 = streams == null ? void 0 : streams.signal) == null ? void 0 : _a3.throwIfAborted();
|
|
@@ -69985,7 +70054,7 @@ function initChildProcess(vfs2) {
|
|
|
69985
70054
|
...runPid(runToken) ? { pid: runPid(runToken).pid, ppid: runPid(runToken).ppid } : {}
|
|
69986
70055
|
});
|
|
69987
70056
|
const proc = runtime2.getProcess();
|
|
69988
|
-
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid);
|
|
70057
|
+
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid, { argv: ["node", ...args], cwd: ctx.cwd });
|
|
69989
70058
|
const releaseRun = runToken === null ? null : __recordRun(runToken, {
|
|
69990
70059
|
process: proc,
|
|
69991
70060
|
stdout: appendStdout,
|
|
@@ -70711,7 +70780,12 @@ function startChildRun(request) {
|
|
|
70711
70780
|
var _a3;
|
|
70712
70781
|
const token = `child-${__nextChildRun++}`;
|
|
70713
70782
|
const pid = mintPid();
|
|
70714
|
-
setRunPid(
|
|
70783
|
+
setRunPid(
|
|
70784
|
+
token,
|
|
70785
|
+
pid,
|
|
70786
|
+
((_a3 = runPid(__currentProcessToken() ?? __lastLaunchedToken)) == null ? void 0 : _a3.pid) ?? 0,
|
|
70787
|
+
{ argv: request.args.length ? request.args : [request.file], ...request.cwd ? { cwd: request.cwd } : {} }
|
|
70788
|
+
);
|
|
70715
70789
|
const controller = new AbortController();
|
|
70716
70790
|
const pendingStdin = [];
|
|
70717
70791
|
const admittedNode = nodeProcessHostInstalled();
|
|
@@ -75839,17 +75913,27 @@ function __substrateCallSiteFileNames() {
|
|
|
75839
75913
|
}
|
|
75840
75914
|
return namer;
|
|
75841
75915
|
};
|
|
75842
|
-
const
|
|
75843
|
-
const
|
|
75844
|
-
if (
|
|
75845
|
-
|
|
75846
|
-
|
|
75847
|
-
|
|
75848
|
-
|
|
75849
|
-
|
|
75916
|
+
const overridingFor = (guest) => {
|
|
75917
|
+
const known = typeof guest === "function" ? overridings.get(guest) : overridingNone;
|
|
75918
|
+
if (known) return known;
|
|
75919
|
+
const overriding = (error, sites) => {
|
|
75920
|
+
const override = error !== null && typeof error === "object" ? stackOverrides.get(error) : void 0;
|
|
75921
|
+
if (override) {
|
|
75922
|
+
stackOverrides.delete(error);
|
|
75923
|
+
return override(error, sites.map(named));
|
|
75924
|
+
}
|
|
75925
|
+
if (typeof guest === "function") return namerFor(guest)(error, sites);
|
|
75926
|
+
return `${Error.prototype.toString.call(error)}${sites.map((site) => `
|
|
75850
75927
|
at ${String(site)}`).join("")}`;
|
|
75928
|
+
};
|
|
75929
|
+
guests.set(overriding, guest);
|
|
75930
|
+
if (typeof guest === "function") overridings.set(guest, overriding);
|
|
75931
|
+
else overridingNone = overriding;
|
|
75932
|
+
return overriding;
|
|
75851
75933
|
};
|
|
75852
|
-
const
|
|
75934
|
+
const overridings = /* @__PURE__ */ new WeakMap();
|
|
75935
|
+
let overridingNone;
|
|
75936
|
+
const get2 = () => stackOverrides.size > 0 ? overridingFor(guestPrepare) : typeof guestPrepare === "function" ? namerFor(guestPrepare) : guestPrepare;
|
|
75853
75937
|
get2.__substrateNamesFrames = true;
|
|
75854
75938
|
defineOnHost(Error, "prepareStackTrace", {
|
|
75855
75939
|
get: get2,
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export { restoreHostGlobals, guestRealmInstalled } from './host-globals';
|
|
11
11
|
export { VirtualFS } from './virtual-fs';
|
|
12
|
-
export type { FSNode, Stats, FSWatcher, WatchListener, WatchEventType } from './virtual-fs';
|
|
12
|
+
export type { FSNode, MountedTree, Stats, FSWatcher, WatchListener, WatchEventType } from './virtual-fs';
|
|
13
13
|
export { Runtime, execute } from './runtime';
|
|
14
14
|
export type { Module, RuntimeOptions, RequireFunction } from './runtime';
|
|
15
15
|
export { createRuntime, WorkerRuntime } from './create-runtime';
|
|
@@ -46,7 +46,7 @@ import { ServerBridge } from './server-bridge';
|
|
|
46
46
|
import { type Socket as NetSocket } from './node-lib/net-module';
|
|
47
47
|
import { runPid, processByPid } from './process-tokens';
|
|
48
48
|
export { runPid, processByPid };
|
|
49
|
-
export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope } from './process-tokens';
|
|
49
|
+
export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope, ownerProcessTable } from './process-tokens';
|
|
50
50
|
export { installNodeProcessHost, nodeProcessHostInstalled } from './node-process-host';
|
|
51
51
|
export type { NodeProcessLaunch, NodeProcessHost } from './node-process-host';
|
|
52
52
|
export type { ProcessIdentity, ProcessRegistry, ProcessRegistryScope, InitialProcessRegistration } from './process-registry';
|
|
@@ -112,6 +112,10 @@ export declare function createContainer(options?: ContainerOptions): {
|
|
|
112
112
|
run: (command: string, options?: RunOptions) => Promise<RunResult>;
|
|
113
113
|
pendingTimers: (token: string) => number;
|
|
114
114
|
processPorts: (token: string) => number[];
|
|
115
|
+
/** The pid of the process listening on a port of this engine, where a guest process is: `/proc`'s socket owner. */
|
|
116
|
+
portPid: (port: number) => number | undefined;
|
|
117
|
+
/** Deliver a signal to the named run as another process's `kill(pid)` does; false when the run is gone. */
|
|
118
|
+
signalProcess: (token: string, signal: string) => boolean;
|
|
115
119
|
stopProcess: (token: string) => boolean;
|
|
116
120
|
/**
|
|
117
121
|
* Input for one run's guest, by its process token; without a token, the most
|
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 =
|
|
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
|
}
|
|
@@ -27297,6 +27325,10 @@ const stackOverrides = /* @__PURE__ */ new Map();
|
|
|
27297
27325
|
const stackOverrideMap = {
|
|
27298
27326
|
set(error, override) {
|
|
27299
27327
|
stackOverrides.set(error, override);
|
|
27328
|
+
const nativeSetTimeout = globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout;
|
|
27329
|
+
nativeSetTimeout.call(globalThis, () => {
|
|
27330
|
+
if (stackOverrides.get(error) === override) stackOverrides.delete(error);
|
|
27331
|
+
}, 0);
|
|
27300
27332
|
return this;
|
|
27301
27333
|
},
|
|
27302
27334
|
get: (error) => stackOverrides.get(error),
|
|
@@ -27740,14 +27772,16 @@ function createNodeError(code2, syscall, path2, message) {
|
|
|
27740
27772
|
ENOTDIR: -20,
|
|
27741
27773
|
EISDIR: -21,
|
|
27742
27774
|
EEXIST: -17,
|
|
27743
|
-
ENOTEMPTY: -39
|
|
27775
|
+
ENOTEMPTY: -39,
|
|
27776
|
+
EROFS: -30
|
|
27744
27777
|
};
|
|
27745
27778
|
const messages2 = {
|
|
27746
27779
|
ENOENT: "no such file or directory",
|
|
27747
27780
|
ENOTDIR: "not a directory",
|
|
27748
27781
|
EISDIR: "is a directory",
|
|
27749
27782
|
EEXIST: "file already exists",
|
|
27750
|
-
ENOTEMPTY: "directory not empty"
|
|
27783
|
+
ENOTEMPTY: "directory not empty",
|
|
27784
|
+
EROFS: "read-only file system"
|
|
27751
27785
|
};
|
|
27752
27786
|
const err2 = new Error(
|
|
27753
27787
|
`${code2}: ${messages2[code2]}, ${syscall} '${path2}'`
|
|
@@ -27806,6 +27840,7 @@ class VirtualFS {
|
|
|
27806
27840
|
return { files };
|
|
27807
27841
|
}
|
|
27808
27842
|
serializeNode(path2, node, files) {
|
|
27843
|
+
if (node.mount) return;
|
|
27809
27844
|
if (node.type === "file") {
|
|
27810
27845
|
let content = "";
|
|
27811
27846
|
if (node.content && node.content.length > 0) {
|
|
@@ -27861,6 +27896,7 @@ class VirtualFS {
|
|
|
27861
27896
|
*/
|
|
27862
27897
|
writeFileSyncInternal(path2, data2, emitEvent) {
|
|
27863
27898
|
const normalized = this.normalizePath(path2);
|
|
27899
|
+
this.assertWritable(normalized, "open");
|
|
27864
27900
|
const parentPath = this.getParentPath(normalized);
|
|
27865
27901
|
const basename2 = this.getBasename(normalized);
|
|
27866
27902
|
if (!basename2) {
|
|
@@ -27936,11 +27972,12 @@ class VirtualFS {
|
|
|
27936
27972
|
if (depth > 40) throw createNodeError("ELOOP", "stat", path2);
|
|
27937
27973
|
const segments = this.getPathSegments(path2);
|
|
27938
27974
|
let current3 = this.root;
|
|
27975
|
+
let mounted;
|
|
27939
27976
|
for (let index2 = 0; index2 < segments.length; index2 += 1) {
|
|
27940
|
-
if (current3.type !== "directory" || !current3.children) {
|
|
27977
|
+
if (current3.type !== "directory" || !mounted && !current3.children) {
|
|
27941
27978
|
return void 0;
|
|
27942
27979
|
}
|
|
27943
|
-
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]);
|
|
27944
27981
|
if (!child) {
|
|
27945
27982
|
return void 0;
|
|
27946
27983
|
}
|
|
@@ -27950,16 +27987,53 @@ class VirtualFS {
|
|
|
27950
27987
|
const target = this.__substrateLinkTarget(linkPath, child.target);
|
|
27951
27988
|
return this.__substrateNode(rest ? target + "/" + rest : target, follow, depth + 1);
|
|
27952
27989
|
}
|
|
27953
|
-
|
|
27990
|
+
if (!mounted && child.mount) {
|
|
27991
|
+
mounted = { tree: child.mount, at: index2 };
|
|
27992
|
+
current3 = child.mount.node("/") ?? child;
|
|
27993
|
+
} else {
|
|
27994
|
+
current3 = child;
|
|
27995
|
+
}
|
|
27954
27996
|
}
|
|
27955
27997
|
return current3;
|
|
27956
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
|
+
}
|
|
27957
28030
|
/** A link's target as an absolute path: absolute as written, or relative to the link's directory. */
|
|
27958
28031
|
__substrateLinkTarget(linkPath, target) {
|
|
27959
28032
|
return this.normalizePath(target.startsWith("/") ? target : this.getParentPath(linkPath) + "/" + target);
|
|
27960
28033
|
}
|
|
27961
28034
|
symlinkSync(target, path2) {
|
|
27962
28035
|
const normalized = this.normalizePath(path2);
|
|
28036
|
+
this.assertWritable(normalized, "symlink");
|
|
27963
28037
|
const parentPath = this.getParentPath(normalized);
|
|
27964
28038
|
const basename2 = this.getBasename(normalized);
|
|
27965
28039
|
const parent = this.getNode(parentPath);
|
|
@@ -28020,11 +28094,13 @@ class VirtualFS {
|
|
|
28020
28094
|
* Get stats for path
|
|
28021
28095
|
*/
|
|
28022
28096
|
chmodSync(path2, mode) {
|
|
28097
|
+
this.assertWritable(path2, "chmod");
|
|
28023
28098
|
const node = this.getNode(path2);
|
|
28024
28099
|
if (!node) throw createNodeError("ENOENT", "chmod", path2);
|
|
28025
28100
|
node.mode = mode & 4095;
|
|
28026
28101
|
}
|
|
28027
28102
|
utimesSync(path2, atime, mtime) {
|
|
28103
|
+
this.assertWritable(path2, "utime");
|
|
28028
28104
|
const node = this.getNode(path2);
|
|
28029
28105
|
if (!node) throw createNodeError("ENOENT", "utimes", path2);
|
|
28030
28106
|
node.atime = atime instanceof Date ? atime.getTime() : atime * 1e3;
|
|
@@ -28130,7 +28206,10 @@ class VirtualFS {
|
|
|
28130
28206
|
* Create directory, optionally with recursive parent creation
|
|
28131
28207
|
*/
|
|
28132
28208
|
mkdirSync(path2, options) {
|
|
28209
|
+
var _a2;
|
|
28133
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");
|
|
28134
28213
|
if (options == null ? void 0 : options.recursive) {
|
|
28135
28214
|
this.ensureDirectory(normalized);
|
|
28136
28215
|
return;
|
|
@@ -28175,6 +28254,7 @@ class VirtualFS {
|
|
|
28175
28254
|
*/
|
|
28176
28255
|
unlinkSync(path2) {
|
|
28177
28256
|
const normalized = this.normalizePath(path2);
|
|
28257
|
+
this.assertWritable(normalized, "unlink");
|
|
28178
28258
|
const parentPath = this.getParentPath(normalized);
|
|
28179
28259
|
const basename2 = this.getBasename(normalized);
|
|
28180
28260
|
const parent = this.getNode(parentPath);
|
|
@@ -28197,6 +28277,7 @@ class VirtualFS {
|
|
|
28197
28277
|
*/
|
|
28198
28278
|
rmdirSync(path2) {
|
|
28199
28279
|
const normalized = this.normalizePath(path2);
|
|
28280
|
+
this.assertWritable(normalized, "rmdir");
|
|
28200
28281
|
const parentPath = this.getParentPath(normalized);
|
|
28201
28282
|
const basename2 = this.getBasename(normalized);
|
|
28202
28283
|
if (!basename2) {
|
|
@@ -28225,6 +28306,8 @@ class VirtualFS {
|
|
|
28225
28306
|
renameSync(oldPath, newPath) {
|
|
28226
28307
|
const normalizedOld = this.normalizePath(oldPath);
|
|
28227
28308
|
const normalizedNew = this.normalizePath(newPath);
|
|
28309
|
+
this.assertWritable(normalizedOld, "rename");
|
|
28310
|
+
this.assertWritable(normalizedNew, "rename");
|
|
28228
28311
|
const oldParentPath = this.getParentPath(normalizedOld);
|
|
28229
28312
|
const oldBasename = this.getBasename(normalizedOld);
|
|
28230
28313
|
const newParentPath = this.getParentPath(normalizedNew);
|
|
@@ -28323,10 +28406,12 @@ class VirtualFS {
|
|
|
28323
28406
|
for (let hops = 0; hops <= 40; hops += 1) {
|
|
28324
28407
|
const segments = resolved.split("/").filter(Boolean);
|
|
28325
28408
|
let current3 = this.root;
|
|
28409
|
+
let mounted;
|
|
28326
28410
|
let linked = false;
|
|
28327
28411
|
for (let index2 = 0; index2 < segments.length; index2 += 1) {
|
|
28328
|
-
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;
|
|
28329
28413
|
if (!child) throw createNodeError("ENOENT", "realpath", path2);
|
|
28414
|
+
if (!mounted && child.mount) mounted = { tree: child.mount, at: index2 };
|
|
28330
28415
|
if (child.type === "symlink") {
|
|
28331
28416
|
const linkPath = "/" + segments.slice(0, index2 + 1).join("/");
|
|
28332
28417
|
const rest = segments.slice(index2 + 1).join("/");
|
|
@@ -29382,7 +29467,7 @@ const _runStreams = /* @__PURE__ */ new Map();
|
|
|
29382
29467
|
async function runHostedNode(host2, launch) {
|
|
29383
29468
|
var _a2;
|
|
29384
29469
|
const { token, streams } = launch;
|
|
29385
|
-
if (!runPid(token)) setRunPid(token, mintPid(), 0);
|
|
29470
|
+
if (!runPid(token)) setRunPid(token, mintPid(), 0, { argv: ["node", ...launch.argv], cwd: launch.cwd });
|
|
29386
29471
|
let input;
|
|
29387
29472
|
try {
|
|
29388
29473
|
(_a2 = streams == null ? void 0 : streams.signal) == null ? void 0 : _a2.throwIfAborted();
|
|
@@ -29641,7 +29726,7 @@ function initChildProcess(vfs2) {
|
|
|
29641
29726
|
...runPid(runToken) ? { pid: runPid(runToken).pid, ppid: runPid(runToken).ppid } : {}
|
|
29642
29727
|
});
|
|
29643
29728
|
const proc = runtime.getProcess();
|
|
29644
|
-
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 });
|
|
29645
29730
|
const releaseRun = runToken === null ? null : __recordRun(runToken, {
|
|
29646
29731
|
process: proc,
|
|
29647
29732
|
stdout: appendStdout,
|
|
@@ -30387,7 +30472,12 @@ function startChildRun(request) {
|
|
|
30387
30472
|
var _a2;
|
|
30388
30473
|
const token = `child-${__nextChildRun++}`;
|
|
30389
30474
|
const pid = mintPid();
|
|
30390
|
-
setRunPid(
|
|
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
|
+
);
|
|
30391
30481
|
const controller = new AbortController();
|
|
30392
30482
|
const pendingStdin = [];
|
|
30393
30483
|
const admittedNode = nodeProcessHostInstalled() && engineProgramFor(request.file, request.cwd) === "node";
|
|
@@ -35520,17 +35610,27 @@ function __substrateCallSiteFileNames() {
|
|
|
35520
35610
|
}
|
|
35521
35611
|
return namer;
|
|
35522
35612
|
};
|
|
35523
|
-
const
|
|
35524
|
-
const
|
|
35525
|
-
if (
|
|
35526
|
-
|
|
35527
|
-
|
|
35528
|
-
|
|
35529
|
-
|
|
35530
|
-
|
|
35613
|
+
const overridingFor = (guest) => {
|
|
35614
|
+
const known = typeof guest === "function" ? overridings.get(guest) : overridingNone;
|
|
35615
|
+
if (known) return known;
|
|
35616
|
+
const overriding = (error, sites) => {
|
|
35617
|
+
const override = error !== null && typeof error === "object" ? stackOverrides.get(error) : void 0;
|
|
35618
|
+
if (override) {
|
|
35619
|
+
stackOverrides.delete(error);
|
|
35620
|
+
return override(error, sites.map(named));
|
|
35621
|
+
}
|
|
35622
|
+
if (typeof guest === "function") return namerFor(guest)(error, sites);
|
|
35623
|
+
return `${Error.prototype.toString.call(error)}${sites.map((site) => `
|
|
35531
35624
|
at ${String(site)}`).join("")}`;
|
|
35532
|
-
|
|
35533
|
-
|
|
35625
|
+
};
|
|
35626
|
+
guests.set(overriding, guest);
|
|
35627
|
+
if (typeof guest === "function") overridings.set(guest, overriding);
|
|
35628
|
+
else overridingNone = overriding;
|
|
35629
|
+
return overriding;
|
|
35630
|
+
};
|
|
35631
|
+
const overridings = /* @__PURE__ */ new WeakMap();
|
|
35632
|
+
let overridingNone;
|
|
35633
|
+
const get2 = () => stackOverrides.size > 0 ? overridingFor(guestPrepare) : typeof guestPrepare === "function" ? namerFor(guestPrepare) : guestPrepare;
|
|
35534
35634
|
get2.__substrateNamesFrames = true;
|
|
35535
35635
|
defineOnHost(Error, "prepareStackTrace", {
|
|
35536
35636
|
get: get2,
|
|
@@ -35555,7 +35655,7 @@ class WorkerRuntime {
|
|
|
35555
35655
|
this.worker = new Worker(
|
|
35556
35656
|
new URL(
|
|
35557
35657
|
/* @vite-ignore */
|
|
35558
|
-
"/assets/runtime-worker-
|
|
35658
|
+
"/assets/runtime-worker-iInsmIAS.js",
|
|
35559
35659
|
import.meta.url
|
|
35560
35660
|
),
|
|
35561
35661
|
{ type: "module" }
|
|
@@ -38271,6 +38371,16 @@ function createContainer(options) {
|
|
|
38271
38371
|
},
|
|
38272
38372
|
/** The ports the servers the named run opened are listening on. */
|
|
38273
38373
|
processPorts: (token) => __ownedServerPorts(token),
|
|
38374
|
+
portPid: (port) => {
|
|
38375
|
+
var _a2;
|
|
38376
|
+
const listener = listenerOnPort(port);
|
|
38377
|
+
return listener ? (_a2 = runPid(ownerOf(listener))) == null ? void 0 : _a2.pid : void 0;
|
|
38378
|
+
},
|
|
38379
|
+
/**
|
|
38380
|
+
* Deliver a signal to the named run as another process's `kill(pid)` does:
|
|
38381
|
+
* the guest's listeners for it run, else its default action ends the run.
|
|
38382
|
+
*/
|
|
38383
|
+
signalProcess: (token, signal) => __signalOwnedProcess(token, signal),
|
|
38274
38384
|
/** End the named run: its timers stop and its servers are released. */
|
|
38275
38385
|
stopProcess: (token) => {
|
|
38276
38386
|
const known = __runFor(token) !== void 0 || __ownedServerPorts(token).length > 0;
|
|
@@ -38343,6 +38453,7 @@ export {
|
|
|
38343
38453
|
nodeProcessHostInstalled,
|
|
38344
38454
|
index as npm,
|
|
38345
38455
|
ownerProcessRegistryScope,
|
|
38456
|
+
ownerProcessTable,
|
|
38346
38457
|
path,
|
|
38347
38458
|
perfHooksShim as perf_hooks,
|
|
38348
38459
|
processByPid,
|
|
@@ -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
|
};
|
package/dist/process-tokens.d.ts
CHANGED
|
@@ -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
|
|
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;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* registration; `Error.prepareStackTrace` is the door it has. Every process's
|
|
10
10
|
* `internal/errors` writes here, and the realm's hook reads here, so the
|
|
11
11
|
* override reaches V8 whichever process set it. An entry is consumed by the
|
|
12
|
-
* stack it formats, as Node's is.
|
|
12
|
+
* stack it formats, as Node's is, and lapses at the end of the task that set it.
|
|
13
13
|
*/
|
|
14
14
|
export type StackOverride = (error: unknown, sites: unknown[]) => unknown;
|
|
15
15
|
export declare const stackOverrides: Map<object, StackOverride>;
|
package/dist/virtual-fs.d.ts
CHANGED
|
@@ -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
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,12 @@ 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';
|
|
58
59
|
|
|
59
|
-
import { __currentProcessToken, __runFor, __stopOwnedProcess, runPid, processByPid } from './process-tokens';
|
|
60
|
+
import { __currentProcessToken, __runFor, __signalOwnedProcess, __stopOwnedProcess, runPid, processByPid } from './process-tokens';
|
|
60
61
|
export { runPid, processByPid };
|
|
61
|
-
export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope } from './process-tokens';
|
|
62
|
+
export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope, ownerProcessTable } from './process-tokens';
|
|
62
63
|
export { installNodeProcessHost, nodeProcessHostInstalled } from './node-process-host';
|
|
63
64
|
export type { NodeProcessLaunch, NodeProcessHost } from './node-process-host';
|
|
64
65
|
export type { ProcessIdentity, ProcessRegistry, ProcessRegistryScope, InitialProcessRegistration } from './process-registry';
|
|
@@ -123,6 +124,10 @@ export function createContainer(options?: ContainerOptions): {
|
|
|
123
124
|
run: (command: string, options?: RunOptions) => Promise<RunResult>;
|
|
124
125
|
pendingTimers: (token: string) => number;
|
|
125
126
|
processPorts: (token: string) => number[];
|
|
127
|
+
/** The pid of the process listening on a port of this engine, where a guest process is: `/proc`'s socket owner. */
|
|
128
|
+
portPid: (port: number) => number | undefined;
|
|
129
|
+
/** Deliver a signal to the named run as another process's `kill(pid)` does; false when the run is gone. */
|
|
130
|
+
signalProcess: (token: string, signal: string) => boolean;
|
|
126
131
|
stopProcess: (token: string) => boolean;
|
|
127
132
|
/**
|
|
128
133
|
* Input for one run's guest, by its process token; without a token, the most
|
|
@@ -208,6 +213,15 @@ export function createContainer(options?: ContainerOptions): {
|
|
|
208
213
|
pendingTimers: (token: string): number => __runFor(token)?.pendingTimers() ?? 0,
|
|
209
214
|
/** The ports the servers the named run opened are listening on. */
|
|
210
215
|
processPorts: (token: string): number[] => __ownedServerPorts(token),
|
|
216
|
+
portPid: (port: number): number | undefined => {
|
|
217
|
+
const listener = listenerOnPort(port);
|
|
218
|
+
return listener ? runPid(ownerOf(listener as unknown as OwnedHandle))?.pid : undefined;
|
|
219
|
+
},
|
|
220
|
+
/**
|
|
221
|
+
* Deliver a signal to the named run as another process's `kill(pid)` does:
|
|
222
|
+
* the guest's listeners for it run, else its default action ends the run.
|
|
223
|
+
*/
|
|
224
|
+
signalProcess: (token: string, signal: string): boolean => __signalOwnedProcess(token, signal),
|
|
211
225
|
/** End the named run: its timers stop and its servers are released. */
|
|
212
226
|
stopProcess: (token: string): boolean => {
|
|
213
227
|
const known = __runFor(token) !== undefined || __ownedServerPorts(token).length > 0;
|
|
@@ -40,7 +40,7 @@ import { errname } from './uv';
|
|
|
40
40
|
* the same shape here rather than widened there.
|
|
41
41
|
*/
|
|
42
42
|
function createNodeError(code: string, syscall: string, path: string): Error {
|
|
43
|
-
const known = ['EEXIST', 'EINVAL', 'EISDIR', 'ENOENT', 'ENOTDIR', 'ENOTEMPTY', 'ELOOP'];
|
|
43
|
+
const known = ['EEXIST', 'EINVAL', 'EISDIR', 'ENOENT', 'ENOTDIR', 'ENOTEMPTY', 'ELOOP', 'EROFS'];
|
|
44
44
|
if (known.includes(code)) return vfsError(code as 'ENOENT', syscall, path);
|
|
45
45
|
return Object.assign(new Error(`${code}: ${syscall} '${path}'`), { code, syscall, path, errno: -9 });
|
|
46
46
|
}
|
package/src/process-registry.ts
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
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;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The parts of an identity a starter may add, checked before they are kept. */
|
|
14
|
+
function describedIdentity(identity: ProcessIdentity): ProcessIdentity {
|
|
15
|
+
const { pid, ppid, argv, cwd, startedAt } = identity;
|
|
16
|
+
const described: { pid: number; ppid: number; argv?: readonly string[]; cwd?: string; startedAt?: number } = { pid, ppid };
|
|
17
|
+
if (Array.isArray(argv) && argv.length <= 4096 && argv.every(arg => typeof arg === 'string')) described.argv = Object.freeze([...argv]);
|
|
18
|
+
if (typeof cwd === 'string' && cwd.startsWith('/')) described.cwd = cwd;
|
|
19
|
+
if (typeof startedAt === 'number' && Number.isFinite(startedAt)) described.startedAt = startedAt;
|
|
20
|
+
return Object.freeze(described);
|
|
5
21
|
}
|
|
6
22
|
|
|
7
23
|
/** A run already admitted by the container owner into this realm's scope. */
|
|
@@ -38,7 +54,7 @@ export interface ProcessRegistryScope extends ProcessRegistry {
|
|
|
38
54
|
* so identical local run tokens cannot overwrite or release another realm's
|
|
39
55
|
* process. Allocation alone does not announce a live process.
|
|
40
56
|
*/
|
|
41
|
-
export function createProcessRegistryOwner(): { createScope(): ProcessRegistryScope } {
|
|
57
|
+
export function createProcessRegistryOwner(): { createScope(): ProcessRegistryScope; table(): ProcessIdentity[] } {
|
|
42
58
|
let nextPid = 1000 + Math.floor(Math.random() * 30000);
|
|
43
59
|
const live = new Map<number, ProcessIdentity>();
|
|
44
60
|
// Which realm's scope answers for each live pid, including after handoff.
|
|
@@ -104,7 +120,7 @@ export function createProcessRegistryOwner(): { createScope(): ProcessRegistrySc
|
|
|
104
120
|
if (identity.ppid !== 0 && !Array.from(runs.values()).some(parent => parent.pid === identity.ppid)) {
|
|
105
121
|
throw new Error('Parent process is not owned by this realm.');
|
|
106
122
|
}
|
|
107
|
-
const entry =
|
|
123
|
+
const entry = describedIdentity(identity);
|
|
108
124
|
runs.set(token, entry);
|
|
109
125
|
live.set(entry.pid, entry);
|
|
110
126
|
receivers.set(entry.pid, receiver);
|
|
@@ -140,5 +156,9 @@ export function createProcessRegistryOwner(): { createScope(): ProcessRegistrySc
|
|
|
140
156
|
scopes.set(scope, { allocated, runs, active });
|
|
141
157
|
return scope;
|
|
142
158
|
},
|
|
159
|
+
/** Every live process of the container, in pid order: what `/proc` lists. */
|
|
160
|
+
table() {
|
|
161
|
+
return [...live.values()].sort((a, b) => a.pid - b.pid);
|
|
162
|
+
},
|
|
143
163
|
};
|
|
144
164
|
}
|
package/src/process-tokens.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import type { Process } from './shims/process';
|
|
19
19
|
import { AsyncLocalStorage } from './shims/async_hooks';
|
|
20
|
-
import { createProcessRegistryOwner, type ProcessRegistry, type ProcessRegistryScope, type InitialProcessRegistration } from './process-registry';
|
|
20
|
+
import { createProcessRegistryOwner, type ProcessIdentity, type ProcessRegistry, type ProcessRegistryScope, type InitialProcessRegistration } from './process-registry';
|
|
21
21
|
|
|
22
22
|
/** Whatever the embedding runtime uses to name one guest process. */
|
|
23
23
|
export type ProcessToken = string;
|
|
@@ -113,6 +113,27 @@ export function __stopOwnedProcess(token: ProcessToken): void {
|
|
|
113
113
|
runs.get(token)?.stopTimers();
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Another process's `kill(pid, signal)`, delivered to a named run's guest.
|
|
118
|
+
* On a machine the target's handlers run, or its default action ends it; the
|
|
119
|
+
* guest's own `process.kill(process.pid, signal)` is exactly that, so the
|
|
120
|
+
* signal is raised through it, as the run. False when the run is gone or the
|
|
121
|
+
* signal is not one Node names.
|
|
122
|
+
*/
|
|
123
|
+
export function __signalOwnedProcess(token: ProcessToken, signal: string): boolean {
|
|
124
|
+
const run = runs.get(token);
|
|
125
|
+
if (!run) return false;
|
|
126
|
+
const guest = run.process as unknown as { pid: number; kill(pid: number, signal: string): unknown };
|
|
127
|
+
try { enterRun(token, () => guest.kill(guest.pid, signal)); }
|
|
128
|
+
catch (error) {
|
|
129
|
+
// A default action ends the process by `exit`, which a synchronous guest
|
|
130
|
+
// frame reports by throwing; that is the signal delivered.
|
|
131
|
+
if (error instanceof Error && error.message.startsWith("Process exited with code")) return true;
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
116
137
|
/**
|
|
117
138
|
* A process's own number.
|
|
118
139
|
*
|
|
@@ -147,6 +168,12 @@ export function createProcessRegistryScope(): ProcessRegistryScope {
|
|
|
147
168
|
return processRegistryOwner.createScope();
|
|
148
169
|
}
|
|
149
170
|
|
|
171
|
+
/** Trusted container owner only: every live process of the container, from every realm. */
|
|
172
|
+
export function ownerProcessTable(): ProcessIdentity[] {
|
|
173
|
+
if (registryInstalled) throw new Error('This realm is a process registry client, not the container owner.');
|
|
174
|
+
return processRegistryOwner.table();
|
|
175
|
+
}
|
|
176
|
+
|
|
150
177
|
/** Trusted container owner only; worker clients cannot expose their authority. */
|
|
151
178
|
export function ownerProcessRegistryScope(): ProcessRegistryScope {
|
|
152
179
|
if (registryInstalled) throw new Error('This realm is a process registry client, not the container owner.');
|
|
@@ -177,9 +204,9 @@ export function mintPid(): number {
|
|
|
177
204
|
}
|
|
178
205
|
|
|
179
206
|
/** Record the numbers a named run was started with, for the run to read back. */
|
|
180
|
-
export function setRunPid(token: ProcessToken, pid: number, ppid: number): void {
|
|
207
|
+
export function setRunPid(token: ProcessToken, pid: number, ppid: number, started?: { argv?: readonly string[]; cwd?: string }): void {
|
|
181
208
|
registryUsed = true;
|
|
182
|
-
processRegistry.publish(token, { pid, ppid });
|
|
209
|
+
processRegistry.publish(token, { pid, ppid, ...started, startedAt: Date.now() });
|
|
183
210
|
pidsOfRuns.set(token, { pid, ppid });
|
|
184
211
|
}
|
|
185
212
|
|
package/src/runtime.ts
CHANGED
|
@@ -2942,13 +2942,25 @@ function __substrateCallSiteFileNames(): void {
|
|
|
2942
2942
|
// override and every other error what it would have had: the guest's
|
|
2943
2943
|
// function, else V8's own text, which is the error and one `at` line
|
|
2944
2944
|
// per call site. Otherwise V8 is handed exactly what the guest set.
|
|
2945
|
-
const
|
|
2946
|
-
const
|
|
2947
|
-
if (
|
|
2948
|
-
|
|
2949
|
-
|
|
2945
|
+
const overridingFor = (guest: unknown): Prepare => {
|
|
2946
|
+
const known = typeof guest === 'function' ? overridings.get(guest) : overridingNone;
|
|
2947
|
+
if (known) return known;
|
|
2948
|
+
const overriding: Prepare = (error, sites) => {
|
|
2949
|
+
const override = error !== null && typeof error === 'object' ? stackOverrides.get(error) : undefined;
|
|
2950
|
+
if (override) { stackOverrides.delete(error as object); return override(error, sites.map(named)); }
|
|
2951
|
+
if (typeof guest === 'function') return namerFor(guest as Prepare)(error, sites);
|
|
2952
|
+
return `${Error.prototype.toString.call(error)}${sites.map(site => `\n at ${String(site)}`).join('')}`;
|
|
2953
|
+
};
|
|
2954
|
+
// A guest that saves the hook and restores it later restores the function
|
|
2955
|
+
// it read; that stands for the guest's own, as a namer does.
|
|
2956
|
+
guests.set(overriding, guest);
|
|
2957
|
+
if (typeof guest === 'function') overridings.set(guest, overriding);
|
|
2958
|
+
else overridingNone = overriding;
|
|
2959
|
+
return overriding;
|
|
2950
2960
|
};
|
|
2951
|
-
const
|
|
2961
|
+
const overridings = new WeakMap<object, Prepare>();
|
|
2962
|
+
let overridingNone: Prepare | undefined;
|
|
2963
|
+
const get = () => (stackOverrides.size > 0 ? overridingFor(guestPrepare)
|
|
2952
2964
|
: typeof guestPrepare === 'function' ? namerFor(guestPrepare as Prepare) : guestPrepare);
|
|
2953
2965
|
(get as { __substrateNamesFrames?: boolean }).__substrateNamesFrames = true;
|
|
2954
2966
|
defineOnHost(Error, 'prepareStackTrace', {
|
|
@@ -199,7 +199,7 @@ const _runStreams = new Map<ProcessToken, RunStreams>();
|
|
|
199
199
|
/** Submit before returning to a spawning parent; completion remains async. */
|
|
200
200
|
async function runHostedNode(host: NodeProcessHost, launch: Omit<NodeProcessLaunch, 'identity' | 'inherited' | 'stdinStream'>): Promise<CommandOutcome> {
|
|
201
201
|
const { token, streams } = launch;
|
|
202
|
-
if (!runPid(token)) setRunPid(token, mintPid(), 0);
|
|
202
|
+
if (!runPid(token)) setRunPid(token, mintPid(), 0, { argv: ['node', ...launch.argv], cwd: launch.cwd });
|
|
203
203
|
let input: ReturnType<typeof nodeProcessInput> | undefined;
|
|
204
204
|
try {
|
|
205
205
|
streams?.signal?.throwIfAborted();
|
|
@@ -578,7 +578,7 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
578
578
|
// This run's numbers, recorded under its name: a child it spawns reads
|
|
579
579
|
// them for its own `ppid`, and `process.kill(pid, 0)` asks this registry
|
|
580
580
|
// whether a pid is a live process.
|
|
581
|
-
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid);
|
|
581
|
+
if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid, { argv: ['node', ...args], cwd: ctx.cwd });
|
|
582
582
|
const releaseRun = runToken === null ? null : __recordRun(runToken, {
|
|
583
583
|
process: proc,
|
|
584
584
|
stdout: appendStdout,
|
|
@@ -1680,7 +1680,8 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1680
1680
|
const pid = mintPid();
|
|
1681
1681
|
// In a process realm the admitted guest is always the spawning parent.
|
|
1682
1682
|
// Async bookkeeping for a routed child must not make that child its parent.
|
|
1683
|
-
setRunPid(token, pid, runPid(nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken)?.pid ?? 0
|
|
1683
|
+
setRunPid(token, pid, runPid(nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken)?.pid ?? 0,
|
|
1684
|
+
{ argv: request.args.length ? request.args : [request.file], ...(request.cwd ? { cwd: request.cwd } : {}) });
|
|
1684
1685
|
const controller = new AbortController();
|
|
1685
1686
|
const pendingStdin: Array<Uint8Array | null> = [];
|
|
1686
1687
|
// A host terminal consumes input incrementally. The old string-only
|
package/src/stack-overrides.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* registration; `Error.prepareStackTrace` is the door it has. Every process's
|
|
10
10
|
* `internal/errors` writes here, and the realm's hook reads here, so the
|
|
11
11
|
* override reaches V8 whichever process set it. An entry is consumed by the
|
|
12
|
-
* stack it formats, as Node's is.
|
|
12
|
+
* stack it formats, as Node's is, and lapses at the end of the task that set it.
|
|
13
13
|
*/
|
|
14
14
|
export type StackOverride = (error: unknown, sites: unknown[]) => unknown;
|
|
15
15
|
|
|
@@ -17,7 +17,16 @@ export const stackOverrides = new Map<object, StackOverride>();
|
|
|
17
17
|
|
|
18
18
|
/** A WeakMap-shaped view of the table, which is what `internal/errors` holds. */
|
|
19
19
|
export const stackOverrideMap = {
|
|
20
|
-
set(this: object, error: object, override: StackOverride) {
|
|
20
|
+
set(this: object, error: object, override: StackOverride) {
|
|
21
|
+
stackOverrides.set(error, override);
|
|
22
|
+
// Node's map is weak, so an override whose stack is never read costs nothing
|
|
23
|
+
// there. Here it would hold its error and keep every stack on the fallback
|
|
24
|
+
// formatter; Node's readers read in the same task, so it lapses after it.
|
|
25
|
+
// The realm's own timer: a guest's would count as that guest's work.
|
|
26
|
+
const nativeSetTimeout = (globalThis as { __browserRuntimeNativeSetTimeout?: typeof setTimeout }).__browserRuntimeNativeSetTimeout ?? setTimeout;
|
|
27
|
+
nativeSetTimeout.call(globalThis, () => { if (stackOverrides.get(error) === override) stackOverrides.delete(error); }, 0);
|
|
28
|
+
return this;
|
|
29
|
+
},
|
|
21
30
|
get: (error: object) => stackOverrides.get(error),
|
|
22
31
|
has: (error: object) => stackOverrides.has(error),
|
|
23
32
|
delete: (error: object) => stackOverrides.delete(error),
|
package/src/virtual-fs.ts
CHANGED
|
@@ -44,6 +44,23 @@ export interface FSNode {
|
|
|
44
44
|
* apart). Absent until something sets it, which is the write time.
|
|
45
45
|
*/
|
|
46
46
|
atime?: number;
|
|
47
|
+
/**
|
|
48
|
+
* A tree mounted here: the paths below this node are the tree's, answered
|
|
49
|
+
* when they are read, and never this filesystem's to write. Linux's
|
|
50
|
+
* `/proc` is one.
|
|
51
|
+
*/
|
|
52
|
+
mount?: MountedTree;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A read-only tree another party answers, mounted at a directory: `node`
|
|
57
|
+
* returns what is at a path relative to the mount point (`/` is the mount
|
|
58
|
+
* point itself), computed when it is asked, or undefined where nothing is.
|
|
59
|
+
* A directory's `children` names what a listing shows; a link's `target` is
|
|
60
|
+
* followed as any link is.
|
|
61
|
+
*/
|
|
62
|
+
export interface MountedTree {
|
|
63
|
+
node(path: string): FSNode | undefined;
|
|
47
64
|
}
|
|
48
65
|
|
|
49
66
|
// Identity belongs to the node, so writes and renames preserve it while an
|
|
@@ -116,7 +133,7 @@ export interface NodeError extends Error {
|
|
|
116
133
|
}
|
|
117
134
|
|
|
118
135
|
export function createNodeError(
|
|
119
|
-
code: 'ENOENT' | 'ENOTDIR' | 'EISDIR' | 'EEXIST' | 'ENOTEMPTY' | 'ELOOP' | 'EINVAL',
|
|
136
|
+
code: 'ENOENT' | 'ENOTDIR' | 'EISDIR' | 'EEXIST' | 'ENOTEMPTY' | 'ELOOP' | 'EINVAL' | 'EROFS',
|
|
120
137
|
syscall: string,
|
|
121
138
|
path: string,
|
|
122
139
|
message?: string
|
|
@@ -127,6 +144,7 @@ export function createNodeError(
|
|
|
127
144
|
EISDIR: -21,
|
|
128
145
|
EEXIST: -17,
|
|
129
146
|
ENOTEMPTY: -39,
|
|
147
|
+
EROFS: -30,
|
|
130
148
|
};
|
|
131
149
|
|
|
132
150
|
const messages: Record<string, string> = {
|
|
@@ -135,6 +153,7 @@ export function createNodeError(
|
|
|
135
153
|
EISDIR: 'is a directory',
|
|
136
154
|
EEXIST: 'file already exists',
|
|
137
155
|
ENOTEMPTY: 'directory not empty',
|
|
156
|
+
EROFS: 'read-only file system',
|
|
138
157
|
};
|
|
139
158
|
|
|
140
159
|
const err = new Error(
|
|
@@ -216,6 +235,7 @@ export class VirtualFS {
|
|
|
216
235
|
}
|
|
217
236
|
|
|
218
237
|
private serializeNode(path: string, node: FSNode, files: VFSFileEntry[]): void {
|
|
238
|
+
if (node.mount) return; // a mounted tree is its answerer's, never the filesystem's contents
|
|
219
239
|
if (node.type === 'file') {
|
|
220
240
|
// Encode binary content as base64
|
|
221
241
|
let content = '';
|
|
@@ -284,6 +304,7 @@ export class VirtualFS {
|
|
|
284
304
|
*/
|
|
285
305
|
private writeFileSyncInternal(path: string, data: string | Uint8Array, emitEvent: boolean): void {
|
|
286
306
|
const normalized = this.normalizePath(path);
|
|
307
|
+
this.assertWritable(normalized, 'open');
|
|
287
308
|
const parentPath = this.getParentPath(normalized);
|
|
288
309
|
const basename = this.getBasename(normalized);
|
|
289
310
|
|
|
@@ -377,11 +398,14 @@ export class VirtualFS {
|
|
|
377
398
|
if (depth > 40) throw createNodeError('ELOOP', 'stat', path);
|
|
378
399
|
const segments = this.getPathSegments(path);
|
|
379
400
|
let current = this.root;
|
|
401
|
+
let mounted: { tree: MountedTree; at: number } | undefined;
|
|
380
402
|
for (let index = 0; index < segments.length; index += 1) {
|
|
381
|
-
if (current.type !== 'directory' || !current.children) {
|
|
403
|
+
if (current.type !== 'directory' || (!mounted && !current.children)) {
|
|
382
404
|
return undefined;
|
|
383
405
|
}
|
|
384
|
-
const child =
|
|
406
|
+
const child = mounted
|
|
407
|
+
? mounted.tree.node('/' + segments.slice(mounted.at + 1, index + 1).join('/'))
|
|
408
|
+
: current.children!.get(segments[index]);
|
|
385
409
|
if (!child) {
|
|
386
410
|
return undefined;
|
|
387
411
|
}
|
|
@@ -391,16 +415,51 @@ export class VirtualFS {
|
|
|
391
415
|
const target = this.__substrateLinkTarget(linkPath, child.target!);
|
|
392
416
|
return this.__substrateNode(rest ? target + '/' + rest : target, follow, depth + 1);
|
|
393
417
|
}
|
|
394
|
-
|
|
418
|
+
if (!mounted && child.mount) {
|
|
419
|
+
mounted = { tree: child.mount, at: index };
|
|
420
|
+
current = child.mount.node('/') ?? child;
|
|
421
|
+
} else {
|
|
422
|
+
current = child;
|
|
423
|
+
}
|
|
395
424
|
}
|
|
396
425
|
return current;
|
|
397
426
|
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Mount a read-only tree at a directory, as Linux mounts `/proc`: reads
|
|
430
|
+
* below it are the tree's answers, writes fail with `EROFS`, and a snapshot
|
|
431
|
+
* leaves it out. Answers the unmount.
|
|
432
|
+
*/
|
|
433
|
+
mount(path: string, tree: MountedTree): () => void {
|
|
434
|
+
const normalized = this.normalizePath(path);
|
|
435
|
+
const basename = this.getBasename(normalized);
|
|
436
|
+
if (!basename) throw createNodeError('EINVAL', 'mount', path);
|
|
437
|
+
const parent = this.ensureDirectory(this.getParentPath(normalized));
|
|
438
|
+
const existing = parent.children!.get(basename);
|
|
439
|
+
if (existing && (existing.type !== 'directory' || existing.mount || existing.children?.size)) {
|
|
440
|
+
throw createNodeError('EEXIST', 'mount', path);
|
|
441
|
+
}
|
|
442
|
+
const node: FSNode = { type: 'directory', children: new Map(), mtime: Date.now(), mode: 0o555, mount: tree };
|
|
443
|
+
parent.children!.set(basename, node);
|
|
444
|
+
return () => { if (parent.children!.get(basename) === node) parent.children!.delete(basename); };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** A write at or below a mount point is refused, as a read-only filesystem refuses it. */
|
|
448
|
+
private assertWritable(path: string, syscall: string): void {
|
|
449
|
+
let current: FSNode | undefined = this.root;
|
|
450
|
+
for (const segment of this.getPathSegments(path)) {
|
|
451
|
+
current = current?.children?.get(segment);
|
|
452
|
+
if (!current) return;
|
|
453
|
+
if (current.mount) throw createNodeError('EROFS', syscall, path);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
398
456
|
/** A link's target as an absolute path: absolute as written, or relative to the link's directory. */
|
|
399
457
|
__substrateLinkTarget(linkPath: string, target: string): string {
|
|
400
458
|
return this.normalizePath(target.startsWith('/') ? target : this.getParentPath(linkPath) + '/' + target);
|
|
401
459
|
}
|
|
402
460
|
symlinkSync(target: string, path: string): void {
|
|
403
461
|
const normalized = this.normalizePath(path);
|
|
462
|
+
this.assertWritable(normalized, 'symlink');
|
|
404
463
|
const parentPath = this.getParentPath(normalized);
|
|
405
464
|
const basename = this.getBasename(normalized);
|
|
406
465
|
const parent = this.getNode(parentPath);
|
|
@@ -472,12 +531,14 @@ export class VirtualFS {
|
|
|
472
531
|
* Get stats for path
|
|
473
532
|
*/
|
|
474
533
|
chmodSync(path: string, mode: number): void {
|
|
534
|
+
this.assertWritable(path, 'chmod');
|
|
475
535
|
const node = this.getNode(path);
|
|
476
536
|
if (!node) throw createNodeError('ENOENT', 'chmod', path);
|
|
477
537
|
node.mode = mode & 0o7777;
|
|
478
538
|
}
|
|
479
539
|
|
|
480
540
|
utimesSync(path: string, atime: number | Date, mtime: number | Date): void {
|
|
541
|
+
this.assertWritable(path, 'utime');
|
|
481
542
|
const node = this.getNode(path);
|
|
482
543
|
if (!node) throw createNodeError('ENOENT', 'utimes', path);
|
|
483
544
|
node.atime = atime instanceof Date ? atime.getTime() : atime * 1000;
|
|
@@ -600,6 +661,9 @@ export class VirtualFS {
|
|
|
600
661
|
*/
|
|
601
662
|
mkdirSync(path: string, options?: { recursive?: boolean }): void {
|
|
602
663
|
const normalized = this.normalizePath(path);
|
|
664
|
+
// `mkdir -p` of a directory that is there, a mount point among them, is done
|
|
665
|
+
if (options?.recursive && this.getNode(normalized)?.type === 'directory') return;
|
|
666
|
+
this.assertWritable(normalized, 'mkdir');
|
|
603
667
|
|
|
604
668
|
if (options?.recursive) {
|
|
605
669
|
this.ensureDirectory(normalized);
|
|
@@ -657,6 +721,7 @@ export class VirtualFS {
|
|
|
657
721
|
*/
|
|
658
722
|
unlinkSync(path: string): void {
|
|
659
723
|
const normalized = this.normalizePath(path);
|
|
724
|
+
this.assertWritable(normalized, 'unlink');
|
|
660
725
|
const parentPath = this.getParentPath(normalized);
|
|
661
726
|
const basename = this.getBasename(normalized);
|
|
662
727
|
|
|
@@ -689,6 +754,7 @@ export class VirtualFS {
|
|
|
689
754
|
*/
|
|
690
755
|
rmdirSync(path: string): void {
|
|
691
756
|
const normalized = this.normalizePath(path);
|
|
757
|
+
this.assertWritable(normalized, 'rmdir');
|
|
692
758
|
const parentPath = this.getParentPath(normalized);
|
|
693
759
|
const basename = this.getBasename(normalized);
|
|
694
760
|
|
|
@@ -727,6 +793,8 @@ export class VirtualFS {
|
|
|
727
793
|
renameSync(oldPath: string, newPath: string): void {
|
|
728
794
|
const normalizedOld = this.normalizePath(oldPath);
|
|
729
795
|
const normalizedNew = this.normalizePath(newPath);
|
|
796
|
+
this.assertWritable(normalizedOld, 'rename');
|
|
797
|
+
this.assertWritable(normalizedNew, 'rename');
|
|
730
798
|
|
|
731
799
|
const oldParentPath = this.getParentPath(normalizedOld);
|
|
732
800
|
const oldBasename = this.getBasename(normalizedOld);
|
|
@@ -846,10 +914,14 @@ export class VirtualFS {
|
|
|
846
914
|
for (let hops = 0; hops <= 40; hops += 1) {
|
|
847
915
|
const segments = resolved.split('/').filter(Boolean);
|
|
848
916
|
let current: FSNode | undefined = this.root;
|
|
917
|
+
let mounted: { tree: MountedTree; at: number } | undefined;
|
|
849
918
|
let linked = false;
|
|
850
919
|
for (let index = 0; index < segments.length; index += 1) {
|
|
851
|
-
const child: FSNode | undefined =
|
|
920
|
+
const child: FSNode | undefined = mounted
|
|
921
|
+
? mounted.tree.node('/' + segments.slice(mounted.at + 1, index + 1).join('/'))
|
|
922
|
+
: current && current.children ? current.children.get(segments[index]) : void 0;
|
|
852
923
|
if (!child) throw createNodeError('ENOENT', 'realpath', path);
|
|
924
|
+
if (!mounted && child.mount) mounted = { tree: child.mount, at: index };
|
|
853
925
|
if (child.type === 'symlink') {
|
|
854
926
|
const linkPath = '/' + segments.slice(0, index + 1).join('/');
|
|
855
927
|
const rest = segments.slice(index + 1).join('/');
|