@tlockney/cdp-kernel 0.2.0 → 0.3.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.
@@ -0,0 +1,274 @@
1
+ // The celld target (#25). celld (denoland/celld) runs Workers and Durable
2
+ // Objects on its own storage; like workerd it has no I/O outside a request,
3
+ // so it runs the same driver Worker (runtime/workerd-worker.mjs) over the
4
+ // same WebSocket session (src/workerd-target.ts). The differences:
5
+ // - celld has no UnsafeEval binding and doesn't need one: the driver uses
6
+ // a plain global eval.
7
+ // - It listens only on TCP, so the session is a loopback WebSocket opened
8
+ // with a per-launch token (in the query string: the host's built-in
9
+ // WebSocket can't set headers).
10
+ // - It takes one module, so the host bundles the driver (and a project's
11
+ // main) with esbuild and hands celld the result with `no_bundle`.
12
+ // In a celld project (a wrangler.jsonc or wrangler.json at or above the
13
+ // notebook), its bindings come along: the generated config copies them, and
14
+ // lives in the project's .celld/cdp-kernel, so state stays beside, not in,
15
+ // `celld dev`'s own.
16
+ import process from "node:process";
17
+ import { execFileSync, spawn } from "node:child_process";
18
+ import { randomBytes } from "node:crypto";
19
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
20
+ import { createServer } from "node:net";
21
+ import { homedir, tmpdir } from "node:os";
22
+ import { dirname, join, resolve } from "node:path";
23
+ import { build } from "esbuild";
24
+ import { parseJsonc } from "./deno-config.js";
25
+ import { runtimeDir } from "./package.js";
26
+ import { cellMeta, WorkerdTarget } from "./workerd-target.js";
27
+ import { builtinAliases, findWranglerConfig } from "./workers-project.js";
28
+ /** The project keys the generated config keeps: bindings and compatibility. */
29
+ export const PROJECT_KEYS = [
30
+ "compatibility_date",
31
+ "compatibility_flags",
32
+ "durable_objects",
33
+ "migrations",
34
+ "vars",
35
+ "d1_databases",
36
+ "kv_namespaces",
37
+ "r2_buckets",
38
+ "queues",
39
+ "workflows",
40
+ "services",
41
+ "worker_loaders",
42
+ ];
43
+ /**
44
+ * The celld config for the driver: the project's bindings (or none), the
45
+ * pre-built driver as its one module, and the kernel's token and import.meta
46
+ * as vars, which the driver hides from cells' `env`.
47
+ */
48
+ export function celldConfig(project, { token, meta }) {
49
+ const config = { name: "cdpk", main: "driver.mjs", no_bundle: true, compatibility_date: "2026-09-01" };
50
+ for (const key of PROJECT_KEYS)
51
+ if (project && project[key] !== undefined)
52
+ config[key] = project[key];
53
+ const vars = project && typeof project.vars === "object" && project.vars !== null ? project.vars : {};
54
+ config.vars = { ...vars, TOKEN: token, META: JSON.stringify(meta) };
55
+ return config;
56
+ }
57
+ /**
58
+ * celld's installer puts it in ~/.local/bin, which a Jupyter server's PATH
59
+ * often lacks, so look there before falling back to PATH.
60
+ */
61
+ export function findCelld(home = homedir()) {
62
+ const installed = join(home, ".local", "bin", "celld");
63
+ return existsSync(installed) ? installed : "celld";
64
+ }
65
+ /** The version in `celld --version`'s output ("celld X.Y.Z"). */
66
+ export function celldVersionOf(output) {
67
+ return /\bcelld (\d+\.\d+\.\d+\S*)/.exec(output)?.[1] ?? "";
68
+ }
69
+ /** The driver, and a project's main, as the single module celld runs. */
70
+ export async function bundleDriver(runtime, projectMain, define = {}) {
71
+ const driver = JSON.stringify(join(runtime, "workerd-worker.mjs"));
72
+ const contents = projectMain === undefined ? `export { default } from ${driver};\n` : [
73
+ `import * as project from ${JSON.stringify(projectMain)};`,
74
+ `export * from ${JSON.stringify(projectMain)};`,
75
+ `export { default } from ${driver};`,
76
+ "globalThis.worker = project.default;",
77
+ "",
78
+ ].join("\n");
79
+ const result = await build({
80
+ stdin: { contents, resolveDir: runtime, loader: "js" },
81
+ bundle: true,
82
+ write: false,
83
+ format: "esm",
84
+ platform: "browser",
85
+ conditions: ["workerd", "worker", "browser"],
86
+ mainFields: ["browser", "module", "main"],
87
+ alias: builtinAliases(),
88
+ external: ["node:*", "cloudflare:*"],
89
+ define,
90
+ target: "es2024",
91
+ logLevel: "silent",
92
+ });
93
+ return result.outputFiles[0].text;
94
+ }
95
+ /** A free loopback port: celld dev needs a number, and rejects 0. */
96
+ export function freePort() {
97
+ return new Promise((resolve, reject) => {
98
+ const server = createServer();
99
+ server.once("error", reject);
100
+ server.listen(0, "127.0.0.1", () => {
101
+ const address = server.address();
102
+ const port = typeof address === "object" && address !== null ? address.port : 0;
103
+ server.close(() => resolve(port));
104
+ });
105
+ });
106
+ }
107
+ /** The host's built-in WebSocket as the session channel. */
108
+ function channelOf(ws) {
109
+ return {
110
+ send: (text) => ws.send(text),
111
+ onMessage: (listener) => ws.addEventListener("message", (event) => listener(String(event.data))),
112
+ onClose: (listener) => ws.addEventListener("close", () => listener()),
113
+ close() {
114
+ try {
115
+ ws.close();
116
+ }
117
+ catch {
118
+ // Already closed.
119
+ }
120
+ },
121
+ };
122
+ }
123
+ /** celld dev's own children, which run in their own process groups. */
124
+ function childPids(pid) {
125
+ try {
126
+ return execFileSync("pgrep", ["-P", String(pid)], { encoding: "utf8" }).split(/\s+/).filter(Boolean).map(Number);
127
+ }
128
+ catch {
129
+ return []; // none
130
+ }
131
+ }
132
+ /** Kills celld dev's node, then the supervisor itself. */
133
+ function killTree(child) {
134
+ for (const pid of childPids(child.pid ?? -1)) {
135
+ try {
136
+ process.kill(pid, "SIGKILL");
137
+ }
138
+ catch {
139
+ // Gone already.
140
+ }
141
+ }
142
+ child.kill("SIGKILL");
143
+ }
144
+ /** The driver on `celld dev`. */
145
+ class CelldHost {
146
+ runtime;
147
+ project;
148
+ channel;
149
+ #child;
150
+ constructor(child, channel, runtime, project) {
151
+ this.#child = child;
152
+ this.channel = channel;
153
+ this.runtime = runtime;
154
+ this.project = project;
155
+ }
156
+ static async start(cwd, projectConfig, executable) {
157
+ const celld = executable ?? findCelld();
158
+ const version = celldVersionOf(execFileSync(celld, ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
159
+ const source = await runtimeDir(new URL("../runtime/", import.meta.url).href);
160
+ let project = null;
161
+ let dir;
162
+ if (projectConfig !== null) {
163
+ if (projectConfig.endsWith(".toml"))
164
+ throw new Error("celld reads wrangler.jsonc or wrangler.json, not wrangler.toml");
165
+ const parsed = parseJsonc(readFileSync(projectConfig, "utf8"));
166
+ if (typeof parsed !== "object" || parsed === null)
167
+ throw new Error(`${projectConfig} is not an object`);
168
+ project = parsed;
169
+ dir = join(dirname(projectConfig), ".celld", "cdp-kernel");
170
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
171
+ }
172
+ else {
173
+ dir = mkdtempSync(join(tmpdir(), "cdpk-celld-"));
174
+ }
175
+ const main = project && typeof project.main === "string" ? resolve(dirname(projectConfig ?? cwd), project.main) : undefined;
176
+ const define = project && typeof project.define === "object" && project.define !== null
177
+ ? project.define
178
+ : {};
179
+ const token = randomBytes(32).toString("hex");
180
+ // 0600: the config carries this launch's token.
181
+ writeFileSync(join(dir, "driver.mjs"), await bundleDriver(source, main, define));
182
+ writeFileSync(join(dir, "wrangler.jsonc"), JSON.stringify(celldConfig(project, { token, meta: cellMeta(cwd) }), null, 2), {
183
+ mode: 0o600,
184
+ });
185
+ const port = await freePort();
186
+ const child = spawn(celld, ["dev", dir, "--host", "127.0.0.1", "--port", String(port), "--no-watch"], {
187
+ stdio: ["ignore", "pipe", "pipe"],
188
+ });
189
+ child.once("exit", () => {
190
+ if (projectConfig === null)
191
+ rmSync(dir, { recursive: true, force: true });
192
+ else
193
+ for (const name of ["driver.mjs", "wrangler.jsonc"])
194
+ rmSync(join(dir, name), { force: true });
195
+ });
196
+ // Cell output is captured in the driver; celld's own lines go to the
197
+ // server log.
198
+ let early = "";
199
+ const log = (chunk) => {
200
+ early += chunk.toString();
201
+ process.stderr.write(`[cdp-kernel celld] ${chunk}`);
202
+ };
203
+ child.stdout?.on("data", log);
204
+ child.stderr?.on("data", log);
205
+ const exited = new Promise((_, reject) => {
206
+ child.once("error", reject);
207
+ child.once("exit", (code) => reject(new Error(`celld exited (${code}) before it was ready:\n${early}`)));
208
+ });
209
+ exited.catch(() => { });
210
+ const url = `127.0.0.1:${port}/?token=${token}`;
211
+ const ready = (async () => {
212
+ // The driver answers 426 to a plain request once celld serves it.
213
+ for (let i = 0;; i++) {
214
+ const status = await fetch(`http://${url}`).then((r) => r.status, () => 0);
215
+ if (status === 426)
216
+ break;
217
+ if (i >= 600)
218
+ throw new Error(`celld never served the driver (last status ${status}):\n${early}`);
219
+ await new Promise((r) => setTimeout(r, 50));
220
+ }
221
+ const ws = new WebSocket(`ws://${url}`);
222
+ await new Promise((resolve, reject) => {
223
+ ws.addEventListener("open", () => resolve(), { once: true });
224
+ ws.addEventListener("error", () => reject(new Error("the session WebSocket didn't open")), { once: true });
225
+ });
226
+ return channelOf(ws);
227
+ })();
228
+ try {
229
+ return new CelldHost(child, await Promise.race([ready, exited]), `celld ${version}`.trim(), projectConfig ?? undefined);
230
+ }
231
+ catch (err) {
232
+ killTree(child);
233
+ throw err;
234
+ }
235
+ }
236
+ /**
237
+ * celld dev's graceful stop waits for the app, which a busy cell never
238
+ * lets go, and its node runs in its own process group. So a forced stop
239
+ * kills the node, then the supervisor; a graceful one falls back to that.
240
+ */
241
+ stop(now) {
242
+ if (now)
243
+ return killTree(this.#child);
244
+ this.#child.kill("SIGTERM");
245
+ setTimeout(() => {
246
+ if (this.#child.exitCode === null && this.#child.signalCode === null)
247
+ killTree(this.#child);
248
+ }, 3000).unref();
249
+ }
250
+ onExit(listener) {
251
+ this.#child.once("exit", listener);
252
+ }
253
+ }
254
+ /**
255
+ * A celld target. In a celld project its bindings come along; a project
256
+ * that can't load is reported in the first cell, and the driver runs alone.
257
+ */
258
+ export async function launchCelld(events, options = {}) {
259
+ const cwd = options.cwd ?? process.cwd();
260
+ const project = findWranglerConfig(cwd);
261
+ let host = null;
262
+ let notice;
263
+ if (project !== null) {
264
+ try {
265
+ host = await CelldHost.start(cwd, project, options.executable);
266
+ }
267
+ catch (err) {
268
+ const reason = err instanceof Error ? err.message : String(err);
269
+ notice = `[cdp-kernel] couldn't load the celld project at ${project}: ${reason}; running without its bindings`;
270
+ }
271
+ }
272
+ host ??= await CelldHost.start(cwd, null, options.executable);
273
+ return await WorkerdTarget.open(host, cwd, events, "celld", notice);
274
+ }
package/dist/kernel.js CHANGED
@@ -10,7 +10,10 @@ 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 { CellSyntaxError, objectLiteralCandidate, transformCell } from "./transform.js";
13
+ import { BunTarget } from "./bun-target.js";
14
+ import { WorkerdTarget } from "./workerd-target.js";
15
+ import { launchCelld } from "./celld-target.js";
16
+ import { CellSyntaxError, objectLiteralCandidate } from "./transform.js";
14
17
  import { loadDenoProject } from "./deno-config.js";
15
18
  import { checkIsComplete, codePointToUtf16Index, exprAtCursor, utf16ToCodePointIndex } from "./text.js";
16
19
  import { VERSION } from "./package.js";
@@ -48,6 +51,7 @@ export class Kernel {
48
51
  // A Deno target in a Deno project uses its config (#13).
49
52
  #project;
50
53
  #projectWarning = null;
54
+ #noticeShownFor = null;
51
55
  #session = randomUUID();
52
56
  #shell;
53
57
  #control;
@@ -162,13 +166,22 @@ export class Kernel {
162
166
  }
163
167
  }
164
168
  // --- target lifecycle -----------------------------------------------------
169
+ #launchTarget() {
170
+ if (this.#runtime === "bun")
171
+ return BunTarget.launch(this.#targetEvents());
172
+ if (this.#runtime === "workerd")
173
+ return WorkerdTarget.launch(this.#targetEvents());
174
+ if (this.#runtime === "celld")
175
+ return launchCelld(this.#targetEvents());
176
+ return Target.launch(this.#runtime, this.#targetEvents(), {
177
+ denoCompat: this.#denoCompat,
178
+ denoConfig: this.#project?.configPath,
179
+ });
180
+ }
165
181
  #ensureTarget() {
166
182
  if (this.#target)
167
183
  return Promise.resolve(this.#target);
168
- this.#targetStarting ??= Target.launch(this.#runtime, this.#targetEvents(), {
169
- denoCompat: this.#denoCompat,
170
- denoConfig: this.#project?.configPath,
171
- }).then((target) => {
184
+ this.#targetStarting ??= this.#launchTarget().then((target) => {
172
185
  this.#target = target;
173
186
  this.#targetStarting = null;
174
187
  return target;
@@ -182,10 +195,11 @@ export class Kernel {
182
195
  return {
183
196
  stream: (name, text) => this.#publish("stream", { name, text }),
184
197
  message: (msg) => void this.#onTargetMessage(msg),
185
- exit: (code) => {
198
+ exit: (code, note) => {
186
199
  this.#target = null;
187
200
  if (!this.#shuttingDown) {
188
- this.#publish("stream", { name: "stderr", text: `\n[cdp-kernel] ${this.#runtime} exited (${code}); restarting on next cell\n` });
201
+ const text = note ?? `[cdp-kernel] ${this.#runtime} exited (${code}); restarting on next cell`;
202
+ this.#publish("stream", { name: "stderr", text: `\n${text}\n` });
189
203
  }
190
204
  },
191
205
  };
@@ -256,7 +270,7 @@ export class Kernel {
256
270
  codemirror_mode: "typescript",
257
271
  nbconvert_exporter: "script",
258
272
  },
259
- banner: `cdp-kernel on ${this.#runtime} ${versions?.runtime ?? ""} (V8 ${versions?.v8 ?? "?"})`,
273
+ banner: `cdp-kernel on ${this.#runtime} ${versions?.runtime ?? ""} (${versions?.engine || "?"})`,
260
274
  help_links: [],
261
275
  debugger: false,
262
276
  };
@@ -376,7 +390,7 @@ export class Kernel {
376
390
  const wrapped = objectLiteralCandidate(code);
377
391
  if (wrapped !== null) {
378
392
  try {
379
- const result = await target.evaluate(transformCell(wrapped, this.#transformOptions()));
393
+ const result = await target.evaluate(target.transform(wrapped, this.#transformOptions()));
380
394
  if (!result.exceptionDetails)
381
395
  return result;
382
396
  }
@@ -385,7 +399,7 @@ export class Kernel {
385
399
  throw err;
386
400
  }
387
401
  }
388
- return target.evaluate(transformCell(code, this.#transformOptions()));
402
+ return target.evaluate(target.transform(code, this.#transformOptions()));
389
403
  }
390
404
  async #execute(msg, peer) {
391
405
  const parent = msg.header;
@@ -407,6 +421,11 @@ export class Kernel {
407
421
  this.#executing = true;
408
422
  try {
409
423
  const target = await this.#ensureTarget();
424
+ if (target.notice !== undefined && this.#noticeShownFor !== target) {
425
+ // Once per target, in the first cell it runs.
426
+ this.#noticeShownFor = target;
427
+ this.#publish("stream", { name: "stderr", text: `${target.notice}\n` });
428
+ }
410
429
  const result = await this.#evaluateCell(target, code);
411
430
  if (result.exceptionDetails) {
412
431
  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)
@@ -59,7 +59,7 @@ const { positionals, values } = parseArgs({
59
59
  });
60
60
  const runtime = values.runtime;
61
61
  if (!isRuntime(runtime)) {
62
- console.error(`unknown runtime ${runtime}; expected node or deno`);
62
+ console.error(`unknown runtime ${runtime}; expected node, deno, bun, workerd or celld`);
63
63
  process.exit(2);
64
64
  }
65
65
  // Deno targets have the real namespace; the shim is for Node.
@@ -83,6 +83,6 @@ switch (positionals[0]) {
83
83
  console.log(VERSION);
84
84
  break;
85
85
  default:
86
- console.error("usage: cdp-kernel install [--runtime node|deno] [--deno-compat] | kernel --runtime node|deno --conn FILE | version");
86
+ 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
87
  process.exit(2);
88
88
  }
package/dist/package.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
2
- export declare const VERSION = "0.2.0";
3
- export declare const RUNTIME_FILES: readonly ["bootstrap.mjs", "display.mjs", "comm.mjs", "widget.mjs", "node-hooks.mjs", "deno-compat.mjs", "node-modules-resolve.mjs"];
2
+ export declare const VERSION = "0.3.0";
3
+ export declare const RUNTIME_FILES: readonly ["bootstrap.mjs", "bun-channel.mjs", "evaluator.mjs", "workerd-worker.mjs", "display.mjs", "comm.mjs", "widget.mjs", "node-hooks.mjs", "deno-compat.mjs", "node-modules-resolve.mjs"];
4
4
  export type HostRuntime = "node" | "deno";
5
5
  export declare function hostRuntime(): HostRuntime;
6
6
  /** Where the CLI module was loaded from. */
package/dist/package.js CHANGED
@@ -14,9 +14,12 @@ 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.2.0";
17
+ export const VERSION = "0.3.0";
18
18
  export const RUNTIME_FILES = [
19
19
  "bootstrap.mjs",
20
+ "bun-channel.mjs",
21
+ "evaluator.mjs",
22
+ "workerd-worker.mjs",
20
23
  "display.mjs",
21
24
  "comm.mjs",
22
25
  "widget.mjs",
package/dist/target.d.ts CHANGED
@@ -1,5 +1,45 @@
1
1
  import { type CdpParams, type EvaluateResult, type RemoteObject } from "./cdp.ts";
2
- export type RuntimeName = "node" | "deno";
2
+ import { type CellTransformOptions, transformCell } from "./transform.ts";
3
+ /** Targets driven over CDP. */
4
+ export type CdpRuntime = "node" | "deno";
5
+ /** Bun and workerd have their own backends: src/bun-target.ts, src/workerd-target.ts. */
6
+ export type RuntimeName = CdpRuntime | "bun" | "workerd" | "celld";
7
+ /** What the kernel needs from a target runtime. */
8
+ export interface KernelTarget {
9
+ readonly runtime: RuntimeName;
10
+ readonly versions: {
11
+ runtime: string;
12
+ engine: string;
13
+ };
14
+ /** Shown once, in the first cell: e.g. which project's bindings loaded. */
15
+ readonly notice?: string;
16
+ /** Turns a cell into the script evaluate() takes. */
17
+ transform(code: string, options: CellTransformOptions): string;
18
+ /** Runs a transformed cell. Rejects with Interrupted after interrupt(). */
19
+ evaluate(script: string): Promise<EvaluateResult>;
20
+ interrupt(): Promise<void>;
21
+ /** Resolves once all output and messages sent so far have been delivered. */
22
+ flush(): Promise<void>;
23
+ broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
24
+ dispatchComm(msgType: string, content: CdpParams, metadata: CdpParams, buffers: Uint8Array[]): Promise<{
25
+ handled: boolean;
26
+ } | {
27
+ error: {
28
+ name: string;
29
+ message: string;
30
+ stack: string;
31
+ };
32
+ }>;
33
+ describeError(exception: RemoteObject): Promise<{
34
+ name: string;
35
+ message: string;
36
+ stack: string;
37
+ }>;
38
+ propertyNames(expression: string): Promise<string[]>;
39
+ lexicalNames(): Promise<string[]>;
40
+ replyInput(value: string | null): void;
41
+ kill(): void;
42
+ }
3
43
  export interface LaunchOptions {
4
44
  /** Runtime binary; defaults to `deno`, or `node` (this one when hosted by Node). */
5
45
  executable?: string;
@@ -7,7 +47,7 @@ export interface LaunchOptions {
7
47
  denoConfig?: string;
8
48
  }
9
49
  /** `bootstrap` is the local path of runtime/bootstrap.mjs (see runtimeDir). */
10
- export declare function launchCommand(runtime: RuntimeName, bootstrap: string, { executable, denoConfig }?: LaunchOptions): {
50
+ export declare function launchCommand(runtime: CdpRuntime, bootstrap: string, { executable, denoConfig }?: LaunchOptions): {
11
51
  cmd: string;
12
52
  args: string[];
13
53
  };
@@ -25,6 +65,7 @@ export type TargetMessage = {
25
65
  password: boolean;
26
66
  barrier: number;
27
67
  };
68
+ export declare function isTargetMessage(value: unknown): value is TargetMessage;
28
69
  /**
29
70
  * Splits one output pipe into text and barrier markers, and releases them
30
71
  * in order. A gated marker (one that announces a binding message) pauses
@@ -42,23 +83,32 @@ export declare class PipeDemux {
42
83
  /** Release everything (the process died). */
43
84
  close(): void;
44
85
  }
86
+ /** Promises keyed by barrier number, resolvable before or after first use. */
87
+ export declare class Gates {
88
+ #private;
89
+ wait(n: number): Promise<void>;
90
+ open(n: number): void;
91
+ openAll(): void;
92
+ }
45
93
  export interface TargetEvents {
46
94
  stream(name: "stdout" | "stderr", text: string): void;
47
95
  message(msg: TargetMessage): void;
48
- exit(code: number | null): void;
96
+ /** `note` says why, when the target was stopped on purpose. */
97
+ exit(code: number | null, note?: string): void;
49
98
  }
50
99
  export declare class Interrupted extends Error {
51
100
  name: string;
52
101
  }
53
- export declare class Target {
102
+ export declare class Target implements KernelTarget {
54
103
  #private;
55
- readonly runtime: RuntimeName;
104
+ readonly runtime: CdpRuntime;
105
+ readonly transform: typeof transformCell;
56
106
  readonly versions: {
57
107
  runtime: string;
58
- v8: string;
108
+ engine: string;
59
109
  };
60
110
  private constructor();
61
- static launch(runtime: RuntimeName, events: TargetEvents, options?: {
111
+ static launch(runtime: CdpRuntime, events: TargetEvents, options?: {
62
112
  cwd?: string;
63
113
  executable?: string;
64
114
  denoCompat?: boolean;
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;
@@ -253,7 +254,7 @@ export class Target {
253
254
  await shell.send("Runtime.enable");
254
255
  await shell.send("Runtime.addBinding", { name: BINDING });
255
256
  const contextId = await contextReady;
256
- const target = new Target(runtime, child, shell, control, stdout, stderr, gates, contextId, { runtime: "", v8: "" });
257
+ const target = new Target(runtime, child, shell, control, stdout, stderr, gates, contextId, { runtime: "", engine: "" });
257
258
  shell.on("Runtime.bindingCalled", (params) => {
258
259
  if (params.name !== BINDING || typeof params.payload !== "string")
259
260
  return;
@@ -268,7 +269,7 @@ export class Target {
268
269
  break;
269
270
  await new Promise((r) => setTimeout(r, 25));
270
271
  }
271
- const versions = await target.#evaluateRaw(`JSON.stringify(typeof Deno !== "undefined" ? { runtime: Deno.version.deno, v8: Deno.version.v8 } : { runtime: process.versions.node, v8: process.versions.v8 })`);
272
+ 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
273
  Object.assign(target.versions, JSON.parse(String(versions.result.value)));
273
274
  return target;
274
275
  }
@@ -23,6 +23,12 @@ export interface CellTransformOptions {
23
23
  /** Rewrites module-only syntax in plain JavaScript into script syntax. */
24
24
  export declare function rewriteModuleSyntax(js: string): string;
25
25
  export declare function transformCell(code: string, options?: CellTransformOptions): string;
26
+ /**
27
+ * Like transformCell, for a target that evaluates cells itself, as global
28
+ * code (an indirect eval), instead of through the inspector's replMode: see
29
+ * hoistForEval. The script's value is a promise of the boxed result.
30
+ */
31
+ export declare function transformCellForEval(code: string, options?: CellTransformOptions): string;
26
32
  /**
27
33
  * Deno treats a cell like `{ "a": 1 }` as an object literal rather than a
28
34
  * block, as browser consoles do. Returns the wrapped form to try first, or