@tlockney/cdp-kernel 0.3.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 +93 -16
- package/dist/bun-target.d.ts +2 -0
- package/dist/bun-target.js +5 -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 +65 -8
- package/dist/main.js +3 -1
- package/dist/murmur2.d.ts +2 -0
- package/dist/murmur2.js +41 -0
- package/dist/package.d.ts +16 -2
- package/dist/package.js +25 -2
- package/dist/sourcemap.d.ts +22 -0
- package/dist/sourcemap.js +86 -0
- package/dist/target.d.ts +8 -0
- package/dist/target.js +51 -1
- package/dist/text.d.ts +6 -0
- package/dist/text.js +17 -0
- package/dist/transform.d.ts +10 -0
- package/dist/transform.js +38 -5
- package/dist/workerd-target.d.ts +2 -0
- package/dist/workerd-target.js +8 -3
- package/package.json +2 -2
- package/runtime/bootstrap.mjs +17 -2
- 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 +4 -0
- package/runtime/evaluator.mjs +8 -0
- package/runtime/introspect.d.mts +13 -0
- package/runtime/introspect.mjs +120 -0
- package/runtime/node-hooks.d.mts +19 -1
- package/runtime/node-hooks.mjs +144 -16
- package/runtime/workerd-worker.mjs +5 -0
package/README.md
CHANGED
|
@@ -45,7 +45,8 @@ Each install writes a Jupyter kernelspec: `cdp-node`, `cdp-deno`,
|
|
|
45
45
|
`cdp-node-deno` (Node with the Deno compatibility namespace), `cdp-bun`,
|
|
46
46
|
`cdp-workerd`, or `cdp-celld`. Then pick the kernel in JupyterLab or VS Code.
|
|
47
47
|
|
|
48
|
-
**From npm** (host runs on Node 24
|
|
48
|
+
**From npm** (host runs on Node 24+). CI tests Node 24 and 26 with Deno 2.9
|
|
49
|
+
and the latest 2.x, on Linux and macOS. Windows isn't supported yet.
|
|
49
50
|
|
|
50
51
|
```sh
|
|
51
52
|
npm install -g @tlockney/cdp-kernel
|
|
@@ -80,10 +81,17 @@ Windows build).
|
|
|
80
81
|
Deno 2.9+ refuses package versions less than 24 hours old by default
|
|
81
82
|
([minimum dependency age](https://docs.deno.com/go/minimum-dependency-age)).
|
|
82
83
|
In a release's first day, the install command fails with "blocked by the
|
|
83
|
-
minimum dependency age policy".
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
minimum dependency age policy". Pass `--min-dep-age=0` to the install
|
|
85
|
+
command to get it through:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
deno run -A --min-dep-age=0 jsr:@tlockney/cdp-kernel/cli install --runtime deno
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The kernelspec it writes works on release day too. It relaunches the kernel
|
|
92
|
+
with a dependency cutoff of two days after the release
|
|
93
|
+
(`--min-dep-age=<date>`), so it never resolves anything published after
|
|
94
|
+
that.
|
|
87
95
|
|
|
88
96
|
**From a checkout**, for working on the kernel itself:
|
|
89
97
|
|
|
@@ -116,7 +124,20 @@ In a cell:
|
|
|
116
124
|
from the notebook's `node_modules`, `node:` builtins, and `npm:pkg@range`
|
|
117
125
|
or `jsr:@scope/pkg@range`.
|
|
118
126
|
- On Node, `npm:` and `jsr:` install on first use into
|
|
119
|
-
`~/.cache/cdp-kernel/npm`.
|
|
127
|
+
`~/.cache/cdp-kernel/npm`. An exact version (`npm:react@19.0.0`) installs
|
|
128
|
+
exactly that version. A range (`npm:react@19`) is resolved the first
|
|
129
|
+
time it's imported and stays at that version, even as other packages
|
|
130
|
+
are installed later. Caches written by cdp-kernel 0.3.0 or earlier
|
|
131
|
+
recorded ranges, which later installs could re-resolve. Delete
|
|
132
|
+
`~/.cache/cdp-kernel/npm` to start fresh. `jsr:` works with npm 12,
|
|
133
|
+
which otherwise refuses JSR's registry tarballs.
|
|
134
|
+
- **Lockfile** (cdp-kernel 0.4.0 or later). To pin versions, create a `cdp-kernel.lock.json` in the
|
|
135
|
+
notebook's directory (or above it) containing `{}`. From then on, each
|
|
136
|
+
`npm:`/`jsr:` range is recorded with the exact version it resolved to,
|
|
137
|
+
and installed at that version, so the notebook gets the same packages
|
|
138
|
+
on another machine. Commit it next to the notebook. Deno targets use
|
|
139
|
+
Deno's own resolution and lock instead. Installs into the shared cache
|
|
140
|
+
are serialized, so two kernels installing at once can't corrupt it.
|
|
120
141
|
- On Deno they use Deno's global cache, unless the notebook is in a Deno
|
|
121
142
|
project (below).
|
|
122
143
|
- On Bun they install with `bun add` into `~/.cache/cdp-kernel/bun`. Other
|
|
@@ -143,6 +164,13 @@ In a cell:
|
|
|
143
164
|
- `prompt()` and `confirm()` work over the stdin channel, except on
|
|
144
165
|
workerd. Interrupt keeps state, except for a busy cell on Bun or workerd
|
|
145
166
|
(below).
|
|
167
|
+
- **Shift-Tab** shows a tooltip for the name at the cursor: its type, a
|
|
168
|
+
function's signature (and its source at the second press), a preview, and
|
|
169
|
+
its properties. Nothing is run to get it. On Node and Deno the value is
|
|
170
|
+
evaluated with V8's side-effect check (`throwOnSideEffect`), which also
|
|
171
|
+
guards completion. Bun, workerd and celld look the name up by property,
|
|
172
|
+
without calling getters. Executed cells are kept for `history_request`
|
|
173
|
+
(tail, range and search).
|
|
146
174
|
|
|
147
175
|
## Bun
|
|
148
176
|
|
|
@@ -248,6 +276,35 @@ all work as on workerd. The differences:
|
|
|
248
276
|
waits for it to stop gracefully, which a busy cell never allows. So an
|
|
249
277
|
interrupt of a busy cell kills the node, then `celld dev`.
|
|
250
278
|
|
|
279
|
+
## Debugger
|
|
280
|
+
|
|
281
|
+
On Node and Deno targets, JupyterLab's visual debugger works: the bug
|
|
282
|
+
button in the notebook toolbar, breakpoints in the cell gutter, the call
|
|
283
|
+
stack, variables and scopes, stepping, pause on exceptions, the debug
|
|
284
|
+
console, and the rich variable viewer. The kernel translates the Jupyter
|
|
285
|
+
debug protocol (DAP over `debug_request`) to V8's `Debugger` domain on the
|
|
286
|
+
session it already holds (`src/debugger.ts`):
|
|
287
|
+
|
|
288
|
+
- **Cell paths.** Each cell is named as JupyterLab names it: a temp-dir
|
|
289
|
+
prefix plus the Murmur2 hash of its code (`src/murmur2.ts`, matched
|
|
290
|
+
against ipykernel's). It's evaluated with that name as its
|
|
291
|
+
`//# sourceURL`, so breakpoints bind to the cell and frames point back at
|
|
292
|
+
it.
|
|
293
|
+
- **Lines.** Breakpoint and frame lines go through the cell's source map
|
|
294
|
+
(`src/sourcemap.ts`), since esbuild rewrites the TypeScript. A breakpoint
|
|
295
|
+
on a blank or type-only line moves to the next statement.
|
|
296
|
+
- **Nothing runs on its own.** The variable explorer between cells reads
|
|
297
|
+
the cells' declarations with V8's side-effect check, so a getter never
|
|
298
|
+
runs. Frames, scopes and the rich viewer evaluate on the paused frame.
|
|
299
|
+
- **Kernelspec.** `metadata.debugger` is set for Node and Deno kernelspecs,
|
|
300
|
+
which is what JupyterLab checks. Reinstall older kernelspecs to get it.
|
|
301
|
+
|
|
302
|
+
`CDPK_DEBUG_TRACE=1` logs every debug request, reply and event to the
|
|
303
|
+
Jupyter server's log.
|
|
304
|
+
|
|
305
|
+
Bun, workerd and celld don't run over an inspector, so they have no
|
|
306
|
+
debugger.
|
|
307
|
+
|
|
251
308
|
## Editor types
|
|
252
309
|
|
|
253
310
|
Cells run untyped, as in `deno jupyter`. Notebook code your editor
|
|
@@ -357,12 +414,15 @@ CLI: an installed npm bin, or `deno run -A src/main.ts` to test a Deno host.
|
|
|
357
414
|
One version number lives in three places: `package.json`, `deno.jsonc` and
|
|
358
415
|
`src/package.ts`. `test/package.test.ts` fails if they disagree. To release:
|
|
359
416
|
|
|
360
|
-
1. Bump all three,
|
|
417
|
+
1. Bump all three, and set `RELEASE_DATE` in `src/package.ts` to today
|
|
418
|
+
(UTC). Commit, and merge to `main`.
|
|
361
419
|
2. Tag and push: `git tag v0.1.1 && git push origin v0.1.1`.
|
|
362
420
|
|
|
363
421
|
The `publish` workflow then:
|
|
364
422
|
|
|
365
|
-
1. Checks that the tag matches the version
|
|
423
|
+
1. Checks that the tag matches the version, and that `RELEASE_DATE` is
|
|
424
|
+
today or yesterday. A JSR kernelspec's dependency cutoff comes from that
|
|
425
|
+
date, so a stale one would block the release it ships in.
|
|
366
426
|
2. Runs the type check, the unit tests, and the e2e suite against the packed
|
|
367
427
|
npm tarball and against a Deno host.
|
|
368
428
|
3. Publishes to JSR (`deno publish`).
|
|
@@ -381,15 +441,32 @@ that failed.
|
|
|
381
441
|
|
|
382
442
|
- Widgets are anywidget-style (`jupyter.widget`). The classic ipywidgets
|
|
383
443
|
controls (sliders, dropdowns) are Python classes, so they're not available.
|
|
384
|
-
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
444
|
+
- Inspection tooltips show what's found at runtime (type, signature,
|
|
445
|
+
preview, properties), not TypeScript types or JSDoc. On Bun, workerd and
|
|
446
|
+
celld, a getter on the path is reported but not called. History is kept
|
|
447
|
+
for the current kernel session only.
|
|
448
|
+
- The debugger is for Node and Deno targets only. Breakpoints are per line,
|
|
449
|
+
conditional breakpoints and logpoints aren't supported, variables can't be
|
|
450
|
+
set from the debugger, and "copy to globals" isn't offered.
|
|
451
|
+
- `npm:` on Node, Bun, workerd and celld:
|
|
452
|
+
- one npm alias per `name@range` (or exact version, when locked);
|
|
453
|
+
- the cache is shared by every notebook, so peer dependencies resolve by
|
|
454
|
+
real name from it;
|
|
455
|
+
- a project's own `package.json` doesn't decide an `npm:` import's version
|
|
456
|
+
(bare imports do use its `node_modules`).
|
|
390
457
|
- No type checking. Deno doesn't type-check REPL cells either.
|
|
391
|
-
- Results are formatted
|
|
392
|
-
|
|
458
|
+
- Results are formatted by each runtime's own inspector: `util.inspect` on
|
|
459
|
+
Node, Bun, workerd and celld, and `Deno.inspect` on Deno. On a fixture of
|
|
460
|
+
30 typical values, 23 format identically on Node and Deno. The rest
|
|
461
|
+
differ in two ways:
|
|
462
|
+
- strings are quoted `'…'` on Node and `"…"` on Deno, wherever they
|
|
463
|
+
appear;
|
|
464
|
+
- a displayed `Error`'s stack also lists Node's own module-loader frames.
|
|
465
|
+
|
|
466
|
+
That's deliberate: each runtime's users see the format they expect, and
|
|
467
|
+
Node's inspector has no quote-style option, short of porting Deno's
|
|
468
|
+
formatter. `test/formatting.test.ts` runs both runtimes on the fixture
|
|
469
|
+
and fails if a new kind of difference appears (#17).
|
|
393
470
|
- WebGPU texture and buffer display is dropped.
|
|
394
471
|
- On Bun:
|
|
395
472
|
- Interrupting a busy cell restarts Bun and loses state. An awaiting cell
|
package/dist/bun-target.d.ts
CHANGED
|
@@ -51,6 +51,8 @@ export declare class BunTarget implements KernelTarget {
|
|
|
51
51
|
stack: string;
|
|
52
52
|
}>;
|
|
53
53
|
propertyNames(expression: string): Promise<string[]>;
|
|
54
|
+
/** Resolved by property descriptor in the target, so nothing runs (#10). */
|
|
55
|
+
inspect(path: string, detail: number): Promise<string | null>;
|
|
54
56
|
/** None: top-level declarations here are properties of globalThis. */
|
|
55
57
|
lexicalNames(): Promise<string[]>;
|
|
56
58
|
replyInput(value: string | null): void;
|
package/dist/bun-target.js
CHANGED
|
@@ -218,6 +218,11 @@ export class BunTarget {
|
|
|
218
218
|
const reply = await this.#request("props", { expr: expression }).catch(() => null);
|
|
219
219
|
return Array.isArray(reply?.names) ? reply.names.filter((n) => typeof n === "string") : [];
|
|
220
220
|
}
|
|
221
|
+
/** Resolved by property descriptor in the target, so nothing runs (#10). */
|
|
222
|
+
async inspect(path, detail) {
|
|
223
|
+
const reply = await this.#request("inspect", { path, detail }).catch(() => null);
|
|
224
|
+
return typeof reply?.text === "string" ? reply.text : null;
|
|
225
|
+
}
|
|
221
226
|
/** None: top-level declarations here are properties of globalThis. */
|
|
222
227
|
lexicalNames() {
|
|
223
228
|
return Promise.resolve([]);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CdpParams, RemoteObject } from "./cdp.ts";
|
|
2
|
+
import { type CellTransformOptions } from "./transform.ts";
|
|
3
|
+
/** What the debugger needs from a CDP target: its session, with the Debugger domain. */
|
|
4
|
+
export interface DebugChannel {
|
|
5
|
+
send(method: string, params?: CdpParams): Promise<CdpParams>;
|
|
6
|
+
on(method: string, listener: (params: CdpParams) => void): void;
|
|
7
|
+
/** The execution context cells run in. */
|
|
8
|
+
readonly contextId: number;
|
|
9
|
+
/** Enumerable globals the runtime had before any cell ran. */
|
|
10
|
+
readonly baselineGlobals: ReadonlySet<string>;
|
|
11
|
+
}
|
|
12
|
+
export type DapMessage = {
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
};
|
|
15
|
+
/** ipykernel's seed, so a kernel switch doesn't move JupyterLab's breakpoints. */
|
|
16
|
+
export declare const HASH_SEED = 3339675911;
|
|
17
|
+
export declare const THREAD_ID = 1;
|
|
18
|
+
/** A DAP value string for a CDP RemoteObject. */
|
|
19
|
+
export declare function describeRemote(object: RemoteObject): string;
|
|
20
|
+
export declare class Debugger {
|
|
21
|
+
#private;
|
|
22
|
+
readonly prefix: string;
|
|
23
|
+
readonly suffix = ".ts";
|
|
24
|
+
constructor(emit: (event: DapMessage) => void, transformOptions?: () => CellTransformOptions);
|
|
25
|
+
get started(): boolean;
|
|
26
|
+
get stopped(): boolean;
|
|
27
|
+
/** A cell's path, as JupyterLab computes it for the same code. */
|
|
28
|
+
pathFor(code: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* The target to debug: called with each target the kernel launches. A
|
|
31
|
+
* restarted target gets the session's breakpoints again.
|
|
32
|
+
*/
|
|
33
|
+
attachTarget(channel: DebugChannel | null): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* The script for a cell while debugging: transformed with its line map
|
|
36
|
+
* kept, and named for its path. `source` is what actually runs (the cell,
|
|
37
|
+
* or the cell wrapped as an object literal); the path comes from `code`,
|
|
38
|
+
* the cell as the frontend sent it.
|
|
39
|
+
*/
|
|
40
|
+
cellScript(code: string, source: string): string;
|
|
41
|
+
handle(request: DapMessage): Promise<DapMessage>;
|
|
42
|
+
}
|