@ai-ecoverse/slicc-kernel 1.8.12 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/dist/index.js +424 -2
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +581 -68
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +308 -16
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +1058 -166
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +308 -16
- package/dist/process-worker.js.map +4 -4
- package/package.json +1 -1
package/dist/node.js
CHANGED
|
@@ -1,5 +1,512 @@
|
|
|
1
1
|
// src/node.ts
|
|
2
|
-
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { MessageChannel as MessageChannel2, Worker } from "node:worker_threads";
|
|
3
|
+
|
|
4
|
+
// src/fs/types.ts
|
|
5
|
+
function fsError(code, path) {
|
|
6
|
+
return Object.assign(new Error(`${code}: ${path}`), { code });
|
|
7
|
+
}
|
|
8
|
+
function normalizePath(path) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
for (const part of path.split("/")) {
|
|
11
|
+
if (part === "" || part === ".") continue;
|
|
12
|
+
if (part === "..") parts.pop();
|
|
13
|
+
else parts.push(part);
|
|
14
|
+
}
|
|
15
|
+
return `/${parts.join("/")}`;
|
|
16
|
+
}
|
|
17
|
+
function resolvePath(base, path) {
|
|
18
|
+
return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
|
|
19
|
+
}
|
|
20
|
+
function inodeOf(path) {
|
|
21
|
+
let h1 = 3735928559;
|
|
22
|
+
let h2 = 1103547991;
|
|
23
|
+
for (let i = 0; i < path.length; i++) {
|
|
24
|
+
const c = path.charCodeAt(i);
|
|
25
|
+
h1 = Math.imul(h1 ^ c, 2654435761);
|
|
26
|
+
h2 = Math.imul(h2 ^ c, 1597334677);
|
|
27
|
+
}
|
|
28
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
29
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
30
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/kernel/signals.ts
|
|
34
|
+
var SIG = {
|
|
35
|
+
HUP: 1,
|
|
36
|
+
INT: 2,
|
|
37
|
+
QUIT: 3,
|
|
38
|
+
ILL: 4,
|
|
39
|
+
TRAP: 5,
|
|
40
|
+
ABRT: 6,
|
|
41
|
+
BUS: 7,
|
|
42
|
+
FPE: 8,
|
|
43
|
+
KILL: 9,
|
|
44
|
+
USR1: 10,
|
|
45
|
+
SEGV: 11,
|
|
46
|
+
USR2: 12,
|
|
47
|
+
PIPE: 13,
|
|
48
|
+
ALRM: 14,
|
|
49
|
+
TERM: 15,
|
|
50
|
+
STKFLT: 16,
|
|
51
|
+
CHLD: 17,
|
|
52
|
+
CONT: 18,
|
|
53
|
+
STOP: 19,
|
|
54
|
+
TSTP: 20,
|
|
55
|
+
TTIN: 21,
|
|
56
|
+
TTOU: 22,
|
|
57
|
+
URG: 23,
|
|
58
|
+
XCPU: 24,
|
|
59
|
+
XFSZ: 25,
|
|
60
|
+
VTALRM: 26,
|
|
61
|
+
PROF: 27,
|
|
62
|
+
WINCH: 28,
|
|
63
|
+
POLL: 29,
|
|
64
|
+
PWR: 30,
|
|
65
|
+
SYS: 31
|
|
66
|
+
};
|
|
67
|
+
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
68
|
+
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
69
|
+
function defaultAction(sig) {
|
|
70
|
+
if (sig === SIG.CONT) return "continue";
|
|
71
|
+
if (STOPPING.has(sig)) return "stop";
|
|
72
|
+
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
73
|
+
}
|
|
74
|
+
function isSignal(sig) {
|
|
75
|
+
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
76
|
+
}
|
|
77
|
+
function sigbit(sig) {
|
|
78
|
+
return 1 << sig;
|
|
79
|
+
}
|
|
80
|
+
var SIGNAL_BY_NAME = {
|
|
81
|
+
SIGINT: SIG.INT,
|
|
82
|
+
SIGTERM: SIG.TERM,
|
|
83
|
+
SIGKILL: SIG.KILL,
|
|
84
|
+
SIGSTOP: SIG.STOP,
|
|
85
|
+
SIGCONT: SIG.CONT
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/body-idle.ts
|
|
89
|
+
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
90
|
+
function idleFailure(ms) {
|
|
91
|
+
return Object.assign(new Error(`the response body sent no data for ${ms} ms`), {
|
|
92
|
+
code: "ETIMEDOUT"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function readWithin(reader, ms) {
|
|
96
|
+
if (!(ms > 0 && ms < Number.POSITIVE_INFINITY)) return reader.read();
|
|
97
|
+
let timer;
|
|
98
|
+
const idle = new Promise((_, reject) => {
|
|
99
|
+
timer = setTimeout(() => reject(idleFailure(ms)), ms);
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
return await Promise.race([reader.read(), idle]);
|
|
103
|
+
} finally {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/transport.ts
|
|
109
|
+
var MAX_REQUEST_BODY = 64 * 1024 * 1024;
|
|
110
|
+
function joinHeaders(headers) {
|
|
111
|
+
const out = new Headers();
|
|
112
|
+
for (const [name2, value] of headers) {
|
|
113
|
+
try {
|
|
114
|
+
out.append(name2, value);
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
async function* chunks(body, idleMs) {
|
|
121
|
+
if (!body) return;
|
|
122
|
+
const reader = body.getReader();
|
|
123
|
+
try {
|
|
124
|
+
for (let next = await readWithin(reader, idleMs); !next.done; next = await readWithin(reader, idleMs)) {
|
|
125
|
+
if (next.value.byteLength > 0) yield next.value;
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
await reader.cancel().catch(() => void 0);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function fetchTransport(options = {}) {
|
|
132
|
+
const send = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
133
|
+
return {
|
|
134
|
+
traits: {
|
|
135
|
+
manualRedirects: false,
|
|
136
|
+
encodedBodies: false,
|
|
137
|
+
maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
|
|
138
|
+
crossOrigin: "cors"
|
|
139
|
+
},
|
|
140
|
+
async fetch(request) {
|
|
141
|
+
let response;
|
|
142
|
+
try {
|
|
143
|
+
response = await send(request.url, {
|
|
144
|
+
method: request.method,
|
|
145
|
+
headers: joinHeaders(request.headers),
|
|
146
|
+
...request.body ? { body: request.body } : {},
|
|
147
|
+
signal: request.signal,
|
|
148
|
+
redirect: "follow",
|
|
149
|
+
credentials: "omit",
|
|
150
|
+
mode: "cors"
|
|
151
|
+
});
|
|
152
|
+
} catch (e) {
|
|
153
|
+
if (request.signal.aborted) throw e;
|
|
154
|
+
throw Object.assign(
|
|
155
|
+
new Error(
|
|
156
|
+
`fetch ${request.url} failed (unreachable, or not allowed by CORS)${options.hint ? `: ${options.hint}` : ""}`
|
|
157
|
+
),
|
|
158
|
+
{ status: 502 }
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const headers = [];
|
|
162
|
+
response.headers.forEach((value, name2) => {
|
|
163
|
+
headers.push([name2, value]);
|
|
164
|
+
});
|
|
165
|
+
const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
|
|
166
|
+
return {
|
|
167
|
+
status: response.status,
|
|
168
|
+
statusText: response.statusText,
|
|
169
|
+
headers,
|
|
170
|
+
body,
|
|
171
|
+
cancel: async () => {
|
|
172
|
+
await body.return(void 0);
|
|
173
|
+
await response.body?.cancel().catch(() => void 0);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function serveTransport(peer, transport) {
|
|
180
|
+
const open = /* @__PURE__ */ new Map();
|
|
181
|
+
const fail2 = (nid, e) => {
|
|
182
|
+
open.delete(nid);
|
|
183
|
+
const { status, code } = e;
|
|
184
|
+
peer.postMessage({
|
|
185
|
+
net: "error",
|
|
186
|
+
nid,
|
|
187
|
+
message: e instanceof Error ? e.message : String(e),
|
|
188
|
+
...typeof status === "number" ? { status } : {},
|
|
189
|
+
...typeof code === "string" ? { code } : {}
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
const start = async (call) => {
|
|
193
|
+
const abort = new AbortController();
|
|
194
|
+
const entry = { abort };
|
|
195
|
+
open.set(call.nid, entry);
|
|
196
|
+
try {
|
|
197
|
+
const response = await transport.fetch({
|
|
198
|
+
url: call.url,
|
|
199
|
+
method: call.method,
|
|
200
|
+
headers: call.headers,
|
|
201
|
+
...call.body ? { body: call.body } : {},
|
|
202
|
+
signal: abort.signal
|
|
203
|
+
});
|
|
204
|
+
if (!open.has(call.nid)) {
|
|
205
|
+
await response.cancel();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
open.set(call.nid, { abort, body: response.body[Symbol.asyncIterator](), response });
|
|
209
|
+
peer.postMessage({
|
|
210
|
+
net: "head",
|
|
211
|
+
nid: call.nid,
|
|
212
|
+
status: response.status,
|
|
213
|
+
statusText: response.statusText,
|
|
214
|
+
headers: response.headers
|
|
215
|
+
});
|
|
216
|
+
} catch (e) {
|
|
217
|
+
fail2(call.nid, e);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const read = async (nid) => {
|
|
221
|
+
const body = open.get(nid)?.body;
|
|
222
|
+
if (!body) return;
|
|
223
|
+
try {
|
|
224
|
+
const next = await body.next();
|
|
225
|
+
if (next.done) {
|
|
226
|
+
open.delete(nid);
|
|
227
|
+
peer.postMessage({ net: "end", nid });
|
|
228
|
+
} else {
|
|
229
|
+
const bytes = next.value.slice();
|
|
230
|
+
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
231
|
+
}
|
|
232
|
+
} catch (e) {
|
|
233
|
+
fail2(nid, e);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
const drop = (nid) => {
|
|
237
|
+
const entry = open.get(nid);
|
|
238
|
+
open.delete(nid);
|
|
239
|
+
entry?.abort.abort();
|
|
240
|
+
void entry?.response?.cancel().catch(() => void 0);
|
|
241
|
+
};
|
|
242
|
+
return {
|
|
243
|
+
answer(call) {
|
|
244
|
+
if (call.net === "fetch") void start(call);
|
|
245
|
+
else if (call.net === "read") void read(call.nid);
|
|
246
|
+
else drop(call.nid);
|
|
247
|
+
},
|
|
248
|
+
close() {
|
|
249
|
+
for (const nid of [...open.keys()]) drop(nid);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/client/protocol.ts
|
|
255
|
+
var PROTOCOL = [1, 1];
|
|
256
|
+
var FS_METHODS = [
|
|
257
|
+
"readFile",
|
|
258
|
+
"writeFile",
|
|
259
|
+
"stat",
|
|
260
|
+
"lstat",
|
|
261
|
+
"readdir",
|
|
262
|
+
"mkdir",
|
|
263
|
+
"rm",
|
|
264
|
+
"rename",
|
|
265
|
+
"realpath",
|
|
266
|
+
"symlink",
|
|
267
|
+
"readlink",
|
|
268
|
+
"exists"
|
|
269
|
+
];
|
|
270
|
+
function locksOf(scope = globalThis) {
|
|
271
|
+
const locks = scope.navigator?.locks;
|
|
272
|
+
return typeof locks?.request === "function" ? locks : void 0;
|
|
273
|
+
}
|
|
274
|
+
function versionError(theirs) {
|
|
275
|
+
const major = Array.isArray(theirs) ? theirs[0] : void 0;
|
|
276
|
+
if (major === PROTOCOL[0]) return void 0;
|
|
277
|
+
const named = typeof major === "number" ? `${major}.x` : "without a version";
|
|
278
|
+
return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/client/serve-client.ts
|
|
282
|
+
var MAX_LINKS = 40;
|
|
283
|
+
function act(session, req, signal) {
|
|
284
|
+
if (req.action === "write") session.write(req.bytes);
|
|
285
|
+
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
286
|
+
else if (req.action === "signal") session.signal(signal(req.signal));
|
|
287
|
+
else session.close();
|
|
288
|
+
}
|
|
289
|
+
async function realpath(fs, path) {
|
|
290
|
+
let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
|
|
291
|
+
let resolved = "";
|
|
292
|
+
for (let hops = 0; parts.length > 0; ) {
|
|
293
|
+
const [name2, ...rest] = parts;
|
|
294
|
+
const next = `${resolved}/${name2}`;
|
|
295
|
+
if (!(await fs.lstat(next)).isSymbolicLink) {
|
|
296
|
+
resolved = next;
|
|
297
|
+
parts = rest;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
|
|
301
|
+
const target = await fs.readlink(next);
|
|
302
|
+
const base = target.startsWith("/") ? "/" : resolved || "/";
|
|
303
|
+
parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest];
|
|
304
|
+
resolved = "";
|
|
305
|
+
}
|
|
306
|
+
return resolved || "/";
|
|
307
|
+
}
|
|
308
|
+
async function fsCall(fs, method, args) {
|
|
309
|
+
const path = (i) => fs.resolvePath("/", String(args[i]));
|
|
310
|
+
switch (method) {
|
|
311
|
+
case "readFile": {
|
|
312
|
+
const bytes = (await fs.readFileBuffer(path(0))).slice();
|
|
313
|
+
return { result: bytes, transfer: [bytes.buffer] };
|
|
314
|
+
}
|
|
315
|
+
case "writeFile":
|
|
316
|
+
await fs.writeFile(path(0), args[1]);
|
|
317
|
+
return { result: true };
|
|
318
|
+
case "stat":
|
|
319
|
+
return { result: await fs.stat(path(0)) };
|
|
320
|
+
case "lstat":
|
|
321
|
+
return { result: await fs.lstat(path(0)) };
|
|
322
|
+
case "readdir":
|
|
323
|
+
return { result: await fs.readdir(path(0)) };
|
|
324
|
+
case "mkdir":
|
|
325
|
+
await fs.mkdir(path(0), { recursive: true });
|
|
326
|
+
return { result: true };
|
|
327
|
+
case "rm":
|
|
328
|
+
await fs.rm(path(0), { recursive: true, force: args[1] === true });
|
|
329
|
+
return { result: true };
|
|
330
|
+
case "rename":
|
|
331
|
+
await fs.rename(path(0), path(1));
|
|
332
|
+
return { result: true };
|
|
333
|
+
case "realpath":
|
|
334
|
+
return { result: await realpath(fs, String(args[0])) };
|
|
335
|
+
case "symlink":
|
|
336
|
+
await fs.symlink(String(args[0]), path(1));
|
|
337
|
+
return { result: true };
|
|
338
|
+
case "readlink":
|
|
339
|
+
return { result: await fs.readlink(path(0)) };
|
|
340
|
+
default:
|
|
341
|
+
return { result: await fs.exists(path(0)) };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function serveClient(port, host) {
|
|
345
|
+
let state = "new";
|
|
346
|
+
const terminals = /* @__PURE__ */ new Map();
|
|
347
|
+
const groups = /* @__PURE__ */ new Set();
|
|
348
|
+
const watches = /* @__PURE__ */ new Map();
|
|
349
|
+
let net;
|
|
350
|
+
const ended = Promise.withResolvers();
|
|
351
|
+
const send = (message2, transfer = []) => {
|
|
352
|
+
if (state !== "closed") port.postMessage(message2, transfer);
|
|
353
|
+
};
|
|
354
|
+
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
355
|
+
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
356
|
+
if (state === "closed") return;
|
|
357
|
+
send({ bye });
|
|
358
|
+
state = "closed";
|
|
359
|
+
net?.close();
|
|
360
|
+
for (const unwatch of watches.values()) unwatch();
|
|
361
|
+
watches.clear();
|
|
362
|
+
for (const session of terminals.values()) session.close();
|
|
363
|
+
terminals.clear();
|
|
364
|
+
const pids = [...groups];
|
|
365
|
+
groups.clear();
|
|
366
|
+
if (kill && pids.length > 0) {
|
|
367
|
+
void host.launcher().then((launcher) => {
|
|
368
|
+
for (const pid of pids) launcher.kill(-pid, SIG.KILL);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
port.close?.();
|
|
372
|
+
ended.resolve();
|
|
373
|
+
}
|
|
374
|
+
async function hello(message2) {
|
|
375
|
+
const refused = versionError(message2.protocol);
|
|
376
|
+
if (refused) {
|
|
377
|
+
send({ hello: { protocol: PROTOCOL, error: refused } });
|
|
378
|
+
detach();
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const launcher = await host.launcher();
|
|
382
|
+
const transport = host.transport?.() ?? launcher.transport;
|
|
383
|
+
net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
|
|
384
|
+
state = "open";
|
|
385
|
+
if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
|
|
386
|
+
send({
|
|
387
|
+
hello: {
|
|
388
|
+
protocol: PROTOCOL,
|
|
389
|
+
traits: transport.traits,
|
|
390
|
+
...host.lock ? { lock: host.lock } : {}
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
async function terminal(req, l) {
|
|
395
|
+
const session = await l.openTerminal(req.argv, {
|
|
396
|
+
...req.options,
|
|
397
|
+
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
398
|
+
});
|
|
399
|
+
if (state === "closed") {
|
|
400
|
+
session.close();
|
|
401
|
+
} else {
|
|
402
|
+
terminals.set(req.id, session);
|
|
403
|
+
reply(req.id, { started: session.pid });
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
return { result: await session.exited };
|
|
407
|
+
} finally {
|
|
408
|
+
terminals.delete(req.id);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function spawn(req, l) {
|
|
412
|
+
const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
|
|
413
|
+
let leader;
|
|
414
|
+
const result = await l.run(req.argv, {
|
|
415
|
+
...req.options,
|
|
416
|
+
collect: false,
|
|
417
|
+
onStdout: out(1),
|
|
418
|
+
onStderr: out(2),
|
|
419
|
+
onStarted: (pid) => {
|
|
420
|
+
leader = pid;
|
|
421
|
+
if (state === "closed") return;
|
|
422
|
+
groups.add(pid);
|
|
423
|
+
reply(req.id, { started: pid });
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
427
|
+
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
428
|
+
return { result: result.status };
|
|
429
|
+
}
|
|
430
|
+
async function handle(req) {
|
|
431
|
+
const l = await host.launcher();
|
|
432
|
+
switch (req.op) {
|
|
433
|
+
case "spawn":
|
|
434
|
+
return spawn(req, l);
|
|
435
|
+
case "open-terminal":
|
|
436
|
+
return terminal(req, l);
|
|
437
|
+
case "terminal": {
|
|
438
|
+
const session = terminals.get(req.terminal);
|
|
439
|
+
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
440
|
+
act(session, req, host.signal);
|
|
441
|
+
return { result: true };
|
|
442
|
+
}
|
|
443
|
+
case "kill":
|
|
444
|
+
if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
445
|
+
return { result: true };
|
|
446
|
+
case "ps":
|
|
447
|
+
return {
|
|
448
|
+
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
449
|
+
};
|
|
450
|
+
case "fs":
|
|
451
|
+
if (!FS_METHODS.includes(req.method)) {
|
|
452
|
+
throw new Error(`unknown file system call ${req.method}`);
|
|
453
|
+
}
|
|
454
|
+
return fsCall(l.fs, req.method, req.args);
|
|
455
|
+
case "watch": {
|
|
456
|
+
const unwatch = l.watchers.watch(
|
|
457
|
+
req.paths,
|
|
458
|
+
{ recursive: req.recursive },
|
|
459
|
+
(change) => send({ watch: req.id, change })
|
|
460
|
+
);
|
|
461
|
+
watches.set(req.id, unwatch);
|
|
462
|
+
return { result: req.id };
|
|
463
|
+
}
|
|
464
|
+
case "unwatch":
|
|
465
|
+
watches.get(req.watch)?.();
|
|
466
|
+
watches.delete(req.watch);
|
|
467
|
+
return { result: true };
|
|
468
|
+
case "detach":
|
|
469
|
+
return { result: true };
|
|
470
|
+
default:
|
|
471
|
+
throw new Error(`unknown op ${req.op}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
port.addEventListener("message", (event) => {
|
|
475
|
+
const data = event.data;
|
|
476
|
+
if (state === "closed") return;
|
|
477
|
+
if (data.hello) {
|
|
478
|
+
if (state === "new") {
|
|
479
|
+
hello(data.hello).catch((err) => {
|
|
480
|
+
send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
|
|
481
|
+
detach();
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (state !== "open") return;
|
|
487
|
+
if (data.net !== void 0) {
|
|
488
|
+
net?.answer(data);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const req = data;
|
|
492
|
+
handle(req).then(
|
|
493
|
+
({ result, transfer }) => {
|
|
494
|
+
reply(req.id, { result }, transfer);
|
|
495
|
+
if (req.op === "detach") detach(req.kill === true);
|
|
496
|
+
},
|
|
497
|
+
(err) => {
|
|
498
|
+
const code = err?.code;
|
|
499
|
+
reply(req.id, {
|
|
500
|
+
error: err instanceof Error ? err.message : String(err),
|
|
501
|
+
...typeof code === "string" ? { code } : {}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
port.addEventListener("close", () => detach());
|
|
507
|
+
port.start?.();
|
|
508
|
+
return { detach, closed: ended.promise };
|
|
509
|
+
}
|
|
3
510
|
|
|
4
511
|
// src/fs/meta.ts
|
|
5
512
|
function parentOf(path) {
|
|
@@ -49,42 +556,13 @@ var MemoryMeta = class {
|
|
|
49
556
|
}
|
|
50
557
|
};
|
|
51
558
|
|
|
52
|
-
// src/fs/types.ts
|
|
53
|
-
function fsError(code, path) {
|
|
54
|
-
return Object.assign(new Error(`${code}: ${path}`), { code });
|
|
55
|
-
}
|
|
56
|
-
function normalizePath(path) {
|
|
57
|
-
const parts = [];
|
|
58
|
-
for (const part of path.split("/")) {
|
|
59
|
-
if (part === "" || part === ".") continue;
|
|
60
|
-
if (part === "..") parts.pop();
|
|
61
|
-
else parts.push(part);
|
|
62
|
-
}
|
|
63
|
-
return `/${parts.join("/")}`;
|
|
64
|
-
}
|
|
65
|
-
function resolvePath(base, path) {
|
|
66
|
-
return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
|
|
67
|
-
}
|
|
68
|
-
function inodeOf(path) {
|
|
69
|
-
let h1 = 3735928559;
|
|
70
|
-
let h2 = 1103547991;
|
|
71
|
-
for (let i = 0; i < path.length; i++) {
|
|
72
|
-
const c = path.charCodeAt(i);
|
|
73
|
-
h1 = Math.imul(h1 ^ c, 2654435761);
|
|
74
|
-
h2 = Math.imul(h2 ^ c, 1597334677);
|
|
75
|
-
}
|
|
76
|
-
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
77
|
-
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
78
|
-
return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
559
|
// src/fs/opfs.ts
|
|
82
560
|
var S_IFDIR = 16384;
|
|
83
561
|
var S_IFREG = 32768;
|
|
84
562
|
var S_IFLNK = 40960;
|
|
85
563
|
var PERM_MASK = 4095;
|
|
86
564
|
var READ_ATTEMPTS = 3;
|
|
87
|
-
var
|
|
565
|
+
var MAX_LINKS2 = 40;
|
|
88
566
|
var CHECK_MS = 1e3;
|
|
89
567
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
90
568
|
var ERRNO_BY_DOM_ERROR = {
|
|
@@ -235,7 +713,7 @@ var OpfsFs = class {
|
|
|
235
713
|
}
|
|
236
714
|
async locate(path, follow) {
|
|
237
715
|
let current = path;
|
|
238
|
-
for (let hops = 0; hops <=
|
|
716
|
+
for (let hops = 0; hops <= MAX_LINKS2; hops++) {
|
|
239
717
|
try {
|
|
240
718
|
return { path: current, handle: await this.handle(current) };
|
|
241
719
|
} catch (err) {
|
|
@@ -699,7 +1177,7 @@ async function scanCommands(fs, modules) {
|
|
|
699
1177
|
var S_IFDIR2 = 16384;
|
|
700
1178
|
var S_IFREG2 = 32768;
|
|
701
1179
|
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
702
|
-
var
|
|
1180
|
+
var MAX_LINKS3 = 40;
|
|
703
1181
|
function missing2(err) {
|
|
704
1182
|
return err?.code === "ENOENT";
|
|
705
1183
|
}
|
|
@@ -719,7 +1197,7 @@ function synthetic(directory, path) {
|
|
|
719
1197
|
}
|
|
720
1198
|
async function followLinks(fs, path) {
|
|
721
1199
|
let current = path;
|
|
722
|
-
for (let hops = 0; hops <
|
|
1200
|
+
for (let hops = 0; hops < MAX_LINKS3; hops++) {
|
|
723
1201
|
const st = await fs.lstat(current).catch(() => void 0);
|
|
724
1202
|
if (!st?.isSymbolicLink) return current;
|
|
725
1203
|
const parent = current.slice(0, current.lastIndexOf("/")) || "/";
|
|
@@ -807,6 +1285,89 @@ function withCommandDirs(fs, names) {
|
|
|
807
1285
|
};
|
|
808
1286
|
}
|
|
809
1287
|
|
|
1288
|
+
// src/fs/watch.ts
|
|
1289
|
+
var WATCH_BATCH_LIMIT = 1e3;
|
|
1290
|
+
var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
1291
|
+
var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
1292
|
+
function touches(watcher, changed) {
|
|
1293
|
+
return watcher.paths.some(
|
|
1294
|
+
(watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf3(changed) === watched)
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
var FsWatchers = class {
|
|
1298
|
+
watchers = /* @__PURE__ */ new Set();
|
|
1299
|
+
watch(paths, options, notify) {
|
|
1300
|
+
const watcher = {
|
|
1301
|
+
paths: paths.map(normalizePath),
|
|
1302
|
+
recursive: options.recursive === true,
|
|
1303
|
+
notify,
|
|
1304
|
+
pending: /* @__PURE__ */ new Set(),
|
|
1305
|
+
scheduled: false
|
|
1306
|
+
};
|
|
1307
|
+
this.watchers.add(watcher);
|
|
1308
|
+
return () => {
|
|
1309
|
+
this.watchers.delete(watcher);
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
changed(...paths) {
|
|
1313
|
+
for (const watcher of this.watchers) {
|
|
1314
|
+
for (const path of paths) {
|
|
1315
|
+
const changed = normalizePath(path);
|
|
1316
|
+
if (touches(watcher, changed)) watcher.pending.add(changed);
|
|
1317
|
+
}
|
|
1318
|
+
if (watcher.pending.size === 0 || watcher.scheduled) continue;
|
|
1319
|
+
watcher.scheduled = true;
|
|
1320
|
+
setTimeout(() => this.flush(watcher), 0);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
flush(watcher) {
|
|
1324
|
+
watcher.scheduled = false;
|
|
1325
|
+
const paths = [...watcher.pending];
|
|
1326
|
+
watcher.pending.clear();
|
|
1327
|
+
if (!this.watchers.has(watcher)) return;
|
|
1328
|
+
watcher.notify(paths.length > WATCH_BATCH_LIMIT ? { overflow: true } : { paths });
|
|
1329
|
+
}
|
|
1330
|
+
wrap(fs) {
|
|
1331
|
+
const after = (run2, changed) => async (...args) => {
|
|
1332
|
+
try {
|
|
1333
|
+
await run2(...args);
|
|
1334
|
+
} finally {
|
|
1335
|
+
this.changed(...changed(...args));
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
const overrides = {
|
|
1339
|
+
writeFile: after(fs.writeFile.bind(fs), (path) => [path]),
|
|
1340
|
+
mkdir: async (path, options) => {
|
|
1341
|
+
const created = [];
|
|
1342
|
+
if (options?.recursive) {
|
|
1343
|
+
const parts = normalizePath(path).split("/").filter(Boolean);
|
|
1344
|
+
for (let at = 1; at < parts.length; at++) {
|
|
1345
|
+
const ancestor = `/${parts.slice(0, at).join("/")}`;
|
|
1346
|
+
if (created.length > 0 || !await fs.exists(ancestor)) created.push(ancestor);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
try {
|
|
1350
|
+
await fs.mkdir(path, options);
|
|
1351
|
+
} finally {
|
|
1352
|
+
this.changed(...created, path);
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
rm: after(fs.rm.bind(fs), (path) => [path]),
|
|
1356
|
+
rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
|
|
1357
|
+
symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
|
|
1358
|
+
chmod: after(fs.chmod.bind(fs), (path) => [path]),
|
|
1359
|
+
utimes: after(fs.utimes.bind(fs), (path) => [path])
|
|
1360
|
+
};
|
|
1361
|
+
return new Proxy(fs, {
|
|
1362
|
+
get(target, key, receiver) {
|
|
1363
|
+
if (Object.hasOwn(overrides, key)) return overrides[key];
|
|
1364
|
+
const value = Reflect.get(target, key, receiver);
|
|
1365
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
|
|
810
1371
|
// src/kernel/pipe.ts
|
|
811
1372
|
var PIPE_CAPACITY = 64 * 1024;
|
|
812
1373
|
var PipeError = class extends Error {
|
|
@@ -3159,61 +3720,6 @@ var HttpHandles = class {
|
|
|
3159
3720
|
}
|
|
3160
3721
|
};
|
|
3161
3722
|
|
|
3162
|
-
// src/kernel/signals.ts
|
|
3163
|
-
var SIG = {
|
|
3164
|
-
HUP: 1,
|
|
3165
|
-
INT: 2,
|
|
3166
|
-
QUIT: 3,
|
|
3167
|
-
ILL: 4,
|
|
3168
|
-
TRAP: 5,
|
|
3169
|
-
ABRT: 6,
|
|
3170
|
-
BUS: 7,
|
|
3171
|
-
FPE: 8,
|
|
3172
|
-
KILL: 9,
|
|
3173
|
-
USR1: 10,
|
|
3174
|
-
SEGV: 11,
|
|
3175
|
-
USR2: 12,
|
|
3176
|
-
PIPE: 13,
|
|
3177
|
-
ALRM: 14,
|
|
3178
|
-
TERM: 15,
|
|
3179
|
-
STKFLT: 16,
|
|
3180
|
-
CHLD: 17,
|
|
3181
|
-
CONT: 18,
|
|
3182
|
-
STOP: 19,
|
|
3183
|
-
TSTP: 20,
|
|
3184
|
-
TTIN: 21,
|
|
3185
|
-
TTOU: 22,
|
|
3186
|
-
URG: 23,
|
|
3187
|
-
XCPU: 24,
|
|
3188
|
-
XFSZ: 25,
|
|
3189
|
-
VTALRM: 26,
|
|
3190
|
-
PROF: 27,
|
|
3191
|
-
WINCH: 28,
|
|
3192
|
-
POLL: 29,
|
|
3193
|
-
PWR: 30,
|
|
3194
|
-
SYS: 31
|
|
3195
|
-
};
|
|
3196
|
-
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
3197
|
-
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
3198
|
-
function defaultAction(sig) {
|
|
3199
|
-
if (sig === SIG.CONT) return "continue";
|
|
3200
|
-
if (STOPPING.has(sig)) return "stop";
|
|
3201
|
-
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
3202
|
-
}
|
|
3203
|
-
function isSignal(sig) {
|
|
3204
|
-
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
3205
|
-
}
|
|
3206
|
-
function sigbit(sig) {
|
|
3207
|
-
return 1 << sig;
|
|
3208
|
-
}
|
|
3209
|
-
var SIGNAL_BY_NAME = {
|
|
3210
|
-
SIGINT: SIG.INT,
|
|
3211
|
-
SIGTERM: SIG.TERM,
|
|
3212
|
-
SIGKILL: SIG.KILL,
|
|
3213
|
-
SIGSTOP: SIG.STOP,
|
|
3214
|
-
SIGCONT: SIG.CONT
|
|
3215
|
-
};
|
|
3216
|
-
|
|
3217
3723
|
// src/kernel/tty.ts
|
|
3218
3724
|
var ICRNL = 256;
|
|
3219
3725
|
var IUTF8 = 16384;
|
|
@@ -4416,6 +4922,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
4416
4922
|
"proc-captured",
|
|
4417
4923
|
"proc-fork",
|
|
4418
4924
|
"proc-kill",
|
|
4925
|
+
"proc-list",
|
|
4419
4926
|
"proc-exec",
|
|
4420
4927
|
"proc-setpgid",
|
|
4421
4928
|
"proc-getpgid",
|
|
@@ -4864,6 +5371,12 @@ var WasmProcess = class {
|
|
|
4864
5371
|
throw new KernelError("ESRCH");
|
|
4865
5372
|
}
|
|
4866
5373
|
return { ok: true, kind: "void" };
|
|
5374
|
+
case "proc-list":
|
|
5375
|
+
return {
|
|
5376
|
+
ok: true,
|
|
5377
|
+
kind: "json",
|
|
5378
|
+
json: this.options.processes?.() ?? { boot: 0, processes: [] }
|
|
5379
|
+
};
|
|
4867
5380
|
case "proc-alarm":
|
|
4868
5381
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
4869
5382
|
return { ok: true, kind: "void" };
|
|
@@ -4967,6 +5480,7 @@ function spawnWasmProcess(opts) {
|
|
|
4967
5480
|
forker: opts.forker,
|
|
4968
5481
|
fs: opts.fs,
|
|
4969
5482
|
kill: opts.kill,
|
|
5483
|
+
processes: opts.processes,
|
|
4970
5484
|
jobs: opts.jobs,
|
|
4971
5485
|
ptys: opts.ptys,
|
|
4972
5486
|
net: opts.net,
|
|
@@ -5111,6 +5625,9 @@ var JobTable = class {
|
|
|
5111
5625
|
this.members.set(pid, member);
|
|
5112
5626
|
return member;
|
|
5113
5627
|
}
|
|
5628
|
+
list() {
|
|
5629
|
+
return [...this.members.values()];
|
|
5630
|
+
}
|
|
5114
5631
|
remove(pid) {
|
|
5115
5632
|
this.members.delete(pid);
|
|
5116
5633
|
}
|
|
@@ -5441,16 +5958,19 @@ var Launcher = class {
|
|
|
5441
5958
|
processes = /* @__PURE__ */ new Map();
|
|
5442
5959
|
zombies = /* @__PURE__ */ new Set();
|
|
5443
5960
|
orphans = /* @__PURE__ */ new Set();
|
|
5961
|
+
described = /* @__PURE__ */ new Map();
|
|
5444
5962
|
jobs = new JobTable();
|
|
5445
5963
|
locks = new LockTable();
|
|
5446
5964
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
5447
5965
|
net = new LoopbackNet();
|
|
5448
5966
|
ca;
|
|
5449
5967
|
transport;
|
|
5968
|
+
watchers = new FsWatchers();
|
|
5450
5969
|
nextPid = 1e3;
|
|
5970
|
+
boot = Date.now();
|
|
5451
5971
|
terminals = 0;
|
|
5452
5972
|
constructor(options) {
|
|
5453
|
-
this.base = options.fs;
|
|
5973
|
+
this.base = this.watchers.wrap(options.fs);
|
|
5454
5974
|
this.createWorker = options.createWorker;
|
|
5455
5975
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
5456
5976
|
this.env = { ...networkEnv(), ...options.env };
|
|
@@ -5461,7 +5981,7 @@ var Launcher = class {
|
|
|
5461
5981
|
engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
|
|
5462
5982
|
ca: this.ca
|
|
5463
5983
|
});
|
|
5464
|
-
this.fs = withCommandDirs(
|
|
5984
|
+
this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
5465
5985
|
}
|
|
5466
5986
|
commands() {
|
|
5467
5987
|
this.catalog ??= scanCommands(this.base, this.modulesDir);
|
|
@@ -5619,6 +6139,7 @@ var Launcher = class {
|
|
|
5619
6139
|
spawner: this.spawner(pid, req.report),
|
|
5620
6140
|
forker: this.forker(pid, req),
|
|
5621
6141
|
kill: (target, sig) => this.kill(target, sig),
|
|
6142
|
+
processes: () => ({ boot: this.boot, processes: this.list() }),
|
|
5622
6143
|
jobs: this.jobs,
|
|
5623
6144
|
ptys: this.ptys,
|
|
5624
6145
|
net: this.net,
|
|
@@ -5630,17 +6151,57 @@ var Launcher = class {
|
|
|
5630
6151
|
});
|
|
5631
6152
|
this.processes.set(pid, handle);
|
|
5632
6153
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
6154
|
+
this.described.set(pid, {
|
|
6155
|
+
argv: [req.argv0, ...req.args],
|
|
6156
|
+
tty: terminal?.name ?? null,
|
|
6157
|
+
started: Date.now()
|
|
6158
|
+
});
|
|
5633
6159
|
void handle.exited.then(() => {
|
|
5634
6160
|
this.processes.delete(pid);
|
|
5635
|
-
if (req.ppid === void 0 || this.orphans.delete(pid)) this.
|
|
6161
|
+
if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
|
|
5636
6162
|
else this.zombies.add(pid);
|
|
5637
6163
|
});
|
|
5638
6164
|
return handle;
|
|
5639
6165
|
}
|
|
6166
|
+
forget(pid) {
|
|
6167
|
+
this.jobs.remove(pid);
|
|
6168
|
+
this.described.delete(pid);
|
|
6169
|
+
}
|
|
5640
6170
|
reaped(pid) {
|
|
5641
|
-
if (this.zombies.delete(pid)) this.
|
|
6171
|
+
if (this.zombies.delete(pid)) this.forget(pid);
|
|
5642
6172
|
else this.orphans.add(pid);
|
|
5643
6173
|
}
|
|
6174
|
+
list() {
|
|
6175
|
+
const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
|
|
6176
|
+
const rootOf = (member) => {
|
|
6177
|
+
let root = member;
|
|
6178
|
+
for (let up = members.get(root.execParent ?? -1); up; up = members.get(up.execParent ?? -1)) {
|
|
6179
|
+
root = up;
|
|
6180
|
+
}
|
|
6181
|
+
return root;
|
|
6182
|
+
};
|
|
6183
|
+
const shown = (pid) => {
|
|
6184
|
+
const member = pid === void 0 ? void 0 : members.get(pid);
|
|
6185
|
+
return member ? rootOf(member).pid : pid ?? 0;
|
|
6186
|
+
};
|
|
6187
|
+
const listed = [];
|
|
6188
|
+
for (const member of members.values()) {
|
|
6189
|
+
const described = this.described.get(member.pid);
|
|
6190
|
+
if (member.execed || !described) continue;
|
|
6191
|
+
const root = rootOf(member);
|
|
6192
|
+
listed.push({
|
|
6193
|
+
pid: root.pid,
|
|
6194
|
+
tid: member.pid,
|
|
6195
|
+
ppid: shown(root.ppid),
|
|
6196
|
+
pgid: member.pgid,
|
|
6197
|
+
sid: member.sid,
|
|
6198
|
+
...described,
|
|
6199
|
+
started: (this.described.get(root.pid) ?? described).started,
|
|
6200
|
+
state: this.processes.has(member.pid) ? "S" : "Z"
|
|
6201
|
+
});
|
|
6202
|
+
}
|
|
6203
|
+
return listed;
|
|
6204
|
+
}
|
|
5644
6205
|
spawner(ppid, report) {
|
|
5645
6206
|
return async (req, fds) => {
|
|
5646
6207
|
const planned = await this.plan(req.file, req.argv, req.cwd);
|
|
@@ -5718,8 +6279,9 @@ var Launcher = class {
|
|
|
5718
6279
|
const env = this.environment(cwd, options.env);
|
|
5719
6280
|
const out = [];
|
|
5720
6281
|
const err = [];
|
|
6282
|
+
const keep = options.collect !== false;
|
|
5721
6283
|
const collect = (chunks2, tee) => (bytes) => {
|
|
5722
|
-
chunks2.push(bytes);
|
|
6284
|
+
if (keep) chunks2.push(bytes);
|
|
5723
6285
|
tee?.(bytes);
|
|
5724
6286
|
};
|
|
5725
6287
|
const stdout = collect(out, options.onStdout);
|
|
@@ -5735,6 +6297,7 @@ var Launcher = class {
|
|
|
5735
6297
|
fds.installAt(1, sinkFile(stdout));
|
|
5736
6298
|
fds.installAt(2, sinkFile(stderr));
|
|
5737
6299
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
6300
|
+
options.onStarted?.(handle.pid);
|
|
5738
6301
|
const status = await handle.exited;
|
|
5739
6302
|
return { status, stdout: concat4(out), stderr: concat4(err) };
|
|
5740
6303
|
}
|
|
@@ -5876,6 +6439,118 @@ function memoryRoot() {
|
|
|
5876
6439
|
return new MemoryDirectory("", null);
|
|
5877
6440
|
}
|
|
5878
6441
|
|
|
6442
|
+
// src/kernel/net/remote-transport.ts
|
|
6443
|
+
var TransportError = class extends Error {
|
|
6444
|
+
status;
|
|
6445
|
+
code;
|
|
6446
|
+
constructor(message2, status, code) {
|
|
6447
|
+
super(message2);
|
|
6448
|
+
this.status = status;
|
|
6449
|
+
this.code = code;
|
|
6450
|
+
}
|
|
6451
|
+
};
|
|
6452
|
+
var RemoteTransport = class {
|
|
6453
|
+
traits;
|
|
6454
|
+
port;
|
|
6455
|
+
waiting = /* @__PURE__ */ new Map();
|
|
6456
|
+
nextId = 0;
|
|
6457
|
+
failure;
|
|
6458
|
+
constructor(port, traits) {
|
|
6459
|
+
this.port = port;
|
|
6460
|
+
this.traits = traits;
|
|
6461
|
+
}
|
|
6462
|
+
receive(reply) {
|
|
6463
|
+
const waiter = this.waiting.get(reply.nid);
|
|
6464
|
+
if (!waiter) return;
|
|
6465
|
+
this.waiting.delete(reply.nid);
|
|
6466
|
+
if (reply.net === "error") {
|
|
6467
|
+
waiter.reject(new TransportError(reply.message, reply.status, reply.code));
|
|
6468
|
+
} else waiter.resolve(reply);
|
|
6469
|
+
}
|
|
6470
|
+
fail(error) {
|
|
6471
|
+
this.failure = error;
|
|
6472
|
+
for (const waiter of this.waiting.values()) waiter.reject(error);
|
|
6473
|
+
this.waiting.clear();
|
|
6474
|
+
}
|
|
6475
|
+
ask(call) {
|
|
6476
|
+
if (this.failure) return Promise.reject(this.failure);
|
|
6477
|
+
return new Promise((resolve, reject) => {
|
|
6478
|
+
this.waiting.set(call.nid, { resolve, reject });
|
|
6479
|
+
this.port.postMessage(call);
|
|
6480
|
+
});
|
|
6481
|
+
}
|
|
6482
|
+
cancel(nid) {
|
|
6483
|
+
const waiter = this.waiting.get(nid);
|
|
6484
|
+
this.waiting.delete(nid);
|
|
6485
|
+
waiter?.reject(new TransportError("the request was cancelled"));
|
|
6486
|
+
this.port.postMessage({ net: "cancel", nid });
|
|
6487
|
+
}
|
|
6488
|
+
async fetch(request) {
|
|
6489
|
+
const nid = ++this.nextId;
|
|
6490
|
+
const { signal } = request;
|
|
6491
|
+
signal.throwIfAborted();
|
|
6492
|
+
let finished = false;
|
|
6493
|
+
const abort = () => {
|
|
6494
|
+
finished = true;
|
|
6495
|
+
this.cancel(nid);
|
|
6496
|
+
};
|
|
6497
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6498
|
+
const finish = () => {
|
|
6499
|
+
if (finished) return;
|
|
6500
|
+
finished = true;
|
|
6501
|
+
signal.removeEventListener("abort", abort);
|
|
6502
|
+
};
|
|
6503
|
+
let head;
|
|
6504
|
+
try {
|
|
6505
|
+
head = await this.ask({
|
|
6506
|
+
net: "fetch",
|
|
6507
|
+
nid,
|
|
6508
|
+
url: request.url,
|
|
6509
|
+
method: request.method,
|
|
6510
|
+
headers: request.headers,
|
|
6511
|
+
...request.body ? { body: request.body } : {}
|
|
6512
|
+
});
|
|
6513
|
+
} catch (e) {
|
|
6514
|
+
finish();
|
|
6515
|
+
throw e;
|
|
6516
|
+
}
|
|
6517
|
+
const { status, statusText, headers } = head;
|
|
6518
|
+
const read = async () => {
|
|
6519
|
+
if (finished) return void 0;
|
|
6520
|
+
const reply = await this.ask({ net: "read", nid }).catch((e) => {
|
|
6521
|
+
finish();
|
|
6522
|
+
throw e;
|
|
6523
|
+
});
|
|
6524
|
+
if (reply.net === "chunk") return reply.bytes;
|
|
6525
|
+
finish();
|
|
6526
|
+
return void 0;
|
|
6527
|
+
};
|
|
6528
|
+
const cancel = async () => {
|
|
6529
|
+
if (finished) return;
|
|
6530
|
+
finish();
|
|
6531
|
+
this.cancel(nid);
|
|
6532
|
+
};
|
|
6533
|
+
return {
|
|
6534
|
+
status,
|
|
6535
|
+
statusText,
|
|
6536
|
+
headers,
|
|
6537
|
+
cancel,
|
|
6538
|
+
body: {
|
|
6539
|
+
[Symbol.asyncIterator]: () => ({
|
|
6540
|
+
next: async () => {
|
|
6541
|
+
const bytes = await read();
|
|
6542
|
+
return bytes ? { value: bytes, done: false } : { value: void 0, done: true };
|
|
6543
|
+
},
|
|
6544
|
+
return: async () => {
|
|
6545
|
+
await cancel();
|
|
6546
|
+
return { value: void 0, done: true };
|
|
6547
|
+
}
|
|
6548
|
+
})
|
|
6549
|
+
}
|
|
6550
|
+
};
|
|
6551
|
+
}
|
|
6552
|
+
};
|
|
6553
|
+
|
|
5879
6554
|
// src/serve.ts
|
|
5880
6555
|
function signalNumber(name2) {
|
|
5881
6556
|
const key = name2.slice(3);
|
|
@@ -5884,97 +6559,298 @@ function signalNumber(name2) {
|
|
|
5884
6559
|
return SIG[key];
|
|
5885
6560
|
}
|
|
5886
6561
|
|
|
5887
|
-
// src/
|
|
5888
|
-
var
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
}
|
|
5893
|
-
}
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
try {
|
|
5901
|
-
return await Promise.race([reader.read(), idle]);
|
|
5902
|
-
} finally {
|
|
5903
|
-
clearTimeout(timer);
|
|
6562
|
+
// src/client/attach.ts
|
|
6563
|
+
var KernelGoneError = class extends Error {
|
|
6564
|
+
constructor(message2 = "the slicc-kernel this client was attached to is gone") {
|
|
6565
|
+
super(message2);
|
|
6566
|
+
this.name = "KernelGoneError";
|
|
6567
|
+
}
|
|
6568
|
+
};
|
|
6569
|
+
var KernelCallError = class extends Error {
|
|
6570
|
+
code;
|
|
6571
|
+
constructor(message2, code) {
|
|
6572
|
+
super(message2);
|
|
6573
|
+
this.name = "KernelCallError";
|
|
6574
|
+
this.code = code;
|
|
5904
6575
|
}
|
|
6576
|
+
};
|
|
6577
|
+
var encoder4 = new TextEncoder();
|
|
6578
|
+
var bytesOf2 = (data) => typeof data === "string" ? encoder4.encode(data) : data;
|
|
6579
|
+
var UNAVAILABLE = {
|
|
6580
|
+
manualRedirects: false,
|
|
6581
|
+
encodedBodies: false,
|
|
6582
|
+
maxRequestBody: 0,
|
|
6583
|
+
unavailable: true
|
|
6584
|
+
};
|
|
6585
|
+
async function holdLock(locks) {
|
|
6586
|
+
if (!locks) return { release() {
|
|
6587
|
+
} };
|
|
6588
|
+
const name2 = `slicc-kernel-client:${crypto.randomUUID()}`;
|
|
6589
|
+
const granted = Promise.withResolvers();
|
|
6590
|
+
const held = Promise.withResolvers();
|
|
6591
|
+
void locks.request(name2, () => {
|
|
6592
|
+
granted.resolve();
|
|
6593
|
+
return held.promise;
|
|
6594
|
+
});
|
|
6595
|
+
await granted.promise;
|
|
6596
|
+
return { name: name2, release: () => held.resolve() };
|
|
5905
6597
|
}
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
try {
|
|
5913
|
-
out.append(name2, value);
|
|
5914
|
-
} catch {
|
|
5915
|
-
}
|
|
6598
|
+
function concat5(chunks2) {
|
|
6599
|
+
const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
|
|
6600
|
+
let at = 0;
|
|
6601
|
+
for (const chunk2 of chunks2) {
|
|
6602
|
+
out.set(chunk2, at);
|
|
6603
|
+
at += chunk2.length;
|
|
5916
6604
|
}
|
|
5917
6605
|
return out;
|
|
5918
6606
|
}
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
6607
|
+
var Channel = class {
|
|
6608
|
+
greeted = Promise.withResolvers();
|
|
6609
|
+
ended = Promise.withResolvers();
|
|
6610
|
+
remote;
|
|
6611
|
+
failure;
|
|
6612
|
+
pending = /* @__PURE__ */ new Map();
|
|
6613
|
+
watching = /* @__PURE__ */ new Map();
|
|
6614
|
+
protocol = PROTOCOL;
|
|
6615
|
+
nextId = 0;
|
|
6616
|
+
port;
|
|
6617
|
+
release;
|
|
6618
|
+
constructor(port, release) {
|
|
6619
|
+
this.port = port;
|
|
6620
|
+
this.release = release;
|
|
6621
|
+
this.greeted.promise.catch(() => void 0);
|
|
6622
|
+
port.addEventListener("message", (event) => this.receive(event.data));
|
|
6623
|
+
port.addEventListener("close", () => this.end(new KernelGoneError()));
|
|
6624
|
+
port.start?.();
|
|
6625
|
+
}
|
|
6626
|
+
post(message2) {
|
|
6627
|
+
if (!this.failure) this.port.postMessage(message2);
|
|
6628
|
+
}
|
|
6629
|
+
end(error) {
|
|
6630
|
+
if (this.failure) return;
|
|
6631
|
+
this.failure = error;
|
|
6632
|
+
this.greeted.reject(error);
|
|
6633
|
+
this.remote?.fail(error);
|
|
6634
|
+
for (const call of this.pending.values()) call.reject(error);
|
|
6635
|
+
this.pending.clear();
|
|
6636
|
+
this.release();
|
|
6637
|
+
this.port.close?.();
|
|
6638
|
+
this.ended.resolve(error);
|
|
6639
|
+
}
|
|
6640
|
+
request(call, handlers = {}) {
|
|
6641
|
+
const id = ++this.nextId;
|
|
6642
|
+
const { failure } = this;
|
|
6643
|
+
if (failure) return { id, done: Promise.reject(failure) };
|
|
6644
|
+
const done4 = new Promise((resolve, reject) => {
|
|
6645
|
+
this.pending.set(id, { resolve, reject, ...handlers });
|
|
6646
|
+
this.port.postMessage({ ...call, id });
|
|
6647
|
+
});
|
|
6648
|
+
return { id, done: done4 };
|
|
6649
|
+
}
|
|
6650
|
+
call(call) {
|
|
6651
|
+
return this.request(call).done;
|
|
6652
|
+
}
|
|
6653
|
+
receive(data) {
|
|
6654
|
+
if (data.watch !== void 0) {
|
|
6655
|
+
this.watching.get(data.watch)?.(data.change);
|
|
6656
|
+
return;
|
|
5925
6657
|
}
|
|
5926
|
-
|
|
5927
|
-
|
|
6658
|
+
if (data.bye !== void 0) {
|
|
6659
|
+
this.end(new KernelGoneError(data.bye));
|
|
6660
|
+
return;
|
|
6661
|
+
}
|
|
6662
|
+
if (data.hello) {
|
|
6663
|
+
this.greeted.resolve(data.hello);
|
|
6664
|
+
return;
|
|
6665
|
+
}
|
|
6666
|
+
if (data.net !== void 0) {
|
|
6667
|
+
this.remote?.receive(data);
|
|
6668
|
+
return;
|
|
6669
|
+
}
|
|
6670
|
+
const reply = data;
|
|
6671
|
+
const call = this.pending.get(reply.id);
|
|
6672
|
+
if (!call) return;
|
|
6673
|
+
if (reply.fd !== void 0) {
|
|
6674
|
+
call.output?.(reply.fd, reply.bytes);
|
|
6675
|
+
return;
|
|
6676
|
+
}
|
|
6677
|
+
if (reply.started !== void 0) {
|
|
6678
|
+
call.started?.(reply.started);
|
|
6679
|
+
return;
|
|
6680
|
+
}
|
|
6681
|
+
this.pending.delete(reply.id);
|
|
6682
|
+
if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
|
|
6683
|
+
else call.resolve(reply.result);
|
|
5928
6684
|
}
|
|
6685
|
+
};
|
|
6686
|
+
async function spawnOn(channel, argv, opts) {
|
|
6687
|
+
const { onStdout, onStderr, stdin, group: _, ...rest } = opts;
|
|
6688
|
+
const started = Promise.withResolvers();
|
|
6689
|
+
const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf2(stdin) } : {} };
|
|
6690
|
+
const { done: done4 } = channel.request(
|
|
6691
|
+
{ op: "spawn", argv, options },
|
|
6692
|
+
{
|
|
6693
|
+
output: (fd, bytes) => (fd === 1 ? onStdout : onStderr)?.(bytes),
|
|
6694
|
+
started: started.resolve
|
|
6695
|
+
}
|
|
6696
|
+
);
|
|
6697
|
+
done4.catch(started.reject);
|
|
6698
|
+
const pid = await started.promise;
|
|
6699
|
+
return {
|
|
6700
|
+
pid,
|
|
6701
|
+
pgid: pid,
|
|
6702
|
+
exited: done4,
|
|
6703
|
+
signal: async (name2 = "SIGTERM") => void await channel.call({ op: "kill", pid: -pid, signal: name2 })
|
|
6704
|
+
};
|
|
5929
6705
|
}
|
|
5930
|
-
function
|
|
5931
|
-
const
|
|
6706
|
+
async function runOn(channel, argv, opts) {
|
|
6707
|
+
const { onStdout, onStderr, ...rest } = opts;
|
|
6708
|
+
const chunks2 = { 1: [], 2: [] };
|
|
6709
|
+
const live = { 1: new TextDecoder(), 2: new TextDecoder() };
|
|
6710
|
+
const tee = (fd, on) => (bytes) => {
|
|
6711
|
+
chunks2[fd].push(bytes);
|
|
6712
|
+
on?.(live[fd].decode(bytes, { stream: true }));
|
|
6713
|
+
};
|
|
6714
|
+
const child = await spawnOn(channel, argv, {
|
|
6715
|
+
...rest,
|
|
6716
|
+
onStdout: tee(1, onStdout),
|
|
6717
|
+
onStderr: tee(2, onStderr)
|
|
6718
|
+
});
|
|
6719
|
+
const status = await child.exited;
|
|
6720
|
+
const text = (fd) => new TextDecoder().decode(concat5(chunks2[fd]));
|
|
6721
|
+
return { pid: child.pid, status, stdout: text(1), stderr: text(2) };
|
|
6722
|
+
}
|
|
6723
|
+
async function terminalOn(channel, argv, opts) {
|
|
6724
|
+
const { onData, ...options } = opts;
|
|
6725
|
+
let listener = onData ?? null;
|
|
6726
|
+
const backlog = [];
|
|
6727
|
+
const started = Promise.withResolvers();
|
|
6728
|
+
const { id, done: done4 } = channel.request(
|
|
6729
|
+
{ op: "open-terminal", argv, options },
|
|
6730
|
+
{
|
|
6731
|
+
output: (_fd, bytes) => listener ? listener(bytes) : backlog.push(bytes),
|
|
6732
|
+
started: started.resolve
|
|
6733
|
+
}
|
|
6734
|
+
);
|
|
6735
|
+
done4.catch(started.reject);
|
|
6736
|
+
const pid = await started.promise;
|
|
6737
|
+
const send = (action) => void channel.call({ op: "terminal", terminal: id, ...action }).catch(() => void 0);
|
|
5932
6738
|
return {
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
crossOrigin: "cors"
|
|
6739
|
+
pid,
|
|
6740
|
+
exited: done4,
|
|
6741
|
+
get onData() {
|
|
6742
|
+
return listener;
|
|
5938
6743
|
},
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
6744
|
+
set onData(next) {
|
|
6745
|
+
listener = next;
|
|
6746
|
+
if (next) for (const bytes of backlog.splice(0)) next(bytes);
|
|
6747
|
+
},
|
|
6748
|
+
write: (data) => send({ action: "write", bytes: bytesOf2(data) }),
|
|
6749
|
+
resize: (cols, rows) => send({ action: "resize", cols, rows }),
|
|
6750
|
+
signal: (name2) => send({ action: "signal", signal: name2 }),
|
|
6751
|
+
close: () => send({ action: "close" })
|
|
6752
|
+
};
|
|
6753
|
+
}
|
|
6754
|
+
function fsOn(channel) {
|
|
6755
|
+
const fs = (method, ...args) => channel.call({ op: "fs", method, args });
|
|
6756
|
+
const done4 = async (method, ...args) => void await fs(method, ...args);
|
|
6757
|
+
return {
|
|
6758
|
+
readFile: (path) => fs("readFile", path),
|
|
6759
|
+
readText: async (path) => new TextDecoder().decode(await fs("readFile", path)),
|
|
6760
|
+
writeFile: (path, data) => done4("writeFile", path, data),
|
|
6761
|
+
stat: (path) => fs("stat", path),
|
|
6762
|
+
lstat: (path) => fs("lstat", path),
|
|
6763
|
+
readdir: (path) => fs("readdir", path),
|
|
6764
|
+
mkdir: (path) => done4("mkdir", path),
|
|
6765
|
+
rm: (path, o = {}) => done4("rm", path, o.force === true),
|
|
6766
|
+
rename: (from, to) => done4("rename", from, to),
|
|
6767
|
+
realpath: (path) => fs("realpath", path),
|
|
6768
|
+
symlink: (target, path) => done4("symlink", target, path),
|
|
6769
|
+
readlink: (path) => fs("readlink", path),
|
|
6770
|
+
exists: (path) => fs("exists", path),
|
|
6771
|
+
async watch(paths, options, onChange) {
|
|
6772
|
+
if (channel.protocol[1] < 1) {
|
|
6773
|
+
throw new KernelCallError(
|
|
6774
|
+
`this kernel speaks protocol ${channel.protocol.join(".")}, which has no watch`,
|
|
6775
|
+
"ENOSYS"
|
|
5958
6776
|
);
|
|
5959
6777
|
}
|
|
5960
|
-
const
|
|
5961
|
-
|
|
5962
|
-
|
|
6778
|
+
const { id, done: done5 } = channel.request({
|
|
6779
|
+
op: "watch",
|
|
6780
|
+
paths,
|
|
6781
|
+
recursive: options.recursive === true
|
|
5963
6782
|
});
|
|
5964
|
-
|
|
6783
|
+
channel.watching.set(id, onChange);
|
|
6784
|
+
try {
|
|
6785
|
+
await done5;
|
|
6786
|
+
} catch (error) {
|
|
6787
|
+
channel.watching.delete(id);
|
|
6788
|
+
throw error;
|
|
6789
|
+
}
|
|
5965
6790
|
return {
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
body,
|
|
5970
|
-
cancel: async () => {
|
|
5971
|
-
await body.return(void 0);
|
|
5972
|
-
await response.body?.cancel().catch(() => void 0);
|
|
6791
|
+
close: () => {
|
|
6792
|
+
if (!channel.watching.delete(id)) return;
|
|
6793
|
+
void channel.call({ op: "unwatch", watch: id }).catch(() => void 0);
|
|
5973
6794
|
}
|
|
5974
6795
|
};
|
|
5975
6796
|
}
|
|
5976
6797
|
};
|
|
5977
6798
|
}
|
|
6799
|
+
async function greet(channel, lock, timeoutMs) {
|
|
6800
|
+
channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
|
|
6801
|
+
const timer = setTimeout(
|
|
6802
|
+
() => channel.end(
|
|
6803
|
+
new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
|
|
6804
|
+
),
|
|
6805
|
+
timeoutMs
|
|
6806
|
+
);
|
|
6807
|
+
let hello;
|
|
6808
|
+
try {
|
|
6809
|
+
hello = await channel.greeted.promise;
|
|
6810
|
+
} finally {
|
|
6811
|
+
clearTimeout(timer);
|
|
6812
|
+
}
|
|
6813
|
+
const refused = hello.error ?? versionError(hello.protocol);
|
|
6814
|
+
if (refused) {
|
|
6815
|
+
const error = new Error(refused);
|
|
6816
|
+
channel.end(error);
|
|
6817
|
+
throw error;
|
|
6818
|
+
}
|
|
6819
|
+
return hello;
|
|
6820
|
+
}
|
|
6821
|
+
async function attachKernel(port, options = {}) {
|
|
6822
|
+
const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
|
|
6823
|
+
const own = await holdLock(locks);
|
|
6824
|
+
const channel = new Channel(port, own.release);
|
|
6825
|
+
const hello = await greet(channel, own.name, options.timeoutMs ?? 1e4);
|
|
6826
|
+
channel.protocol = hello.protocol;
|
|
6827
|
+
if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
|
|
6828
|
+
const transport = new RemoteTransport(
|
|
6829
|
+
{ postMessage: (call) => channel.post(call) },
|
|
6830
|
+
hello.traits ?? UNAVAILABLE
|
|
6831
|
+
);
|
|
6832
|
+
channel.remote = transport;
|
|
6833
|
+
return {
|
|
6834
|
+
protocol: hello.protocol,
|
|
6835
|
+
transport,
|
|
6836
|
+
closed: channel.ended.promise,
|
|
6837
|
+
spawn: (argv, opts = {}) => spawnOn(channel, argv, opts),
|
|
6838
|
+
run: (argv, opts = {}) => runOn(channel, argv, opts),
|
|
6839
|
+
openTerminal: (argv, opts = {}) => terminalOn(channel, argv, opts),
|
|
6840
|
+
ps: () => channel.call({ op: "ps" }),
|
|
6841
|
+
kill: async (pid, signal = "SIGTERM") => void await channel.call({ op: "kill", pid, signal }),
|
|
6842
|
+
fetch: (req) => {
|
|
6843
|
+
const { signal, ...rest } = req;
|
|
6844
|
+
return transport.fetch({ ...rest, signal: signal ?? new AbortController().signal });
|
|
6845
|
+
},
|
|
6846
|
+
fs: fsOn(channel),
|
|
6847
|
+
async close(o = {}) {
|
|
6848
|
+
if (channel.failure) return;
|
|
6849
|
+
await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
|
|
6850
|
+
channel.end(new KernelGoneError("this client is closed"));
|
|
6851
|
+
}
|
|
6852
|
+
};
|
|
6853
|
+
}
|
|
5978
6854
|
|
|
5979
6855
|
// src/node.ts
|
|
5980
6856
|
function nodeTransport() {
|
|
@@ -6033,6 +6909,7 @@ async function createNodeKernel(options = {}) {
|
|
|
6033
6909
|
await fs.reconcile();
|
|
6034
6910
|
const live = /* @__PURE__ */ new Set();
|
|
6035
6911
|
const pending = /* @__PURE__ */ new Set();
|
|
6912
|
+
const clients = /* @__PURE__ */ new Set();
|
|
6036
6913
|
let terminated = false;
|
|
6037
6914
|
const file = options.worker ?? new URL("./node-process-worker.js", import.meta.url);
|
|
6038
6915
|
const transport = options.network?.transport;
|
|
@@ -6102,14 +6979,29 @@ async function createNodeKernel(options = {}) {
|
|
|
6102
6979
|
await fs.writeFile(path, data);
|
|
6103
6980
|
},
|
|
6104
6981
|
readFile: async (path) => fs.readFileBuffer(path),
|
|
6982
|
+
async connect() {
|
|
6983
|
+
if (terminated) throw new Error(TERMINATED);
|
|
6984
|
+
const { port1, port2 } = new MessageChannel2();
|
|
6985
|
+
const served = serveClient(port1, {
|
|
6986
|
+
launcher: async () => launcher,
|
|
6987
|
+
signal: signalNumber
|
|
6988
|
+
});
|
|
6989
|
+
clients.add(served);
|
|
6990
|
+
void served.closed.then(() => clients.delete(served));
|
|
6991
|
+
return port2;
|
|
6992
|
+
},
|
|
6105
6993
|
terminate() {
|
|
6106
6994
|
terminated = true;
|
|
6995
|
+
for (const client of [...clients]) client.detach();
|
|
6107
6996
|
for (const reject of [...pending]) reject(new Error(TERMINATED));
|
|
6108
6997
|
for (const kill of [...live]) kill();
|
|
6109
6998
|
}
|
|
6110
6999
|
};
|
|
6111
7000
|
}
|
|
6112
7001
|
export {
|
|
7002
|
+
KernelCallError,
|
|
7003
|
+
KernelGoneError,
|
|
7004
|
+
attachKernel,
|
|
6113
7005
|
createNodeKernel,
|
|
6114
7006
|
fetchTransport,
|
|
6115
7007
|
memoryRoot,
|