@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/target.js
CHANGED
|
@@ -6,7 +6,7 @@ import process from "node:process";
|
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { asEvaluateResult, CdpAbandoned, CdpSession } from "./cdp.js";
|
|
9
|
-
import { SHIM } from "./transform.js";
|
|
9
|
+
import { SHIM, transformCell } from "./transform.js";
|
|
10
10
|
import { hostRuntime, isInsideNodeModules, runtimeDir } from "./package.js";
|
|
11
11
|
const BINDING = "__cdpk_send";
|
|
12
12
|
const MARKER_RE = /\x1b\[cdpk:(\d+)(m?)\]\x1b/;
|
|
@@ -41,7 +41,7 @@ export function launchCommand(runtime, bootstrap, { executable, denoConfig } = {
|
|
|
41
41
|
const node = hostRuntime() === "node" ? process.execPath : "node";
|
|
42
42
|
return { cmd: executable ?? node, args: [...inspect, bootstrap] };
|
|
43
43
|
}
|
|
44
|
-
function isTargetMessage(value) {
|
|
44
|
+
export function isTargetMessage(value) {
|
|
45
45
|
if (typeof value !== "object" || value === null)
|
|
46
46
|
return false;
|
|
47
47
|
const kind = value.kind;
|
|
@@ -140,7 +140,7 @@ export class PipeDemux {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
/** Promises keyed by barrier number, resolvable before or after first use. */
|
|
143
|
-
class Gates {
|
|
143
|
+
export class Gates {
|
|
144
144
|
#map = new Map();
|
|
145
145
|
#entry(n) {
|
|
146
146
|
let entry = this.#map.get(n);
|
|
@@ -169,6 +169,7 @@ export class Interrupted extends Error {
|
|
|
169
169
|
}
|
|
170
170
|
export class Target {
|
|
171
171
|
runtime;
|
|
172
|
+
transform = transformCell;
|
|
172
173
|
#child;
|
|
173
174
|
#shell;
|
|
174
175
|
#control;
|
|
@@ -178,6 +179,8 @@ export class Target {
|
|
|
178
179
|
#contextId;
|
|
179
180
|
#messages = Promise.resolve();
|
|
180
181
|
#interrupted = false;
|
|
182
|
+
#paused = false;
|
|
183
|
+
#baselineGlobals = new Set();
|
|
181
184
|
versions;
|
|
182
185
|
constructor(runtime, child, shell, control, stdout, stderr, gates, contextId, versions) {
|
|
183
186
|
this.runtime = runtime;
|
|
@@ -253,7 +256,7 @@ export class Target {
|
|
|
253
256
|
await shell.send("Runtime.enable");
|
|
254
257
|
await shell.send("Runtime.addBinding", { name: BINDING });
|
|
255
258
|
const contextId = await contextReady;
|
|
256
|
-
const target = new Target(runtime, child, shell, control, stdout, stderr, gates, contextId, { runtime: "",
|
|
259
|
+
const target = new Target(runtime, child, shell, control, stdout, stderr, gates, contextId, { runtime: "", engine: "" });
|
|
257
260
|
shell.on("Runtime.bindingCalled", (params) => {
|
|
258
261
|
if (params.name !== BINDING || typeof params.payload !== "string")
|
|
259
262
|
return;
|
|
@@ -268,10 +271,28 @@ export class Target {
|
|
|
268
271
|
break;
|
|
269
272
|
await new Promise((r) => setTimeout(r, 25));
|
|
270
273
|
}
|
|
271
|
-
const versions = await target.#evaluateRaw(`JSON.stringify(typeof Deno !== "undefined" ? { runtime: Deno.version.deno,
|
|
274
|
+
const versions = await target.#evaluateRaw(`JSON.stringify(typeof Deno !== "undefined" ? { runtime: Deno.version.deno, engine: "V8 " + Deno.version.v8 } : { runtime: process.versions.node, engine: "V8 " + process.versions.v8 })`);
|
|
272
275
|
Object.assign(target.versions, JSON.parse(String(versions.result.value)));
|
|
276
|
+
// The runtime's own enumerable globals, before any cell: the debugger's
|
|
277
|
+
// variable views show only what cells added (#18).
|
|
278
|
+
const globals = asEvaluateResult(await shell.send("Runtime.evaluate", { expression: "Object.keys(globalThis)", contextId, returnByValue: true }));
|
|
279
|
+
const names = globals.result.value;
|
|
280
|
+
if (Array.isArray(names))
|
|
281
|
+
for (const name of names)
|
|
282
|
+
target.#baselineGlobals.add(String(name));
|
|
283
|
+
shell.on("Debugger.paused", () => (target.#paused = true));
|
|
284
|
+
shell.on("Debugger.resumed", () => (target.#paused = false));
|
|
273
285
|
return target;
|
|
274
286
|
}
|
|
287
|
+
/** The Debugger domain on this target's session, for src/debugger.ts. */
|
|
288
|
+
get debug() {
|
|
289
|
+
return {
|
|
290
|
+
send: (method, params = {}) => this.#shell.send(method, params),
|
|
291
|
+
on: (method, listener) => this.#shell.on(method, listener),
|
|
292
|
+
contextId: this.#contextId,
|
|
293
|
+
baselineGlobals: this.#baselineGlobals,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
275
296
|
// Binding messages wait until both pipes have caught up to their marker,
|
|
276
297
|
// and are delivered strictly in order.
|
|
277
298
|
#deliver(msg, events) {
|
|
@@ -314,6 +335,9 @@ export class Target {
|
|
|
314
335
|
*/
|
|
315
336
|
async interrupt() {
|
|
316
337
|
this.#interrupted = true;
|
|
338
|
+
// A paused isolate runs nothing to terminate: let it go first (#18).
|
|
339
|
+
if (this.#paused)
|
|
340
|
+
await this.#shell.send("Debugger.resume").catch(() => { });
|
|
317
341
|
await this.#control.send("Runtime.terminateExecution");
|
|
318
342
|
this.#shell.abandonPending();
|
|
319
343
|
}
|
|
@@ -380,8 +404,18 @@ export class Target {
|
|
|
380
404
|
const value = res.result?.value;
|
|
381
405
|
return JSON.parse(typeof value === "string" ? value : '{"name":"Error","message":"","stack":""}');
|
|
382
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Evaluates without side effects: V8 throws instead of running anything
|
|
409
|
+
* that would change state (a call, a getter that writes), so completing
|
|
410
|
+
* or inspecting `obj.` never runs user code (#10).
|
|
411
|
+
*/
|
|
412
|
+
#evaluatePure(expression) {
|
|
413
|
+
return this.#shell
|
|
414
|
+
.send("Runtime.evaluate", { expression, contextId: this.#contextId, replMode: true, throwOnSideEffect: true })
|
|
415
|
+
.then(asEvaluateResult, () => null);
|
|
416
|
+
}
|
|
383
417
|
async propertyNames(expression) {
|
|
384
|
-
const evaluated = await this.#
|
|
418
|
+
const evaluated = await this.#evaluatePure(`(${expression})`);
|
|
385
419
|
const objectId = evaluated?.result.objectId;
|
|
386
420
|
if (!objectId || evaluated?.exceptionDetails)
|
|
387
421
|
return [];
|
|
@@ -389,6 +423,23 @@ export class Target {
|
|
|
389
423
|
const list = props.result;
|
|
390
424
|
return (list ?? []).map((p) => p.name);
|
|
391
425
|
}
|
|
426
|
+
async inspect(path, detail) {
|
|
427
|
+
const found = await this.#evaluatePure(path);
|
|
428
|
+
if (!found || found.exceptionDetails)
|
|
429
|
+
return null;
|
|
430
|
+
const value = found.result;
|
|
431
|
+
const arg = value.objectId
|
|
432
|
+
? { objectId: value.objectId }
|
|
433
|
+
: value.unserializableValue !== undefined
|
|
434
|
+
? { unserializableValue: value.unserializableValue }
|
|
435
|
+
: { value: value.value };
|
|
436
|
+
const res = await this.callFunctionOn(`function (v, d) { return globalThis.${SHIM}.describeForInspect(v, d); }`, [
|
|
437
|
+
arg,
|
|
438
|
+
{ value: detail },
|
|
439
|
+
]);
|
|
440
|
+
const text = res.result?.value;
|
|
441
|
+
return typeof text === "string" ? text : null;
|
|
442
|
+
}
|
|
392
443
|
async lexicalNames() {
|
|
393
444
|
const res = await this.#shell.send("Runtime.globalLexicalScopeNames", { executionContextId: this.#contextId });
|
|
394
445
|
return res.names ?? [];
|
package/dist/text.d.ts
CHANGED
|
@@ -10,5 +10,11 @@ export type IsCompleteReply = {
|
|
|
10
10
|
export declare function checkIsComplete(code: string): IsCompleteReply;
|
|
11
11
|
/** The dotted expression ending at the cursor, e.g. "Math.fl". */
|
|
12
12
|
export declare function exprAtCursor(code: string, cursor: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* The dotted name an inspect_request means (#10): the one the cursor is in
|
|
15
|
+
* or just after, or, right after "name(" while typing a call, that callee.
|
|
16
|
+
* Null when it isn't a plain path; nothing else is evaluated.
|
|
17
|
+
*/
|
|
18
|
+
export declare function inspectTarget(code: string, cursor: number): string | null;
|
|
13
19
|
export declare function codePointToUtf16Index(str: string, cpOffset: number): number;
|
|
14
20
|
export declare function utf16ToCodePointIndex(str: string, utf16Offset: number): number;
|
package/dist/text.js
CHANGED
|
@@ -51,6 +51,23 @@ export function exprAtCursor(code, cursor) {
|
|
|
51
51
|
const m = /[\w$.]+$/.exec(code.slice(0, cursor));
|
|
52
52
|
return m ? m[0] : "";
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The dotted name an inspect_request means (#10): the one the cursor is in
|
|
56
|
+
* or just after, or, right after "name(" while typing a call, that callee.
|
|
57
|
+
* Null when it isn't a plain path; nothing else is evaluated.
|
|
58
|
+
*/
|
|
59
|
+
export function inspectTarget(code, cursor) {
|
|
60
|
+
let end = cursor;
|
|
61
|
+
// Inside a call's parentheses with nothing typed yet: "add(|".
|
|
62
|
+
const before = code.slice(0, cursor);
|
|
63
|
+
const call = /([\w$.]+)\(\s*$/.exec(before);
|
|
64
|
+
if (call)
|
|
65
|
+
end = cursor - (before.length - before.lastIndexOf("(", cursor));
|
|
66
|
+
const head = /[\w$.]*$/.exec(code.slice(0, end))?.[0] ?? "";
|
|
67
|
+
const tail = call ? "" : (/^[\w$]*/.exec(code.slice(end))?.[0] ?? "");
|
|
68
|
+
const path = (head + tail).replace(/\.$/, "");
|
|
69
|
+
return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(path) ? path : null;
|
|
70
|
+
}
|
|
54
71
|
// Jupyter measures cursor_pos in Unicode codepoints; JS strings index UTF-16
|
|
55
72
|
// code units (denoland/deno#22771).
|
|
56
73
|
export function codePointToUtf16Index(str, cpOffset) {
|
package/dist/transform.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LineMap } from "./sourcemap.ts";
|
|
1
2
|
export declare const SHIM = "__cdpk";
|
|
2
3
|
export declare class CellSyntaxError extends Error {
|
|
3
4
|
name: string;
|
|
@@ -23,6 +24,21 @@ export interface CellTransformOptions {
|
|
|
23
24
|
/** Rewrites module-only syntax in plain JavaScript into script syntax. */
|
|
24
25
|
export declare function rewriteModuleSyntax(js: string): string;
|
|
25
26
|
export declare function transformCell(code: string, options?: CellTransformOptions): string;
|
|
27
|
+
/**
|
|
28
|
+
* transformCell, plus the map between the cell's lines and the script's,
|
|
29
|
+
* for the debugger (#18). The rewrite keeps lines (see applyEdits), so the
|
|
30
|
+
* script's lines are esbuild's, after the prelude's one.
|
|
31
|
+
*/
|
|
32
|
+
export declare function transformCellWithLines(code: string, options?: CellTransformOptions): {
|
|
33
|
+
script: string;
|
|
34
|
+
lines: LineMap;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Like transformCell, for a target that evaluates cells itself, as global
|
|
38
|
+
* code (an indirect eval), instead of through the inspector's replMode: see
|
|
39
|
+
* hoistForEval. The script's value is a promise of the boxed result.
|
|
40
|
+
*/
|
|
41
|
+
export declare function transformCellForEval(code: string, options?: CellTransformOptions): string;
|
|
26
42
|
/**
|
|
27
43
|
* Deno treats a cell like `{ "a": 1 }` as an object literal rather than a
|
|
28
44
|
* block, as browser consoles do. Returns the wrapped form to try first, or
|
package/dist/transform.js
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
// callback was not specified").
|
|
17
17
|
import { transformSync } from "esbuild";
|
|
18
18
|
import { parse } from "acorn";
|
|
19
|
-
import { full } from "acorn-walk";
|
|
19
|
+
import { full, recursive } from "acorn-walk";
|
|
20
|
+
import { LineMap } from "./sourcemap.js";
|
|
20
21
|
export const SHIM = "__cdpk";
|
|
21
22
|
// Each cell first yields one macrotask. User code then runs as an ordinary
|
|
22
23
|
// task instead of nested inside the inspector's dispatch of Runtime.evaluate,
|
|
@@ -33,7 +34,8 @@ function jsxSettings(jsx) {
|
|
|
33
34
|
}
|
|
34
35
|
return { jsx: "transform", jsxFactory: jsx?.factory, jsxFragment: jsx?.fragment };
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
/** esbuild's output, with its source map's `mappings` when asked for. */
|
|
38
|
+
function stripTypesMapped(code, options, sourcemap) {
|
|
37
39
|
const compilerOptions = {
|
|
38
40
|
verbatimModuleSyntax: true,
|
|
39
41
|
...(options.experimentalDecorators ? { experimentalDecorators: true } : {}),
|
|
@@ -42,13 +44,19 @@ function stripTypes(code, options) {
|
|
|
42
44
|
let firstError;
|
|
43
45
|
for (const loader of ["tsx", "ts"]) {
|
|
44
46
|
try {
|
|
45
|
-
|
|
47
|
+
const result = transformSync(code, {
|
|
46
48
|
loader,
|
|
47
49
|
target: "esnext",
|
|
48
50
|
...jsxSettings(options.jsx),
|
|
49
51
|
tsconfigRaw: { compilerOptions },
|
|
50
52
|
logLevel: "silent",
|
|
51
|
-
|
|
53
|
+
...(sourcemap ? { sourcemap: "external" } : {}),
|
|
54
|
+
});
|
|
55
|
+
const map = sourcemap ? JSON.parse(result.map) : null;
|
|
56
|
+
const mappings = typeof map?.mappings === "string"
|
|
57
|
+
? map.mappings
|
|
58
|
+
: "";
|
|
59
|
+
return { code: result.code, mappings };
|
|
52
60
|
}
|
|
53
61
|
catch (err) {
|
|
54
62
|
firstError ??= err;
|
|
@@ -57,6 +65,9 @@ function stripTypes(code, options) {
|
|
|
57
65
|
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
58
66
|
throw new CellSyntaxError(message.replace(/^Transform failed with \d+ errors?:\n/, ""));
|
|
59
67
|
}
|
|
68
|
+
function stripTypes(code, options) {
|
|
69
|
+
return stripTypesMapped(code, options, false).code;
|
|
70
|
+
}
|
|
60
71
|
function importedName(spec) {
|
|
61
72
|
const imported = spec.imported;
|
|
62
73
|
if (!imported)
|
|
@@ -80,11 +91,9 @@ function rewriteImport(code, node, index) {
|
|
|
80
91
|
});
|
|
81
92
|
return `var ${ns} = ${load}; var ${bindings.join(", ")};`;
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
export function rewriteModuleSyntax(js) {
|
|
85
|
-
let program;
|
|
94
|
+
function parseCell(js) {
|
|
86
95
|
try {
|
|
87
|
-
|
|
96
|
+
return parse(js, {
|
|
88
97
|
ecmaVersion: "latest",
|
|
89
98
|
sourceType: "module",
|
|
90
99
|
allowAwaitOutsideFunction: true,
|
|
@@ -94,6 +103,10 @@ export function rewriteModuleSyntax(js) {
|
|
|
94
103
|
catch (err) {
|
|
95
104
|
throw new CellSyntaxError(err instanceof Error ? err.message : String(err));
|
|
96
105
|
}
|
|
106
|
+
}
|
|
107
|
+
/** Rewrites module-only syntax in plain JavaScript into script syntax. */
|
|
108
|
+
export function rewriteModuleSyntax(js) {
|
|
109
|
+
const program = parseCell(js);
|
|
97
110
|
const edits = [];
|
|
98
111
|
let importIndex = 0;
|
|
99
112
|
full(program, (node) => {
|
|
@@ -136,15 +149,164 @@ export function rewriteModuleSyntax(js) {
|
|
|
136
149
|
});
|
|
137
150
|
// Nested edits can't occur (imports/exports are top level, import()'s
|
|
138
151
|
// keyword edit doesn't overlap its arguments), so apply back to front.
|
|
139
|
-
|
|
152
|
+
return applyEdits(js, edits);
|
|
153
|
+
}
|
|
154
|
+
function newlines(text) {
|
|
155
|
+
let count = 0;
|
|
156
|
+
for (const char of text)
|
|
157
|
+
if (char === "\n")
|
|
158
|
+
count++;
|
|
159
|
+
return count;
|
|
160
|
+
}
|
|
161
|
+
function applyEdits(js, edits) {
|
|
162
|
+
// Back to front; at a shared start, a replacement goes before an insertion.
|
|
163
|
+
edits.sort((a, b) => b.start - a.start || b.end - a.end);
|
|
140
164
|
let out = js;
|
|
141
|
-
for (const edit of edits)
|
|
142
|
-
|
|
165
|
+
for (const edit of edits) {
|
|
166
|
+
// Keep every later line where it was, so a cell's lines still map to
|
|
167
|
+
// esbuild's (the debugger relies on it, #18): a multi-line import
|
|
168
|
+
// rewritten to one line is padded with the newlines it replaced.
|
|
169
|
+
const lost = newlines(js.slice(edit.start, edit.end)) - newlines(edit.text);
|
|
170
|
+
const text = lost > 0 ? edit.text + "\n".repeat(lost) : edit.text;
|
|
171
|
+
out = out.slice(0, edit.start) + text + out.slice(edit.end);
|
|
172
|
+
}
|
|
143
173
|
return out;
|
|
144
174
|
}
|
|
175
|
+
/** Adds the names a binding pattern declares. */
|
|
176
|
+
function patternNames(pattern, names) {
|
|
177
|
+
switch (pattern.type) {
|
|
178
|
+
case "Identifier":
|
|
179
|
+
names.add(pattern.name);
|
|
180
|
+
break;
|
|
181
|
+
case "ObjectPattern":
|
|
182
|
+
for (const prop of pattern.properties)
|
|
183
|
+
patternNames(prop.type === "Property" ? prop.value : prop, names);
|
|
184
|
+
break;
|
|
185
|
+
case "ArrayPattern":
|
|
186
|
+
for (const element of pattern.elements)
|
|
187
|
+
if (element)
|
|
188
|
+
patternNames(element, names);
|
|
189
|
+
break;
|
|
190
|
+
case "RestElement":
|
|
191
|
+
patternNames(pattern.argument, names);
|
|
192
|
+
break;
|
|
193
|
+
case "AssignmentPattern":
|
|
194
|
+
patternNames(pattern.left, names);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Makes a cell's declarations global without replMode, the tslab/IJavascript
|
|
200
|
+
* technique: the cell runs inside an async function (for top-level await),
|
|
201
|
+
* and every name it declares at the top level is declared with `var` in the
|
|
202
|
+
* surrounding script, which a global-code eval turns into a property of
|
|
203
|
+
* globalThis. Inside the function the declarations become assignments to
|
|
204
|
+
* those globals:
|
|
205
|
+
* const { a } = o; class K {} -> var a, K;
|
|
206
|
+
* (async () => { ({ a } = o); K = class K {}; })()
|
|
207
|
+
* So later cells, and closures from this one, all see one binding. A `var`
|
|
208
|
+
* in a top-level block or loop head is global too, as in a script; block
|
|
209
|
+
* scoped and function-local declarations stay where they are. Functions
|
|
210
|
+
* keep their in-cell hoisting and are copied to globalThis on entry.
|
|
211
|
+
*
|
|
212
|
+
* A trailing expression statement becomes `return [value]`; the box keeps a
|
|
213
|
+
* promise from being awaited, so the cell's result is the promise itself, as
|
|
214
|
+
* in Deno.
|
|
215
|
+
*/
|
|
216
|
+
function hoistForEval(js) {
|
|
217
|
+
const program = parseCell(js);
|
|
218
|
+
const names = new Set();
|
|
219
|
+
const functions = [];
|
|
220
|
+
const edits = [];
|
|
221
|
+
const src = (node) => js.slice(node.start, node.end);
|
|
222
|
+
const replace = (node, text) => edits.push({ start: node.start, end: node.end, text });
|
|
223
|
+
// A declarator without an initializer resets the name, except under var,
|
|
224
|
+
// where redeclaring keeps the value.
|
|
225
|
+
const assignments = (decl) => decl.declarations.flatMap((d) => {
|
|
226
|
+
patternNames(d.id, names);
|
|
227
|
+
if (d.init)
|
|
228
|
+
return [`${src(d.id)} = ${src(d.init)}`];
|
|
229
|
+
return decl.kind === "var" ? [] : [`${src(d.id)} = undefined`];
|
|
230
|
+
});
|
|
231
|
+
const statement = (decl) => {
|
|
232
|
+
const parts = assignments(decl);
|
|
233
|
+
replace(decl, parts.length > 0 ? `(${parts.join(", ")});` : ";");
|
|
234
|
+
};
|
|
235
|
+
const isVar = (node) => node?.type === "VariableDeclaration" && node.kind === "var";
|
|
236
|
+
const loopHead = (left) => {
|
|
237
|
+
if (!isVar(left))
|
|
238
|
+
return;
|
|
239
|
+
const id = left.declarations[0].id;
|
|
240
|
+
patternNames(id, names);
|
|
241
|
+
replace(left, src(id));
|
|
242
|
+
};
|
|
243
|
+
// Expressions can't hold statements outside a function or class, so only
|
|
244
|
+
// statement bodies are walked.
|
|
245
|
+
const nestedVars = (node) => recursive(node, null, {
|
|
246
|
+
Function() { },
|
|
247
|
+
Class() { },
|
|
248
|
+
VariableDeclaration(decl) {
|
|
249
|
+
if (decl.kind === "var")
|
|
250
|
+
statement(decl);
|
|
251
|
+
},
|
|
252
|
+
ForStatement(loop, state, walk) {
|
|
253
|
+
if (isVar(loop.init))
|
|
254
|
+
replace(loop.init, assignments(loop.init).join(", "));
|
|
255
|
+
walk(loop.body, state);
|
|
256
|
+
},
|
|
257
|
+
ForInStatement(loop, state, walk) {
|
|
258
|
+
loopHead(loop.left);
|
|
259
|
+
walk(loop.body, state);
|
|
260
|
+
},
|
|
261
|
+
ForOfStatement(loop, state, walk) {
|
|
262
|
+
loopHead(loop.left);
|
|
263
|
+
walk(loop.body, state);
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
program.body.forEach((stmt, i) => {
|
|
267
|
+
if (stmt.type === "VariableDeclaration" && (stmt.kind === "var" || stmt.kind === "let" || stmt.kind === "const")) {
|
|
268
|
+
statement(stmt);
|
|
269
|
+
}
|
|
270
|
+
else if (stmt.type === "FunctionDeclaration") {
|
|
271
|
+
names.add(stmt.id.name);
|
|
272
|
+
functions.push(stmt.id.name);
|
|
273
|
+
}
|
|
274
|
+
else if (stmt.type === "ClassDeclaration") {
|
|
275
|
+
names.add(stmt.id.name);
|
|
276
|
+
edits.push({ start: stmt.start, end: stmt.start, text: `${stmt.id.name} = ` });
|
|
277
|
+
edits.push({ start: stmt.end, end: stmt.end, text: ";" });
|
|
278
|
+
}
|
|
279
|
+
else if (stmt.type === "ExpressionStatement" && i === program.body.length - 1) {
|
|
280
|
+
replace(stmt, `return [${src(stmt.expression)}];`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
nestedVars(stmt);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
const declare = names.size > 0 ? `var ${[...names].join(", ")};\n` : "";
|
|
287
|
+
const publish = functions.map((name) => ` globalThis.${name} = ${name};`).join("");
|
|
288
|
+
return `${declare}(async () => { 'use strict';${publish}\n${applyEdits(js, edits)}\n})()`;
|
|
289
|
+
}
|
|
145
290
|
export function transformCell(code, options = {}) {
|
|
146
291
|
return PRELUDE + rewriteModuleSyntax(stripTypes(code, options));
|
|
147
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* transformCell, plus the map between the cell's lines and the script's,
|
|
295
|
+
* for the debugger (#18). The rewrite keeps lines (see applyEdits), so the
|
|
296
|
+
* script's lines are esbuild's, after the prelude's one.
|
|
297
|
+
*/
|
|
298
|
+
export function transformCellWithLines(code, options = {}) {
|
|
299
|
+
const stripped = stripTypesMapped(code, options, true);
|
|
300
|
+
return { script: PRELUDE + rewriteModuleSyntax(stripped.code), lines: new LineMap(stripped.mappings, newlines(PRELUDE)) };
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Like transformCell, for a target that evaluates cells itself, as global
|
|
304
|
+
* code (an indirect eval), instead of through the inspector's replMode: see
|
|
305
|
+
* hoistForEval. The script's value is a promise of the boxed result.
|
|
306
|
+
*/
|
|
307
|
+
export function transformCellForEval(code, options = {}) {
|
|
308
|
+
return hoistForEval(rewriteModuleSyntax(stripTypes(code, options)));
|
|
309
|
+
}
|
|
148
310
|
/**
|
|
149
311
|
* Deno treats a cell like `{ "a": 1 }` as an object literal rather than a
|
|
150
312
|
* block, as browser consoles do. Returns the wrapped form to try first, or
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { CdpParams, EvaluateResult, RemoteObject } from "./cdp.ts";
|
|
2
|
+
import { transformCellForEval } from "./transform.ts";
|
|
3
|
+
import { type KernelTarget, type TargetEvents } from "./target.ts";
|
|
4
|
+
import { type WsChannel } from "./ws-client.ts";
|
|
5
|
+
/** The driver Worker's modules; the first is its main module. */
|
|
6
|
+
export declare const WORKER_MODULES: string[];
|
|
7
|
+
/** See BunTarget: how long an interrupted cell has to show it isn't busy. */
|
|
8
|
+
export declare const INTERRUPT_GRACE_MS = 500;
|
|
9
|
+
export declare function restartNote(runtime: string): string;
|
|
10
|
+
export declare const RESTART_NOTE: string;
|
|
11
|
+
export declare function workerdConfig({ socket, compatibilityDate }: {
|
|
12
|
+
socket: string;
|
|
13
|
+
compatibilityDate: string;
|
|
14
|
+
}): string;
|
|
15
|
+
/**
|
|
16
|
+
* A Workers project has workerd in its node_modules (wrangler and miniflare
|
|
17
|
+
* depend on it), so look there first, from the notebook's directory up;
|
|
18
|
+
* otherwise use the one on PATH.
|
|
19
|
+
*/
|
|
20
|
+
export declare function findWorkerd(cwd: string): string;
|
|
21
|
+
/** "workerd 2026-10-15" -> "2026-10-15": the newest date it supports. */
|
|
22
|
+
export declare function compatibilityDateOf(versionOutput: string): string;
|
|
23
|
+
/** The literal specifiers a transformed cell imports, in order. */
|
|
24
|
+
export declare function importSpecifiers(script: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Bundles `specifier`, as imported from `cwd`, into global code whose value
|
|
27
|
+
* is a promise of the module's namespace. npm: and jsr: install into the
|
|
28
|
+
* npm cache, as on a Node target. Builtins stay external and are handed to
|
|
29
|
+
* the bundle as `require` (workerd-worker.mjs's requireFor). A specifier
|
|
30
|
+
* that can't be bundled becomes code that throws when imported, so the
|
|
31
|
+
* error appears in the cell.
|
|
32
|
+
*/
|
|
33
|
+
export declare function bundleModule(specifier: string, cwd: string): Promise<string>;
|
|
34
|
+
/** What import.meta says in a cell: the notebook's directory, not workerd's. */
|
|
35
|
+
export declare function cellMeta(cwd: string): {
|
|
36
|
+
url: string;
|
|
37
|
+
dirname: string;
|
|
38
|
+
filename: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* How the target reaches its driver Worker: a workerd of its own
|
|
42
|
+
* (RawWorkerd, below), or a Workers project's Miniflare
|
|
43
|
+
* (src/workers-project.ts), which adds the project's bindings.
|
|
44
|
+
*/
|
|
45
|
+
export interface WorkerHost {
|
|
46
|
+
/** The runtime's name for the banner: workerd's date, or Miniflare's version. */
|
|
47
|
+
readonly runtime: string;
|
|
48
|
+
/** The wrangler config whose bindings the driver has, if any. */
|
|
49
|
+
readonly project?: string;
|
|
50
|
+
/** The open session with the driver. */
|
|
51
|
+
readonly channel: WsChannel;
|
|
52
|
+
/** Stops the runtime; `now` even while a cell is busy. */
|
|
53
|
+
stop(now: boolean): void;
|
|
54
|
+
onExit(listener: (code: number | null) => void): void;
|
|
55
|
+
}
|
|
56
|
+
/** The driver on a workerd of its own, behind a Unix socket. */
|
|
57
|
+
export declare class RawWorkerd implements WorkerHost {
|
|
58
|
+
#private;
|
|
59
|
+
readonly runtime: string;
|
|
60
|
+
readonly channel: WsChannel;
|
|
61
|
+
private constructor();
|
|
62
|
+
static start(cwd: string, executable?: string): Promise<RawWorkerd>;
|
|
63
|
+
stop(now: boolean): void;
|
|
64
|
+
onExit(listener: (code: number | null) => void): void;
|
|
65
|
+
}
|
|
66
|
+
/** A runtime that runs the driver Worker: workerd, or celld (src/celld-target.ts). */
|
|
67
|
+
export type WorkersRuntime = "workerd" | "celld";
|
|
68
|
+
export declare class WorkerdTarget implements KernelTarget {
|
|
69
|
+
#private;
|
|
70
|
+
readonly runtime: WorkersRuntime;
|
|
71
|
+
readonly versions: {
|
|
72
|
+
runtime: string;
|
|
73
|
+
engine: string;
|
|
74
|
+
};
|
|
75
|
+
notice: string | undefined;
|
|
76
|
+
readonly transform: typeof transformCellForEval;
|
|
77
|
+
private constructor();
|
|
78
|
+
/**
|
|
79
|
+
* In a Workers project (a wrangler config at or above `cwd`), the
|
|
80
|
+
* project's own Miniflare runs the driver with the project's bindings;
|
|
81
|
+
* otherwise a workerd of its own does (#26).
|
|
82
|
+
*/
|
|
83
|
+
static launch(events: TargetEvents, options?: {
|
|
84
|
+
cwd?: string;
|
|
85
|
+
executable?: string;
|
|
86
|
+
}): Promise<WorkerdTarget>;
|
|
87
|
+
/**
|
|
88
|
+
* A target over a started host's session. `notice` (a project that
|
|
89
|
+
* failed to load) is replaced by the bindings when the host has a project.
|
|
90
|
+
*/
|
|
91
|
+
static open(host: WorkerHost, cwd: string, events: TargetEvents, runtime: WorkersRuntime, notice?: string): Promise<WorkerdTarget>;
|
|
92
|
+
evaluate(script: string): Promise<EvaluateResult>;
|
|
93
|
+
/**
|
|
94
|
+
* A cell that's waiting on something leaves the driver free: it answers a
|
|
95
|
+
* ping, and the cell is abandoned with the session intact (its pending
|
|
96
|
+
* work still runs, as on Bun). A busy cell can't be reached, so workerd is
|
|
97
|
+
* stopped and restarts on the next cell.
|
|
98
|
+
*/
|
|
99
|
+
interrupt(): Promise<void>;
|
|
100
|
+
/** Output arrives on the session before the reply it precedes. */
|
|
101
|
+
flush(): Promise<void>;
|
|
102
|
+
broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
|
|
103
|
+
dispatchComm(msgType: string, content: CdpParams, metadata: CdpParams, buffers: Uint8Array[]): Promise<{
|
|
104
|
+
handled: boolean;
|
|
105
|
+
} | {
|
|
106
|
+
error: {
|
|
107
|
+
name: string;
|
|
108
|
+
message: string;
|
|
109
|
+
stack: string;
|
|
110
|
+
};
|
|
111
|
+
}>;
|
|
112
|
+
describeError(exception: RemoteObject): Promise<{
|
|
113
|
+
name: string;
|
|
114
|
+
message: string;
|
|
115
|
+
stack: string;
|
|
116
|
+
}>;
|
|
117
|
+
propertyNames(expression: string): Promise<string[]>;
|
|
118
|
+
/** Resolved by property descriptor in the driver, so nothing runs (#10). */
|
|
119
|
+
inspect(path: string, detail: number): Promise<string | null>;
|
|
120
|
+
/** None: top-level declarations here are properties of globalThis. */
|
|
121
|
+
lexicalNames(): Promise<string[]>;
|
|
122
|
+
/** Never asked for: prompt() in workerd answers null without asking. */
|
|
123
|
+
replyInput(): void;
|
|
124
|
+
kill(): void;
|
|
125
|
+
}
|