@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.
- package/README.md +166 -17
- package/dist/bun-target.d.ts +58 -0
- package/dist/bun-target.js +231 -0
- package/dist/celld-target.d.ts +39 -0
- package/dist/celld-target.js +274 -0
- package/dist/kernel.js +29 -10
- package/dist/main.js +5 -5
- package/dist/package.d.ts +2 -2
- package/dist/package.js +4 -1
- package/dist/target.d.ts +57 -7
- package/dist/target.js +6 -5
- package/dist/transform.d.ts +6 -0
- package/dist/transform.js +135 -6
- package/dist/workerd-target.d.ts +123 -0
- package/dist/workerd-target.js +450 -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 +17 -3
- package/runtime/bootstrap.mjs +39 -4
- package/runtime/bun-channel.d.mts +3 -0
- package/runtime/bun-channel.mjs +58 -0
- package/runtime/evaluator.d.mts +18 -0
- package/runtime/evaluator.mjs +83 -0
- package/runtime/node-hooks.d.mts +11 -0
- package/runtime/node-hooks.mjs +24 -12
- package/runtime/workerd-worker.mjs +189 -0
|
@@ -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 {
|
|
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 ??=
|
|
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
|
-
|
|
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 ?? ""} (
|
|
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(
|
|
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(
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
104
|
+
readonly runtime: CdpRuntime;
|
|
105
|
+
readonly transform: typeof transformCell;
|
|
56
106
|
readonly versions: {
|
|
57
107
|
runtime: string;
|
|
58
|
-
|
|
108
|
+
engine: string;
|
|
59
109
|
};
|
|
60
110
|
private constructor();
|
|
61
|
-
static launch(runtime:
|
|
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: "",
|
|
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,
|
|
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
|
}
|
package/dist/transform.d.ts
CHANGED
|
@@ -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
|