@ai-ecoverse/slicc-kernel 1.8.12 → 1.9.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 +27 -1
- package/dist/index.js +391 -2
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +479 -66
- 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 +939 -180
- 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,496 @@
|
|
|
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, 0];
|
|
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
|
+
let net;
|
|
349
|
+
const ended = Promise.withResolvers();
|
|
350
|
+
const send = (message2, transfer = []) => {
|
|
351
|
+
if (state !== "closed") port.postMessage(message2, transfer);
|
|
352
|
+
};
|
|
353
|
+
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
354
|
+
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
355
|
+
if (state === "closed") return;
|
|
356
|
+
send({ bye });
|
|
357
|
+
state = "closed";
|
|
358
|
+
net?.close();
|
|
359
|
+
for (const session of terminals.values()) session.close();
|
|
360
|
+
terminals.clear();
|
|
361
|
+
const pids = [...groups];
|
|
362
|
+
groups.clear();
|
|
363
|
+
if (kill && pids.length > 0) {
|
|
364
|
+
void host.launcher().then((launcher) => {
|
|
365
|
+
for (const pid of pids) launcher.kill(-pid, SIG.KILL);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
port.close?.();
|
|
369
|
+
ended.resolve();
|
|
370
|
+
}
|
|
371
|
+
async function hello(message2) {
|
|
372
|
+
const refused = versionError(message2.protocol);
|
|
373
|
+
if (refused) {
|
|
374
|
+
send({ hello: { protocol: PROTOCOL, error: refused } });
|
|
375
|
+
detach();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const launcher = await host.launcher();
|
|
379
|
+
const transport = host.transport?.() ?? launcher.transport;
|
|
380
|
+
net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
|
|
381
|
+
state = "open";
|
|
382
|
+
if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
|
|
383
|
+
send({
|
|
384
|
+
hello: {
|
|
385
|
+
protocol: PROTOCOL,
|
|
386
|
+
traits: transport.traits,
|
|
387
|
+
...host.lock ? { lock: host.lock } : {}
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
async function terminal(req, l) {
|
|
392
|
+
const session = await l.openTerminal(req.argv, {
|
|
393
|
+
...req.options,
|
|
394
|
+
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
395
|
+
});
|
|
396
|
+
if (state === "closed") {
|
|
397
|
+
session.close();
|
|
398
|
+
} else {
|
|
399
|
+
terminals.set(req.id, session);
|
|
400
|
+
reply(req.id, { started: session.pid });
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
return { result: await session.exited };
|
|
404
|
+
} finally {
|
|
405
|
+
terminals.delete(req.id);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function spawn(req, l) {
|
|
409
|
+
const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
|
|
410
|
+
let leader;
|
|
411
|
+
const result = await l.run(req.argv, {
|
|
412
|
+
...req.options,
|
|
413
|
+
collect: false,
|
|
414
|
+
onStdout: out(1),
|
|
415
|
+
onStderr: out(2),
|
|
416
|
+
onStarted: (pid) => {
|
|
417
|
+
leader = pid;
|
|
418
|
+
if (state === "closed") return;
|
|
419
|
+
groups.add(pid);
|
|
420
|
+
reply(req.id, { started: pid });
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
424
|
+
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
425
|
+
return { result: result.status };
|
|
426
|
+
}
|
|
427
|
+
async function handle(req) {
|
|
428
|
+
const l = await host.launcher();
|
|
429
|
+
switch (req.op) {
|
|
430
|
+
case "spawn":
|
|
431
|
+
return spawn(req, l);
|
|
432
|
+
case "open-terminal":
|
|
433
|
+
return terminal(req, l);
|
|
434
|
+
case "terminal": {
|
|
435
|
+
const session = terminals.get(req.terminal);
|
|
436
|
+
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
437
|
+
act(session, req, host.signal);
|
|
438
|
+
return { result: true };
|
|
439
|
+
}
|
|
440
|
+
case "kill":
|
|
441
|
+
if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
442
|
+
return { result: true };
|
|
443
|
+
case "ps":
|
|
444
|
+
return {
|
|
445
|
+
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
446
|
+
};
|
|
447
|
+
case "fs":
|
|
448
|
+
if (!FS_METHODS.includes(req.method)) {
|
|
449
|
+
throw new Error(`unknown file system call ${req.method}`);
|
|
450
|
+
}
|
|
451
|
+
return fsCall(l.fs, req.method, req.args);
|
|
452
|
+
case "detach":
|
|
453
|
+
return { result: true };
|
|
454
|
+
default:
|
|
455
|
+
throw new Error(`unknown op ${req.op}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
port.addEventListener("message", (event) => {
|
|
459
|
+
const data = event.data;
|
|
460
|
+
if (state === "closed") return;
|
|
461
|
+
if (data.hello) {
|
|
462
|
+
if (state === "new") {
|
|
463
|
+
hello(data.hello).catch((err) => {
|
|
464
|
+
send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
|
|
465
|
+
detach();
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (state !== "open") return;
|
|
471
|
+
if (data.net !== void 0) {
|
|
472
|
+
net?.answer(data);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const req = data;
|
|
476
|
+
handle(req).then(
|
|
477
|
+
({ result, transfer }) => {
|
|
478
|
+
reply(req.id, { result }, transfer);
|
|
479
|
+
if (req.op === "detach") detach(req.kill === true);
|
|
480
|
+
},
|
|
481
|
+
(err) => {
|
|
482
|
+
const code = err?.code;
|
|
483
|
+
reply(req.id, {
|
|
484
|
+
error: err instanceof Error ? err.message : String(err),
|
|
485
|
+
...typeof code === "string" ? { code } : {}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
});
|
|
490
|
+
port.addEventListener("close", () => detach());
|
|
491
|
+
port.start?.();
|
|
492
|
+
return { detach, closed: ended.promise };
|
|
493
|
+
}
|
|
3
494
|
|
|
4
495
|
// src/fs/meta.ts
|
|
5
496
|
function parentOf(path) {
|
|
@@ -34,49 +525,20 @@ var MemoryMeta = class {
|
|
|
34
525
|
this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));
|
|
35
526
|
}
|
|
36
527
|
async remove(paths) {
|
|
37
|
-
for (const key of [...this.entries.keys()]) {
|
|
38
|
-
if (paths.some((path) => under(key, path))) this.entries.delete(key);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
async links(dir) {
|
|
42
|
-
return [...this.entries.values()].filter((entry) => entry.dir === dir);
|
|
43
|
-
}
|
|
44
|
-
async under(path) {
|
|
45
|
-
return [...this.entries.values()].filter((entry) => under(entry.path, path));
|
|
46
|
-
}
|
|
47
|
-
async all() {
|
|
48
|
-
return [...this.entries.values()];
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
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);
|
|
528
|
+
for (const key of [...this.entries.keys()]) {
|
|
529
|
+
if (paths.some((path) => under(key, path))) this.entries.delete(key);
|
|
530
|
+
}
|
|
62
531
|
}
|
|
63
|
-
|
|
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);
|
|
532
|
+
async links(dir) {
|
|
533
|
+
return [...this.entries.values()].filter((entry) => entry.dir === dir);
|
|
75
534
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
535
|
+
async under(path) {
|
|
536
|
+
return [...this.entries.values()].filter((entry) => under(entry.path, path));
|
|
537
|
+
}
|
|
538
|
+
async all() {
|
|
539
|
+
return [...this.entries.values()];
|
|
540
|
+
}
|
|
541
|
+
};
|
|
80
542
|
|
|
81
543
|
// src/fs/opfs.ts
|
|
82
544
|
var S_IFDIR = 16384;
|
|
@@ -84,7 +546,7 @@ var S_IFREG = 32768;
|
|
|
84
546
|
var S_IFLNK = 40960;
|
|
85
547
|
var PERM_MASK = 4095;
|
|
86
548
|
var READ_ATTEMPTS = 3;
|
|
87
|
-
var
|
|
549
|
+
var MAX_LINKS2 = 40;
|
|
88
550
|
var CHECK_MS = 1e3;
|
|
89
551
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
90
552
|
var ERRNO_BY_DOM_ERROR = {
|
|
@@ -235,7 +697,7 @@ var OpfsFs = class {
|
|
|
235
697
|
}
|
|
236
698
|
async locate(path, follow) {
|
|
237
699
|
let current = path;
|
|
238
|
-
for (let hops = 0; hops <=
|
|
700
|
+
for (let hops = 0; hops <= MAX_LINKS2; hops++) {
|
|
239
701
|
try {
|
|
240
702
|
return { path: current, handle: await this.handle(current) };
|
|
241
703
|
} catch (err) {
|
|
@@ -699,7 +1161,7 @@ async function scanCommands(fs, modules) {
|
|
|
699
1161
|
var S_IFDIR2 = 16384;
|
|
700
1162
|
var S_IFREG2 = 32768;
|
|
701
1163
|
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
702
|
-
var
|
|
1164
|
+
var MAX_LINKS3 = 40;
|
|
703
1165
|
function missing2(err) {
|
|
704
1166
|
return err?.code === "ENOENT";
|
|
705
1167
|
}
|
|
@@ -719,7 +1181,7 @@ function synthetic(directory, path) {
|
|
|
719
1181
|
}
|
|
720
1182
|
async function followLinks(fs, path) {
|
|
721
1183
|
let current = path;
|
|
722
|
-
for (let hops = 0; hops <
|
|
1184
|
+
for (let hops = 0; hops < MAX_LINKS3; hops++) {
|
|
723
1185
|
const st = await fs.lstat(current).catch(() => void 0);
|
|
724
1186
|
if (!st?.isSymbolicLink) return current;
|
|
725
1187
|
const parent = current.slice(0, current.lastIndexOf("/")) || "/";
|
|
@@ -3159,61 +3621,6 @@ var HttpHandles = class {
|
|
|
3159
3621
|
}
|
|
3160
3622
|
};
|
|
3161
3623
|
|
|
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
3624
|
// src/kernel/tty.ts
|
|
3218
3625
|
var ICRNL = 256;
|
|
3219
3626
|
var IUTF8 = 16384;
|
|
@@ -4416,6 +4823,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
4416
4823
|
"proc-captured",
|
|
4417
4824
|
"proc-fork",
|
|
4418
4825
|
"proc-kill",
|
|
4826
|
+
"proc-list",
|
|
4419
4827
|
"proc-exec",
|
|
4420
4828
|
"proc-setpgid",
|
|
4421
4829
|
"proc-getpgid",
|
|
@@ -4864,6 +5272,12 @@ var WasmProcess = class {
|
|
|
4864
5272
|
throw new KernelError("ESRCH");
|
|
4865
5273
|
}
|
|
4866
5274
|
return { ok: true, kind: "void" };
|
|
5275
|
+
case "proc-list":
|
|
5276
|
+
return {
|
|
5277
|
+
ok: true,
|
|
5278
|
+
kind: "json",
|
|
5279
|
+
json: this.options.processes?.() ?? { boot: 0, processes: [] }
|
|
5280
|
+
};
|
|
4867
5281
|
case "proc-alarm":
|
|
4868
5282
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
4869
5283
|
return { ok: true, kind: "void" };
|
|
@@ -4967,6 +5381,7 @@ function spawnWasmProcess(opts) {
|
|
|
4967
5381
|
forker: opts.forker,
|
|
4968
5382
|
fs: opts.fs,
|
|
4969
5383
|
kill: opts.kill,
|
|
5384
|
+
processes: opts.processes,
|
|
4970
5385
|
jobs: opts.jobs,
|
|
4971
5386
|
ptys: opts.ptys,
|
|
4972
5387
|
net: opts.net,
|
|
@@ -5111,6 +5526,9 @@ var JobTable = class {
|
|
|
5111
5526
|
this.members.set(pid, member);
|
|
5112
5527
|
return member;
|
|
5113
5528
|
}
|
|
5529
|
+
list() {
|
|
5530
|
+
return [...this.members.values()];
|
|
5531
|
+
}
|
|
5114
5532
|
remove(pid) {
|
|
5115
5533
|
this.members.delete(pid);
|
|
5116
5534
|
}
|
|
@@ -5441,6 +5859,7 @@ var Launcher = class {
|
|
|
5441
5859
|
processes = /* @__PURE__ */ new Map();
|
|
5442
5860
|
zombies = /* @__PURE__ */ new Set();
|
|
5443
5861
|
orphans = /* @__PURE__ */ new Set();
|
|
5862
|
+
described = /* @__PURE__ */ new Map();
|
|
5444
5863
|
jobs = new JobTable();
|
|
5445
5864
|
locks = new LockTable();
|
|
5446
5865
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
@@ -5448,6 +5867,7 @@ var Launcher = class {
|
|
|
5448
5867
|
ca;
|
|
5449
5868
|
transport;
|
|
5450
5869
|
nextPid = 1e3;
|
|
5870
|
+
boot = Date.now();
|
|
5451
5871
|
terminals = 0;
|
|
5452
5872
|
constructor(options) {
|
|
5453
5873
|
this.base = options.fs;
|
|
@@ -5619,6 +6039,7 @@ var Launcher = class {
|
|
|
5619
6039
|
spawner: this.spawner(pid, req.report),
|
|
5620
6040
|
forker: this.forker(pid, req),
|
|
5621
6041
|
kill: (target, sig) => this.kill(target, sig),
|
|
6042
|
+
processes: () => ({ boot: this.boot, processes: this.list() }),
|
|
5622
6043
|
jobs: this.jobs,
|
|
5623
6044
|
ptys: this.ptys,
|
|
5624
6045
|
net: this.net,
|
|
@@ -5630,17 +6051,57 @@ var Launcher = class {
|
|
|
5630
6051
|
});
|
|
5631
6052
|
this.processes.set(pid, handle);
|
|
5632
6053
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
6054
|
+
this.described.set(pid, {
|
|
6055
|
+
argv: [req.argv0, ...req.args],
|
|
6056
|
+
tty: terminal?.name ?? null,
|
|
6057
|
+
started: Date.now()
|
|
6058
|
+
});
|
|
5633
6059
|
void handle.exited.then(() => {
|
|
5634
6060
|
this.processes.delete(pid);
|
|
5635
|
-
if (req.ppid === void 0 || this.orphans.delete(pid)) this.
|
|
6061
|
+
if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
|
|
5636
6062
|
else this.zombies.add(pid);
|
|
5637
6063
|
});
|
|
5638
6064
|
return handle;
|
|
5639
6065
|
}
|
|
6066
|
+
forget(pid) {
|
|
6067
|
+
this.jobs.remove(pid);
|
|
6068
|
+
this.described.delete(pid);
|
|
6069
|
+
}
|
|
5640
6070
|
reaped(pid) {
|
|
5641
|
-
if (this.zombies.delete(pid)) this.
|
|
6071
|
+
if (this.zombies.delete(pid)) this.forget(pid);
|
|
5642
6072
|
else this.orphans.add(pid);
|
|
5643
6073
|
}
|
|
6074
|
+
list() {
|
|
6075
|
+
const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
|
|
6076
|
+
const rootOf = (member) => {
|
|
6077
|
+
let root = member;
|
|
6078
|
+
for (let up = members.get(root.execParent ?? -1); up; up = members.get(up.execParent ?? -1)) {
|
|
6079
|
+
root = up;
|
|
6080
|
+
}
|
|
6081
|
+
return root;
|
|
6082
|
+
};
|
|
6083
|
+
const shown = (pid) => {
|
|
6084
|
+
const member = pid === void 0 ? void 0 : members.get(pid);
|
|
6085
|
+
return member ? rootOf(member).pid : pid ?? 0;
|
|
6086
|
+
};
|
|
6087
|
+
const listed = [];
|
|
6088
|
+
for (const member of members.values()) {
|
|
6089
|
+
const described = this.described.get(member.pid);
|
|
6090
|
+
if (member.execed || !described) continue;
|
|
6091
|
+
const root = rootOf(member);
|
|
6092
|
+
listed.push({
|
|
6093
|
+
pid: root.pid,
|
|
6094
|
+
tid: member.pid,
|
|
6095
|
+
ppid: shown(root.ppid),
|
|
6096
|
+
pgid: member.pgid,
|
|
6097
|
+
sid: member.sid,
|
|
6098
|
+
...described,
|
|
6099
|
+
started: (this.described.get(root.pid) ?? described).started,
|
|
6100
|
+
state: this.processes.has(member.pid) ? "S" : "Z"
|
|
6101
|
+
});
|
|
6102
|
+
}
|
|
6103
|
+
return listed;
|
|
6104
|
+
}
|
|
5644
6105
|
spawner(ppid, report) {
|
|
5645
6106
|
return async (req, fds) => {
|
|
5646
6107
|
const planned = await this.plan(req.file, req.argv, req.cwd);
|
|
@@ -5718,8 +6179,9 @@ var Launcher = class {
|
|
|
5718
6179
|
const env = this.environment(cwd, options.env);
|
|
5719
6180
|
const out = [];
|
|
5720
6181
|
const err = [];
|
|
6182
|
+
const keep = options.collect !== false;
|
|
5721
6183
|
const collect = (chunks2, tee) => (bytes) => {
|
|
5722
|
-
chunks2.push(bytes);
|
|
6184
|
+
if (keep) chunks2.push(bytes);
|
|
5723
6185
|
tee?.(bytes);
|
|
5724
6186
|
};
|
|
5725
6187
|
const stdout = collect(out, options.onStdout);
|
|
@@ -5735,6 +6197,7 @@ var Launcher = class {
|
|
|
5735
6197
|
fds.installAt(1, sinkFile(stdout));
|
|
5736
6198
|
fds.installAt(2, sinkFile(stderr));
|
|
5737
6199
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
6200
|
+
options.onStarted?.(handle.pid);
|
|
5738
6201
|
const status = await handle.exited;
|
|
5739
6202
|
return { status, stdout: concat4(out), stderr: concat4(err) };
|
|
5740
6203
|
}
|
|
@@ -5876,6 +6339,118 @@ function memoryRoot() {
|
|
|
5876
6339
|
return new MemoryDirectory("", null);
|
|
5877
6340
|
}
|
|
5878
6341
|
|
|
6342
|
+
// src/kernel/net/remote-transport.ts
|
|
6343
|
+
var TransportError = class extends Error {
|
|
6344
|
+
status;
|
|
6345
|
+
code;
|
|
6346
|
+
constructor(message2, status, code) {
|
|
6347
|
+
super(message2);
|
|
6348
|
+
this.status = status;
|
|
6349
|
+
this.code = code;
|
|
6350
|
+
}
|
|
6351
|
+
};
|
|
6352
|
+
var RemoteTransport = class {
|
|
6353
|
+
traits;
|
|
6354
|
+
port;
|
|
6355
|
+
waiting = /* @__PURE__ */ new Map();
|
|
6356
|
+
nextId = 0;
|
|
6357
|
+
failure;
|
|
6358
|
+
constructor(port, traits) {
|
|
6359
|
+
this.port = port;
|
|
6360
|
+
this.traits = traits;
|
|
6361
|
+
}
|
|
6362
|
+
receive(reply) {
|
|
6363
|
+
const waiter = this.waiting.get(reply.nid);
|
|
6364
|
+
if (!waiter) return;
|
|
6365
|
+
this.waiting.delete(reply.nid);
|
|
6366
|
+
if (reply.net === "error") {
|
|
6367
|
+
waiter.reject(new TransportError(reply.message, reply.status, reply.code));
|
|
6368
|
+
} else waiter.resolve(reply);
|
|
6369
|
+
}
|
|
6370
|
+
fail(error) {
|
|
6371
|
+
this.failure = error;
|
|
6372
|
+
for (const waiter of this.waiting.values()) waiter.reject(error);
|
|
6373
|
+
this.waiting.clear();
|
|
6374
|
+
}
|
|
6375
|
+
ask(call) {
|
|
6376
|
+
if (this.failure) return Promise.reject(this.failure);
|
|
6377
|
+
return new Promise((resolve, reject) => {
|
|
6378
|
+
this.waiting.set(call.nid, { resolve, reject });
|
|
6379
|
+
this.port.postMessage(call);
|
|
6380
|
+
});
|
|
6381
|
+
}
|
|
6382
|
+
cancel(nid) {
|
|
6383
|
+
const waiter = this.waiting.get(nid);
|
|
6384
|
+
this.waiting.delete(nid);
|
|
6385
|
+
waiter?.reject(new TransportError("the request was cancelled"));
|
|
6386
|
+
this.port.postMessage({ net: "cancel", nid });
|
|
6387
|
+
}
|
|
6388
|
+
async fetch(request) {
|
|
6389
|
+
const nid = ++this.nextId;
|
|
6390
|
+
const { signal } = request;
|
|
6391
|
+
signal.throwIfAborted();
|
|
6392
|
+
let finished = false;
|
|
6393
|
+
const abort = () => {
|
|
6394
|
+
finished = true;
|
|
6395
|
+
this.cancel(nid);
|
|
6396
|
+
};
|
|
6397
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6398
|
+
const finish = () => {
|
|
6399
|
+
if (finished) return;
|
|
6400
|
+
finished = true;
|
|
6401
|
+
signal.removeEventListener("abort", abort);
|
|
6402
|
+
};
|
|
6403
|
+
let head;
|
|
6404
|
+
try {
|
|
6405
|
+
head = await this.ask({
|
|
6406
|
+
net: "fetch",
|
|
6407
|
+
nid,
|
|
6408
|
+
url: request.url,
|
|
6409
|
+
method: request.method,
|
|
6410
|
+
headers: request.headers,
|
|
6411
|
+
...request.body ? { body: request.body } : {}
|
|
6412
|
+
});
|
|
6413
|
+
} catch (e) {
|
|
6414
|
+
finish();
|
|
6415
|
+
throw e;
|
|
6416
|
+
}
|
|
6417
|
+
const { status, statusText, headers } = head;
|
|
6418
|
+
const read = async () => {
|
|
6419
|
+
if (finished) return void 0;
|
|
6420
|
+
const reply = await this.ask({ net: "read", nid }).catch((e) => {
|
|
6421
|
+
finish();
|
|
6422
|
+
throw e;
|
|
6423
|
+
});
|
|
6424
|
+
if (reply.net === "chunk") return reply.bytes;
|
|
6425
|
+
finish();
|
|
6426
|
+
return void 0;
|
|
6427
|
+
};
|
|
6428
|
+
const cancel = async () => {
|
|
6429
|
+
if (finished) return;
|
|
6430
|
+
finish();
|
|
6431
|
+
this.cancel(nid);
|
|
6432
|
+
};
|
|
6433
|
+
return {
|
|
6434
|
+
status,
|
|
6435
|
+
statusText,
|
|
6436
|
+
headers,
|
|
6437
|
+
cancel,
|
|
6438
|
+
body: {
|
|
6439
|
+
[Symbol.asyncIterator]: () => ({
|
|
6440
|
+
next: async () => {
|
|
6441
|
+
const bytes = await read();
|
|
6442
|
+
return bytes ? { value: bytes, done: false } : { value: void 0, done: true };
|
|
6443
|
+
},
|
|
6444
|
+
return: async () => {
|
|
6445
|
+
await cancel();
|
|
6446
|
+
return { value: void 0, done: true };
|
|
6447
|
+
}
|
|
6448
|
+
})
|
|
6449
|
+
}
|
|
6450
|
+
};
|
|
6451
|
+
}
|
|
6452
|
+
};
|
|
6453
|
+
|
|
5879
6454
|
// src/serve.ts
|
|
5880
6455
|
function signalNumber(name2) {
|
|
5881
6456
|
const key = name2.slice(3);
|
|
@@ -5884,94 +6459,262 @@ function signalNumber(name2) {
|
|
|
5884
6459
|
return SIG[key];
|
|
5885
6460
|
}
|
|
5886
6461
|
|
|
5887
|
-
// src/
|
|
5888
|
-
var
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
6462
|
+
// src/client/attach.ts
|
|
6463
|
+
var KernelGoneError = class extends Error {
|
|
6464
|
+
constructor(message2 = "the slicc-kernel this client was attached to is gone") {
|
|
6465
|
+
super(message2);
|
|
6466
|
+
this.name = "KernelGoneError";
|
|
6467
|
+
}
|
|
6468
|
+
};
|
|
6469
|
+
var KernelCallError = class extends Error {
|
|
6470
|
+
code;
|
|
6471
|
+
constructor(message2, code) {
|
|
6472
|
+
super(message2);
|
|
6473
|
+
this.name = "KernelCallError";
|
|
6474
|
+
this.code = code;
|
|
6475
|
+
}
|
|
6476
|
+
};
|
|
6477
|
+
var encoder4 = new TextEncoder();
|
|
6478
|
+
var bytesOf2 = (data) => typeof data === "string" ? encoder4.encode(data) : data;
|
|
6479
|
+
var UNAVAILABLE = {
|
|
6480
|
+
manualRedirects: false,
|
|
6481
|
+
encodedBodies: false,
|
|
6482
|
+
maxRequestBody: 0,
|
|
6483
|
+
unavailable: true
|
|
6484
|
+
};
|
|
6485
|
+
async function holdLock(locks) {
|
|
6486
|
+
if (!locks) return { release() {
|
|
6487
|
+
} };
|
|
6488
|
+
const name2 = `slicc-kernel-client:${crypto.randomUUID()}`;
|
|
6489
|
+
const granted = Promise.withResolvers();
|
|
6490
|
+
const held = Promise.withResolvers();
|
|
6491
|
+
void locks.request(name2, () => {
|
|
6492
|
+
granted.resolve();
|
|
6493
|
+
return held.promise;
|
|
5892
6494
|
});
|
|
6495
|
+
await granted.promise;
|
|
6496
|
+
return { name: name2, release: () => held.resolve() };
|
|
5893
6497
|
}
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
let
|
|
5897
|
-
const
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
try {
|
|
5901
|
-
return await Promise.race([reader.read(), idle]);
|
|
5902
|
-
} finally {
|
|
5903
|
-
clearTimeout(timer);
|
|
6498
|
+
function concat5(chunks2) {
|
|
6499
|
+
const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
|
|
6500
|
+
let at = 0;
|
|
6501
|
+
for (const chunk2 of chunks2) {
|
|
6502
|
+
out.set(chunk2, at);
|
|
6503
|
+
at += chunk2.length;
|
|
5904
6504
|
}
|
|
6505
|
+
return out;
|
|
5905
6506
|
}
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
6507
|
+
var Channel = class {
|
|
6508
|
+
greeted = Promise.withResolvers();
|
|
6509
|
+
ended = Promise.withResolvers();
|
|
6510
|
+
remote;
|
|
6511
|
+
failure;
|
|
6512
|
+
pending = /* @__PURE__ */ new Map();
|
|
6513
|
+
nextId = 0;
|
|
6514
|
+
port;
|
|
6515
|
+
release;
|
|
6516
|
+
constructor(port, release) {
|
|
6517
|
+
this.port = port;
|
|
6518
|
+
this.release = release;
|
|
6519
|
+
this.greeted.promise.catch(() => void 0);
|
|
6520
|
+
port.addEventListener("message", (event) => this.receive(event.data));
|
|
6521
|
+
port.addEventListener("close", () => this.end(new KernelGoneError()));
|
|
6522
|
+
port.start?.();
|
|
6523
|
+
}
|
|
6524
|
+
post(message2) {
|
|
6525
|
+
if (!this.failure) this.port.postMessage(message2);
|
|
6526
|
+
}
|
|
6527
|
+
end(error) {
|
|
6528
|
+
if (this.failure) return;
|
|
6529
|
+
this.failure = error;
|
|
6530
|
+
this.greeted.reject(error);
|
|
6531
|
+
this.remote?.fail(error);
|
|
6532
|
+
for (const call of this.pending.values()) call.reject(error);
|
|
6533
|
+
this.pending.clear();
|
|
6534
|
+
this.release();
|
|
6535
|
+
this.port.close?.();
|
|
6536
|
+
this.ended.resolve(error);
|
|
6537
|
+
}
|
|
6538
|
+
request(call, handlers = {}) {
|
|
6539
|
+
const id = ++this.nextId;
|
|
6540
|
+
const { failure } = this;
|
|
6541
|
+
if (failure) return { id, done: Promise.reject(failure) };
|
|
6542
|
+
const done4 = new Promise((resolve, reject) => {
|
|
6543
|
+
this.pending.set(id, { resolve, reject, ...handlers });
|
|
6544
|
+
this.port.postMessage({ ...call, id });
|
|
6545
|
+
});
|
|
6546
|
+
return { id, done: done4 };
|
|
6547
|
+
}
|
|
6548
|
+
call(call) {
|
|
6549
|
+
return this.request(call).done;
|
|
6550
|
+
}
|
|
6551
|
+
receive(data) {
|
|
6552
|
+
if (data.bye !== void 0) {
|
|
6553
|
+
this.end(new KernelGoneError(data.bye));
|
|
6554
|
+
return;
|
|
6555
|
+
}
|
|
6556
|
+
if (data.hello) {
|
|
6557
|
+
this.greeted.resolve(data.hello);
|
|
6558
|
+
return;
|
|
5915
6559
|
}
|
|
6560
|
+
if (data.net !== void 0) {
|
|
6561
|
+
this.remote?.receive(data);
|
|
6562
|
+
return;
|
|
6563
|
+
}
|
|
6564
|
+
const reply = data;
|
|
6565
|
+
const call = this.pending.get(reply.id);
|
|
6566
|
+
if (!call) return;
|
|
6567
|
+
if (reply.fd !== void 0) {
|
|
6568
|
+
call.output?.(reply.fd, reply.bytes);
|
|
6569
|
+
return;
|
|
6570
|
+
}
|
|
6571
|
+
if (reply.started !== void 0) {
|
|
6572
|
+
call.started?.(reply.started);
|
|
6573
|
+
return;
|
|
6574
|
+
}
|
|
6575
|
+
this.pending.delete(reply.id);
|
|
6576
|
+
if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
|
|
6577
|
+
else call.resolve(reply.result);
|
|
5916
6578
|
}
|
|
5917
|
-
|
|
6579
|
+
};
|
|
6580
|
+
async function spawnOn(channel, argv, opts) {
|
|
6581
|
+
const { onStdout, onStderr, stdin, group: _, ...rest } = opts;
|
|
6582
|
+
const started = Promise.withResolvers();
|
|
6583
|
+
const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf2(stdin) } : {} };
|
|
6584
|
+
const { done: done4 } = channel.request(
|
|
6585
|
+
{ op: "spawn", argv, options },
|
|
6586
|
+
{
|
|
6587
|
+
output: (fd, bytes) => (fd === 1 ? onStdout : onStderr)?.(bytes),
|
|
6588
|
+
started: started.resolve
|
|
6589
|
+
}
|
|
6590
|
+
);
|
|
6591
|
+
done4.catch(started.reject);
|
|
6592
|
+
const pid = await started.promise;
|
|
6593
|
+
return {
|
|
6594
|
+
pid,
|
|
6595
|
+
pgid: pid,
|
|
6596
|
+
exited: done4,
|
|
6597
|
+
signal: async (name2 = "SIGTERM") => void await channel.call({ op: "kill", pid: -pid, signal: name2 })
|
|
6598
|
+
};
|
|
5918
6599
|
}
|
|
5919
|
-
async function
|
|
5920
|
-
|
|
5921
|
-
const
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
6600
|
+
async function runOn(channel, argv, opts) {
|
|
6601
|
+
const { onStdout, onStderr, ...rest } = opts;
|
|
6602
|
+
const chunks2 = { 1: [], 2: [] };
|
|
6603
|
+
const live = { 1: new TextDecoder(), 2: new TextDecoder() };
|
|
6604
|
+
const tee = (fd, on) => (bytes) => {
|
|
6605
|
+
chunks2[fd].push(bytes);
|
|
6606
|
+
on?.(live[fd].decode(bytes, { stream: true }));
|
|
6607
|
+
};
|
|
6608
|
+
const child = await spawnOn(channel, argv, {
|
|
6609
|
+
...rest,
|
|
6610
|
+
onStdout: tee(1, onStdout),
|
|
6611
|
+
onStderr: tee(2, onStderr)
|
|
6612
|
+
});
|
|
6613
|
+
const status = await child.exited;
|
|
6614
|
+
const text = (fd) => new TextDecoder().decode(concat5(chunks2[fd]));
|
|
6615
|
+
return { pid: child.pid, status, stdout: text(1), stderr: text(2) };
|
|
6616
|
+
}
|
|
6617
|
+
async function terminalOn(channel, argv, opts) {
|
|
6618
|
+
const { onData, ...options } = opts;
|
|
6619
|
+
let listener = onData ?? null;
|
|
6620
|
+
const backlog = [];
|
|
6621
|
+
const started = Promise.withResolvers();
|
|
6622
|
+
const { id, done: done4 } = channel.request(
|
|
6623
|
+
{ op: "open-terminal", argv, options },
|
|
6624
|
+
{
|
|
6625
|
+
output: (_fd, bytes) => listener ? listener(bytes) : backlog.push(bytes),
|
|
6626
|
+
started: started.resolve
|
|
5925
6627
|
}
|
|
6628
|
+
);
|
|
6629
|
+
done4.catch(started.reject);
|
|
6630
|
+
const pid = await started.promise;
|
|
6631
|
+
const send = (action) => void channel.call({ op: "terminal", terminal: id, ...action }).catch(() => void 0);
|
|
6632
|
+
return {
|
|
6633
|
+
pid,
|
|
6634
|
+
exited: done4,
|
|
6635
|
+
get onData() {
|
|
6636
|
+
return listener;
|
|
6637
|
+
},
|
|
6638
|
+
set onData(next) {
|
|
6639
|
+
listener = next;
|
|
6640
|
+
if (next) for (const bytes of backlog.splice(0)) next(bytes);
|
|
6641
|
+
},
|
|
6642
|
+
write: (data) => send({ action: "write", bytes: bytesOf2(data) }),
|
|
6643
|
+
resize: (cols, rows) => send({ action: "resize", cols, rows }),
|
|
6644
|
+
signal: (name2) => send({ action: "signal", signal: name2 }),
|
|
6645
|
+
close: () => send({ action: "close" })
|
|
6646
|
+
};
|
|
6647
|
+
}
|
|
6648
|
+
function fsOn(channel) {
|
|
6649
|
+
const fs = (method, ...args) => channel.call({ op: "fs", method, args });
|
|
6650
|
+
const done4 = async (method, ...args) => void await fs(method, ...args);
|
|
6651
|
+
return {
|
|
6652
|
+
readFile: (path) => fs("readFile", path),
|
|
6653
|
+
readText: async (path) => new TextDecoder().decode(await fs("readFile", path)),
|
|
6654
|
+
writeFile: (path, data) => done4("writeFile", path, data),
|
|
6655
|
+
stat: (path) => fs("stat", path),
|
|
6656
|
+
lstat: (path) => fs("lstat", path),
|
|
6657
|
+
readdir: (path) => fs("readdir", path),
|
|
6658
|
+
mkdir: (path) => done4("mkdir", path),
|
|
6659
|
+
rm: (path, o = {}) => done4("rm", path, o.force === true),
|
|
6660
|
+
rename: (from, to) => done4("rename", from, to),
|
|
6661
|
+
realpath: (path) => fs("realpath", path),
|
|
6662
|
+
symlink: (target, path) => done4("symlink", target, path),
|
|
6663
|
+
readlink: (path) => fs("readlink", path),
|
|
6664
|
+
exists: (path) => fs("exists", path)
|
|
6665
|
+
};
|
|
6666
|
+
}
|
|
6667
|
+
async function greet(channel, lock, timeoutMs) {
|
|
6668
|
+
channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
|
|
6669
|
+
const timer = setTimeout(
|
|
6670
|
+
() => channel.end(
|
|
6671
|
+
new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
|
|
6672
|
+
),
|
|
6673
|
+
timeoutMs
|
|
6674
|
+
);
|
|
6675
|
+
let hello;
|
|
6676
|
+
try {
|
|
6677
|
+
hello = await channel.greeted.promise;
|
|
5926
6678
|
} finally {
|
|
5927
|
-
|
|
6679
|
+
clearTimeout(timer);
|
|
5928
6680
|
}
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
6681
|
+
const refused = hello.error ?? versionError(hello.protocol);
|
|
6682
|
+
if (refused) {
|
|
6683
|
+
const error = new Error(refused);
|
|
6684
|
+
channel.end(error);
|
|
6685
|
+
throw error;
|
|
6686
|
+
}
|
|
6687
|
+
return hello;
|
|
6688
|
+
}
|
|
6689
|
+
async function attachKernel(port, options = {}) {
|
|
6690
|
+
const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
|
|
6691
|
+
const own = await holdLock(locks);
|
|
6692
|
+
const channel = new Channel(port, own.release);
|
|
6693
|
+
const hello = await greet(channel, own.name, options.timeoutMs ?? 1e4);
|
|
6694
|
+
if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
|
|
6695
|
+
const transport = new RemoteTransport(
|
|
6696
|
+
{ postMessage: (call) => channel.post(call) },
|
|
6697
|
+
hello.traits ?? UNAVAILABLE
|
|
6698
|
+
);
|
|
6699
|
+
channel.remote = transport;
|
|
5932
6700
|
return {
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
6701
|
+
protocol: hello.protocol,
|
|
6702
|
+
transport,
|
|
6703
|
+
closed: channel.ended.promise,
|
|
6704
|
+
spawn: (argv, opts = {}) => spawnOn(channel, argv, opts),
|
|
6705
|
+
run: (argv, opts = {}) => runOn(channel, argv, opts),
|
|
6706
|
+
openTerminal: (argv, opts = {}) => terminalOn(channel, argv, opts),
|
|
6707
|
+
ps: () => channel.call({ op: "ps" }),
|
|
6708
|
+
kill: async (pid, signal = "SIGTERM") => void await channel.call({ op: "kill", pid, signal }),
|
|
6709
|
+
fetch: (req) => {
|
|
6710
|
+
const { signal, ...rest } = req;
|
|
6711
|
+
return transport.fetch({ ...rest, signal: signal ?? new AbortController().signal });
|
|
5938
6712
|
},
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
headers: joinHeaders(request.headers),
|
|
5945
|
-
...request.body ? { body: request.body } : {},
|
|
5946
|
-
signal: request.signal,
|
|
5947
|
-
redirect: "follow",
|
|
5948
|
-
credentials: "omit",
|
|
5949
|
-
mode: "cors"
|
|
5950
|
-
});
|
|
5951
|
-
} catch (e) {
|
|
5952
|
-
if (request.signal.aborted) throw e;
|
|
5953
|
-
throw Object.assign(
|
|
5954
|
-
new Error(
|
|
5955
|
-
`fetch ${request.url} failed (unreachable, or not allowed by CORS)${options.hint ? `: ${options.hint}` : ""}`
|
|
5956
|
-
),
|
|
5957
|
-
{ status: 502 }
|
|
5958
|
-
);
|
|
5959
|
-
}
|
|
5960
|
-
const headers = [];
|
|
5961
|
-
response.headers.forEach((value, name2) => {
|
|
5962
|
-
headers.push([name2, value]);
|
|
5963
|
-
});
|
|
5964
|
-
const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
|
|
5965
|
-
return {
|
|
5966
|
-
status: response.status,
|
|
5967
|
-
statusText: response.statusText,
|
|
5968
|
-
headers,
|
|
5969
|
-
body,
|
|
5970
|
-
cancel: async () => {
|
|
5971
|
-
await body.return(void 0);
|
|
5972
|
-
await response.body?.cancel().catch(() => void 0);
|
|
5973
|
-
}
|
|
5974
|
-
};
|
|
6713
|
+
fs: fsOn(channel),
|
|
6714
|
+
async close(o = {}) {
|
|
6715
|
+
if (channel.failure) return;
|
|
6716
|
+
await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
|
|
6717
|
+
channel.end(new KernelGoneError("this client is closed"));
|
|
5975
6718
|
}
|
|
5976
6719
|
};
|
|
5977
6720
|
}
|
|
@@ -6033,6 +6776,7 @@ async function createNodeKernel(options = {}) {
|
|
|
6033
6776
|
await fs.reconcile();
|
|
6034
6777
|
const live = /* @__PURE__ */ new Set();
|
|
6035
6778
|
const pending = /* @__PURE__ */ new Set();
|
|
6779
|
+
const clients = /* @__PURE__ */ new Set();
|
|
6036
6780
|
let terminated = false;
|
|
6037
6781
|
const file = options.worker ?? new URL("./node-process-worker.js", import.meta.url);
|
|
6038
6782
|
const transport = options.network?.transport;
|
|
@@ -6102,14 +6846,29 @@ async function createNodeKernel(options = {}) {
|
|
|
6102
6846
|
await fs.writeFile(path, data);
|
|
6103
6847
|
},
|
|
6104
6848
|
readFile: async (path) => fs.readFileBuffer(path),
|
|
6849
|
+
async connect() {
|
|
6850
|
+
if (terminated) throw new Error(TERMINATED);
|
|
6851
|
+
const { port1, port2 } = new MessageChannel2();
|
|
6852
|
+
const served = serveClient(port1, {
|
|
6853
|
+
launcher: async () => launcher,
|
|
6854
|
+
signal: signalNumber
|
|
6855
|
+
});
|
|
6856
|
+
clients.add(served);
|
|
6857
|
+
void served.closed.then(() => clients.delete(served));
|
|
6858
|
+
return port2;
|
|
6859
|
+
},
|
|
6105
6860
|
terminate() {
|
|
6106
6861
|
terminated = true;
|
|
6862
|
+
for (const client of [...clients]) client.detach();
|
|
6107
6863
|
for (const reject of [...pending]) reject(new Error(TERMINATED));
|
|
6108
6864
|
for (const kill of [...live]) kill();
|
|
6109
6865
|
}
|
|
6110
6866
|
};
|
|
6111
6867
|
}
|
|
6112
6868
|
export {
|
|
6869
|
+
KernelCallError,
|
|
6870
|
+
KernelGoneError,
|
|
6871
|
+
attachKernel,
|
|
6113
6872
|
createNodeKernel,
|
|
6114
6873
|
fetchTransport,
|
|
6115
6874
|
memoryRoot,
|