@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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Who holds a local TCP port, for the error when a connection-file port
|
|
2
|
+
// stays taken past the startup retry (#257). Linux only: it reads
|
|
3
|
+
// /proc/net/tcp{,6} and matches socket inodes against /proc/<pid>/fd.
|
|
4
|
+
import { readdirSync, readFileSync, readlinkSync } from "node:fs";
|
|
5
|
+
const STATES = {
|
|
6
|
+
"01": "ESTABLISHED",
|
|
7
|
+
"02": "SYN_SENT",
|
|
8
|
+
"03": "SYN_RECV",
|
|
9
|
+
"04": "FIN_WAIT1",
|
|
10
|
+
"05": "FIN_WAIT2",
|
|
11
|
+
"06": "TIME_WAIT",
|
|
12
|
+
"07": "CLOSE",
|
|
13
|
+
"08": "CLOSE_WAIT",
|
|
14
|
+
"09": "LAST_ACK",
|
|
15
|
+
"0A": "LISTEN",
|
|
16
|
+
"0B": "CLOSING",
|
|
17
|
+
};
|
|
18
|
+
/** The sockets in a /proc/net/tcp table whose local port is `port`. */
|
|
19
|
+
export function socketsOnPort(table, port) {
|
|
20
|
+
const found = [];
|
|
21
|
+
for (const line of table.split("\n").slice(1)) {
|
|
22
|
+
const fields = line.trim().split(/\s+/);
|
|
23
|
+
if (fields.length < 10)
|
|
24
|
+
continue;
|
|
25
|
+
const [, local, remote, state] = fields;
|
|
26
|
+
if (parseInt(local.slice(local.lastIndexOf(":") + 1), 16) !== port)
|
|
27
|
+
continue;
|
|
28
|
+
found.push({ remote, state: STATES[state] ?? state, inode: fields[9] });
|
|
29
|
+
}
|
|
30
|
+
return found;
|
|
31
|
+
}
|
|
32
|
+
function describePid(pid) {
|
|
33
|
+
let cmdline = "?";
|
|
34
|
+
let ppid = "?";
|
|
35
|
+
try {
|
|
36
|
+
cmdline = readFileSync(`/proc/${pid}/cmdline`, "utf8").split("\0").filter(Boolean).join(" ");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Exited since.
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
// The command name in parentheses may hold spaces; ppid is the second field after it.
|
|
43
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
44
|
+
ppid = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1];
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Exited since.
|
|
48
|
+
}
|
|
49
|
+
return `pid ${pid} (ppid ${ppid}): ${cmdline}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One line per socket on `port`: its state, remote end and owning process,
|
|
53
|
+
* or an empty list when it can't be told (not Linux, or nothing found).
|
|
54
|
+
*/
|
|
55
|
+
export function portHolders(port) {
|
|
56
|
+
const sockets = [];
|
|
57
|
+
for (const table of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
58
|
+
try {
|
|
59
|
+
sockets.push(...socketsOnPort(readFileSync(table, "utf8"), port));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// No /proc, or no IPv6.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (sockets.length === 0)
|
|
66
|
+
return [];
|
|
67
|
+
const owners = new Map();
|
|
68
|
+
const wanted = new Set(sockets.map((s) => `socket:[${s.inode}]`));
|
|
69
|
+
let pids = [];
|
|
70
|
+
try {
|
|
71
|
+
pids = readdirSync("/proc").filter((name) => /^\d+$/.test(name));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Leave owners unknown.
|
|
75
|
+
}
|
|
76
|
+
for (const pid of pids) {
|
|
77
|
+
let fds;
|
|
78
|
+
try {
|
|
79
|
+
fds = readdirSync(`/proc/${pid}/fd`);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
continue; // Another user's, or exited.
|
|
83
|
+
}
|
|
84
|
+
for (const fd of fds) {
|
|
85
|
+
let link;
|
|
86
|
+
try {
|
|
87
|
+
link = readlinkSync(`/proc/${pid}/fd/${fd}`);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!wanted.has(link))
|
|
93
|
+
continue;
|
|
94
|
+
const list = owners.get(link) ?? [];
|
|
95
|
+
if (!list.includes(pid))
|
|
96
|
+
list.push(pid);
|
|
97
|
+
owners.set(link, list);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return sockets.map((s) => {
|
|
101
|
+
const pidsOf = owners.get(`socket:[${s.inode}]`) ?? [];
|
|
102
|
+
const who = pidsOf.length > 0 ? pidsOf.map(describePid).join("; ") : s.inode === "0" ? "no process" : "owner not visible";
|
|
103
|
+
return `${s.state} to ${s.remote}, ${who}`;
|
|
104
|
+
});
|
|
105
|
+
}
|
package/dist/sourcemap.d.ts
CHANGED
|
@@ -9,8 +9,13 @@ export declare function generatedToOriginalLines(mappings: string): number[];
|
|
|
9
9
|
/** Both directions between the lines of a cell and of its script (0-based). */
|
|
10
10
|
export declare class LineMap {
|
|
11
11
|
#private;
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* `offset`: lines the script adds before esbuild's output (the prelude).
|
|
14
|
+
* `generated`: that output. A stripper that keeps lines blanks a
|
|
15
|
+
* type-only line in place and still maps it; with the output given, a
|
|
16
|
+
* blank line counts as unmapped, so a breakpoint there moves down (#239).
|
|
17
|
+
*/
|
|
18
|
+
constructor(mappings: string, offset?: number, generated?: string);
|
|
14
19
|
/** The cell line a script line came from: its own mapping, or the nearest one above. */
|
|
15
20
|
toOriginal(generated: number): number;
|
|
16
21
|
/**
|
package/dist/sourcemap.js
CHANGED
|
@@ -52,9 +52,15 @@ export function generatedToOriginalLines(mappings) {
|
|
|
52
52
|
export class LineMap {
|
|
53
53
|
#toOriginal;
|
|
54
54
|
#toGenerated = new Map();
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
/**
|
|
56
|
+
* `offset`: lines the script adds before esbuild's output (the prelude).
|
|
57
|
+
* `generated`: that output. A stripper that keeps lines blanks a
|
|
58
|
+
* type-only line in place and still maps it; with the output given, a
|
|
59
|
+
* blank line counts as unmapped, so a breakpoint there moves down (#239).
|
|
60
|
+
*/
|
|
61
|
+
constructor(mappings, offset = 0, generated) {
|
|
62
|
+
const blank = generated?.split("\n").map((line) => line.trim() === "");
|
|
63
|
+
const fromEsbuild = generatedToOriginalLines(mappings).map((original, line) => blank?.[line] ? -1 : original);
|
|
58
64
|
this.#toOriginal = [...Array(offset).fill(-1), ...fromEsbuild];
|
|
59
65
|
this.#toOriginal.forEach((original, generated) => {
|
|
60
66
|
if (original !== -1 && !this.#toGenerated.has(original))
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// The default type stripper: esbuild. Loaded by loadTypeStripper (see
|
|
2
|
+
// src/transform.ts) only when a kernel uses it, so a host that can't run
|
|
3
|
+
// esbuild's native binary never imports it (#238).
|
|
4
|
+
import { transformSync } from "esbuild";
|
|
5
|
+
import { CellSyntaxError } from "./transform.js";
|
|
6
|
+
function jsxSettings(jsx) {
|
|
7
|
+
if (jsx?.mode === "automatic") {
|
|
8
|
+
return { jsx: "automatic", jsxImportSource: jsx.importSource, jsxDev: jsx.development };
|
|
9
|
+
}
|
|
10
|
+
return { jsx: "transform", jsxFactory: jsx?.factory, jsxFragment: jsx?.fragment };
|
|
11
|
+
}
|
|
12
|
+
export const stripTypes = (code, options, sourcemap) => {
|
|
13
|
+
const compilerOptions = {
|
|
14
|
+
verbatimModuleSyntax: true,
|
|
15
|
+
...(options.experimentalDecorators ? { experimentalDecorators: true } : {}),
|
|
16
|
+
};
|
|
17
|
+
// Like Deno: prefer TSX, fall back to TS for `<T>x` assertions.
|
|
18
|
+
let firstError;
|
|
19
|
+
for (const loader of ["tsx", "ts"]) {
|
|
20
|
+
try {
|
|
21
|
+
const result = transformSync(code, {
|
|
22
|
+
loader,
|
|
23
|
+
target: "esnext",
|
|
24
|
+
// Neither acorn nor V8 parses standard (TC39) decorators or
|
|
25
|
+
// `accessor` yet, so esbuild lowers them (#142).
|
|
26
|
+
supported: { decorators: false },
|
|
27
|
+
...jsxSettings(options.jsx),
|
|
28
|
+
tsconfigRaw: { compilerOptions },
|
|
29
|
+
logLevel: "silent",
|
|
30
|
+
...(sourcemap ? { sourcemap: "external" } : {}),
|
|
31
|
+
});
|
|
32
|
+
const map = sourcemap ? JSON.parse(result.map) : null;
|
|
33
|
+
const mappings = typeof map?.mappings === "string"
|
|
34
|
+
? map.mappings
|
|
35
|
+
: "";
|
|
36
|
+
return { code: result.code, mappings };
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
firstError ??= err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
43
|
+
throw new CellSyntaxError(message.replace(/^Transform failed with \d+ errors?:\n/, ""));
|
|
44
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// A type stripper in plain JavaScript, for a kernel host that can't run
|
|
2
|
+
// esbuild's native binary or WebAssembly (#234). sucrase is an optional
|
|
3
|
+
// dependency, imported only when a kernel picks it (see loadTypeStripper).
|
|
4
|
+
//
|
|
5
|
+
// sucrase removes types without parsing them fully or lowering anything,
|
|
6
|
+
// and keeps every line where it was. What it leaves alone, a cell can't
|
|
7
|
+
// use: decorators and `accessor` fields (which acorn and V8 don't parse
|
|
8
|
+
// yet, #142) and namespaces with values (which it drops), so those are
|
|
9
|
+
// rejected here with a clear error.
|
|
10
|
+
import { parse } from "acorn";
|
|
11
|
+
import { transform } from "sucrase";
|
|
12
|
+
import { CellSyntaxError } from "./transform.js";
|
|
13
|
+
const FILE = "cell.tsx";
|
|
14
|
+
function jsxSettings(options) {
|
|
15
|
+
const jsx = options.jsx;
|
|
16
|
+
if (jsx?.mode === "automatic") {
|
|
17
|
+
return { jsxRuntime: "automatic", jsxImportSource: jsx.importSource, production: !jsx.development };
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
jsxRuntime: "classic",
|
|
21
|
+
jsxPragma: jsx?.factory ?? "React.createElement",
|
|
22
|
+
jsxFragmentPragma: jsx?.fragment ?? "React.Fragment",
|
|
23
|
+
production: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// A namespace that isn't `declare`d: sucrase drops it, values and all.
|
|
27
|
+
const NAMESPACE = /^[ \t]*(?:export\s+)?(?:namespace|module)\s+[\w$.]+\s*\{/m;
|
|
28
|
+
/** A clearer error than acorn's for syntax sucrase leaves in the output. */
|
|
29
|
+
function unsupported(js) {
|
|
30
|
+
if (!js.includes("@") && !js.includes("accessor"))
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
parse(js, { ecmaVersion: "latest", sourceType: "module", allowAwaitOutsideFunction: true });
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const pos = err.pos;
|
|
38
|
+
if (typeof pos !== "number")
|
|
39
|
+
return null;
|
|
40
|
+
if (js[pos] === "@")
|
|
41
|
+
return "decorators are not supported with the sucrase type stripper";
|
|
42
|
+
if (/\baccessor\s*$/.test(js.slice(0, pos)))
|
|
43
|
+
return "accessor fields are not supported with the sucrase type stripper";
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export const stripTypes = (code, options, sourcemap) => {
|
|
48
|
+
if (NAMESPACE.test(code)) {
|
|
49
|
+
throw new CellSyntaxError("namespaces are not supported with the sucrase type stripper; use declare namespace for types only");
|
|
50
|
+
}
|
|
51
|
+
// Like the esbuild stripper: prefer TSX, fall back to TS for `<T>x`
|
|
52
|
+
// assertions.
|
|
53
|
+
let firstError;
|
|
54
|
+
for (const transforms of [["typescript", "jsx"], ["typescript"]]) {
|
|
55
|
+
try {
|
|
56
|
+
const result = transform(code, {
|
|
57
|
+
transforms: [...transforms],
|
|
58
|
+
...jsxSettings(options),
|
|
59
|
+
// As verbatimModuleSyntax: only `import type` goes.
|
|
60
|
+
keepUnusedImports: true,
|
|
61
|
+
disableESTransforms: true,
|
|
62
|
+
filePath: FILE,
|
|
63
|
+
...(sourcemap ? { sourceMapOptions: { compiledFilename: FILE } } : {}),
|
|
64
|
+
});
|
|
65
|
+
const reason = unsupported(result.code);
|
|
66
|
+
if (reason !== null)
|
|
67
|
+
throw new CellSyntaxError(reason);
|
|
68
|
+
return { code: result.code, mappings: result.sourceMap?.mappings ?? "" };
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (err instanceof CellSyntaxError)
|
|
72
|
+
throw err;
|
|
73
|
+
firstError ??= err;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
77
|
+
throw new CellSyntaxError(message.replace(`Error transforming ${FILE}: `, ""));
|
|
78
|
+
};
|
package/dist/target.d.ts
CHANGED
|
@@ -44,6 +44,10 @@ export interface KernelTarget {
|
|
|
44
44
|
readonly debug?: DebugChannel;
|
|
45
45
|
/** Answers the target's pending prompt; `interrupted` makes it throw instead (#121). */
|
|
46
46
|
replyInput(value: string | null, interrupted?: boolean): void;
|
|
47
|
+
/** What the host waits on, for a kernel that has stopped answering (#237). */
|
|
48
|
+
diagnose?(): Promise<CdpParams>;
|
|
49
|
+
/** Lets go of the last cell's result and error, once they are shown (#286). */
|
|
50
|
+
releaseResults?(): Promise<void>;
|
|
47
51
|
kill(): void;
|
|
48
52
|
}
|
|
49
53
|
export interface LaunchOptions {
|
|
@@ -64,7 +68,7 @@ export declare function launchCommand(runtime: CdpRuntime, bootstrap: string, {
|
|
|
64
68
|
* install and in Deno's npm cache alike.
|
|
65
69
|
*/
|
|
66
70
|
export declare function esbuildBinary(resolve?: (specifier: string) => string, platform?: string, arch?: string): string | null;
|
|
67
|
-
/** Messages the bootstrap sends
|
|
71
|
+
/** Messages the bootstrap sends: message records on stdout, or Bun's channel. */
|
|
68
72
|
export type TargetMessage = {
|
|
69
73
|
kind: "iopub";
|
|
70
74
|
msg_type: string;
|
|
@@ -83,18 +87,30 @@ export declare function parseJson(text: string): unknown;
|
|
|
83
87
|
export declare function isTargetMessage(value: unknown): value is TargetMessage;
|
|
84
88
|
/**
|
|
85
89
|
* Splits one output pipe into text and barrier markers, and releases them
|
|
86
|
-
* in order. A gated marker (one that announces a
|
|
90
|
+
* in order. A gated marker (one that announces a message) pauses
|
|
87
91
|
* the pipe until `gate(n)` resolves, i.e. until that message has been
|
|
88
92
|
* delivered, so later output can't overtake it. A possible partial marker
|
|
89
93
|
* at the end of a chunk is held back until the next chunk decides it.
|
|
94
|
+
*
|
|
95
|
+
* With `onMessage`, the pipe also carries message records. A record stands
|
|
96
|
+
* where its gated marker would, and pauses the pipe the same way.
|
|
90
97
|
*/
|
|
91
98
|
export declare class PipeDemux {
|
|
92
99
|
#private;
|
|
93
100
|
readonly onText: (text: string) => void;
|
|
94
101
|
readonly gate: (n: number) => Promise<void>;
|
|
95
|
-
|
|
102
|
+
readonly onMessage: ((msg: TargetMessage) => void) | null;
|
|
103
|
+
constructor(onText: (text: string) => void, gate?: (n: number) => Promise<void>, onMessage?: ((msg: TargetMessage) => void) | null);
|
|
96
104
|
push(chunk: string): void;
|
|
97
105
|
waitFor(n: number): Promise<void>;
|
|
106
|
+
/** Where the pipe stands, for a state dump (#237). */
|
|
107
|
+
state(): {
|
|
108
|
+
reached: number;
|
|
109
|
+
waitingFor: number[];
|
|
110
|
+
gatedOn: number | null;
|
|
111
|
+
queued: number;
|
|
112
|
+
held: number;
|
|
113
|
+
};
|
|
98
114
|
/** Release everything (the process died). */
|
|
99
115
|
close(): void;
|
|
100
116
|
}
|
|
@@ -145,10 +161,18 @@ export declare class Target implements KernelTarget {
|
|
|
145
161
|
}): Promise<Target>;
|
|
146
162
|
/** The Debugger domain on this target's session, for src/debugger.ts. */
|
|
147
163
|
get debug(): DebugChannel;
|
|
164
|
+
/**
|
|
165
|
+
* What the host is waiting on, whether the runtime still evaluates, runs
|
|
166
|
+
* immediates and runs timers, and where its JavaScript is if it is
|
|
167
|
+
* running any, for a kernel that has stopped answering (#237). The probes
|
|
168
|
+
* go over the control session, which no cell holds.
|
|
169
|
+
*/
|
|
170
|
+
diagnose(): Promise<CdpParams>;
|
|
148
171
|
/** Resolves once all output and messages sent so far have been delivered. */
|
|
149
172
|
flush(): Promise<void>;
|
|
150
173
|
/** Runs transformed cell code. Rejects with Interrupted after interrupt(). */
|
|
151
174
|
evaluate(script: string): Promise<EvaluateResult>;
|
|
175
|
+
releaseResults(): Promise<void>;
|
|
152
176
|
/**
|
|
153
177
|
* Deno calls IsolateHandle::terminate_execution from another thread. The
|
|
154
178
|
* CDP equivalent is Runtime.terminateExecution, sent on a second session
|