@tlockney/cdp-kernel 0.5.0 → 0.6.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 +27 -6
- package/dist/cdp.d.ts +2 -0
- package/dist/cdp.js +6 -1
- package/dist/celld-target.js +9 -12
- package/dist/children.d.ts +25 -3
- package/dist/children.js +84 -2
- package/dist/deno-types.d.ts +30 -0
- package/dist/deno-types.js +93 -0
- package/dist/kernel.d.ts +7 -1
- package/dist/kernel.js +187 -29
- package/dist/language-service-worker.d.ts +1 -0
- package/dist/language-service-worker.js +88 -0
- package/dist/language-service.d.ts +64 -0
- package/dist/language-service.js +128 -0
- package/dist/main.js +102 -8
- package/dist/mod.d.ts +1 -0
- package/dist/mod.js +1 -0
- package/dist/notebook-types.d.ts +62 -0
- package/dist/notebook-types.js +310 -0
- package/dist/package.d.ts +2 -2
- package/dist/package.js +2 -2
- package/dist/parent.d.ts +12 -0
- package/dist/parent.js +39 -0
- package/dist/port-holder.d.ts +13 -0
- package/dist/port-holder.js +105 -0
- package/dist/sourcemap.d.ts +7 -2
- package/dist/sourcemap.js +9 -3
- package/dist/strip-esbuild.d.ts +2 -0
- package/dist/strip-esbuild.js +44 -0
- package/dist/strip-sucrase.d.ts +2 -0
- package/dist/strip-sucrase.js +78 -0
- package/dist/target.d.ts +27 -3
- package/dist/target.js +193 -36
- package/dist/transform.d.ts +21 -1
- package/dist/transform.js +38 -42
- package/dist/workerd-target.js +1 -1
- package/dist/workers-project.js +13 -2
- package/dist/zmtp.d.ts +2 -0
- package/dist/zmtp.js +12 -0
- package/package.json +9 -4
- package/runtime/bootstrap.mjs +113 -33
- package/runtime/introspect.mjs +1 -1
- package/runtime/node-hooks.d.mts +6 -0
- package/runtime/node-hooks.mjs +11 -0
package/dist/main.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// cdp-kernel CLI.
|
|
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
|
|
3
|
+
// cdp-kernel install [--runtime node|deno|bun|workerd|celld] [--deno-compat] [--type-strip esbuild|sucrase] [--no-language-service] [--name NAME] [--display NAME]
|
|
4
|
+
// cdp-kernel kernel --runtime node|deno|bun|workerd|celld [--deno-compat] [--type-strip esbuild|sucrase] [--no-language-service] --conn CONNECTION_FILE
|
|
5
|
+
// cdp-kernel list
|
|
6
|
+
// cdp-kernel uninstall [NAME...] [--name NAME] [--all]
|
|
5
7
|
// cdp-kernel version
|
|
6
8
|
//
|
|
7
9
|
// --deno-compat (Node only) adds a `Deno` namespace for notebooks written for
|
|
8
|
-
// `deno jupyter`; see runtime/deno-compat.mjs.
|
|
10
|
+
// `deno jupyter`; see runtime/deno-compat.mjs. --type-strip picks what
|
|
11
|
+
// removes cells' types (src/transform.ts). --no-language-service turns off
|
|
12
|
+
// typed completion (src/language-service.ts), which costs about 150 MB
|
|
13
|
+
// while in use.
|
|
9
14
|
import process from "node:process";
|
|
10
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
11
16
|
import { homedir, platform } from "node:os";
|
|
12
17
|
import { join } from "node:path";
|
|
13
18
|
import { parseArgs } from "node:util";
|
|
14
19
|
import { Kernel, parseConnectionInfo } from "./kernel.js";
|
|
20
|
+
import { isTypeStripperName } from "./transform.js";
|
|
15
21
|
import { hostRuntime, kernelArgv, moduleOrigin, VERSION } from "./package.js";
|
|
16
22
|
function isRuntime(value) {
|
|
17
23
|
return value === "node" || value === "deno" || value === "bun" || value === "workerd" || value === "celld";
|
|
@@ -25,7 +31,7 @@ function kernelsDir() {
|
|
|
25
31
|
return join(process.env.APPDATA ?? homedir(), "jupyter", "kernels");
|
|
26
32
|
return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), "jupyter", "kernels");
|
|
27
33
|
}
|
|
28
|
-
function install(runtime, denoCompat, name, display) {
|
|
34
|
+
function install(runtime, denoCompat, typeStrip, languageService, name, display) {
|
|
29
35
|
const dir = join(kernelsDir(), name);
|
|
30
36
|
mkdirSync(dir, { recursive: true });
|
|
31
37
|
const spec = {
|
|
@@ -34,6 +40,8 @@ function install(runtime, denoCompat, name, display) {
|
|
|
34
40
|
"--runtime",
|
|
35
41
|
runtime,
|
|
36
42
|
...(denoCompat ? ["--deno-compat"] : []),
|
|
43
|
+
...(typeStrip !== "esbuild" ? ["--type-strip", typeStrip] : []),
|
|
44
|
+
...(languageService ? [] : ["--no-language-service"]),
|
|
37
45
|
"--conn",
|
|
38
46
|
"{connection_file}",
|
|
39
47
|
]),
|
|
@@ -49,6 +57,77 @@ function install(runtime, denoCompat, name, display) {
|
|
|
49
57
|
writeFileSync(join(dir, "kernel.json"), JSON.stringify(spec, null, 2) + "\n");
|
|
50
58
|
console.log(`installed kernelspec ${name} -> ${dir}`);
|
|
51
59
|
}
|
|
60
|
+
// A kernelspec this package wrote: install() always records the runtime in
|
|
61
|
+
// metadata and passes the connection file to the kernel subcommand. Other
|
|
62
|
+
// kernels' specs in the same directory are left alone.
|
|
63
|
+
function readOwnSpec(name) {
|
|
64
|
+
const dir = join(kernelsDir(), name);
|
|
65
|
+
let spec;
|
|
66
|
+
try {
|
|
67
|
+
spec = JSON.parse(readFileSync(join(dir, "kernel.json"), "utf8"));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const argv = Array.isArray(spec.argv) ? spec.argv : [];
|
|
73
|
+
const runtime = spec.metadata?.runtime;
|
|
74
|
+
if (!isRuntime(runtime) || !argv.includes("kernel") || !argv.includes("{connection_file}"))
|
|
75
|
+
return undefined;
|
|
76
|
+
const version = typeof spec.metadata?.version === "string" ? spec.metadata.version : undefined;
|
|
77
|
+
return { name, dir, runtime, version };
|
|
78
|
+
}
|
|
79
|
+
function installedSpecs() {
|
|
80
|
+
const root = kernelsDir();
|
|
81
|
+
if (!existsSync(root))
|
|
82
|
+
return [];
|
|
83
|
+
return readdirSync(root, { withFileTypes: true })
|
|
84
|
+
.filter((entry) => entry.isDirectory())
|
|
85
|
+
.map((entry) => readOwnSpec(entry.name))
|
|
86
|
+
.filter((spec) => spec !== undefined)
|
|
87
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
88
|
+
}
|
|
89
|
+
function list() {
|
|
90
|
+
const specs = installedSpecs();
|
|
91
|
+
if (specs.length === 0) {
|
|
92
|
+
console.log(`no cdp-kernel kernelspecs in ${kernelsDir()}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const width = Math.max(...specs.map((s) => s.name.length));
|
|
96
|
+
for (const s of specs) {
|
|
97
|
+
console.log(`${s.name.padEnd(width)} ${s.runtime.padEnd(7)} ${(s.version ?? "-").padEnd(7)} ${s.dir}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Jupyter's kernel names; anything else could point outside the kernels
|
|
101
|
+
// directory.
|
|
102
|
+
const KERNEL_NAME = /^[A-Za-z0-9._-]+$/;
|
|
103
|
+
function uninstall(names, all) {
|
|
104
|
+
if (all)
|
|
105
|
+
names = installedSpecs().map((s) => s.name);
|
|
106
|
+
else if (names.length === 0) {
|
|
107
|
+
console.error("uninstall: name the kernelspecs to remove, or pass --all. Installed:");
|
|
108
|
+
list();
|
|
109
|
+
process.exit(2);
|
|
110
|
+
}
|
|
111
|
+
let failed = false;
|
|
112
|
+
for (const name of names) {
|
|
113
|
+
const valid = KERNEL_NAME.test(name) && name !== "." && name !== "..";
|
|
114
|
+
const spec = valid ? readOwnSpec(name) : undefined;
|
|
115
|
+
if (!spec) {
|
|
116
|
+
const exists = valid && existsSync(join(kernelsDir(), name));
|
|
117
|
+
console.error(exists
|
|
118
|
+
? `uninstall: ${name} wasn't installed by cdp-kernel; leaving it (use jupyter kernelspec remove)`
|
|
119
|
+
: `uninstall: no kernelspec ${name} in ${kernelsDir()}`);
|
|
120
|
+
failed = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
rmSync(spec.dir, { recursive: true, force: true });
|
|
124
|
+
console.log(`removed kernelspec ${name} (${spec.dir})`);
|
|
125
|
+
}
|
|
126
|
+
if (all && names.length === 0)
|
|
127
|
+
console.log(`no cdp-kernel kernelspecs in ${kernelsDir()}`);
|
|
128
|
+
if (failed)
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
52
131
|
const { positionals, values } = parseArgs({
|
|
53
132
|
allowPositionals: true,
|
|
54
133
|
options: {
|
|
@@ -56,7 +135,10 @@ const { positionals, values } = parseArgs({
|
|
|
56
135
|
conn: { type: "string" },
|
|
57
136
|
name: { type: "string" },
|
|
58
137
|
display: { type: "string" },
|
|
138
|
+
all: { type: "boolean", default: false },
|
|
59
139
|
"deno-compat": { type: "boolean", default: false },
|
|
140
|
+
"type-strip": { type: "string", default: "esbuild" },
|
|
141
|
+
"no-language-service": { type: "boolean", default: false },
|
|
60
142
|
},
|
|
61
143
|
});
|
|
62
144
|
const runtime = values.runtime;
|
|
@@ -64,12 +146,18 @@ if (!isRuntime(runtime)) {
|
|
|
64
146
|
console.error(`unknown runtime ${runtime}; expected node, deno, bun, workerd or celld`);
|
|
65
147
|
process.exit(2);
|
|
66
148
|
}
|
|
149
|
+
const typeStrip = values["type-strip"];
|
|
150
|
+
if (!isTypeStripperName(typeStrip)) {
|
|
151
|
+
console.error(`unknown type stripper ${typeStrip}; expected esbuild or sucrase`);
|
|
152
|
+
process.exit(2);
|
|
153
|
+
}
|
|
154
|
+
const languageService = values["no-language-service"] !== true;
|
|
67
155
|
// Deno targets have the real namespace; the shim is for Node.
|
|
68
156
|
const denoCompat = values["deno-compat"] === true && runtime === "node";
|
|
69
157
|
switch (positionals[0]) {
|
|
70
158
|
case "install": {
|
|
71
159
|
const suffix = denoCompat ? "-deno" : "";
|
|
72
|
-
install(runtime, denoCompat, values.name ?? `cdp-${runtime}${suffix}`, values.display ?? `TypeScript (cdp-kernel, ${runtime}${denoCompat ? ", Deno compat" : ""})`);
|
|
160
|
+
install(runtime, denoCompat, typeStrip, languageService, values.name ?? `cdp-${runtime}${suffix}`, values.display ?? `TypeScript (cdp-kernel, ${runtime}${denoCompat ? ", Deno compat" : ""})`);
|
|
73
161
|
break;
|
|
74
162
|
}
|
|
75
163
|
case "kernel": {
|
|
@@ -77,14 +165,20 @@ switch (positionals[0]) {
|
|
|
77
165
|
console.error("kernel: --conn CONNECTION_FILE is required");
|
|
78
166
|
process.exit(2);
|
|
79
167
|
}
|
|
80
|
-
const kernel = new Kernel(parseConnectionInfo(readFileSync(values.conn, "utf8")), runtime, { denoCompat });
|
|
168
|
+
const kernel = new Kernel(parseConnectionInfo(readFileSync(values.conn, "utf8")), runtime, { denoCompat, typeStrip, languageService });
|
|
81
169
|
await kernel.run();
|
|
82
170
|
break;
|
|
83
171
|
}
|
|
172
|
+
case "list":
|
|
173
|
+
list();
|
|
174
|
+
break;
|
|
175
|
+
case "uninstall":
|
|
176
|
+
uninstall([...positionals.slice(1), ...(values.name ? [values.name] : [])], values.all === true);
|
|
177
|
+
break;
|
|
84
178
|
case "version":
|
|
85
179
|
console.log(VERSION);
|
|
86
180
|
break;
|
|
87
181
|
default:
|
|
88
|
-
console.error("usage: cdp-kernel install [--runtime node|deno|bun|workerd|celld] [--deno-compat] | kernel --runtime node|deno|bun|workerd|celld --conn FILE | version");
|
|
182
|
+
console.error("usage: cdp-kernel install [--runtime node|deno|bun|workerd|celld] [--deno-compat] [--type-strip esbuild|sucrase] [--no-language-service] | list | uninstall [NAME...] [--all] | kernel --runtime node|deno|bun|workerd|celld --conn FILE | version");
|
|
89
183
|
process.exit(2);
|
|
90
184
|
}
|
package/dist/mod.d.ts
CHANGED
|
@@ -22,4 +22,5 @@
|
|
|
22
22
|
*/
|
|
23
23
|
export { type ConnectionInfo, Kernel, parseConnectionInfo } from "./kernel.ts";
|
|
24
24
|
export type { RuntimeName } from "./target.ts";
|
|
25
|
+
export { CellSyntaxError, type CellTransformOptions, type JsxOptions, type TypeStripper } from "./transform.ts";
|
|
25
26
|
export { VERSION } from "./package.ts";
|
package/dist/mod.js
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type TS from "typescript";
|
|
2
|
+
import type { DenoResolver } from "./deno-types.ts";
|
|
3
|
+
export interface NotebookTypesOptions {
|
|
4
|
+
notebookDir: string;
|
|
5
|
+
/**
|
|
6
|
+
* The installer cache npm: and jsr: imports resolve from: where the
|
|
7
|
+
* target installs them (Bun's own for Bun, npm's for the rest). Null for
|
|
8
|
+
* Deno targets, which resolve them natively (see `deno`).
|
|
9
|
+
*/
|
|
10
|
+
npmCache: "npm" | "bun" | null;
|
|
11
|
+
/**
|
|
12
|
+
* For Deno targets: where Deno put what the executed cells imported
|
|
13
|
+
* (npm:, jsr:, URLs, the project's import map).
|
|
14
|
+
*/
|
|
15
|
+
deno?: DenoResolver;
|
|
16
|
+
/** compilerOptions.types: type packages found from the notebook's directory. */
|
|
17
|
+
types: string[];
|
|
18
|
+
/** Declaration files every cell sees, e.g. runtime types. */
|
|
19
|
+
typeFiles: string[];
|
|
20
|
+
/** Declarations for the kernel's own globals (jupyter, prompt, confirm). */
|
|
21
|
+
globals: string;
|
|
22
|
+
}
|
|
23
|
+
export interface CompletionEntry {
|
|
24
|
+
name: string;
|
|
25
|
+
/** TypeScript's ScriptElementKind: "method", "property", "const", ... */
|
|
26
|
+
kind: string;
|
|
27
|
+
}
|
|
28
|
+
export interface CompletionResult {
|
|
29
|
+
/** Where the completed name starts in the cell, as a UTF-16 index. */
|
|
30
|
+
start: number;
|
|
31
|
+
entries: CompletionEntry[];
|
|
32
|
+
}
|
|
33
|
+
export declare class NotebookTypes {
|
|
34
|
+
#private;
|
|
35
|
+
constructor(ts: typeof TS, options: NotebookTypesOptions);
|
|
36
|
+
/** An executed cell: its declarations are in scope for later ones. */
|
|
37
|
+
addCell(code: string): void;
|
|
38
|
+
/** The runtime restarted and lost its state. */
|
|
39
|
+
reset(): void;
|
|
40
|
+
/** Adds declaration files, e.g. runtime types installed after startup. */
|
|
41
|
+
addTypeFiles(files: string[]): void;
|
|
42
|
+
/** Adds a declaration file held in memory, e.g. the output of `deno types`. */
|
|
43
|
+
addVirtualTypeFile(name: string, text: string): void;
|
|
44
|
+
/** Adds type packages (compilerOptions.types) that resolve from the notebook. */
|
|
45
|
+
addTypeNames(names: string[]): void;
|
|
46
|
+
/** Whether a type package (compilerOptions.types) resolves from the notebook. */
|
|
47
|
+
resolvesTypes(name: string): boolean;
|
|
48
|
+
/** Completions at `cursor` (a UTF-16 index) in `code`, the cell being edited. */
|
|
49
|
+
complete(code: string, cursor: number): CompletionResult;
|
|
50
|
+
/**
|
|
51
|
+
* The type, signature and JSDoc of the name at `cursor`: the one the
|
|
52
|
+
* cursor is in or just after, or, right after "name(", that callee.
|
|
53
|
+
*/
|
|
54
|
+
quickInfo(code: string, cursor: number): string | null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Declarations for the names the kernel adds to every target, as in
|
|
58
|
+
* src/globals.ts. `display` is runtime/display.mjs's path; its sibling
|
|
59
|
+
* display.d.mts types `jupyter`. Without it (a kernel run from JSR, whose
|
|
60
|
+
* runtime files aren't local), `jupyter` is untyped.
|
|
61
|
+
*/
|
|
62
|
+
export declare function kernelGlobals(display: string | null): string;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// TypeScript's language service over a notebook (#263): completion from
|
|
2
|
+
// declared types, where the runtime only knows values. Runs in a worker
|
|
3
|
+
// (src/language-service-worker.ts); see probes/ts-language-service.md for
|
|
4
|
+
// the design and its measurements.
|
|
5
|
+
//
|
|
6
|
+
// The service sees one virtual file, never written to disk, in the
|
|
7
|
+
// notebook's directory (so relative imports resolve from there): the cells
|
|
8
|
+
// executed so far, then the cell being edited. REPL cells redeclare names
|
|
9
|
+
// all the time (`const x = 1`, later `const x = "s"`), which in one file is
|
|
10
|
+
// an error that keeps the first type, so a declaration is dropped from the
|
|
11
|
+
// history when a later cell, or the one being edited, declares all of its
|
|
12
|
+
// names again.
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { findLockFile, installedPackageDir, jsrToNpm, parsePackageSpecifier } from "../runtime/node-hooks.mjs";
|
|
15
|
+
// The document is a module, so top-level await in a cell isn't an error.
|
|
16
|
+
const PREFIX = "export {};\n";
|
|
17
|
+
export class NotebookTypes {
|
|
18
|
+
#ts;
|
|
19
|
+
#options;
|
|
20
|
+
#file;
|
|
21
|
+
#globalsFile;
|
|
22
|
+
#service;
|
|
23
|
+
#compilerOptions;
|
|
24
|
+
#resolutionCache;
|
|
25
|
+
/** ts.sys, plus the remote modules a Deno target fetched (virtual paths). */
|
|
26
|
+
#resolutionHost;
|
|
27
|
+
#cells = [];
|
|
28
|
+
/** Specifiers executed cells imported: the target has fetched them. */
|
|
29
|
+
#imported = new Set();
|
|
30
|
+
/** Declaration files that exist only in memory, e.g. `deno types`. */
|
|
31
|
+
#virtualTypes = new Map();
|
|
32
|
+
#typeFiles;
|
|
33
|
+
#text = PREFIX;
|
|
34
|
+
#version = 0;
|
|
35
|
+
#projectVersion = 0;
|
|
36
|
+
constructor(ts, options) {
|
|
37
|
+
this.#ts = ts;
|
|
38
|
+
this.#options = options;
|
|
39
|
+
this.#typeFiles = [...options.typeFiles];
|
|
40
|
+
this.#file = join(options.notebookDir, "__cdpk_notebook__.ts");
|
|
41
|
+
this.#globalsFile = join(options.notebookDir, "__cdpk_globals__.d.ts");
|
|
42
|
+
this.#compilerOptions = {
|
|
43
|
+
target: ts.ScriptTarget.ESNext,
|
|
44
|
+
module: ts.ModuleKind.Preserve,
|
|
45
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
46
|
+
allowImportingTsExtensions: true,
|
|
47
|
+
allowJs: true,
|
|
48
|
+
strict: true,
|
|
49
|
+
noEmit: true,
|
|
50
|
+
skipLibCheck: true,
|
|
51
|
+
lib: ["lib.esnext.d.ts"],
|
|
52
|
+
types: options.types,
|
|
53
|
+
};
|
|
54
|
+
const deno = options.deno;
|
|
55
|
+
this.#resolutionHost = {
|
|
56
|
+
...ts.sys,
|
|
57
|
+
fileExists: (f) => deno?.localFile(f) !== undefined || ts.sys.fileExists(f),
|
|
58
|
+
readFile: (f) => {
|
|
59
|
+
const local = deno?.localFile(f);
|
|
60
|
+
return ts.sys.readFile(local ?? f);
|
|
61
|
+
},
|
|
62
|
+
directoryExists: (d) => (deno !== undefined && d.startsWith(deno.root)) || ts.sys.directoryExists(d),
|
|
63
|
+
getCurrentDirectory: () => options.notebookDir,
|
|
64
|
+
};
|
|
65
|
+
const host = this.#host();
|
|
66
|
+
this.#resolutionCache = ts.createModuleResolutionCache(options.notebookDir, (f) => f, this.#compilerOptions);
|
|
67
|
+
this.#service = ts.createLanguageService(host, ts.createDocumentRegistry());
|
|
68
|
+
}
|
|
69
|
+
/** An executed cell: its declarations are in scope for later ones. */
|
|
70
|
+
addCell(code) {
|
|
71
|
+
this.#cells.push({ code, statements: this.#statements(code) });
|
|
72
|
+
const ts = this.#ts;
|
|
73
|
+
const sf = ts.createSourceFile("cell.ts", code, ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS);
|
|
74
|
+
for (const stmt of sf.statements) {
|
|
75
|
+
if ((ts.isImportDeclaration(stmt) || ts.isExportDeclaration(stmt)) && stmt.moduleSpecifier && ts.isStringLiteral(stmt.moduleSpecifier)) {
|
|
76
|
+
this.#imported.add(stmt.moduleSpecifier.text);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** The runtime restarted and lost its state. */
|
|
81
|
+
reset() {
|
|
82
|
+
this.#cells.length = 0;
|
|
83
|
+
}
|
|
84
|
+
/** Adds declaration files, e.g. runtime types installed after startup. */
|
|
85
|
+
addTypeFiles(files) {
|
|
86
|
+
this.#typeFiles.push(...files.filter((f) => !this.#typeFiles.includes(f)));
|
|
87
|
+
this.#projectVersion++;
|
|
88
|
+
}
|
|
89
|
+
/** Adds a declaration file held in memory, e.g. the output of `deno types`. */
|
|
90
|
+
addVirtualTypeFile(name, text) {
|
|
91
|
+
this.#virtualTypes.set(join(this.#options.notebookDir, name), text);
|
|
92
|
+
this.#projectVersion++;
|
|
93
|
+
}
|
|
94
|
+
/** Adds type packages (compilerOptions.types) that resolve from the notebook. */
|
|
95
|
+
addTypeNames(names) {
|
|
96
|
+
const current = this.#compilerOptions.types ?? [];
|
|
97
|
+
// A new object: the service compares options by identity first.
|
|
98
|
+
this.#compilerOptions = { ...this.#compilerOptions, types: [...current, ...names.filter((n) => !current.includes(n))] };
|
|
99
|
+
this.#projectVersion++;
|
|
100
|
+
}
|
|
101
|
+
/** Whether a type package (compilerOptions.types) resolves from the notebook. */
|
|
102
|
+
resolvesTypes(name) {
|
|
103
|
+
// Default type roots are found up from the current directory.
|
|
104
|
+
const host = { ...this.#ts.sys, getCurrentDirectory: () => this.#options.notebookDir };
|
|
105
|
+
const resolved = this.#ts.resolveTypeReferenceDirective(name, this.#file, this.#compilerOptions, host);
|
|
106
|
+
return resolved.resolvedTypeReferenceDirective !== undefined;
|
|
107
|
+
}
|
|
108
|
+
/** Completions at `cursor` (a UTF-16 index) in `code`, the cell being edited. */
|
|
109
|
+
complete(code, cursor) {
|
|
110
|
+
const offset = this.#setDocument(code);
|
|
111
|
+
const prefix = /[\w$]*$/.exec(code.slice(0, cursor))?.[0] ?? "";
|
|
112
|
+
const info = this.#service.getCompletionsAtPosition(this.#file, offset + cursor, {
|
|
113
|
+
includeCompletionsForModuleExports: false,
|
|
114
|
+
includeCompletionsWithInsertText: true,
|
|
115
|
+
});
|
|
116
|
+
const ts = this.#ts;
|
|
117
|
+
const entries = (info?.entries ?? [])
|
|
118
|
+
.filter((e) => e.kind !== ts.ScriptElementKind.keyword &&
|
|
119
|
+
// Entries that would edit more than the name (`?.x`, `["a-b"]`).
|
|
120
|
+
e.insertText === undefined && e.replacementSpan === undefined &&
|
|
121
|
+
e.name.startsWith(prefix) && !e.name.startsWith("__cdpk"))
|
|
122
|
+
.sort((a, b) => (a.sortText < b.sortText ? -1 : a.sortText > b.sortText ? 1 : a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
123
|
+
.map((e) => ({ name: e.name, kind: e.kind }));
|
|
124
|
+
return { start: cursor - prefix.length, entries };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The type, signature and JSDoc of the name at `cursor`: the one the
|
|
128
|
+
* cursor is in or just after, or, right after "name(", that callee.
|
|
129
|
+
*/
|
|
130
|
+
quickInfo(code, cursor) {
|
|
131
|
+
const offset = this.#setDocument(code);
|
|
132
|
+
const before = code.slice(0, cursor);
|
|
133
|
+
const call = /[\w$]\(\s*$/.exec(before);
|
|
134
|
+
// The name's last character when the cursor is just past it.
|
|
135
|
+
const position = call ? call.index : /[\w$]/.test(code[cursor - 1] ?? "") ? cursor - 1 : cursor;
|
|
136
|
+
const info = this.#service.getQuickInfoAtPosition(this.#file, offset + position);
|
|
137
|
+
if (info === undefined)
|
|
138
|
+
return null;
|
|
139
|
+
const ts = this.#ts;
|
|
140
|
+
// An imported name's "import x" line repeats the cell; alone, the import
|
|
141
|
+
// didn't resolve. "any" alone is what an undeclared name gets, e.g. a
|
|
142
|
+
// global a library set at runtime.
|
|
143
|
+
const display = ts.displayPartsToString(info.displayParts).split("\n").filter((l) => !/^import [\w$]+$/.test(l)).join("\n");
|
|
144
|
+
if (display === "" || display === "any")
|
|
145
|
+
return null;
|
|
146
|
+
const lines = [display];
|
|
147
|
+
const docs = ts.displayPartsToString(info.documentation);
|
|
148
|
+
if (docs !== "")
|
|
149
|
+
lines.push(docs);
|
|
150
|
+
for (const tag of info.tags ?? []) {
|
|
151
|
+
const text = ts.displayPartsToString(tag.text);
|
|
152
|
+
lines.push(`@${tag.name}${text === "" ? "" : ` ${text}`}`);
|
|
153
|
+
}
|
|
154
|
+
return lines.join("\n");
|
|
155
|
+
}
|
|
156
|
+
/** Sets the virtual file to the history plus `code`; returns where `code` starts. */
|
|
157
|
+
#setDocument(code) {
|
|
158
|
+
const history = this.#history(new Set(this.#statements(code).flatMap((s) => s.names)));
|
|
159
|
+
const text = `${PREFIX}${history}\n`;
|
|
160
|
+
this.#text = text + code;
|
|
161
|
+
this.#version++;
|
|
162
|
+
return text.length;
|
|
163
|
+
}
|
|
164
|
+
/** The executed cells, minus declarations a later cell (or `seen`) makes again. */
|
|
165
|
+
#history(seen) {
|
|
166
|
+
const kept = [];
|
|
167
|
+
for (let i = this.#cells.length - 1; i >= 0; i--) {
|
|
168
|
+
const { code, statements } = this.#cells[i];
|
|
169
|
+
let text = code;
|
|
170
|
+
// Back to front, so earlier offsets stay valid.
|
|
171
|
+
for (let j = statements.length - 1; j >= 0; j--) {
|
|
172
|
+
const { start, end, names } = statements[j];
|
|
173
|
+
if (names.length > 0 && names.every((n) => seen.has(n)))
|
|
174
|
+
text = text.slice(0, start) + text.slice(end);
|
|
175
|
+
}
|
|
176
|
+
for (const s of statements)
|
|
177
|
+
for (const n of s.names)
|
|
178
|
+
seen.add(n);
|
|
179
|
+
kept.push(text);
|
|
180
|
+
}
|
|
181
|
+
return kept.reverse().join("\n");
|
|
182
|
+
}
|
|
183
|
+
#statements(code) {
|
|
184
|
+
const ts = this.#ts;
|
|
185
|
+
const sf = ts.createSourceFile("cell.ts", code, ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS);
|
|
186
|
+
return sf.statements.map((stmt) => ({ start: stmt.pos, end: stmt.end, names: this.#declaredNames(stmt) }));
|
|
187
|
+
}
|
|
188
|
+
#declaredNames(stmt) {
|
|
189
|
+
const ts = this.#ts;
|
|
190
|
+
const bound = (name) => ts.isIdentifier(name)
|
|
191
|
+
? [name.text]
|
|
192
|
+
: name.elements.flatMap((e) => (ts.isOmittedExpression(e) ? [] : bound(e.name)));
|
|
193
|
+
if (ts.isVariableStatement(stmt))
|
|
194
|
+
return stmt.declarationList.declarations.flatMap((d) => bound(d.name));
|
|
195
|
+
if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt) || ts.isInterfaceDeclaration(stmt) ||
|
|
196
|
+
ts.isTypeAliasDeclaration(stmt) || ts.isEnumDeclaration(stmt)) && stmt.name) {
|
|
197
|
+
return [stmt.name.text];
|
|
198
|
+
}
|
|
199
|
+
if (ts.isImportDeclaration(stmt) && stmt.importClause) {
|
|
200
|
+
const clause = stmt.importClause;
|
|
201
|
+
const names = clause.name ? [clause.name.text] : [];
|
|
202
|
+
const bindings = clause.namedBindings;
|
|
203
|
+
if (bindings && ts.isNamedImports(bindings))
|
|
204
|
+
names.push(...bindings.elements.map((e) => e.name.text));
|
|
205
|
+
if (bindings && ts.isNamespaceImport(bindings))
|
|
206
|
+
names.push(bindings.name.text);
|
|
207
|
+
return names;
|
|
208
|
+
}
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
/** Where an npm:/jsr: import is installed in the kernel's npm cache, if it is. */
|
|
212
|
+
#cachedPackage(specifier) {
|
|
213
|
+
try {
|
|
214
|
+
const pkg = specifier.startsWith("npm:")
|
|
215
|
+
? parsePackageSpecifier(specifier.slice(4))
|
|
216
|
+
: jsrToNpm(specifier.slice(4));
|
|
217
|
+
const installer = this.#options.npmCache ?? "npm";
|
|
218
|
+
const installed = installedPackageDir(pkg, installer, { lockFile: findLockFile(this.#options.notebookDir) });
|
|
219
|
+
return installed === null ? null : { ...installed, subpath: pkg.subpath };
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
#resolve(name, containingFile) {
|
|
226
|
+
const ts = this.#ts;
|
|
227
|
+
const deno = this.#options.deno;
|
|
228
|
+
// Only what an executed cell imported (or a fetched module imports):
|
|
229
|
+
// looking a specifier up never makes Deno fetch something new.
|
|
230
|
+
if (deno !== undefined && !/^(?:\.{0,2}\/|node:)/.test(name) && (this.#imported.has(name) || containingFile.startsWith(deno.root))) {
|
|
231
|
+
const found = deno.lookup(name);
|
|
232
|
+
if (found?.kind === "npm") {
|
|
233
|
+
return ts.resolveModuleName(join(found.dir, found.subpath), containingFile, this.#compilerOptions, this.#resolutionHost, this.#resolutionCache);
|
|
234
|
+
}
|
|
235
|
+
const extension = found?.kind === "remote" ? extensionOf(ts, found.path) : undefined;
|
|
236
|
+
if (found?.kind === "remote" && extension !== undefined) {
|
|
237
|
+
return { resolvedModule: { resolvedFileName: found.path, extension, isExternalLibraryImport: false } };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (this.#options.npmCache !== null && /^(?:npm|jsr):/.test(name)) {
|
|
241
|
+
const cached = this.#cachedPackage(name);
|
|
242
|
+
// Resolved as its alias from the cache directory, so package.json
|
|
243
|
+
// "exports" and "types" apply as for any package.
|
|
244
|
+
if (cached !== null) {
|
|
245
|
+
const from = join(cached.dir, "__cdpk__.ts");
|
|
246
|
+
return ts.resolveModuleName(cached.alias + cached.subpath, from, this.#compilerOptions, this.#resolutionHost, this.#resolutionCache);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return ts.resolveModuleName(name, containingFile, this.#compilerOptions, this.#resolutionHost, this.#resolutionCache);
|
|
250
|
+
}
|
|
251
|
+
#host() {
|
|
252
|
+
const ts = this.#ts;
|
|
253
|
+
const file = this.#file;
|
|
254
|
+
const globalsFile = this.#globalsFile;
|
|
255
|
+
const deno = this.#options.deno;
|
|
256
|
+
const virtual = (f) => {
|
|
257
|
+
if (f === file)
|
|
258
|
+
return this.#text;
|
|
259
|
+
if (f === globalsFile)
|
|
260
|
+
return this.#options.globals;
|
|
261
|
+
const local = deno?.localFile(f);
|
|
262
|
+
return local !== undefined ? ts.sys.readFile(local) : this.#virtualTypes.get(f);
|
|
263
|
+
};
|
|
264
|
+
return {
|
|
265
|
+
getCompilationSettings: () => this.#compilerOptions,
|
|
266
|
+
getProjectVersion: () => `${this.#projectVersion}.${this.#version}`,
|
|
267
|
+
getScriptFileNames: () => [file, globalsFile, ...this.#virtualTypes.keys(), ...this.#typeFiles],
|
|
268
|
+
getScriptVersion: (f) => (f === file ? String(this.#version) : "0"),
|
|
269
|
+
getScriptSnapshot: (f) => {
|
|
270
|
+
const text = virtual(f) ?? ts.sys.readFile(f);
|
|
271
|
+
return text === undefined ? undefined : ts.ScriptSnapshot.fromString(text);
|
|
272
|
+
},
|
|
273
|
+
getCurrentDirectory: () => this.#options.notebookDir,
|
|
274
|
+
getDefaultLibFileName: (o) => ts.getDefaultLibFilePath(o),
|
|
275
|
+
fileExists: (f) => f === file || f === globalsFile || this.#virtualTypes.has(f) || deno?.localFile(f) !== undefined || ts.sys.fileExists(f),
|
|
276
|
+
readFile: (f) => virtual(f) ?? ts.sys.readFile(f),
|
|
277
|
+
readDirectory: ts.sys.readDirectory,
|
|
278
|
+
directoryExists: ts.sys.directoryExists,
|
|
279
|
+
getDirectories: ts.sys.getDirectories,
|
|
280
|
+
resolveModuleNameLiterals: (literals, containingFile) => literals.map((literal) => this.#resolve(literal.text, containingFile)),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** The extension TypeScript should load a remote module's path as, if it's one it can. */
|
|
285
|
+
function extensionOf(ts, path) {
|
|
286
|
+
for (const ext of [ts.Extension.Dts, ts.Extension.Dmts, ts.Extension.Ts, ts.Extension.Mts, ts.Extension.Tsx, ts.Extension.Js, ts.Extension.Mjs, ts.Extension.Jsx]) {
|
|
287
|
+
if (path.endsWith(ext))
|
|
288
|
+
return ext;
|
|
289
|
+
}
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Declarations for the names the kernel adds to every target, as in
|
|
294
|
+
* src/globals.ts. `display` is runtime/display.mjs's path; its sibling
|
|
295
|
+
* display.d.mts types `jupyter`. Without it (a kernel run from JSR, whose
|
|
296
|
+
* runtime files aren't local), `jupyter` is untyped.
|
|
297
|
+
*/
|
|
298
|
+
export function kernelGlobals(display) {
|
|
299
|
+
const jupyter = display === null ? "any" : `import(${JSON.stringify(display)}).JupyterNamespace`;
|
|
300
|
+
return `declare global {
|
|
301
|
+
/** The notebook display API: display, md/html/svg templates, image, format, broadcast. */
|
|
302
|
+
var jupyter: ${jupyter};
|
|
303
|
+
/** Asks the frontend for a line of input. Null when the frontend didn't allow input for this cell. */
|
|
304
|
+
function prompt(message?: string, defaultValue?: string, options?: { password?: boolean }): string | null;
|
|
305
|
+
/** Asks a yes/no question over the Jupyter stdin channel. */
|
|
306
|
+
function confirm(message?: string): boolean;
|
|
307
|
+
}
|
|
308
|
+
export {};
|
|
309
|
+
`;
|
|
310
|
+
}
|
package/dist/package.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.6.0";
|
|
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
|
|
6
6
|
* stale: see dependencyCutoff.
|
|
7
7
|
*/
|
|
8
|
-
export declare const RELEASE_DATE = "2026-10-
|
|
8
|
+
export declare const RELEASE_DATE = "2026-10-18";
|
|
9
9
|
/**
|
|
10
10
|
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
11
11
|
* a kernelspec relaunching a release from JSR would fail on release day. It
|
package/dist/package.js
CHANGED
|
@@ -14,13 +14,13 @@ 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.6.0";
|
|
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
|
|
21
21
|
* stale: see dependencyCutoff.
|
|
22
22
|
*/
|
|
23
|
-
export const RELEASE_DATE = "2026-10-
|
|
23
|
+
export const RELEASE_DATE = "2026-10-18";
|
|
24
24
|
/**
|
|
25
25
|
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
26
26
|
* a kernelspec relaunching a release from JSR would fail on release day. It
|
package/dist/parent.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calls `onGone` once the parent named by JPY_PARENT_PID has exited: its pid
|
|
3
|
+
* is gone, or this process was reparented. A no-op when the variable is
|
|
4
|
+
* unset, so a kernel started by hand keeps running; and on Windows, where
|
|
5
|
+
* ipykernel's variable is a handle, not a pid. A pid that doesn't exist at
|
|
6
|
+
* start (the server is in another pid namespace) is ignored, leaving only
|
|
7
|
+
* the reparenting check.
|
|
8
|
+
*/
|
|
9
|
+
export declare function watchParent(onGone: () => void, { env, intervalMs }?: {
|
|
10
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
11
|
+
intervalMs?: number | undefined;
|
|
12
|
+
}): () => void;
|
package/dist/parent.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// The server that started the kernel, watched so a server that dies without
|
|
2
|
+
// shutting the kernel down (SIGKILL, a crash) doesn't leave it running with
|
|
3
|
+
// its runtime and ports (#254). jupyter_server and Ganymede put their pid in
|
|
4
|
+
// JPY_PARENT_PID; ipykernel's ParentPollerUnix polls for the same thing.
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
function exists(pid) {
|
|
7
|
+
try {
|
|
8
|
+
process.kill(pid, 0);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
// EPERM: it exists, under another user.
|
|
13
|
+
return err.code === "EPERM";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Calls `onGone` once the parent named by JPY_PARENT_PID has exited: its pid
|
|
18
|
+
* is gone, or this process was reparented. A no-op when the variable is
|
|
19
|
+
* unset, so a kernel started by hand keeps running; and on Windows, where
|
|
20
|
+
* ipykernel's variable is a handle, not a pid. A pid that doesn't exist at
|
|
21
|
+
* start (the server is in another pid namespace) is ignored, leaving only
|
|
22
|
+
* the reparenting check.
|
|
23
|
+
*/
|
|
24
|
+
export function watchParent(onGone, { env = process.env, intervalMs = 1000 } = {}) {
|
|
25
|
+
const parent = Number(env.JPY_PARENT_PID);
|
|
26
|
+
if (process.platform === "win32" || !Number.isInteger(parent) || parent <= 1)
|
|
27
|
+
return () => { };
|
|
28
|
+
const ppid = process.ppid;
|
|
29
|
+
const checkPid = exists(parent);
|
|
30
|
+
const timer = setInterval(() => {
|
|
31
|
+
if (process.ppid === ppid && (!checkPid || exists(parent)))
|
|
32
|
+
return;
|
|
33
|
+
clearInterval(timer);
|
|
34
|
+
onGone();
|
|
35
|
+
}, intervalMs);
|
|
36
|
+
// The kernel's sockets keep it running; this mustn't.
|
|
37
|
+
timer.unref();
|
|
38
|
+
return () => clearInterval(timer);
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface PortSocket {
|
|
2
|
+
/** The remote end as hex `address:port`, as /proc prints it. */
|
|
3
|
+
remote: string;
|
|
4
|
+
state: string;
|
|
5
|
+
inode: string;
|
|
6
|
+
}
|
|
7
|
+
/** The sockets in a /proc/net/tcp table whose local port is `port`. */
|
|
8
|
+
export declare function socketsOnPort(table: string, port: number): PortSocket[];
|
|
9
|
+
/**
|
|
10
|
+
* One line per socket on `port`: its state, remote end and owning process,
|
|
11
|
+
* or an empty list when it can't be told (not Linux, or nothing found).
|
|
12
|
+
*/
|
|
13
|
+
export declare function portHolders(port: number): string[];
|