@tlockney/cdp-kernel 0.4.0 → 0.5.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 +60 -481
- package/THIRD-PARTY-NOTICES +62 -0
- package/dist/bun-target.d.ts +2 -2
- package/dist/bun-target.js +30 -8
- package/dist/cdp.d.ts +6 -1
- package/dist/cdp.js +25 -10
- package/dist/celld-target.d.ts +1 -1
- package/dist/celld-target.js +46 -8
- package/dist/children.d.ts +7 -0
- package/dist/children.js +32 -0
- package/dist/debugger.d.ts +1 -1
- package/dist/debugger.js +21 -10
- package/dist/globals.d.ts +3 -3
- package/dist/globals.js +3 -3
- package/dist/kernel.js +163 -41
- package/dist/mod.d.ts +5 -3
- package/dist/mod.js +5 -3
- package/dist/package.d.ts +2 -2
- package/dist/package.js +3 -3
- package/dist/target.d.ts +31 -3
- package/dist/target.js +233 -32
- package/dist/text.d.ts +5 -1
- package/dist/text.js +55 -2
- package/dist/transform.d.ts +9 -0
- package/dist/transform.js +37 -9
- package/dist/wire.d.ts +8 -1
- package/dist/wire.js +3 -1
- package/dist/workerd-target.d.ts +7 -1
- package/dist/workerd-target.js +102 -8
- package/dist/ws-client.d.ts +5 -1
- package/dist/ws-client.js +46 -13
- package/dist/zmtp.d.ts +7 -1
- package/dist/zmtp.js +30 -1
- package/package.json +9 -3
- package/runtime/bootstrap.mjs +121 -10
- package/runtime/bun-channel.d.mts +3 -0
- package/runtime/bun-channel.mjs +2 -2
- package/runtime/comm.mjs +11 -1
- package/runtime/deno-compat.d.mts +2 -0
- package/runtime/deno-compat.mjs +14 -1
- package/runtime/display.mjs +8 -1
- package/runtime/node-hooks.d.mts +42 -0
- package/runtime/node-hooks.mjs +246 -41
- package/runtime/workerd-worker.mjs +77 -6
package/dist/kernel.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { Buffer } from "node:buffer";
|
|
8
8
|
import process from "node:process";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
|
-
import { decodeMessage, encodeMessage, PROTOCOL_VERSION, verify } from "./wire.js";
|
|
10
|
+
import { decodeMessage, encodeMessage, makeHeader, PROTOCOL_VERSION, verify } from "./wire.js";
|
|
11
11
|
import { PubSocket, RouterSocket, startHeartbeat } from "./zmtp.js";
|
|
12
12
|
import { Interrupted, Target } from "./target.js";
|
|
13
13
|
import { BunTarget } from "./bun-target.js";
|
|
@@ -28,6 +28,10 @@ export function parseConnectionInfo(json) {
|
|
|
28
28
|
if (typeof info[port] !== "number")
|
|
29
29
|
throw new Error(`connection file: missing ${port}`);
|
|
30
30
|
}
|
|
31
|
+
for (const field of ["ip", "key"]) {
|
|
32
|
+
if (info[field] !== undefined && typeof info[field] !== "string")
|
|
33
|
+
throw new Error(`connection file: ${field} is not a string`);
|
|
34
|
+
}
|
|
31
35
|
if (info.transport !== undefined && info.transport !== "tcp") {
|
|
32
36
|
throw new Error(`unsupported transport ${info.transport}; only tcp is implemented`);
|
|
33
37
|
}
|
|
@@ -46,6 +50,18 @@ export function parseConnectionInfo(json) {
|
|
|
46
50
|
hb_port: info.hb_port,
|
|
47
51
|
};
|
|
48
52
|
}
|
|
53
|
+
/** How long interrupt_request may wait on the target before it's answered anyway (#121). */
|
|
54
|
+
const INTERRUPT_REPLY_WAIT_MS = 5_000;
|
|
55
|
+
const NO_INPUT = { value: null, interrupted: false };
|
|
56
|
+
/** Shell requests other than execute_request, and their reply types. */
|
|
57
|
+
const SHELL_REPLIES = {
|
|
58
|
+
kernel_info_request: "kernel_info_reply",
|
|
59
|
+
complete_request: "complete_reply",
|
|
60
|
+
is_complete_request: "is_complete_reply",
|
|
61
|
+
inspect_request: "inspect_reply",
|
|
62
|
+
history_request: "history_reply",
|
|
63
|
+
comm_info_request: "comm_info_reply",
|
|
64
|
+
};
|
|
49
65
|
export class Kernel {
|
|
50
66
|
#conn;
|
|
51
67
|
#runtime;
|
|
@@ -69,6 +85,16 @@ export class Kernel {
|
|
|
69
85
|
#parent = {};
|
|
70
86
|
#allowStdin = false;
|
|
71
87
|
#executing = false;
|
|
88
|
+
/**
|
|
89
|
+
* stop_on_error (#123): how many of the shell messages queued when a cell
|
|
90
|
+
* failed are still to come, and whether the one being handled is one.
|
|
91
|
+
*/
|
|
92
|
+
#abortQueued = 0;
|
|
93
|
+
#aborting = false;
|
|
94
|
+
/** The input_request waiting for its input_reply, if any. */
|
|
95
|
+
#input = null;
|
|
96
|
+
/** An interrupt is waiting on the target. */
|
|
97
|
+
#interrupting = false;
|
|
72
98
|
// Open comms, for comm_info_request: comm_id -> target_name (#9).
|
|
73
99
|
#comms = new Map();
|
|
74
100
|
#shuttingDown = false;
|
|
@@ -90,8 +116,17 @@ export class Kernel {
|
|
|
90
116
|
// Start the runtime eagerly so the first cell doesn't pay for it.
|
|
91
117
|
this.#ensureTarget().catch((err) => this.#log(`target failed to start: ${err}`));
|
|
92
118
|
process.on("SIGINT", () => void this.#interrupt());
|
|
119
|
+
// Exiting runs the exit hook that kills the runtime (src/children.ts);
|
|
120
|
+
// these signals' default action would skip it (#218).
|
|
121
|
+
for (const [signal, code] of [["SIGTERM", 143], ["SIGHUP", 129]]) {
|
|
122
|
+
process.on(signal, () => {
|
|
123
|
+
this.#shuttingDown = true;
|
|
124
|
+
process.exit(code);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
93
127
|
await Promise.all([this.#loop(this.#shell, (m, p) => this.#handleShell(m, p)),
|
|
94
|
-
this.#loop(this.#control, (m, p) => this.#handleControl(m, p))
|
|
128
|
+
this.#loop(this.#control, (m, p) => this.#handleControl(m, p)),
|
|
129
|
+
this.#loop(this.#stdin, (m) => this.#handleStdin(m))]);
|
|
95
130
|
}
|
|
96
131
|
#log(text) {
|
|
97
132
|
process.stderr.write(`[cdp-kernel] ${text}\n`);
|
|
@@ -205,9 +240,11 @@ export class Kernel {
|
|
|
205
240
|
#targetEvents() {
|
|
206
241
|
return {
|
|
207
242
|
stream: (name, text) => this.#publish("stream", { name, text }),
|
|
208
|
-
message: (msg) => void this.#onTargetMessage(msg),
|
|
243
|
+
message: (msg) => void this.#onTargetMessage(msg).catch((err) => this.#log(`error handling target message: ${err}`)),
|
|
209
244
|
exit: (code, note) => {
|
|
210
245
|
this.#target = null;
|
|
246
|
+
// Its prompt, if any, died with it; a late reply is dropped.
|
|
247
|
+
this.#input = null;
|
|
211
248
|
void this.#debugger?.attachTarget(null);
|
|
212
249
|
if (!this.#shuttingDown) {
|
|
213
250
|
const text = note ?? `[cdp-kernel] ${this.#runtime} exited (${code}); restarting on next cell`;
|
|
@@ -225,18 +262,37 @@ export class Kernel {
|
|
|
225
262
|
});
|
|
226
263
|
return;
|
|
227
264
|
}
|
|
228
|
-
|
|
265
|
+
// The answer goes to the target that asked, not one started since.
|
|
266
|
+
const target = this.#target;
|
|
267
|
+
let answer = NO_INPUT;
|
|
229
268
|
try {
|
|
230
|
-
|
|
269
|
+
answer = await this.#requestInput(msg.prompt, msg.password);
|
|
231
270
|
}
|
|
232
271
|
finally {
|
|
233
272
|
// Always answer exactly once so the target's blocking read returns.
|
|
234
|
-
this.#target
|
|
273
|
+
if (target !== null && target === this.#target)
|
|
274
|
+
target.replyInput(answer.value, answer.interrupted);
|
|
235
275
|
}
|
|
236
276
|
}
|
|
237
277
|
async #interrupt() {
|
|
238
|
-
|
|
278
|
+
// A target blocked reading its prompt's answer can't run
|
|
279
|
+
// terminateExecution, and would never answer it. Make the prompt throw
|
|
280
|
+
// instead, which ends the cell (#121).
|
|
281
|
+
const input = this.#input;
|
|
282
|
+
if (input !== null) {
|
|
283
|
+
this.#input = null;
|
|
284
|
+
input.settle({ value: null, interrupted: true });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!this.#target || !this.#executing)
|
|
288
|
+
return;
|
|
289
|
+
this.#interrupting = true;
|
|
290
|
+
try {
|
|
239
291
|
await this.#target.interrupt();
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
this.#interrupting = false;
|
|
295
|
+
}
|
|
240
296
|
}
|
|
241
297
|
// --- messaging --------------------------------------------------------------
|
|
242
298
|
#encode(msgType, content, parentHeader, extra = {}) {
|
|
@@ -254,6 +310,11 @@ export class Kernel {
|
|
|
254
310
|
async #loop(socket, handle) {
|
|
255
311
|
while (!this.#shuttingDown) {
|
|
256
312
|
const { peer, frames } = await socket.recv();
|
|
313
|
+
if (socket === this.#shell) {
|
|
314
|
+
this.#aborting = this.#abortQueued > 0;
|
|
315
|
+
if (this.#aborting)
|
|
316
|
+
this.#abortQueued--;
|
|
317
|
+
}
|
|
257
318
|
try {
|
|
258
319
|
const msg = decodeMessage(frames);
|
|
259
320
|
// Unsigned or forged messages are dropped, as in Deno.
|
|
@@ -292,40 +353,48 @@ export class Kernel {
|
|
|
292
353
|
}
|
|
293
354
|
// --- control channel ------------------------------------------------------
|
|
294
355
|
async #handleControl(msg, peer) {
|
|
356
|
+
// Every request is wrapped in busy/idle, as on shell (#124). JupyterLab
|
|
357
|
+
// treats a debug request as done only once the kernel reports idle for
|
|
358
|
+
// it. (Jupyter Server leaves debug_request out of the kernel's status.)
|
|
359
|
+
this.#status("busy", msg.header);
|
|
360
|
+
try {
|
|
361
|
+
await this.#controlRequest(this.#control, msg, peer);
|
|
362
|
+
}
|
|
363
|
+
finally {
|
|
364
|
+
this.#status("idle", msg.header);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/** Control requests; shutdown and interrupt may also arrive on shell (#124). */
|
|
368
|
+
async #controlRequest(socket, msg, peer) {
|
|
295
369
|
switch (msg.header.msg_type) {
|
|
296
370
|
case "kernel_info_request":
|
|
297
|
-
this.#reply(
|
|
371
|
+
this.#reply(socket, peer, msg, "kernel_info_reply", this.#kernelInfo());
|
|
298
372
|
break;
|
|
299
373
|
case "interrupt_request":
|
|
300
|
-
|
|
301
|
-
|
|
374
|
+
// Answer even if the target doesn't (#121); the interrupt goes on.
|
|
375
|
+
await Promise.race([
|
|
376
|
+
this.#interrupt().catch((err) => this.#log(`interrupt failed: ${err}`)),
|
|
377
|
+
new Promise((r) => setTimeout(r, INTERRUPT_REPLY_WAIT_MS)),
|
|
378
|
+
]);
|
|
379
|
+
this.#reply(socket, peer, msg, "interrupt_reply", { status: "ok" });
|
|
302
380
|
break;
|
|
303
381
|
case "debug_request": {
|
|
304
|
-
// JupyterLab treats a debug request as done only once the kernel
|
|
305
|
-
// reports idle for it, as ipykernel does for control messages.
|
|
306
|
-
// (Jupyter Server leaves debug_request out of the kernel's status.)
|
|
307
|
-
this.#status("busy", msg.header);
|
|
308
382
|
let reply;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
reply = { type: "response", request_seq: msg.content.seq, command: msg.content.command, success: false, message: `${this.#runtime} targets have no debugger`, body: {} };
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
// Everything but debugInfo talks to the target, so make sure there is one.
|
|
315
|
-
if (msg.content.command !== "debugInfo")
|
|
316
|
-
await this.#ensureTarget().catch(() => null);
|
|
317
|
-
reply = await this.#debugger.handle(msg.content);
|
|
318
|
-
}
|
|
319
|
-
this.#reply(this.#control, peer, msg, "debug_reply", reply);
|
|
383
|
+
if (this.#debugger === null) {
|
|
384
|
+
reply = { type: "response", request_seq: msg.content.seq, command: msg.content.command, success: false, message: `${this.#runtime} targets have no debugger`, body: {} };
|
|
320
385
|
}
|
|
321
|
-
|
|
322
|
-
|
|
386
|
+
else {
|
|
387
|
+
// Everything but debugInfo talks to the target, so make sure there is one.
|
|
388
|
+
if (msg.content.command !== "debugInfo")
|
|
389
|
+
await this.#ensureTarget().catch(() => null);
|
|
390
|
+
reply = await this.#debugger.handle(msg.content);
|
|
323
391
|
}
|
|
392
|
+
this.#reply(socket, peer, msg, "debug_reply", reply);
|
|
324
393
|
break;
|
|
325
394
|
}
|
|
326
395
|
case "shutdown_request": {
|
|
327
396
|
const restart = msg.content.restart === true;
|
|
328
|
-
this.#reply(
|
|
397
|
+
this.#reply(socket, peer, msg, "shutdown_reply", { status: "ok", restart });
|
|
329
398
|
this.#shuttingDown = true;
|
|
330
399
|
this.#target?.kill();
|
|
331
400
|
// Frontends spawn a fresh kernel even on restart; exit after the
|
|
@@ -367,8 +436,27 @@ export class Kernel {
|
|
|
367
436
|
case "comm_close":
|
|
368
437
|
await this.#handleComm(msg);
|
|
369
438
|
break;
|
|
439
|
+
case "shutdown_request":
|
|
440
|
+
case "interrupt_request":
|
|
441
|
+
// Deprecated on shell, but still allowed, and ipykernel takes them.
|
|
442
|
+
await this.#controlRequest(this.#shell, msg, peer);
|
|
443
|
+
break;
|
|
444
|
+
default:
|
|
445
|
+
this.#log(`ignoring unknown shell message ${type}`);
|
|
370
446
|
}
|
|
371
447
|
}
|
|
448
|
+
catch (err) {
|
|
449
|
+
// E.g. the runtime failed to launch. Answer anyway, so the frontend
|
|
450
|
+
// doesn't wait on the request forever (#125).
|
|
451
|
+
const replyType = SHELL_REPLIES[type];
|
|
452
|
+
if (replyType !== undefined) {
|
|
453
|
+
const name = err instanceof Error ? err.name : "Error";
|
|
454
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
455
|
+
const error = { ename: name, evalue: message, traceback: [`${name}: ${message}`] };
|
|
456
|
+
this.#reply(this.#shell, peer, msg, replyType, { status: "error", ...error });
|
|
457
|
+
}
|
|
458
|
+
throw err;
|
|
459
|
+
}
|
|
372
460
|
finally {
|
|
373
461
|
this.#status("idle", msg.header);
|
|
374
462
|
}
|
|
@@ -417,23 +505,41 @@ export class Kernel {
|
|
|
417
505
|
};
|
|
418
506
|
}
|
|
419
507
|
async #requestInput(prompt, password) {
|
|
508
|
+
// The target asked while an interrupt was already waiting on it.
|
|
509
|
+
if (this.#interrupting)
|
|
510
|
+
return { value: null, interrupted: true };
|
|
420
511
|
const parent = this.#parent;
|
|
421
512
|
if (!this.#allowStdin || typeof parent.msg_id !== "string")
|
|
422
|
-
return
|
|
513
|
+
return NO_INPUT;
|
|
423
514
|
if (this.#stdin.peers.size === 0)
|
|
424
515
|
await new Promise((r) => setTimeout(r, 100));
|
|
425
516
|
if (this.#stdin.peers.size === 0)
|
|
426
|
-
return
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
517
|
+
return NO_INPUT;
|
|
518
|
+
const header = makeHeader(this.#session, "input_request");
|
|
519
|
+
const answer = new Promise((settle) => (this.#input = { msgId: header.msg_id, settle }));
|
|
520
|
+
this.#stdin.sendAll(encodeMessage(this.#session, this.#conn.key, "input_request", { prompt, password }, {
|
|
521
|
+
header,
|
|
522
|
+
parentHeader: parent,
|
|
523
|
+
}));
|
|
524
|
+
return answer;
|
|
525
|
+
}
|
|
526
|
+
/** stdin carries only input_replys. */
|
|
527
|
+
async #handleStdin(reply) {
|
|
528
|
+
if (reply.header.msg_type !== "input_reply")
|
|
529
|
+
return;
|
|
530
|
+
const input = this.#input;
|
|
531
|
+
// A reply with nothing waiting is late (its prompt was interrupted), and
|
|
532
|
+
// one naming another request is for an earlier prompt: drop both rather
|
|
533
|
+
// than answer the next prompt with them (#121). Some clients
|
|
534
|
+
// (jupyter_client's input()) send no parent header, so only a present
|
|
535
|
+
// one is checked.
|
|
536
|
+
if (input === null)
|
|
537
|
+
return;
|
|
538
|
+
const parentId = reply.parentHeader.msg_id;
|
|
539
|
+
if (typeof parentId === "string" && parentId !== "" && parentId !== input.msgId)
|
|
540
|
+
return;
|
|
541
|
+
this.#input = null;
|
|
542
|
+
input.settle({ value: typeof reply.content.value === "string" ? reply.content.value : null, interrupted: false });
|
|
437
543
|
}
|
|
438
544
|
async #evaluateCell(target, code) {
|
|
439
545
|
// Object-literal wrapping (Deno's evaluate_line_with_object_wrapping):
|
|
@@ -458,6 +564,13 @@ export class Kernel {
|
|
|
458
564
|
}
|
|
459
565
|
async #execute(msg, peer) {
|
|
460
566
|
const parent = msg.header;
|
|
567
|
+
if (this.#aborting) {
|
|
568
|
+
// Queued behind a failed cell: not run, not counted (#123).
|
|
569
|
+
this.#status("busy", parent);
|
|
570
|
+
this.#reply(this.#shell, peer, msg, "execute_reply", { status: "aborted" });
|
|
571
|
+
this.#status("idle", parent);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
461
574
|
const code = String(msg.content.code ?? "");
|
|
462
575
|
const silent = msg.content.silent === true;
|
|
463
576
|
if (!silent && msg.content.store_history !== false) {
|
|
@@ -468,7 +581,9 @@ export class Kernel {
|
|
|
468
581
|
this.#parent = parent;
|
|
469
582
|
this.#allowStdin = msg.content.allow_stdin === true;
|
|
470
583
|
this.#status("busy", parent);
|
|
471
|
-
|
|
584
|
+
// Silent requests broadcast nothing on iopub but status (#124).
|
|
585
|
+
if (!silent)
|
|
586
|
+
this.#publish("execute_input", { code, execution_count: count });
|
|
472
587
|
if (this.#projectWarning !== null) {
|
|
473
588
|
// Shown once, in the first cell, where the user will see it.
|
|
474
589
|
this.#publish("stream", { name: "stderr", text: `[cdp-kernel] ${this.#projectWarning}\n` });
|
|
@@ -518,6 +633,13 @@ export class Kernel {
|
|
|
518
633
|
this.#executing = false;
|
|
519
634
|
}
|
|
520
635
|
if (error) {
|
|
636
|
+
if (!silent && msg.content.stop_on_error !== false) {
|
|
637
|
+
// Abort what was queued behind this cell (JupyterLab's Run All sends
|
|
638
|
+
// every cell at once), but nothing sent after the client sees the
|
|
639
|
+
// error. A macrotask first lets bytes already received be parsed.
|
|
640
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
641
|
+
this.#abortQueued = this.#shell.incoming.size;
|
|
642
|
+
}
|
|
521
643
|
this.#publish("error", error);
|
|
522
644
|
this.#reply(this.#shell, peer, msg, "execute_reply", { status: "error", execution_count: count, ...error });
|
|
523
645
|
}
|
package/dist/mod.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cdp-kernel:
|
|
3
|
-
*
|
|
4
|
-
* Deno
|
|
2
|
+
* cdp-kernel: a Jupyter kernel for TypeScript and JavaScript, extracted from
|
|
3
|
+
* Deno's, that runs notebooks on several JavaScript runtimes. The kernel host
|
|
4
|
+
* runs on Node or Deno; cells run in a separate target runtime: Node or Deno
|
|
5
|
+
* over the Chrome DevTools Protocol, or Bun, workerd and celld, which
|
|
6
|
+
* evaluate cells in-process.
|
|
5
7
|
*
|
|
6
8
|
* Most people want the CLI (`cdp-kernel install`). This module exposes
|
|
7
9
|
* the kernel for embedding: give it a parsed connection file and a target
|
package/dist/mod.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cdp-kernel:
|
|
3
|
-
*
|
|
4
|
-
* Deno
|
|
2
|
+
* cdp-kernel: a Jupyter kernel for TypeScript and JavaScript, extracted from
|
|
3
|
+
* Deno's, that runs notebooks on several JavaScript runtimes. The kernel host
|
|
4
|
+
* runs on Node or Deno; cells run in a separate target runtime: Node or Deno
|
|
5
|
+
* over the Chrome DevTools Protocol, or Bun, workerd and celld, which
|
|
6
|
+
* evaluate cells in-process.
|
|
5
7
|
*
|
|
6
8
|
* Most people want the CLI (`cdp-kernel install`). This module exposes
|
|
7
9
|
* the kernel for embedding: give it a parsed connection file and a target
|
package/dist/package.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.5.0";
|
|
3
3
|
/**
|
|
4
4
|
* The UTC day this version was released, set with VERSION. The publish
|
|
5
5
|
* workflow refuses a tag unless it's today or yesterday, so it can't go
|
|
6
6
|
* stale: see dependencyCutoff.
|
|
7
7
|
*/
|
|
8
|
-
export declare const RELEASE_DATE = "2026-10-
|
|
8
|
+
export declare const RELEASE_DATE = "2026-10-16";
|
|
9
9
|
/**
|
|
10
10
|
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
11
11
|
* a kernelspec relaunching a release from JSR would fail on release day. It
|
package/dist/package.js
CHANGED
|
@@ -14,13 +14,13 @@ import { join, sep } from "node:path";
|
|
|
14
14
|
import process from "node:process";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
/** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
|
|
17
|
-
export const VERSION = "0.
|
|
17
|
+
export const VERSION = "0.5.0";
|
|
18
18
|
/**
|
|
19
19
|
* The UTC day this version was released, set with VERSION. The publish
|
|
20
20
|
* workflow refuses a tag unless it's today or yesterday, so it can't go
|
|
21
21
|
* stale: see dependencyCutoff.
|
|
22
22
|
*/
|
|
23
|
-
export const RELEASE_DATE = "2026-10-
|
|
23
|
+
export const RELEASE_DATE = "2026-10-16";
|
|
24
24
|
/**
|
|
25
25
|
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
26
26
|
* a kernelspec relaunching a release from JSR would fail on release day. It
|
|
@@ -82,7 +82,7 @@ export function kernelArgv(host, execPath, origin, args) {
|
|
|
82
82
|
return [execPath, "run", "--allow-all", ...flags, entry, ...args];
|
|
83
83
|
}
|
|
84
84
|
export function cacheRoot() {
|
|
85
|
-
const base = process.env.XDG_CACHE_HOME
|
|
85
|
+
const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
86
86
|
return join(base, "cdp-kernel");
|
|
87
87
|
}
|
|
88
88
|
async function fetchText(url) {
|
package/dist/target.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type CellTransformOptions, transformCell } from "./transform.ts";
|
|
|
3
3
|
import type { DebugChannel } from "./debugger.ts";
|
|
4
4
|
/** Targets driven over CDP. */
|
|
5
5
|
export type CdpRuntime = "node" | "deno";
|
|
6
|
-
/** Bun and
|
|
6
|
+
/** Bun, workerd and celld have their own backends: src/bun-target.ts, src/workerd-target.ts, src/celld-target.ts. */
|
|
7
7
|
export type RuntimeName = CdpRuntime | "bun" | "workerd" | "celld";
|
|
8
8
|
/** What the kernel needs from a target runtime. */
|
|
9
9
|
export interface KernelTarget {
|
|
@@ -42,7 +42,8 @@ export interface KernelTarget {
|
|
|
42
42
|
inspect(path: string, detail: number): Promise<string | null>;
|
|
43
43
|
/** The Debugger domain, on targets driven over CDP (#18). */
|
|
44
44
|
readonly debug?: DebugChannel;
|
|
45
|
-
|
|
45
|
+
/** Answers the target's pending prompt; `interrupted` makes it throw instead (#121). */
|
|
46
|
+
replyInput(value: string | null, interrupted?: boolean): void;
|
|
46
47
|
kill(): void;
|
|
47
48
|
}
|
|
48
49
|
export interface LaunchOptions {
|
|
@@ -56,6 +57,13 @@ export declare function launchCommand(runtime: CdpRuntime, bootstrap: string, {
|
|
|
56
57
|
cmd: string;
|
|
57
58
|
args: string[];
|
|
58
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* The esbuild binary this host's esbuild runs, for a Node target to compile
|
|
62
|
+
* imported TypeScript Node can't strip (#73); null when it can't be found.
|
|
63
|
+
* Resolved from esbuild's own location, so it's found in a node_modules
|
|
64
|
+
* install and in Deno's npm cache alike.
|
|
65
|
+
*/
|
|
66
|
+
export declare function esbuildBinary(resolve?: (specifier: string) => string, platform?: string, arch?: string): string | null;
|
|
59
67
|
/** Messages the bootstrap sends through the CDP binding. */
|
|
60
68
|
export type TargetMessage = {
|
|
61
69
|
kind: "iopub";
|
|
@@ -70,6 +78,8 @@ export type TargetMessage = {
|
|
|
70
78
|
password: boolean;
|
|
71
79
|
barrier: number;
|
|
72
80
|
};
|
|
81
|
+
/** The parsed value, or undefined for text that isn't JSON. */
|
|
82
|
+
export declare function parseJson(text: string): unknown;
|
|
73
83
|
export declare function isTargetMessage(value: unknown): value is TargetMessage;
|
|
74
84
|
/**
|
|
75
85
|
* Splits one output pipe into text and barrier markers, and releases them
|
|
@@ -88,6 +98,19 @@ export declare class PipeDemux {
|
|
|
88
98
|
/** Release everything (the process died). */
|
|
89
99
|
close(): void;
|
|
90
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Removes the runtime's own stderr lines (see INSPECTOR_CHATTER) and passes
|
|
103
|
+
* everything else on. A chatter line can arrive split across chunks, so a
|
|
104
|
+
* trailing partial line that could still turn out to be one is held back
|
|
105
|
+
* until its newline arrives; any other partial line passes straight through.
|
|
106
|
+
*/
|
|
107
|
+
export declare class StderrFilter {
|
|
108
|
+
#private;
|
|
109
|
+
constructor(runtime: CdpRuntime, emit: (text: string) => void);
|
|
110
|
+
push(chunk: string): void;
|
|
111
|
+
/** Releases a held partial line (the process died). */
|
|
112
|
+
flush(): void;
|
|
113
|
+
}
|
|
91
114
|
/** Promises keyed by barrier number, resolvable before or after first use. */
|
|
92
115
|
export declare class Gates {
|
|
93
116
|
#private;
|
|
@@ -118,6 +141,7 @@ export declare class Target implements KernelTarget {
|
|
|
118
141
|
executable?: string;
|
|
119
142
|
denoCompat?: boolean;
|
|
120
143
|
denoConfig?: string;
|
|
144
|
+
startTimeoutMs?: number;
|
|
121
145
|
}): Promise<Target>;
|
|
122
146
|
/** The Debugger domain on this target's session, for src/debugger.ts. */
|
|
123
147
|
get debug(): DebugChannel;
|
|
@@ -133,6 +157,10 @@ export declare class Target implements KernelTarget {
|
|
|
133
157
|
* evaluate is abandoned here and the session stays usable.
|
|
134
158
|
*/
|
|
135
159
|
interrupt(): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* For synchronous functions. With awaitPromise the inspector would wrap
|
|
162
|
+
* even a plain return value in a promise it holds only weakly (#55).
|
|
163
|
+
*/
|
|
136
164
|
callFunctionOn(functionDeclaration: string, args: CdpParams[]): Promise<CdpParams>;
|
|
137
165
|
/** The broadcastResult step of Deno's execute flow (jupyter/mod.rs). */
|
|
138
166
|
broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
|
|
@@ -158,6 +186,6 @@ export declare class Target implements KernelTarget {
|
|
|
158
186
|
propertyNames(expression: string): Promise<string[]>;
|
|
159
187
|
inspect(path: string, detail: number): Promise<string | null>;
|
|
160
188
|
lexicalNames(): Promise<string[]>;
|
|
161
|
-
replyInput(value: string | null): void;
|
|
189
|
+
replyInput(value: string | null, interrupted?: boolean): void;
|
|
162
190
|
kill(): void;
|
|
163
191
|
}
|