@tlockney/cdp-kernel 0.2.0 → 0.4.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 +259 -33
- package/dist/bun-target.d.ts +60 -0
- package/dist/bun-target.js +236 -0
- package/dist/celld-target.d.ts +39 -0
- package/dist/celld-target.js +274 -0
- package/dist/debugger.d.ts +42 -0
- package/dist/debugger.js +534 -0
- package/dist/history.d.ts +18 -0
- package/dist/history.js +45 -0
- package/dist/kernel.js +91 -15
- package/dist/main.js +8 -6
- package/dist/murmur2.d.ts +2 -0
- package/dist/murmur2.js +41 -0
- package/dist/package.d.ts +16 -2
- package/dist/package.js +28 -2
- package/dist/sourcemap.d.ts +22 -0
- package/dist/sourcemap.js +86 -0
- package/dist/target.d.ts +65 -7
- package/dist/target.js +57 -6
- package/dist/text.d.ts +6 -0
- package/dist/text.js +17 -0
- package/dist/transform.d.ts +16 -0
- package/dist/transform.js +173 -11
- package/dist/workerd-target.d.ts +125 -0
- package/dist/workerd-target.js +455 -0
- package/dist/workers-project.d.ts +27 -0
- package/dist/workers-project.js +195 -0
- package/dist/ws-client.d.ts +32 -0
- package/dist/ws-client.js +166 -0
- package/package.json +18 -4
- package/runtime/bootstrap.mjs +55 -5
- package/runtime/bun-channel.d.mts +3 -0
- package/runtime/bun-channel.mjs +58 -0
- package/runtime/deno-compat.mjs +3 -1
- package/runtime/display.d.mts +2 -0
- package/runtime/display.mjs +9 -1
- package/runtime/evaluator.d.mts +22 -0
- package/runtime/evaluator.mjs +91 -0
- package/runtime/introspect.d.mts +13 -0
- package/runtime/introspect.mjs +120 -0
- package/runtime/node-hooks.d.mts +29 -0
- package/runtime/node-hooks.mjs +160 -20
- package/runtime/workerd-worker.mjs +194 -0
package/dist/kernel.js
CHANGED
|
@@ -10,9 +10,14 @@ import { randomUUID } from "node:crypto";
|
|
|
10
10
|
import { decodeMessage, encodeMessage, PROTOCOL_VERSION, verify } from "./wire.js";
|
|
11
11
|
import { PubSocket, RouterSocket, startHeartbeat } from "./zmtp.js";
|
|
12
12
|
import { Interrupted, Target } from "./target.js";
|
|
13
|
-
import {
|
|
13
|
+
import { BunTarget } from "./bun-target.js";
|
|
14
|
+
import { Debugger } from "./debugger.js";
|
|
15
|
+
import { WorkerdTarget } from "./workerd-target.js";
|
|
16
|
+
import { launchCelld } from "./celld-target.js";
|
|
17
|
+
import { CellSyntaxError, objectLiteralCandidate } from "./transform.js";
|
|
14
18
|
import { loadDenoProject } from "./deno-config.js";
|
|
15
|
-
import { checkIsComplete, codePointToUtf16Index, exprAtCursor, utf16ToCodePointIndex } from "./text.js";
|
|
19
|
+
import { checkIsComplete, codePointToUtf16Index, exprAtCursor, inspectTarget, utf16ToCodePointIndex } from "./text.js";
|
|
20
|
+
import { queryHistory } from "./history.js";
|
|
16
21
|
import { VERSION } from "./package.js";
|
|
17
22
|
export function parseConnectionInfo(json) {
|
|
18
23
|
const raw = JSON.parse(json);
|
|
@@ -48,6 +53,11 @@ export class Kernel {
|
|
|
48
53
|
// A Deno target in a Deno project uses its config (#13).
|
|
49
54
|
#project;
|
|
50
55
|
#projectWarning = null;
|
|
56
|
+
#noticeShownFor = null;
|
|
57
|
+
/** JupyterLab's visual debugger, for targets driven over CDP (#18). */
|
|
58
|
+
#debugger;
|
|
59
|
+
/** Cells run with store_history, for history_request (#10). */
|
|
60
|
+
#history = [];
|
|
51
61
|
#session = randomUUID();
|
|
52
62
|
#shell;
|
|
53
63
|
#control;
|
|
@@ -67,6 +77,9 @@ export class Kernel {
|
|
|
67
77
|
this.#runtime = runtime;
|
|
68
78
|
this.#denoCompat = options.denoCompat ?? false;
|
|
69
79
|
this.#project = runtime === "deno" ? this.#loadProject(options.notebookDir ?? process.cwd()) : null;
|
|
80
|
+
this.#debugger = runtime === "node" || runtime === "deno"
|
|
81
|
+
? new Debugger((event) => this.#publish("debug_event", event), () => this.#transformOptions())
|
|
82
|
+
: null;
|
|
70
83
|
startHeartbeat(conn.hb_port, conn.ip);
|
|
71
84
|
this.#shell = new RouterSocket(conn.shell_port, conn.ip);
|
|
72
85
|
this.#control = new RouterSocket(conn.control_port, conn.ip);
|
|
@@ -162,13 +175,24 @@ export class Kernel {
|
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
// --- target lifecycle -----------------------------------------------------
|
|
178
|
+
#launchTarget() {
|
|
179
|
+
if (this.#runtime === "bun")
|
|
180
|
+
return BunTarget.launch(this.#targetEvents());
|
|
181
|
+
if (this.#runtime === "workerd")
|
|
182
|
+
return WorkerdTarget.launch(this.#targetEvents());
|
|
183
|
+
if (this.#runtime === "celld")
|
|
184
|
+
return launchCelld(this.#targetEvents());
|
|
185
|
+
return Target.launch(this.#runtime, this.#targetEvents(), {
|
|
186
|
+
denoCompat: this.#denoCompat,
|
|
187
|
+
denoConfig: this.#project?.configPath,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
165
190
|
#ensureTarget() {
|
|
166
191
|
if (this.#target)
|
|
167
192
|
return Promise.resolve(this.#target);
|
|
168
|
-
this.#targetStarting ??=
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}).then((target) => {
|
|
193
|
+
this.#targetStarting ??= this.#launchTarget().then(async (target) => {
|
|
194
|
+
// Before any cell runs on it, so breakpoints already apply.
|
|
195
|
+
await this.#debugger?.attachTarget(target.debug ?? null);
|
|
172
196
|
this.#target = target;
|
|
173
197
|
this.#targetStarting = null;
|
|
174
198
|
return target;
|
|
@@ -182,10 +206,12 @@ export class Kernel {
|
|
|
182
206
|
return {
|
|
183
207
|
stream: (name, text) => this.#publish("stream", { name, text }),
|
|
184
208
|
message: (msg) => void this.#onTargetMessage(msg),
|
|
185
|
-
exit: (code) => {
|
|
209
|
+
exit: (code, note) => {
|
|
186
210
|
this.#target = null;
|
|
211
|
+
void this.#debugger?.attachTarget(null);
|
|
187
212
|
if (!this.#shuttingDown) {
|
|
188
|
-
|
|
213
|
+
const text = note ?? `[cdp-kernel] ${this.#runtime} exited (${code}); restarting on next cell`;
|
|
214
|
+
this.#publish("stream", { name: "stderr", text: `\n${text}\n` });
|
|
189
215
|
}
|
|
190
216
|
},
|
|
191
217
|
};
|
|
@@ -256,9 +282,12 @@ export class Kernel {
|
|
|
256
282
|
codemirror_mode: "typescript",
|
|
257
283
|
nbconvert_exporter: "script",
|
|
258
284
|
},
|
|
259
|
-
banner: `cdp-kernel on ${this.#runtime} ${versions?.runtime ?? ""} (
|
|
285
|
+
banner: `cdp-kernel on ${this.#runtime} ${versions?.runtime ?? ""} (${versions?.engine || "?"})`,
|
|
260
286
|
help_links: [],
|
|
261
|
-
debugger:
|
|
287
|
+
debugger: this.#debugger !== null,
|
|
288
|
+
// What current ipykernel advertises; JupyterLab itself goes by the
|
|
289
|
+
// kernelspec's metadata.debugger.
|
|
290
|
+
supported_features: this.#debugger !== null ? ["debugger"] : [],
|
|
262
291
|
};
|
|
263
292
|
}
|
|
264
293
|
// --- control channel ------------------------------------------------------
|
|
@@ -271,6 +300,29 @@ export class Kernel {
|
|
|
271
300
|
await this.#interrupt();
|
|
272
301
|
this.#reply(this.#control, peer, msg, "interrupt_reply", { status: "ok" });
|
|
273
302
|
break;
|
|
303
|
+
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
|
+
let reply;
|
|
309
|
+
try {
|
|
310
|
+
if (this.#debugger === null) {
|
|
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);
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
this.#status("idle", msg.header);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
274
326
|
case "shutdown_request": {
|
|
275
327
|
const restart = msg.content.restart === true;
|
|
276
328
|
this.#reply(this.#control, peer, msg, "shutdown_reply", { status: "ok", restart });
|
|
@@ -302,10 +354,10 @@ export class Kernel {
|
|
|
302
354
|
this.#reply(this.#shell, peer, msg, "is_complete_reply", checkIsComplete(String(msg.content.code ?? "")));
|
|
303
355
|
break;
|
|
304
356
|
case "inspect_request":
|
|
305
|
-
this.#reply(this.#shell, peer, msg, "inspect_reply",
|
|
357
|
+
this.#reply(this.#shell, peer, msg, "inspect_reply", await this.#inspect(msg));
|
|
306
358
|
break;
|
|
307
359
|
case "history_request":
|
|
308
|
-
this.#reply(this.#shell, peer, msg, "history_reply", { status: "ok", history:
|
|
360
|
+
this.#reply(this.#shell, peer, msg, "history_reply", { status: "ok", history: queryHistory(this.#history, msg.content) });
|
|
309
361
|
break;
|
|
310
362
|
case "comm_info_request":
|
|
311
363
|
this.#reply(this.#shell, peer, msg, "comm_info_reply", { status: "ok", comms: this.#commInfo(msg.content) });
|
|
@@ -321,6 +373,19 @@ export class Kernel {
|
|
|
321
373
|
this.#status("idle", msg.header);
|
|
322
374
|
}
|
|
323
375
|
}
|
|
376
|
+
/** The tooltip (Shift-Tab) for the name at the cursor (#10). */
|
|
377
|
+
async #inspect(msg) {
|
|
378
|
+
const code = String(msg.content.code ?? "");
|
|
379
|
+
const cursorCp = typeof msg.content.cursor_pos === "number"
|
|
380
|
+
? msg.content.cursor_pos
|
|
381
|
+
: utf16ToCodePointIndex(code, code.length);
|
|
382
|
+
const path = inspectTarget(code, codePointToUtf16Index(code, cursorCp));
|
|
383
|
+
const detail = msg.content.detail_level === 1 ? 1 : 0;
|
|
384
|
+
const text = path === null ? null : await (await this.#ensureTarget()).inspect(path, detail);
|
|
385
|
+
return text === null
|
|
386
|
+
? { status: "ok", found: false, data: {}, metadata: {} }
|
|
387
|
+
: { status: "ok", found: true, data: { "text/plain": text }, metadata: {} };
|
|
388
|
+
}
|
|
324
389
|
async #complete(msg) {
|
|
325
390
|
const code = String(msg.content.code ?? "");
|
|
326
391
|
const cursorCp = typeof msg.content.cursor_pos === "number"
|
|
@@ -373,10 +438,14 @@ export class Kernel {
|
|
|
373
438
|
async #evaluateCell(target, code) {
|
|
374
439
|
// Object-literal wrapping (Deno's evaluate_line_with_object_wrapping):
|
|
375
440
|
// try `({ ... })` first and fall back to the code as written.
|
|
441
|
+
// While debugging, cells carry the path JupyterLab gave them and keep a
|
|
442
|
+
// line map, so breakpoints bind and frames point at the cell (#18).
|
|
443
|
+
const debugging = this.#debugger?.started === true && target.debug !== undefined;
|
|
444
|
+
const transform = (source) => debugging && this.#debugger ? this.#debugger.cellScript(code, source) : target.transform(source, this.#transformOptions());
|
|
376
445
|
const wrapped = objectLiteralCandidate(code);
|
|
377
446
|
if (wrapped !== null) {
|
|
378
447
|
try {
|
|
379
|
-
const result = await target.evaluate(
|
|
448
|
+
const result = await target.evaluate(transform(wrapped));
|
|
380
449
|
if (!result.exceptionDetails)
|
|
381
450
|
return result;
|
|
382
451
|
}
|
|
@@ -385,14 +454,16 @@ export class Kernel {
|
|
|
385
454
|
throw err;
|
|
386
455
|
}
|
|
387
456
|
}
|
|
388
|
-
return target.evaluate(
|
|
457
|
+
return target.evaluate(transform(code));
|
|
389
458
|
}
|
|
390
459
|
async #execute(msg, peer) {
|
|
391
460
|
const parent = msg.header;
|
|
392
461
|
const code = String(msg.content.code ?? "");
|
|
393
462
|
const silent = msg.content.silent === true;
|
|
394
|
-
if (!silent && msg.content.store_history !== false)
|
|
463
|
+
if (!silent && msg.content.store_history !== false) {
|
|
395
464
|
this.#executionCount++;
|
|
465
|
+
this.#history.push({ line: this.#executionCount, input: code });
|
|
466
|
+
}
|
|
396
467
|
const count = this.#executionCount;
|
|
397
468
|
this.#parent = parent;
|
|
398
469
|
this.#allowStdin = msg.content.allow_stdin === true;
|
|
@@ -407,6 +478,11 @@ export class Kernel {
|
|
|
407
478
|
this.#executing = true;
|
|
408
479
|
try {
|
|
409
480
|
const target = await this.#ensureTarget();
|
|
481
|
+
if (target.notice !== undefined && this.#noticeShownFor !== target) {
|
|
482
|
+
// Once per target, in the first cell it runs.
|
|
483
|
+
this.#noticeShownFor = target;
|
|
484
|
+
this.#publish("stream", { name: "stderr", text: `${target.notice}\n` });
|
|
485
|
+
}
|
|
410
486
|
const result = await this.#evaluateCell(target, code);
|
|
411
487
|
if (result.exceptionDetails) {
|
|
412
488
|
const exception = result.exceptionDetails.exception;
|
package/dist/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// cdp-kernel CLI.
|
|
3
|
-
// cdp-kernel install [--runtime node|deno] [--deno-compat] [--name NAME] [--display NAME]
|
|
4
|
-
// cdp-kernel kernel --runtime node|deno [--deno-compat] --conn CONNECTION_FILE
|
|
3
|
+
// cdp-kernel install [--runtime node|deno|bun|workerd|celld] [--deno-compat] [--name NAME] [--display NAME]
|
|
4
|
+
// cdp-kernel kernel --runtime node|deno|bun|workerd|celld [--deno-compat] --conn CONNECTION_FILE
|
|
5
5
|
// cdp-kernel version
|
|
6
6
|
//
|
|
7
7
|
// --deno-compat (Node only) adds a `Deno` namespace for notebooks written for
|
|
@@ -14,7 +14,7 @@ import { parseArgs } from "node:util";
|
|
|
14
14
|
import { Kernel, parseConnectionInfo } from "./kernel.js";
|
|
15
15
|
import { hostRuntime, kernelArgv, moduleOrigin, VERSION } from "./package.js";
|
|
16
16
|
function isRuntime(value) {
|
|
17
|
-
return value === "node" || value === "deno";
|
|
17
|
+
return value === "node" || value === "deno" || value === "bun" || value === "workerd" || value === "celld";
|
|
18
18
|
}
|
|
19
19
|
function kernelsDir() {
|
|
20
20
|
if (process.env.JUPYTER_DATA_DIR)
|
|
@@ -42,7 +42,9 @@ function install(runtime, denoCompat, name, display) {
|
|
|
42
42
|
// Interrupts arrive as interrupt_request on the control channel rather
|
|
43
43
|
// than as SIGINT to this process.
|
|
44
44
|
interrupt_mode: "message",
|
|
45
|
-
|
|
45
|
+
// JupyterLab enables its visual debugger from metadata.debugger (#18);
|
|
46
|
+
// only the targets driven over CDP have one.
|
|
47
|
+
metadata: { runtime, denoCompat, version: VERSION, debugger: runtime === "node" || runtime === "deno" },
|
|
46
48
|
};
|
|
47
49
|
writeFileSync(join(dir, "kernel.json"), JSON.stringify(spec, null, 2) + "\n");
|
|
48
50
|
console.log(`installed kernelspec ${name} -> ${dir}`);
|
|
@@ -59,7 +61,7 @@ const { positionals, values } = parseArgs({
|
|
|
59
61
|
});
|
|
60
62
|
const runtime = values.runtime;
|
|
61
63
|
if (!isRuntime(runtime)) {
|
|
62
|
-
console.error(`unknown runtime ${runtime}; expected node or
|
|
64
|
+
console.error(`unknown runtime ${runtime}; expected node, deno, bun, workerd or celld`);
|
|
63
65
|
process.exit(2);
|
|
64
66
|
}
|
|
65
67
|
// Deno targets have the real namespace; the shim is for Node.
|
|
@@ -83,6 +85,6 @@ switch (positionals[0]) {
|
|
|
83
85
|
console.log(VERSION);
|
|
84
86
|
break;
|
|
85
87
|
default:
|
|
86
|
-
console.error("usage: cdp-kernel install [--runtime node|deno] [--deno-compat] | kernel --runtime node|deno --conn FILE | version");
|
|
88
|
+
console.error("usage: cdp-kernel install [--runtime node|deno|bun|workerd|celld] [--deno-compat] | kernel --runtime node|deno|bun|workerd|celld --conn FILE | version");
|
|
87
89
|
process.exit(2);
|
|
88
90
|
}
|
package/dist/murmur2.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// MurmurHash2 (32-bit), as the Jupyter debugger uses it to name a cell's
|
|
2
|
+
// source (#18). JupyterLab computes a cell's path itself, as tmpFilePrefix +
|
|
3
|
+
// murmur2(code, hashSeed) + tmpFileSuffix, and the kernel must name the
|
|
4
|
+
// cell's script the same way, so this follows JupyterLab's
|
|
5
|
+
// packages/debugger/src/hash.ts (Modified BSD License, Copyright (c) Jupyter
|
|
6
|
+
// Development Team), itself adapted from Gary Court's murmurhash2_gc.js
|
|
7
|
+
// (MIT License, Copyright (c) 2011 Gary Court).
|
|
8
|
+
const M = 0x5bd1e995;
|
|
9
|
+
const ENCODER = new TextEncoder();
|
|
10
|
+
/** 32-bit multiply by M, without leaving the safe-integer range. */
|
|
11
|
+
function mul(x) {
|
|
12
|
+
return (x & 0xffff) * M + ((((x >>> 16) * M) & 0xffff) << 16);
|
|
13
|
+
}
|
|
14
|
+
/** The hash of a string's UTF-8 bytes. */
|
|
15
|
+
export function murmur2(str, seed) {
|
|
16
|
+
const data = ENCODER.encode(str);
|
|
17
|
+
let len = data.length;
|
|
18
|
+
let h = seed ^ len;
|
|
19
|
+
let i = 0;
|
|
20
|
+
while (len >= 4) {
|
|
21
|
+
let k = data[i] | (data[i + 1] << 8) | (data[i + 2] << 16) | (data[i + 3] << 24);
|
|
22
|
+
k = mul(k);
|
|
23
|
+
k ^= k >>> 24;
|
|
24
|
+
k = mul(k);
|
|
25
|
+
h = mul(h) ^ k;
|
|
26
|
+
len -= 4;
|
|
27
|
+
i += 4;
|
|
28
|
+
}
|
|
29
|
+
if (len === 3)
|
|
30
|
+
h ^= data[i + 2] << 16;
|
|
31
|
+
if (len >= 2)
|
|
32
|
+
h ^= data[i + 1] << 8;
|
|
33
|
+
if (len >= 1) {
|
|
34
|
+
h ^= data[i];
|
|
35
|
+
h = mul(h);
|
|
36
|
+
}
|
|
37
|
+
h ^= h >>> 13;
|
|
38
|
+
h = mul(h);
|
|
39
|
+
h ^= h >>> 15;
|
|
40
|
+
return h >>> 0;
|
|
41
|
+
}
|
package/dist/package.d.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
/** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
3
|
-
|
|
2
|
+
export declare const VERSION = "0.4.0";
|
|
3
|
+
/**
|
|
4
|
+
* The UTC day this version was released, set with VERSION. The publish
|
|
5
|
+
* workflow refuses a tag unless it's today or yesterday, so it can't go
|
|
6
|
+
* stale: see dependencyCutoff.
|
|
7
|
+
*/
|
|
8
|
+
export declare const RELEASE_DATE = "2026-10-15";
|
|
9
|
+
/**
|
|
10
|
+
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
11
|
+
* a kernelspec relaunching a release from JSR would fail on release day. It
|
|
12
|
+
* relaunches with this cutoff instead (#12): anything published up to two
|
|
13
|
+
* days after the release is allowed (the release itself included, however
|
|
14
|
+
* late in the day its tag was pushed), and nothing newer ever is.
|
|
15
|
+
*/
|
|
16
|
+
export declare function dependencyCutoff(releaseDate?: string): string;
|
|
17
|
+
export declare const RUNTIME_FILES: readonly ["bootstrap.mjs", "bun-channel.mjs", "evaluator.mjs", "introspect.mjs", "workerd-worker.mjs", "display.mjs", "comm.mjs", "widget.mjs", "node-hooks.mjs", "deno-compat.mjs", "node-modules-resolve.mjs"];
|
|
4
18
|
export type HostRuntime = "node" | "deno";
|
|
5
19
|
export declare function hostRuntime(): HostRuntime;
|
|
6
20
|
/** Where the CLI module was loaded from. */
|
package/dist/package.js
CHANGED
|
@@ -14,9 +14,33 @@ 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.4.0";
|
|
18
|
+
/**
|
|
19
|
+
* The UTC day this version was released, set with VERSION. The publish
|
|
20
|
+
* workflow refuses a tag unless it's today or yesterday, so it can't go
|
|
21
|
+
* stale: see dependencyCutoff.
|
|
22
|
+
*/
|
|
23
|
+
export const RELEASE_DATE = "2026-10-15";
|
|
24
|
+
/**
|
|
25
|
+
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
26
|
+
* a kernelspec relaunching a release from JSR would fail on release day. It
|
|
27
|
+
* relaunches with this cutoff instead (#12): anything published up to two
|
|
28
|
+
* days after the release is allowed (the release itself included, however
|
|
29
|
+
* late in the day its tag was pushed), and nothing newer ever is.
|
|
30
|
+
*/
|
|
31
|
+
export function dependencyCutoff(releaseDate = RELEASE_DATE) {
|
|
32
|
+
const day = new Date(`${releaseDate}T00:00:00Z`);
|
|
33
|
+
if (Number.isNaN(day.getTime()))
|
|
34
|
+
throw new Error(`not a date: ${releaseDate}`);
|
|
35
|
+
day.setUTCDate(day.getUTCDate() + 2);
|
|
36
|
+
return day.toISOString().slice(0, 10);
|
|
37
|
+
}
|
|
18
38
|
export const RUNTIME_FILES = [
|
|
19
39
|
"bootstrap.mjs",
|
|
40
|
+
"bun-channel.mjs",
|
|
41
|
+
"evaluator.mjs",
|
|
42
|
+
"introspect.mjs",
|
|
43
|
+
"workerd-worker.mjs",
|
|
20
44
|
"display.mjs",
|
|
21
45
|
"comm.mjs",
|
|
22
46
|
"widget.mjs",
|
|
@@ -51,7 +75,9 @@ export function kernelArgv(host, execPath, origin, args) {
|
|
|
51
75
|
}
|
|
52
76
|
// Deno: a checkout needs its deno.jsonc import map; a JSR package brings
|
|
53
77
|
// its own, so skip whatever config the notebook's directory has.
|
|
54
|
-
const flags = origin.kind === "jsr"
|
|
78
|
+
const flags = origin.kind === "jsr"
|
|
79
|
+
? ["--no-config", "--no-lock", `--min-dep-age=${dependencyCutoff()}`]
|
|
80
|
+
: ["--no-lock"];
|
|
55
81
|
const entry = origin.kind === "jsr" ? origin.specifier : origin.path;
|
|
56
82
|
return [execPath, "run", "--allow-all", ...flags, entry, ...args];
|
|
57
83
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** One segment's VLQ fields: generated column, source, line, column, name. */
|
|
2
|
+
export declare function decodeVlq(segment: string): number[];
|
|
3
|
+
/**
|
|
4
|
+
* For each generated line (0-based), the first original line (0-based) a
|
|
5
|
+
* segment on it maps to, or -1. Source-map fields are relative to the
|
|
6
|
+
* previous segment's; only the original line is tracked.
|
|
7
|
+
*/
|
|
8
|
+
export declare function generatedToOriginalLines(mappings: string): number[];
|
|
9
|
+
/** Both directions between the lines of a cell and of its script (0-based). */
|
|
10
|
+
export declare class LineMap {
|
|
11
|
+
#private;
|
|
12
|
+
/** `offset`: lines the script adds before esbuild's output (the prelude). */
|
|
13
|
+
constructor(mappings: string, offset?: number);
|
|
14
|
+
/** The cell line a script line came from: its own mapping, or the nearest one above. */
|
|
15
|
+
toOriginal(generated: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* The script line for a cell line, or for the next cell line that has
|
|
18
|
+
* code (a breakpoint on a blank or comment line moves down, as debuggers
|
|
19
|
+
* do), or null past the end.
|
|
20
|
+
*/
|
|
21
|
+
toGenerated(original: number): number | null;
|
|
22
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Line mapping between a cell as written and the script that runs, for the
|
|
2
|
+
// debugger (#18): a breakpoint set on a cell's line must land on the line
|
|
3
|
+
// esbuild's output put it on, and a paused frame must report the line the
|
|
4
|
+
// user wrote. Only lines: Jupyter's breakpoints are per line.
|
|
5
|
+
const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
6
|
+
const DIGIT = new Map([...BASE64].map((c, i) => [c, i]));
|
|
7
|
+
/** One segment's VLQ fields: generated column, source, line, column, name. */
|
|
8
|
+
export function decodeVlq(segment) {
|
|
9
|
+
const values = [];
|
|
10
|
+
let value = 0;
|
|
11
|
+
let shift = 0;
|
|
12
|
+
for (const char of segment) {
|
|
13
|
+
const digit = DIGIT.get(char);
|
|
14
|
+
if (digit === undefined)
|
|
15
|
+
throw new Error(`bad base64 VLQ digit ${JSON.stringify(char)}`);
|
|
16
|
+
value += (digit & 31) << shift;
|
|
17
|
+
if (digit & 32) {
|
|
18
|
+
shift += 5;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
values.push(value & 1 ? -(value >>> 1) : value >>> 1);
|
|
22
|
+
value = 0;
|
|
23
|
+
shift = 0;
|
|
24
|
+
}
|
|
25
|
+
return values;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* For each generated line (0-based), the first original line (0-based) a
|
|
29
|
+
* segment on it maps to, or -1. Source-map fields are relative to the
|
|
30
|
+
* previous segment's; only the original line is tracked.
|
|
31
|
+
*/
|
|
32
|
+
export function generatedToOriginalLines(mappings) {
|
|
33
|
+
const lines = [];
|
|
34
|
+
let original = 0;
|
|
35
|
+
for (const line of mappings.split(";")) {
|
|
36
|
+
let first = -1;
|
|
37
|
+
for (const segment of line.split(",")) {
|
|
38
|
+
if (!segment)
|
|
39
|
+
continue;
|
|
40
|
+
const fields = decodeVlq(segment);
|
|
41
|
+
if (fields.length >= 4) {
|
|
42
|
+
original += fields[2];
|
|
43
|
+
if (first === -1)
|
|
44
|
+
first = original;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
lines.push(first);
|
|
48
|
+
}
|
|
49
|
+
return lines;
|
|
50
|
+
}
|
|
51
|
+
/** Both directions between the lines of a cell and of its script (0-based). */
|
|
52
|
+
export class LineMap {
|
|
53
|
+
#toOriginal;
|
|
54
|
+
#toGenerated = new Map();
|
|
55
|
+
/** `offset`: lines the script adds before esbuild's output (the prelude). */
|
|
56
|
+
constructor(mappings, offset = 0) {
|
|
57
|
+
const fromEsbuild = generatedToOriginalLines(mappings);
|
|
58
|
+
this.#toOriginal = [...Array(offset).fill(-1), ...fromEsbuild];
|
|
59
|
+
this.#toOriginal.forEach((original, generated) => {
|
|
60
|
+
if (original !== -1 && !this.#toGenerated.has(original))
|
|
61
|
+
this.#toGenerated.set(original, generated);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** The cell line a script line came from: its own mapping, or the nearest one above. */
|
|
65
|
+
toOriginal(generated) {
|
|
66
|
+
for (let line = Math.min(generated, this.#toOriginal.length - 1); line >= 0; line--) {
|
|
67
|
+
if (this.#toOriginal[line] !== -1)
|
|
68
|
+
return this.#toOriginal[line];
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The script line for a cell line, or for the next cell line that has
|
|
74
|
+
* code (a breakpoint on a blank or comment line moves down, as debuggers
|
|
75
|
+
* do), or null past the end.
|
|
76
|
+
*/
|
|
77
|
+
toGenerated(original) {
|
|
78
|
+
const last = Math.max(-1, ...this.#toGenerated.keys());
|
|
79
|
+
for (let line = original; line <= last; line++) {
|
|
80
|
+
const generated = this.#toGenerated.get(line);
|
|
81
|
+
if (generated !== undefined)
|
|
82
|
+
return generated;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/target.d.ts
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
import { type CdpParams, type EvaluateResult, type RemoteObject } from "./cdp.ts";
|
|
2
|
-
|
|
2
|
+
import { type CellTransformOptions, transformCell } from "./transform.ts";
|
|
3
|
+
import type { DebugChannel } from "./debugger.ts";
|
|
4
|
+
/** Targets driven over CDP. */
|
|
5
|
+
export type CdpRuntime = "node" | "deno";
|
|
6
|
+
/** Bun and workerd have their own backends: src/bun-target.ts, src/workerd-target.ts. */
|
|
7
|
+
export type RuntimeName = CdpRuntime | "bun" | "workerd" | "celld";
|
|
8
|
+
/** What the kernel needs from a target runtime. */
|
|
9
|
+
export interface KernelTarget {
|
|
10
|
+
readonly runtime: RuntimeName;
|
|
11
|
+
readonly versions: {
|
|
12
|
+
runtime: string;
|
|
13
|
+
engine: string;
|
|
14
|
+
};
|
|
15
|
+
/** Shown once, in the first cell: e.g. which project's bindings loaded. */
|
|
16
|
+
readonly notice?: string;
|
|
17
|
+
/** Turns a cell into the script evaluate() takes. */
|
|
18
|
+
transform(code: string, options: CellTransformOptions): string;
|
|
19
|
+
/** Runs a transformed cell. Rejects with Interrupted after interrupt(). */
|
|
20
|
+
evaluate(script: string): Promise<EvaluateResult>;
|
|
21
|
+
interrupt(): Promise<void>;
|
|
22
|
+
/** Resolves once all output and messages sent so far have been delivered. */
|
|
23
|
+
flush(): Promise<void>;
|
|
24
|
+
broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
|
|
25
|
+
dispatchComm(msgType: string, content: CdpParams, metadata: CdpParams, buffers: Uint8Array[]): Promise<{
|
|
26
|
+
handled: boolean;
|
|
27
|
+
} | {
|
|
28
|
+
error: {
|
|
29
|
+
name: string;
|
|
30
|
+
message: string;
|
|
31
|
+
stack: string;
|
|
32
|
+
};
|
|
33
|
+
}>;
|
|
34
|
+
describeError(exception: RemoteObject): Promise<{
|
|
35
|
+
name: string;
|
|
36
|
+
message: string;
|
|
37
|
+
stack: string;
|
|
38
|
+
}>;
|
|
39
|
+
propertyNames(expression: string): Promise<string[]>;
|
|
40
|
+
lexicalNames(): Promise<string[]>;
|
|
41
|
+
/** inspect_request (#10): text describing a dotted path's value, without running anything. */
|
|
42
|
+
inspect(path: string, detail: number): Promise<string | null>;
|
|
43
|
+
/** The Debugger domain, on targets driven over CDP (#18). */
|
|
44
|
+
readonly debug?: DebugChannel;
|
|
45
|
+
replyInput(value: string | null): void;
|
|
46
|
+
kill(): void;
|
|
47
|
+
}
|
|
3
48
|
export interface LaunchOptions {
|
|
4
49
|
/** Runtime binary; defaults to `deno`, or `node` (this one when hosted by Node). */
|
|
5
50
|
executable?: string;
|
|
@@ -7,7 +52,7 @@ export interface LaunchOptions {
|
|
|
7
52
|
denoConfig?: string;
|
|
8
53
|
}
|
|
9
54
|
/** `bootstrap` is the local path of runtime/bootstrap.mjs (see runtimeDir). */
|
|
10
|
-
export declare function launchCommand(runtime:
|
|
55
|
+
export declare function launchCommand(runtime: CdpRuntime, bootstrap: string, { executable, denoConfig }?: LaunchOptions): {
|
|
11
56
|
cmd: string;
|
|
12
57
|
args: string[];
|
|
13
58
|
};
|
|
@@ -25,6 +70,7 @@ export type TargetMessage = {
|
|
|
25
70
|
password: boolean;
|
|
26
71
|
barrier: number;
|
|
27
72
|
};
|
|
73
|
+
export declare function isTargetMessage(value: unknown): value is TargetMessage;
|
|
28
74
|
/**
|
|
29
75
|
* Splits one output pipe into text and barrier markers, and releases them
|
|
30
76
|
* in order. A gated marker (one that announces a binding message) pauses
|
|
@@ -42,28 +88,39 @@ export declare class PipeDemux {
|
|
|
42
88
|
/** Release everything (the process died). */
|
|
43
89
|
close(): void;
|
|
44
90
|
}
|
|
91
|
+
/** Promises keyed by barrier number, resolvable before or after first use. */
|
|
92
|
+
export declare class Gates {
|
|
93
|
+
#private;
|
|
94
|
+
wait(n: number): Promise<void>;
|
|
95
|
+
open(n: number): void;
|
|
96
|
+
openAll(): void;
|
|
97
|
+
}
|
|
45
98
|
export interface TargetEvents {
|
|
46
99
|
stream(name: "stdout" | "stderr", text: string): void;
|
|
47
100
|
message(msg: TargetMessage): void;
|
|
48
|
-
|
|
101
|
+
/** `note` says why, when the target was stopped on purpose. */
|
|
102
|
+
exit(code: number | null, note?: string): void;
|
|
49
103
|
}
|
|
50
104
|
export declare class Interrupted extends Error {
|
|
51
105
|
name: string;
|
|
52
106
|
}
|
|
53
|
-
export declare class Target {
|
|
107
|
+
export declare class Target implements KernelTarget {
|
|
54
108
|
#private;
|
|
55
|
-
readonly runtime:
|
|
109
|
+
readonly runtime: CdpRuntime;
|
|
110
|
+
readonly transform: typeof transformCell;
|
|
56
111
|
readonly versions: {
|
|
57
112
|
runtime: string;
|
|
58
|
-
|
|
113
|
+
engine: string;
|
|
59
114
|
};
|
|
60
115
|
private constructor();
|
|
61
|
-
static launch(runtime:
|
|
116
|
+
static launch(runtime: CdpRuntime, events: TargetEvents, options?: {
|
|
62
117
|
cwd?: string;
|
|
63
118
|
executable?: string;
|
|
64
119
|
denoCompat?: boolean;
|
|
65
120
|
denoConfig?: string;
|
|
66
121
|
}): Promise<Target>;
|
|
122
|
+
/** The Debugger domain on this target's session, for src/debugger.ts. */
|
|
123
|
+
get debug(): DebugChannel;
|
|
67
124
|
/** Resolves once all output and messages sent so far have been delivered. */
|
|
68
125
|
flush(): Promise<void>;
|
|
69
126
|
/** Runs transformed cell code. Rejects with Interrupted after interrupt(). */
|
|
@@ -99,6 +156,7 @@ export declare class Target {
|
|
|
99
156
|
stack: string;
|
|
100
157
|
}>;
|
|
101
158
|
propertyNames(expression: string): Promise<string[]>;
|
|
159
|
+
inspect(path: string, detail: number): Promise<string | null>;
|
|
102
160
|
lexicalNames(): Promise<string[]>;
|
|
103
161
|
replyInput(value: string | null): void;
|
|
104
162
|
kill(): void;
|