@tlockney/cdp-kernel 0.7.0 → 0.7.1
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 +4 -2
- package/dist/browser/kernel.js +136 -10
- package/dist/browser/kernel.js.map +2 -2
- package/dist/browser/mod.d.ts +6 -1
- package/dist/browser/mod.js +4 -1
- package/dist/browser/target.d.ts +23 -0
- package/dist/browser/target.js +62 -4
- package/dist/browser/worker.js +83 -12
- package/dist/browser/worker.js.map +3 -3
- package/dist/bun-target.d.ts +2 -0
- package/dist/bun-target.js +15 -2
- package/dist/cdp.d.ts +2 -0
- package/dist/cdp.js +34 -7
- package/dist/celld-target.js +3 -2
- package/dist/debugger.js +51 -5
- package/dist/kernel-core.d.ts +9 -0
- package/dist/kernel-core.js +73 -3
- package/dist/kernel.js +12 -0
- package/dist/main.js +11 -4
- package/dist/package.d.ts +1 -1
- package/dist/package.js +1 -1
- package/dist/target-api.d.ts +12 -0
- package/dist/target-api.js +26 -0
- package/dist/target.d.ts +3 -2
- package/dist/target.js +25 -13
- package/dist/workerd-target.d.ts +6 -1
- package/dist/workerd-target.js +56 -12
- package/dist/workers-project.d.ts +1 -1
- package/dist/workers-project.js +4 -3
- package/package.json +3 -1
- package/runtime/bootstrap.mjs +2 -0
- package/runtime/browser-worker.mjs +83 -0
- package/runtime/evaluator.mjs +2 -0
- package/runtime/workerd-worker.mjs +2 -0
package/dist/debugger.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// as their //# sourceURL, so breakpoints bind and frames point at the cell.
|
|
9
9
|
// Lines go through the cell's source map (src/sourcemap.ts), since esbuild
|
|
10
10
|
// rewrites TypeScript.
|
|
11
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
13
|
import { join, sep } from "node:path";
|
|
14
14
|
import process from "node:process";
|
|
@@ -23,6 +23,9 @@ const EXCEPTION_FILTERS = [
|
|
|
23
23
|
{ filter: "caught", label: "Caught Exceptions", default: false },
|
|
24
24
|
{ filter: "uncaught", label: "Uncaught Exceptions", default: false },
|
|
25
25
|
];
|
|
26
|
+
/** URLs of the runtime's built-in modules: Deno's `ext:`, Node's `node:`. */
|
|
27
|
+
const RUNTIME_SCRIPTS = ["^ext:", "^node:"];
|
|
28
|
+
const RUNTIME_SCRIPT = /^(ext|node):/;
|
|
26
29
|
/** Bindings the kernel itself installs, hidden from the variable views. */
|
|
27
30
|
const KERNEL_GLOBALS = new Set(["__cdpk", "__cdpk_send", "jupyter", "prompt", "confirm"]);
|
|
28
31
|
const SCOPE_NAMES = {
|
|
@@ -52,6 +55,18 @@ function isCallFrame(value) {
|
|
|
52
55
|
typeof value.location.lineNumber === "number" && Array.isArray(value.scopeChain) &&
|
|
53
56
|
value.scopeChain.every((scope) => isRecord(scope) && typeof scope.type === "string" && isRemoteObject(scope.object));
|
|
54
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether `dir` is a directory, not a symlink, owned by this user and
|
|
60
|
+
* closed to others. Windows has no uids; its tmpdir() is per-user.
|
|
61
|
+
*/
|
|
62
|
+
function isOwnPrivateDir(dir) {
|
|
63
|
+
const stat = lstatSync(dir);
|
|
64
|
+
if (!stat.isDirectory())
|
|
65
|
+
return false;
|
|
66
|
+
if (process.getuid === undefined)
|
|
67
|
+
return true;
|
|
68
|
+
return stat.uid === process.getuid() && (stat.mode & 0o077) === 0;
|
|
69
|
+
}
|
|
55
70
|
/** A DAP value string for a CDP RemoteObject. */
|
|
56
71
|
export function describeRemote(object) {
|
|
57
72
|
if (object.type === "string")
|
|
@@ -256,6 +271,8 @@ export class Debugger {
|
|
|
256
271
|
if (!channel)
|
|
257
272
|
return;
|
|
258
273
|
await channel.send("Debugger.enable");
|
|
274
|
+
// Stepping doesn't enter the runtime's own code (#380).
|
|
275
|
+
await channel.send("Debugger.setBlackboxPatterns", { patterns: RUNTIME_SCRIPTS });
|
|
259
276
|
await channel.send("Debugger.setPauseOnExceptions", { state: this.#pauseOnExceptions });
|
|
260
277
|
this.#cdpBreakpoints.clear();
|
|
261
278
|
for (const path of this.#breakpoints.keys())
|
|
@@ -332,21 +349,38 @@ export class Debugger {
|
|
|
332
349
|
}
|
|
333
350
|
// --- stopped state ----------------------------------------------------------
|
|
334
351
|
#onPaused(params) {
|
|
352
|
+
const hit = Array.isArray(params.hitBreakpoints) && params.hitBreakpoints.length > 0;
|
|
353
|
+
const exception = params.reason === "exception" || params.reason === "promiseRejection";
|
|
354
|
+
if (exception && !hit && this.#inRuntime(params.callFrames)) {
|
|
355
|
+
// Thrown in the runtime's own code, as Chrome's ignore list treats it.
|
|
356
|
+
// Deno's lazy node:process setup throws and catches one there, which
|
|
357
|
+
// blackboxing doesn't always skip, and it held the cell (#380). The
|
|
358
|
+
// cell still sees an error that reaches it.
|
|
359
|
+
void this.#channel?.send("Debugger.resume").catch(() => { });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
335
362
|
this.#frames = Array.isArray(params.callFrames) ? params.callFrames.filter(isCallFrame) : [];
|
|
336
363
|
this.#stopped = true;
|
|
337
364
|
for (const notify of this.#pauseWaiters.splice(0))
|
|
338
365
|
notify();
|
|
339
366
|
this.#refs.clear();
|
|
340
|
-
const hit = Array.isArray(params.hitBreakpoints) && params.hitBreakpoints.length > 0;
|
|
341
367
|
const reason = hit
|
|
342
368
|
? "breakpoint"
|
|
343
|
-
:
|
|
369
|
+
: exception
|
|
344
370
|
? "exception"
|
|
345
371
|
: params.reason === "other"
|
|
346
372
|
? "step"
|
|
347
373
|
: String(params.reason ?? "pause");
|
|
348
374
|
this.#event("stopped", { reason, threadId: THREAD_ID, allThreadsStopped: true });
|
|
349
375
|
}
|
|
376
|
+
/** Whether the top frame is the runtime's own code. */
|
|
377
|
+
#inRuntime(callFrames) {
|
|
378
|
+
const top = Array.isArray(callFrames) ? callFrames[0] : undefined;
|
|
379
|
+
if (!isRecord(top) || !isRecord(top.location))
|
|
380
|
+
return false;
|
|
381
|
+
const url = this.#scripts.get(String(top.location.scriptId)) ?? (typeof top.url === "string" ? top.url : "");
|
|
382
|
+
return RUNTIME_SCRIPT.test(url);
|
|
383
|
+
}
|
|
350
384
|
#resetStop() {
|
|
351
385
|
this.#stopped = false;
|
|
352
386
|
this.#frames = [];
|
|
@@ -526,12 +560,24 @@ export class Debugger {
|
|
|
526
560
|
}
|
|
527
561
|
#writeCell(path, code) {
|
|
528
562
|
try {
|
|
563
|
+
mkdirSync(this.prefix, { recursive: true, mode: 0o700 });
|
|
564
|
+
// The path is predictable (the frontend computes it too), so another
|
|
565
|
+
// user could have made the directory first and planted symlinks in it:
|
|
566
|
+
// write only into a directory that is ours and closed to others (#339).
|
|
567
|
+
if (!isOwnPrivateDir(this.prefix))
|
|
568
|
+
return;
|
|
529
569
|
if (!this.#cleanupRegistered) {
|
|
530
570
|
// The files only serve this kernel's frontend: remove them with it.
|
|
531
|
-
process.once("exit", () =>
|
|
571
|
+
process.once("exit", () => {
|
|
572
|
+
try {
|
|
573
|
+
rmSync(this.prefix, { recursive: true, force: true });
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
// Best effort; exiting matters more.
|
|
577
|
+
}
|
|
578
|
+
});
|
|
532
579
|
this.#cleanupRegistered = true;
|
|
533
580
|
}
|
|
534
|
-
mkdirSync(this.prefix, { recursive: true });
|
|
535
581
|
writeFileSync(path, code);
|
|
536
582
|
}
|
|
537
583
|
catch {
|
package/dist/kernel-core.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface KernelHost {
|
|
|
37
37
|
/** The target's name, for the banner and messages. */
|
|
38
38
|
readonly runtime: string;
|
|
39
39
|
readonly version: string;
|
|
40
|
+
/** The TypeScript version cells are checked against; TYPESCRIPT_VERSION if not given. */
|
|
41
|
+
readonly languageVersion?: string;
|
|
40
42
|
readonly channels: KernelChannels;
|
|
41
43
|
launchTarget(events: TargetEvents): Promise<KernelTarget>;
|
|
42
44
|
/** What strips cells' types; esbuild by default (#238). */
|
|
@@ -46,6 +48,8 @@ export interface KernelHost {
|
|
|
46
48
|
/** Shown once, in the first cell: e.g. why a project config wasn't used. */
|
|
47
49
|
readonly warning?: string | null;
|
|
48
50
|
readonly types?: TypeService | null;
|
|
51
|
+
/** Returned as `capabilities` in kernel_info_reply, when set: what the host offers cells (#384). */
|
|
52
|
+
readonly capabilities?: JsonObject;
|
|
49
53
|
/** JupyterLab's visual debugger, for targets driven over CDP (#18). */
|
|
50
54
|
createDebugger?(publish: (event: JsonObject) => void, transformOptions: () => CellTransformOptions): Debugger;
|
|
51
55
|
/** A file's source, for the lines around a traceback frame (#322). */
|
|
@@ -56,6 +60,11 @@ export interface KernelHost {
|
|
|
56
60
|
/** After shutdown_reply has gone out. */
|
|
57
61
|
exit(): void;
|
|
58
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* kernel_info's language_info.version when the host doesn't know the
|
|
65
|
+
* installed TypeScript's: the major version package.json depends on.
|
|
66
|
+
*/
|
|
67
|
+
export declare const TYPESCRIPT_VERSION = "6";
|
|
59
68
|
export declare class KernelCore {
|
|
60
69
|
#private;
|
|
61
70
|
constructor(host: KernelHost);
|
package/dist/kernel-core.js
CHANGED
|
@@ -14,6 +14,11 @@ import { CellSyntaxError, loadTypeStripper, objectLiteralCandidate, transformCel
|
|
|
14
14
|
import { checkIsComplete, codePointToUtf16Index, exprAtCursor, inspectTarget, utf16ToCodePointIndex } from "./text.js";
|
|
15
15
|
import { queryHistory } from "./history.js";
|
|
16
16
|
import { buildTraceback, cellName, TRACEBACK_MIME } from "./traceback.js";
|
|
17
|
+
/**
|
|
18
|
+
* kernel_info's language_info.version when the host doesn't know the
|
|
19
|
+
* installed TypeScript's: the major version package.json depends on.
|
|
20
|
+
*/
|
|
21
|
+
export const TYPESCRIPT_VERSION = "6";
|
|
17
22
|
function fromBase64(text) {
|
|
18
23
|
return Uint8Array.from(atob(text), (c) => c.charCodeAt(0));
|
|
19
24
|
}
|
|
@@ -90,6 +95,8 @@ export class KernelCore {
|
|
|
90
95
|
#input = null;
|
|
91
96
|
/** An interrupt is waiting on the target. */
|
|
92
97
|
#interrupting = false;
|
|
98
|
+
/** Ends the cell waiting for the target to start, while one is (#336). */
|
|
99
|
+
#interruptStart = null;
|
|
93
100
|
/** The shell message being handled, and since when. */
|
|
94
101
|
#handling = null;
|
|
95
102
|
// Open comms, for comm_info_request: comm_id -> target_name (#9).
|
|
@@ -225,6 +232,23 @@ export class KernelCore {
|
|
|
225
232
|
});
|
|
226
233
|
return this.#targetStarting;
|
|
227
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* The target for a cell. An interrupt while it starts ends the cell; the
|
|
237
|
+
* start goes on, bounded by the target's own deadline, for the next one (#336).
|
|
238
|
+
*/
|
|
239
|
+
async #targetForCell() {
|
|
240
|
+
if (this.#target)
|
|
241
|
+
return this.#target;
|
|
242
|
+
const interrupted = new Promise((_, reject) => {
|
|
243
|
+
this.#interruptStart = () => reject(new Interrupted("Execution interrupted"));
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
return await Promise.race([this.#ensureTarget(), interrupted]);
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
this.#interruptStart = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
228
252
|
#targetEvents() {
|
|
229
253
|
return {
|
|
230
254
|
stream: (name, text) => this.#publish("stream", { name, text }),
|
|
@@ -274,6 +298,10 @@ export class KernelCore {
|
|
|
274
298
|
input.settle({ value: null, interrupted: true });
|
|
275
299
|
return;
|
|
276
300
|
}
|
|
301
|
+
if (this.#interruptStart !== null) {
|
|
302
|
+
this.#interruptStart();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
277
305
|
if (!this.#target || !this.#executing)
|
|
278
306
|
return;
|
|
279
307
|
this.#interrupting = true;
|
|
@@ -306,6 +334,8 @@ export class KernelCore {
|
|
|
306
334
|
async #loop(channel, handle) {
|
|
307
335
|
while (!this.#shuttingDown) {
|
|
308
336
|
const received = await this.#channels.recv(channel);
|
|
337
|
+
// Before the drop below: shellQueued() counted this message too, so
|
|
338
|
+
// a dropped one takes its own abort slot, not a queued cell's (#340).
|
|
309
339
|
if (channel === "shell") {
|
|
310
340
|
this.#aborting = this.#abortQueued > 0;
|
|
311
341
|
if (this.#aborting)
|
|
@@ -338,7 +368,7 @@ export class KernelCore {
|
|
|
338
368
|
implementation_version: this.#host.version,
|
|
339
369
|
language_info: {
|
|
340
370
|
name: "typescript",
|
|
341
|
-
version:
|
|
371
|
+
version: this.#host.languageVersion ?? TYPESCRIPT_VERSION,
|
|
342
372
|
mimetype: "text/x.typescript",
|
|
343
373
|
file_extension: ".ts",
|
|
344
374
|
pygments_lexer: "typescript",
|
|
@@ -352,6 +382,7 @@ export class KernelCore {
|
|
|
352
382
|
// What current ipykernel advertises; JupyterLab itself goes by the
|
|
353
383
|
// kernelspec's metadata.debugger.
|
|
354
384
|
supported_features: this.#debugger !== null ? ["debugger"] : [],
|
|
385
|
+
...(this.#host.capabilities ? { capabilities: this.#host.capabilities } : {}),
|
|
355
386
|
};
|
|
356
387
|
}
|
|
357
388
|
// --- control channel ------------------------------------------------------
|
|
@@ -648,6 +679,41 @@ export class KernelCore {
|
|
|
648
679
|
}
|
|
649
680
|
return target.evaluate(transform(code));
|
|
650
681
|
}
|
|
682
|
+
/** execute_request's user_expressions, each as its value's MIME bundle or the error it threw (#340). */
|
|
683
|
+
async #userExpressions(target, expressions) {
|
|
684
|
+
const results = {};
|
|
685
|
+
if (typeof expressions !== "object" || expressions === null)
|
|
686
|
+
return results;
|
|
687
|
+
for (const [name, expression] of Object.entries(expressions)) {
|
|
688
|
+
results[name] = await this.#userExpression(target, String(expression));
|
|
689
|
+
}
|
|
690
|
+
return results;
|
|
691
|
+
}
|
|
692
|
+
async #userExpression(target, expression) {
|
|
693
|
+
try {
|
|
694
|
+
// On their own lines, so a trailing line comment can't swallow the paren.
|
|
695
|
+
const source = `(\n${expression}\n)`;
|
|
696
|
+
const result = await target.evaluate(`${target.transform(source, this.#transformOptions())}\n//# sourceURL=$cdpk$user-expression.ts`);
|
|
697
|
+
if (result.exceptionDetails) {
|
|
698
|
+
const exception = result.exceptionDetails.exception;
|
|
699
|
+
const described = exception
|
|
700
|
+
? await target.describeError(exception)
|
|
701
|
+
: { name: "Error", message: result.exceptionDetails.text, stack: "" };
|
|
702
|
+
return {
|
|
703
|
+
status: "error",
|
|
704
|
+
ename: described.name || "Error",
|
|
705
|
+
evalue: described.message,
|
|
706
|
+
traceback: (described.stack || `${described.name}: ${described.message}`).split("\n"),
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return { status: "ok", data: await target.format(result.result), metadata: {} };
|
|
710
|
+
}
|
|
711
|
+
catch (err) {
|
|
712
|
+
const ename = err instanceof CellSyntaxError ? "SyntaxError" : err instanceof Interrupted ? "Interrupted" : "Error";
|
|
713
|
+
const evalue = err instanceof Error ? err.message : String(err);
|
|
714
|
+
return { status: "error", ename, evalue, traceback: [`${ename}: ${evalue}`] };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
651
717
|
async #execute(msg, peer) {
|
|
652
718
|
const parent = msg.header;
|
|
653
719
|
if (this.#aborting) {
|
|
@@ -676,11 +742,12 @@ export class KernelCore {
|
|
|
676
742
|
this.#projectWarning = null;
|
|
677
743
|
}
|
|
678
744
|
let error = null;
|
|
745
|
+
let userExpressions = {};
|
|
679
746
|
// nteract's structured traceback, sent as the error message's metadata (#322).
|
|
680
747
|
let structured = null;
|
|
681
748
|
this.#executing = true;
|
|
682
749
|
try {
|
|
683
|
-
const target = await this.#
|
|
750
|
+
const target = await this.#targetForCell();
|
|
684
751
|
if (target.notice !== undefined && this.#noticeShownFor !== target) {
|
|
685
752
|
// Once per target, in the first cell it runs.
|
|
686
753
|
this.#noticeShownFor = target;
|
|
@@ -709,6 +776,9 @@ export class KernelCore {
|
|
|
709
776
|
else if (!silent && result.result.type !== "undefined") {
|
|
710
777
|
await target.broadcastResult(count, result.result);
|
|
711
778
|
}
|
|
779
|
+
// As in ipykernel: only after a cell that succeeded (#340).
|
|
780
|
+
if (!result.exceptionDetails)
|
|
781
|
+
userExpressions = await this.#userExpressions(target, msg.content.user_expressions);
|
|
712
782
|
await target.flush();
|
|
713
783
|
}
|
|
714
784
|
catch (err) {
|
|
@@ -746,7 +816,7 @@ export class KernelCore {
|
|
|
746
816
|
status: "ok",
|
|
747
817
|
execution_count: count,
|
|
748
818
|
payload: [],
|
|
749
|
-
user_expressions:
|
|
819
|
+
user_expressions: userExpressions,
|
|
750
820
|
});
|
|
751
821
|
}
|
|
752
822
|
this.#status("idle", parent);
|
package/dist/kernel.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// file's ZeroMQ ports, with a target runtime launched as a child process,
|
|
3
3
|
// the language service in a worker thread, and the visual debugger.
|
|
4
4
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
5
6
|
import { dirname, join } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
import process from "node:process";
|
|
@@ -17,6 +18,16 @@ import { cacheRoot, VERSION } from "./package.js";
|
|
|
17
18
|
import { watchParent } from "./parent.js";
|
|
18
19
|
import { KernelCore } from "./kernel-core.js";
|
|
19
20
|
import { ZmqChannels } from "./zmq-channels.js";
|
|
21
|
+
/** The installed TypeScript's version, for kernel_info's language_info (#340). */
|
|
22
|
+
function typescriptVersion() {
|
|
23
|
+
try {
|
|
24
|
+
const { version } = createRequire(import.meta.url)("typescript/package.json");
|
|
25
|
+
return typeof version === "string" ? version : undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
20
31
|
/** Larger files get no source lines in a traceback. */
|
|
21
32
|
const MAX_SOURCE_BYTES = 1 << 20;
|
|
22
33
|
/** A traceback frame's file, read from disk when it is a local one (#322). */
|
|
@@ -133,6 +144,7 @@ export class Kernel {
|
|
|
133
144
|
this.#core = new KernelCore({
|
|
134
145
|
runtime,
|
|
135
146
|
version: VERSION,
|
|
147
|
+
languageVersion: typescriptVersion(),
|
|
136
148
|
channels: new ZmqChannels(conn, log),
|
|
137
149
|
launchTarget(events) {
|
|
138
150
|
if (runtime === "bun")
|
package/dist/main.js
CHANGED
|
@@ -31,7 +31,17 @@ function kernelsDir() {
|
|
|
31
31
|
return join(process.env.APPDATA ?? homedir(), "jupyter", "kernels");
|
|
32
32
|
return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), "jupyter", "kernels");
|
|
33
33
|
}
|
|
34
|
+
// Jupyter's kernel names; anything else could point outside the kernels
|
|
35
|
+
// directory.
|
|
36
|
+
const KERNEL_NAME = /^[A-Za-z0-9._-]+$/;
|
|
37
|
+
function isKernelName(name) {
|
|
38
|
+
return KERNEL_NAME.test(name) && name !== "." && name !== "..";
|
|
39
|
+
}
|
|
34
40
|
function install(runtime, denoCompat, typeStrip, languageService, name, display) {
|
|
41
|
+
if (!isKernelName(name)) {
|
|
42
|
+
console.error(`install: invalid kernelspec name ${JSON.stringify(name)}; use letters, digits, '.', '_' and '-'`);
|
|
43
|
+
process.exit(2);
|
|
44
|
+
}
|
|
35
45
|
const dir = join(kernelsDir(), name);
|
|
36
46
|
mkdirSync(dir, { recursive: true });
|
|
37
47
|
const spec = {
|
|
@@ -97,9 +107,6 @@ function list() {
|
|
|
97
107
|
console.log(`${s.name.padEnd(width)} ${s.runtime.padEnd(7)} ${(s.version ?? "-").padEnd(7)} ${s.dir}`);
|
|
98
108
|
}
|
|
99
109
|
}
|
|
100
|
-
// Jupyter's kernel names; anything else could point outside the kernels
|
|
101
|
-
// directory.
|
|
102
|
-
const KERNEL_NAME = /^[A-Za-z0-9._-]+$/;
|
|
103
110
|
function uninstall(names, all) {
|
|
104
111
|
if (all)
|
|
105
112
|
names = installedSpecs().map((s) => s.name);
|
|
@@ -110,7 +117,7 @@ function uninstall(names, all) {
|
|
|
110
117
|
}
|
|
111
118
|
let failed = false;
|
|
112
119
|
for (const name of names) {
|
|
113
|
-
const valid =
|
|
120
|
+
const valid = isKernelName(name);
|
|
114
121
|
const spec = valid ? readOwnSpec(name) : undefined;
|
|
115
122
|
if (!spec) {
|
|
116
123
|
const exists = valid && existsSync(join(kernelsDir(), name));
|
package/dist/package.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
|
|
2
|
-
export declare const VERSION = "0.7.
|
|
2
|
+
export declare const VERSION = "0.7.1";
|
|
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
|
package/dist/package.js
CHANGED
|
@@ -14,7 +14,7 @@ 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.7.
|
|
17
|
+
export const VERSION = "0.7.1";
|
|
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
|
package/dist/target-api.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface KernelTarget {
|
|
|
24
24
|
/** Resolves once all output and messages sent so far have been delivered. */
|
|
25
25
|
flush(): Promise<void>;
|
|
26
26
|
broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
|
|
27
|
+
/** A value's MIME bundle, as a cell's result would show it: for user_expressions (#340). */
|
|
28
|
+
format(value: RemoteObject): Promise<CdpParams>;
|
|
27
29
|
dispatchComm(msgType: string, content: CdpParams, metadata: CdpParams, buffers: Uint8Array[]): Promise<{
|
|
28
30
|
handled: boolean;
|
|
29
31
|
} | {
|
|
@@ -66,6 +68,16 @@ export type TargetMessage = {
|
|
|
66
68
|
password: boolean;
|
|
67
69
|
barrier: number;
|
|
68
70
|
};
|
|
71
|
+
/** A MIME bundle the target sent as JSON text; empty if it isn't one. */
|
|
72
|
+
export declare function parseBundle(text: unknown): CdpParams;
|
|
73
|
+
/** How long a target may take from launch to a usable session. */
|
|
74
|
+
export declare const START_TIMEOUT_MS = 60000;
|
|
75
|
+
/**
|
|
76
|
+
* `start`, or a rejection once `ms` pass without it settling. The caller
|
|
77
|
+
* stops whatever was starting: a runtime that never becomes ready would
|
|
78
|
+
* otherwise wedge the first cell (#52, #336).
|
|
79
|
+
*/
|
|
80
|
+
export declare function withStartTimeout<T>(start: Promise<T>, what: string, ms?: number): Promise<T>;
|
|
69
81
|
/** The parsed value, or undefined for text that isn't JSON. */
|
|
70
82
|
export declare function parseJson(text: string): unknown;
|
|
71
83
|
export declare function isTargetMessage(value: unknown): value is TargetMessage;
|
package/dist/target-api.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
// What the kernel needs from a target runtime, and what a target sends
|
|
2
2
|
// back: the part of src/target.ts with no Node imports, so the kernel's
|
|
3
3
|
// message handling (src/kernel-core.ts) also loads in a browser.
|
|
4
|
+
/** A MIME bundle the target sent as JSON text; empty if it isn't one. */
|
|
5
|
+
export function parseBundle(text) {
|
|
6
|
+
const bundle = typeof text === "string" ? parseJson(text) : undefined;
|
|
7
|
+
return typeof bundle === "object" && bundle !== null && !Array.isArray(bundle) ? bundle : {};
|
|
8
|
+
}
|
|
9
|
+
/** How long a target may take from launch to a usable session. */
|
|
10
|
+
export const START_TIMEOUT_MS = 60_000;
|
|
11
|
+
/**
|
|
12
|
+
* `start`, or a rejection once `ms` pass without it settling. The caller
|
|
13
|
+
* stops whatever was starting: a runtime that never becomes ready would
|
|
14
|
+
* otherwise wedge the first cell (#52, #336).
|
|
15
|
+
*/
|
|
16
|
+
export async function withStartTimeout(start, what, ms = START_TIMEOUT_MS) {
|
|
17
|
+
// Its own rejection, once the deadline has passed, is the caller's to ignore.
|
|
18
|
+
start.catch(() => { });
|
|
19
|
+
let timer;
|
|
20
|
+
const stalled = new Promise((_, reject) => {
|
|
21
|
+
timer = setTimeout(() => reject(new Error(`${what} did not start within ${ms / 1000}s`)), ms);
|
|
22
|
+
});
|
|
23
|
+
try {
|
|
24
|
+
return await Promise.race([start, stalled]);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
4
30
|
/** The parsed value, or undefined for text that isn't JSON. */
|
|
5
31
|
export function parseJson(text) {
|
|
6
32
|
try {
|
package/dist/target.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type CdpParams, type EvaluateResult, type RemoteObject } from "./cdp.ts";
|
|
2
2
|
import { transformCell } from "./transform.ts";
|
|
3
3
|
import type { DebugChannel } from "./debugger.ts";
|
|
4
|
-
import { type CdpRuntime, Interrupted, isTargetMessage, type KernelTarget, parseJson, type TargetEvents, type TargetMessage } from "./target-api.ts";
|
|
5
|
-
export { Interrupted, isTargetMessage, parseJson };
|
|
4
|
+
import { type CdpRuntime, Interrupted, isTargetMessage, type KernelTarget, parseBundle, parseJson, type TargetEvents, type TargetMessage } from "./target-api.ts";
|
|
5
|
+
export { Interrupted, isTargetMessage, parseBundle, parseJson };
|
|
6
6
|
export type { CdpRuntime, KernelTarget, RuntimeName, TargetEvents, TargetMessage } from "./target-api.ts";
|
|
7
7
|
export interface LaunchOptions {
|
|
8
8
|
/** Runtime binary; defaults to `deno`, or `node` (this one when hosted by Node). */
|
|
@@ -116,6 +116,7 @@ export declare class Target implements KernelTarget {
|
|
|
116
116
|
callFunctionOn(functionDeclaration: string, args: CdpParams[]): Promise<CdpParams>;
|
|
117
117
|
/** The broadcastResult step of Deno's execute flow (jupyter/mod.rs). */
|
|
118
118
|
broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
|
|
119
|
+
format(value: RemoteObject): Promise<CdpParams>;
|
|
119
120
|
/**
|
|
120
121
|
* Hands a frontend comm message to the target's comms. Resolves to
|
|
121
122
|
* whether a comm or registered target took it, or to the error a handler
|
package/dist/target.js
CHANGED
|
@@ -11,8 +11,8 @@ import { asEvaluateResult, CdpAbandoned, CdpError, CdpSession } from "./cdp.js";
|
|
|
11
11
|
import { SHIM, transformCell } from "./transform.js";
|
|
12
12
|
import { hostRuntime, isInsideNodeModules, runtimeDir } from "./package.js";
|
|
13
13
|
import { killOnExit } from "./children.js";
|
|
14
|
-
import { Interrupted, isTargetMessage, parseJson } from "./target-api.js";
|
|
15
|
-
export { Interrupted, isTargetMessage, parseJson };
|
|
14
|
+
import { Interrupted, isTargetMessage, parseBundle, parseJson, withStartTimeout, } from "./target-api.js";
|
|
15
|
+
export { Interrupted, isTargetMessage, parseBundle, parseJson };
|
|
16
16
|
/**
|
|
17
17
|
* The inspector keeps every object it returns a remote reference to alive
|
|
18
18
|
* until it is released, so a cell's result would never be collected (#286).
|
|
@@ -21,8 +21,6 @@ export { Interrupted, isTargetMessage, parseJson };
|
|
|
21
21
|
*/
|
|
22
22
|
const CELL_GROUP = "cdpk-cell";
|
|
23
23
|
const LOOKUP_GROUP = "cdpk-lookup";
|
|
24
|
-
/** How long a target may take from spawn to a usable session. */
|
|
25
|
-
const START_TIMEOUT_MS = 60_000;
|
|
26
24
|
const MARKER_RE = /\x1b\[cdpk:(\d+)(m?)\]\x1b/;
|
|
27
25
|
const MAX_MARKER_LEN = 24;
|
|
28
26
|
// Lines the runtimes print to stderr on their own: inspector notices, and
|
|
@@ -386,13 +384,8 @@ export class Target {
|
|
|
386
384
|
// A start that fails or stalls must not leave the runtime running: its
|
|
387
385
|
// pipes and inspector sockets would keep this process alive (#52).
|
|
388
386
|
const sessions = [];
|
|
389
|
-
const timeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;
|
|
390
|
-
let timer;
|
|
391
|
-
const stalled = new Promise((_, reject) => {
|
|
392
|
-
timer = setTimeout(() => reject(new Error(`${cmd} did not start within ${timeoutMs / 1000}s`)), timeoutMs);
|
|
393
|
-
});
|
|
394
387
|
try {
|
|
395
|
-
return await
|
|
388
|
+
return await withStartTimeout(Target.#start(runtime, events, cmd, child, sessions), cmd, options.startTimeoutMs);
|
|
396
389
|
}
|
|
397
390
|
catch (error) {
|
|
398
391
|
for (const session of sessions)
|
|
@@ -400,9 +393,6 @@ export class Target {
|
|
|
400
393
|
child.kill();
|
|
401
394
|
throw error;
|
|
402
395
|
}
|
|
403
|
-
finally {
|
|
404
|
-
clearTimeout(timer);
|
|
405
|
-
}
|
|
406
396
|
}
|
|
407
397
|
static async #start(runtime, events, cmd, child, sessions) {
|
|
408
398
|
child.stdout.setEncoding("utf8");
|
|
@@ -470,6 +460,15 @@ export class Target {
|
|
|
470
460
|
if (params.executionContextId === contextId && target)
|
|
471
461
|
target.#onExit();
|
|
472
462
|
});
|
|
463
|
+
// A session that closes on its own (e.g. a frame that isn't JSON, #337)
|
|
464
|
+
// leaves a target nothing can drive: stop it, so the next cell starts
|
|
465
|
+
// a fresh one.
|
|
466
|
+
for (const session of [shell, control]) {
|
|
467
|
+
session.onClose(() => {
|
|
468
|
+
if (target)
|
|
469
|
+
target.#onExit();
|
|
470
|
+
});
|
|
471
|
+
}
|
|
473
472
|
// Wait for the bootstrap's top-level await to finish installing the
|
|
474
473
|
// shim. There is no cap here: launch's start timeout bounds the wait and
|
|
475
474
|
// kills the child, so a slow bootstrap never yields a target without
|
|
@@ -716,6 +715,19 @@ export class Target {
|
|
|
716
715
|
arg,
|
|
717
716
|
]).catch(interrupted("Execution"));
|
|
718
717
|
}
|
|
718
|
+
async format(value) {
|
|
719
|
+
const arg = value.objectId
|
|
720
|
+
? { objectId: value.objectId }
|
|
721
|
+
: value.unserializableValue !== undefined
|
|
722
|
+
? { unserializableValue: value.unserializableValue }
|
|
723
|
+
: { value: value.value };
|
|
724
|
+
const res = await this.#callAsyncFunctionOn(`function (v) { return globalThis.${SHIM}.formatJson(v); }`, [arg])
|
|
725
|
+
.catch(interrupted("Execution"));
|
|
726
|
+
const thrown = res.exceptionDetails;
|
|
727
|
+
if (thrown)
|
|
728
|
+
throw new Error(thrown.exception?.description ?? thrown.text ?? "the value could not be formatted");
|
|
729
|
+
return parseBundle(res.result?.value);
|
|
730
|
+
}
|
|
719
731
|
/**
|
|
720
732
|
* Hands a frontend comm message to the target's comms. Resolves to
|
|
721
733
|
* whether a comm or registered target took it, or to the error a handler
|
package/dist/workerd-target.d.ts
CHANGED
|
@@ -89,12 +89,16 @@ export declare class WorkerdTarget implements KernelTarget {
|
|
|
89
89
|
static launch(events: TargetEvents, options?: {
|
|
90
90
|
cwd?: string;
|
|
91
91
|
executable?: string;
|
|
92
|
+
startTimeoutMs?: number;
|
|
92
93
|
}): Promise<WorkerdTarget>;
|
|
93
94
|
/**
|
|
94
95
|
* A target over a started host's session. `notice` (a project that
|
|
95
96
|
* failed to load) is replaced by the bindings when the host has a project.
|
|
96
97
|
*/
|
|
97
|
-
static open(host: WorkerHost, cwd: string, events: TargetEvents, runtime: WorkersRuntime,
|
|
98
|
+
static open(host: WorkerHost, cwd: string, events: TargetEvents, runtime: WorkersRuntime, options?: {
|
|
99
|
+
notice?: string;
|
|
100
|
+
startTimeoutMs?: number;
|
|
101
|
+
}): Promise<WorkerdTarget>;
|
|
98
102
|
evaluate(script: string): Promise<EvaluateResult>;
|
|
99
103
|
/**
|
|
100
104
|
* A cell that's waiting on something leaves the driver free: it answers a
|
|
@@ -115,6 +119,7 @@ export declare class WorkerdTarget implements KernelTarget {
|
|
|
115
119
|
stack: string;
|
|
116
120
|
};
|
|
117
121
|
}>;
|
|
122
|
+
format(value: RemoteObject): Promise<CdpParams>;
|
|
118
123
|
describeError(exception: RemoteObject): Promise<{
|
|
119
124
|
name: string;
|
|
120
125
|
message: string;
|