@kodycodes/cli 1.23.0 → 1.23.2
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 -1
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +38 -6
- package/dist/cli.js.map +1 -1
- package/dist/dev/dev-bundler.d.ts +25 -0
- package/dist/dev/dev-bundler.js +51 -0
- package/dist/dev/dev-bundler.js.map +1 -0
- package/dist/dev/dev-events.d.ts +50 -0
- package/dist/dev/dev-events.js +81 -0
- package/dist/dev/dev-events.js.map +1 -0
- package/dist/dev/dev-protocol.d.ts +13 -0
- package/dist/dev/dev-protocol.js +14 -0
- package/dist/dev/dev-protocol.js.map +1 -0
- package/dist/dev/dev-watcher.d.ts +20 -0
- package/dist/dev/dev-watcher.js +108 -0
- package/dist/dev/dev-watcher.js.map +1 -0
- package/dist/dev/error-page.d.ts +21 -0
- package/dist/dev/error-page.js +64 -0
- package/dist/dev/error-page.js.map +1 -0
- package/dist/dev/process-reaper.d.ts +40 -0
- package/dist/dev/process-reaper.js +144 -0
- package/dist/dev/process-reaper.js.map +1 -0
- package/dist/dev/stack-trace.d.ts +11 -0
- package/dist/dev/stack-trace.js +56 -0
- package/dist/dev/stack-trace.js.map +1 -0
- package/dist/dev/workerd-process.d.ts +28 -0
- package/dist/dev/workerd-process.js +118 -0
- package/dist/dev/workerd-process.js.map +1 -0
- package/dist/dev-command.js +17 -4
- package/dist/dev-command.js.map +1 -1
- package/dist/help.js +8 -2
- package/dist/help.js.map +1 -1
- package/dist/local-bundle.d.ts +43 -3
- package/dist/local-bundle.js +94 -33
- package/dist/local-bundle.js.map +1 -1
- package/dist/package-app-dev.d.ts +16 -3
- package/dist/package-app-dev.js +389 -192
- package/dist/package-app-dev.js.map +1 -1
- package/dist/package-local-runtime-source.d.ts +8 -0
- package/dist/package-local-runtime-source.js +69 -5
- package/dist/package-local-runtime-source.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File watching for `kody dev`. chokidar watches the package tree (it re-arms
|
|
3
|
+
* per-directory watchers when editors or `sed -i` / `cp` replace a file with a
|
|
4
|
+
* new inode). A low-frequency stat poll over the files in the current bundle
|
|
5
|
+
* graph backs it up, so a missed event can never leave a stale bundle serving.
|
|
6
|
+
*/
|
|
7
|
+
export type DevWatcher = {
|
|
8
|
+
ready: Promise<void>;
|
|
9
|
+
/** Replace the set of absolute paths the stat poll keeps an eye on. */
|
|
10
|
+
track: (paths: ReadonlyArray<string>) => void;
|
|
11
|
+
close: () => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export declare function createDevWatcher(input: {
|
|
14
|
+
root: string;
|
|
15
|
+
onChange: (path: string) => void;
|
|
16
|
+
/** Extra root-relative path prefixes to ignore. */
|
|
17
|
+
ignore?: ReadonlyArray<string>;
|
|
18
|
+
pollIntervalMs?: number;
|
|
19
|
+
}): DevWatcher;
|
|
20
|
+
export declare function shouldIgnoreWatchPath(relativePath: string, ignorePrefixes?: ReadonlyArray<string>): boolean;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File watching for `kody dev`. chokidar watches the package tree (it re-arms
|
|
3
|
+
* per-directory watchers when editors or `sed -i` / `cp` replace a file with a
|
|
4
|
+
* new inode). A low-frequency stat poll over the files in the current bundle
|
|
5
|
+
* graph backs it up, so a missed event can never leave a stale bundle serving.
|
|
6
|
+
*/
|
|
7
|
+
import { watch } from 'chokidar';
|
|
8
|
+
import { stat } from 'node:fs/promises';
|
|
9
|
+
import { relative, sep } from 'node:path';
|
|
10
|
+
const defaultPollIntervalMs = 1000;
|
|
11
|
+
export function createDevWatcher(input) {
|
|
12
|
+
const ignorePrefixes = (input.ignore ?? []).map(normalizePrefix).filter(Boolean);
|
|
13
|
+
const ignored = (path) => shouldIgnoreWatchPath(relative(input.root, path), ignorePrefixes);
|
|
14
|
+
const watcher = watch(input.root, {
|
|
15
|
+
ignoreInitial: true,
|
|
16
|
+
ignored,
|
|
17
|
+
atomic: true,
|
|
18
|
+
});
|
|
19
|
+
watcher.on('all', (_event, path) => {
|
|
20
|
+
if (!ignored(path))
|
|
21
|
+
input.onChange(path);
|
|
22
|
+
});
|
|
23
|
+
// Init errors reject `ready` (awaited by start). Later errors are swallowed
|
|
24
|
+
// so a transient watch glitch cannot crash the process.
|
|
25
|
+
let readySettled = false;
|
|
26
|
+
const ready = new Promise((resolve, reject) => {
|
|
27
|
+
watcher.once('ready', () => {
|
|
28
|
+
readySettled = true;
|
|
29
|
+
resolve();
|
|
30
|
+
});
|
|
31
|
+
watcher.on('error', (error) => {
|
|
32
|
+
if (readySettled)
|
|
33
|
+
return;
|
|
34
|
+
readySettled = true;
|
|
35
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
let tracked = new Map();
|
|
39
|
+
let polling = false;
|
|
40
|
+
const poll = async () => {
|
|
41
|
+
if (polling)
|
|
42
|
+
return;
|
|
43
|
+
polling = true;
|
|
44
|
+
try {
|
|
45
|
+
for (const [path, previous] of tracked) {
|
|
46
|
+
const next = await fingerprint(path);
|
|
47
|
+
if (!tracked.has(path))
|
|
48
|
+
continue;
|
|
49
|
+
if (previous !== null && next !== previous) {
|
|
50
|
+
tracked.set(path, next);
|
|
51
|
+
input.onChange(path);
|
|
52
|
+
}
|
|
53
|
+
else if (previous === null) {
|
|
54
|
+
tracked.set(path, next);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
polling = false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const timer = setInterval(() => void poll(), input.pollIntervalMs ?? defaultPollIntervalMs);
|
|
63
|
+
timer.unref();
|
|
64
|
+
return {
|
|
65
|
+
ready,
|
|
66
|
+
track: (paths) => {
|
|
67
|
+
const next = new Map();
|
|
68
|
+
for (const path of paths) {
|
|
69
|
+
if (ignored(path))
|
|
70
|
+
continue;
|
|
71
|
+
next.set(path, tracked.get(path) ?? null);
|
|
72
|
+
}
|
|
73
|
+
tracked = next;
|
|
74
|
+
void poll();
|
|
75
|
+
},
|
|
76
|
+
close: async () => {
|
|
77
|
+
clearInterval(timer);
|
|
78
|
+
tracked = new Map();
|
|
79
|
+
await watcher.close();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function fingerprint(path) {
|
|
84
|
+
try {
|
|
85
|
+
const info = await stat(path);
|
|
86
|
+
return `${info.ino}:${info.size}:${info.mtimeMs}`;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return 'missing';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function normalizePrefix(prefix) {
|
|
93
|
+
return prefix.replace(/^\.\//, '').replace(/\/+$/, '').split('/').join(sep);
|
|
94
|
+
}
|
|
95
|
+
export function shouldIgnoreWatchPath(relativePath, ignorePrefixes = []) {
|
|
96
|
+
if (!relativePath || relativePath === '.')
|
|
97
|
+
return false;
|
|
98
|
+
if (relativePath.startsWith('..'))
|
|
99
|
+
return true;
|
|
100
|
+
const segments = relativePath.split(sep);
|
|
101
|
+
if (segments.some((segment) => segment === 'node_modules' || segment.startsWith('.'))) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (relativePath.endsWith('.map'))
|
|
105
|
+
return true;
|
|
106
|
+
return ignorePrefixes.some((prefix) => relativePath === prefix || relativePath.startsWith(`${prefix}${sep}`));
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=dev-watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-watcher.js","sourceRoot":"","sources":["../../src/dev/dev-watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only error responses served by the `kody dev` proxy for build failures
|
|
3
|
+
* and uncaught handler errors. HTML for browsers, plain text for everyone else
|
|
4
|
+
* (curl, agents, `kody dev fetch`).
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
7
|
+
export type DevErrorKind = 'build' | 'runtime';
|
|
8
|
+
export type DevErrorView = {
|
|
9
|
+
kind: DevErrorKind;
|
|
10
|
+
title: string;
|
|
11
|
+
/** Preformatted body: diagnostics with code frames, or a mapped stack. */
|
|
12
|
+
detail: string;
|
|
13
|
+
/** `METHOD /path` for runtime errors. */
|
|
14
|
+
request?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function wantsHtml(req: IncomingMessage): boolean;
|
|
17
|
+
export declare function sendDevError(req: IncomingMessage, res: ServerResponse, status: number, view: DevErrorView, options?: {
|
|
18
|
+
htmlTail?: string;
|
|
19
|
+
}): void;
|
|
20
|
+
export declare function renderDevErrorPage(view: DevErrorView): string;
|
|
21
|
+
export declare function escapeHtml(value: string): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only error responses served by the `kody dev` proxy for build failures
|
|
3
|
+
* and uncaught handler errors. HTML for browsers, plain text for everyone else
|
|
4
|
+
* (curl, agents, `kody dev fetch`).
|
|
5
|
+
*/
|
|
6
|
+
export function wantsHtml(req) {
|
|
7
|
+
const accept = req.headers.accept ?? '';
|
|
8
|
+
return /\btext\/html\b/.test(accept);
|
|
9
|
+
}
|
|
10
|
+
export function sendDevError(req, res, status, view, options = {}) {
|
|
11
|
+
if (res.headersSent) {
|
|
12
|
+
res.end();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const headers = {
|
|
16
|
+
'cache-control': 'no-store',
|
|
17
|
+
'x-kody-dev-error': view.kind,
|
|
18
|
+
};
|
|
19
|
+
if (wantsHtml(req)) {
|
|
20
|
+
res.writeHead(status, { ...headers, 'content-type': 'text/html; charset=utf-8' });
|
|
21
|
+
res.end(renderDevErrorPage(view) + (options.htmlTail ?? ''));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
res.writeHead(status, { ...headers, 'content-type': 'text/plain; charset=utf-8' });
|
|
25
|
+
res.end(`kody dev ${view.kind} error${view.request ? ` (${view.request})` : ''}\n\n${view.detail}\n`);
|
|
26
|
+
}
|
|
27
|
+
export function renderDevErrorPage(view) {
|
|
28
|
+
const label = view.kind === 'build' ? 'Build error' : 'Runtime error';
|
|
29
|
+
return `<!doctype html>
|
|
30
|
+
<html lang="en">
|
|
31
|
+
<head>
|
|
32
|
+
<meta charset="utf-8">
|
|
33
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
34
|
+
<title>${escapeHtml(label)} · kody dev</title>
|
|
35
|
+
<style>
|
|
36
|
+
:root { color-scheme: dark; }
|
|
37
|
+
body { margin: 0; font: 14px/1.5 ui-sans-serif, system-ui, sans-serif; background: #1a1015; color: #f5e9ee; }
|
|
38
|
+
main { max-width: 960px; margin: 0 auto; padding: 32px 24px; }
|
|
39
|
+
.label { display: inline-block; font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: #ff8fab; }
|
|
40
|
+
h1 { font-size: 20px; margin: 8px 0 20px; word-break: break-word; }
|
|
41
|
+
pre { margin: 0; padding: 16px; overflow: auto; background: #0e080b; border: 1px solid #3a2530; border-radius: 8px; font: 13px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre; }
|
|
42
|
+
p { color: #bfa9b3; font-size: 13px; }
|
|
43
|
+
</style>
|
|
44
|
+
</head>
|
|
45
|
+
<body>
|
|
46
|
+
<main>
|
|
47
|
+
<span class="label">${escapeHtml(label)}</span>
|
|
48
|
+
<h1>${escapeHtml(view.title)}</h1>${view.request ? `\n<p>${escapeHtml(view.request)}</p>` : ''}
|
|
49
|
+
<pre>${escapeHtml(view.detail)}</pre>
|
|
50
|
+
<p>Served by <code>kody dev</code> only; published apps never see this page.</p>
|
|
51
|
+
</main>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
export function escapeHtml(value) {
|
|
57
|
+
return value
|
|
58
|
+
.replace(/&/g, '&')
|
|
59
|
+
.replace(/</g, '<')
|
|
60
|
+
.replace(/>/g, '>')
|
|
61
|
+
.replace(/"/g, '"')
|
|
62
|
+
.replace(/'/g, ''');
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=error-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-page.js","sourceRoot":"","sources":["../../src/dev/error-page.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,MAAM,UAAU,SAAS,CAAC,GAAoB;IAC7C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAA;IACvC,OAAO,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAC3B,GAAoB,EACpB,GAAmB,EACnB,MAAc,EACd,IAAkB,EAClB,UAAiC,EAAE;IAEnC,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;QACrB,GAAG,CAAC,GAAG,EAAE,CAAA;QACT,OAAM;IACP,CAAC;IACD,MAAM,OAAO,GAAG;QACf,eAAe,EAAE,UAAU;QAC3B,kBAAkB,EAAE,IAAI,CAAC,IAAI;KAC7B,CAAA;IACD,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;QACpB,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAA;QACjF,GAAG,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAA;QAC5D,OAAM;IACP,CAAC;IACD,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,2BAA2B,EAAE,CAAC,CAAA;IAClF,GAAG,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,CAAA;AACtG,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAkB;IACpD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe,CAAA;IACrE,OAAO;;;;;SAKC,UAAU,CAAC,KAAK,CAAC;;;;;;;;;;;;;sBAaJ,UAAU,CAAC,KAAK,CAAC;MACjC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;OACvF,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;;;;;CAK7B,CAAA;AACD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,OAAO,KAAK;SACV,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AACzB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps `kody dev` from leaking workerd processes and `/tmp/kody-dev-*` dirs.
|
|
3
|
+
*
|
|
4
|
+
* - A tiny detached `sh` reaper holds a pipe from this process. It learns
|
|
5
|
+
* workerd pids over that pipe and, when the pipe closes for any reason
|
|
6
|
+
* (including SIGKILL of the CLI), kills those pids and removes the work dir.
|
|
7
|
+
* - On startup, {@link sweepStaleDevDirs} removes dirs and workerd processes
|
|
8
|
+
* left behind by dead CLI processes (including CLIs that predate the reaper).
|
|
9
|
+
*/
|
|
10
|
+
export declare const devWorkDirPrefix = "kody-dev-";
|
|
11
|
+
export type ProcessReaper = {
|
|
12
|
+
track: (pid: number) => void;
|
|
13
|
+
untrack: (pid: number) => void;
|
|
14
|
+
close: () => void;
|
|
15
|
+
};
|
|
16
|
+
export declare function startProcessReaper(input: {
|
|
17
|
+
workDir: string;
|
|
18
|
+
}): Promise<ProcessReaper>;
|
|
19
|
+
type ListedProcess = {
|
|
20
|
+
pid: number;
|
|
21
|
+
ppid?: number;
|
|
22
|
+
name: string;
|
|
23
|
+
cmd?: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Remove `kody-dev-*` work dirs whose owner is gone and kill workerd processes
|
|
27
|
+
* still serving from them. Best effort; never throws.
|
|
28
|
+
*/
|
|
29
|
+
export declare function sweepStaleDevDirs(input?: {
|
|
30
|
+
tmp?: string;
|
|
31
|
+
now?: number;
|
|
32
|
+
listProcesses?: () => Promise<Array<ListedProcess>>;
|
|
33
|
+
isAlive?: (pid: number) => boolean;
|
|
34
|
+
kill?: (pid: number) => void;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
removed: Array<string>;
|
|
37
|
+
killed: Array<number>;
|
|
38
|
+
}>;
|
|
39
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps `kody dev` from leaking workerd processes and `/tmp/kody-dev-*` dirs.
|
|
3
|
+
*
|
|
4
|
+
* - A tiny detached `sh` reaper holds a pipe from this process. It learns
|
|
5
|
+
* workerd pids over that pipe and, when the pipe closes for any reason
|
|
6
|
+
* (including SIGKILL of the CLI), kills those pids and removes the work dir.
|
|
7
|
+
* - On startup, {@link sweepStaleDevDirs} removes dirs and workerd processes
|
|
8
|
+
* left behind by dead CLI processes (including CLIs that predate the reaper).
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import psList from 'ps-list';
|
|
15
|
+
export const devWorkDirPrefix = 'kody-dev-';
|
|
16
|
+
const ownerFileName = 'owner.json';
|
|
17
|
+
/** Legacy dirs without an owner file are only removed once this old. */
|
|
18
|
+
const legacyDirMinAgeMs = 10 * 60_000;
|
|
19
|
+
const reaperScript = `
|
|
20
|
+
pids=" "
|
|
21
|
+
while IFS= read -r line; do
|
|
22
|
+
case "$line" in
|
|
23
|
+
+*) pids="$pids\${line#+} " ;;
|
|
24
|
+
-*) pids="$(printf '%s' "$pids" | sed "s/ \${line#-} / /g")" ;;
|
|
25
|
+
esac
|
|
26
|
+
done
|
|
27
|
+
for pid in $pids; do kill -9 "$pid" 2>/dev/null; done
|
|
28
|
+
rm -rf "$1"
|
|
29
|
+
`;
|
|
30
|
+
export async function startProcessReaper(input) {
|
|
31
|
+
await writeFile(join(input.workDir, ownerFileName), JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }));
|
|
32
|
+
let reaper;
|
|
33
|
+
try {
|
|
34
|
+
reaper = spawn('/bin/sh', ['-c', reaperScript, 'kody-dev-reaper', input.workDir], {
|
|
35
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
36
|
+
detached: true,
|
|
37
|
+
});
|
|
38
|
+
reaper.on('error', () => {
|
|
39
|
+
reaper = undefined;
|
|
40
|
+
});
|
|
41
|
+
reaper.stdin?.on('error', () => { });
|
|
42
|
+
reaper.unref();
|
|
43
|
+
reaper.stdin?.unref?.();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
reaper = undefined;
|
|
47
|
+
}
|
|
48
|
+
const send = (line) => {
|
|
49
|
+
if (reaper?.stdin?.writable)
|
|
50
|
+
reaper.stdin.write(`${line}\n`);
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
track: (pid) => send(`+${pid}`),
|
|
54
|
+
untrack: (pid) => send(`-${pid}`),
|
|
55
|
+
close: () => {
|
|
56
|
+
reaper?.stdin?.end();
|
|
57
|
+
reaper = undefined;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Remove `kody-dev-*` work dirs whose owner is gone and kill workerd processes
|
|
63
|
+
* still serving from them. Best effort; never throws.
|
|
64
|
+
*/
|
|
65
|
+
export async function sweepStaleDevDirs(input = {}) {
|
|
66
|
+
const tmp = input.tmp ?? tmpdir();
|
|
67
|
+
const now = input.now ?? Date.now();
|
|
68
|
+
const isAlive = input.isAlive ?? isProcessAlive;
|
|
69
|
+
const kill = input.kill ?? ((pid) => process.kill(pid, 'SIGKILL'));
|
|
70
|
+
const removed = [];
|
|
71
|
+
const killed = [];
|
|
72
|
+
let entries;
|
|
73
|
+
try {
|
|
74
|
+
entries = (await readdir(tmp)).filter((name) => name.startsWith(devWorkDirPrefix));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { removed, killed };
|
|
78
|
+
}
|
|
79
|
+
if (entries.length === 0)
|
|
80
|
+
return { removed, killed };
|
|
81
|
+
let processes = [];
|
|
82
|
+
try {
|
|
83
|
+
processes = await (input.listProcesses ?? psList)();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
processes = [];
|
|
87
|
+
}
|
|
88
|
+
const byPid = new Map(processes.map((item) => [item.pid, item]));
|
|
89
|
+
for (const name of entries) {
|
|
90
|
+
const dir = join(tmp, name);
|
|
91
|
+
try {
|
|
92
|
+
const owner = await readOwnerPid(dir);
|
|
93
|
+
if (owner !== null && isAlive(owner))
|
|
94
|
+
continue;
|
|
95
|
+
const workerds = processes.filter((item) => /workerd/.test(item.name) && (item.cmd ?? '').includes(`${dir}/`));
|
|
96
|
+
if (owner === null) {
|
|
97
|
+
const info = await stat(dir);
|
|
98
|
+
if (now - info.mtimeMs < legacyDirMinAgeMs)
|
|
99
|
+
continue;
|
|
100
|
+
// Legacy CLI without an owner file: only reap workerds whose parent died.
|
|
101
|
+
if (workerds.some((item) => !isOrphaned(item, byPid, isAlive)))
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
for (const item of workerds) {
|
|
105
|
+
try {
|
|
106
|
+
kill(item.pid);
|
|
107
|
+
killed.push(item.pid);
|
|
108
|
+
}
|
|
109
|
+
catch { }
|
|
110
|
+
}
|
|
111
|
+
await rm(dir, { recursive: true, force: true });
|
|
112
|
+
removed.push(dir);
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
}
|
|
116
|
+
return { removed, killed };
|
|
117
|
+
}
|
|
118
|
+
function isOrphaned(item, byPid, isAlive) {
|
|
119
|
+
if (item.ppid === undefined)
|
|
120
|
+
return false;
|
|
121
|
+
if (item.ppid <= 1 || !isAlive(item.ppid))
|
|
122
|
+
return true;
|
|
123
|
+
const parent = byPid.get(item.ppid);
|
|
124
|
+
return parent !== undefined && !/node|kody/i.test(`${parent.name} ${parent.cmd ?? ''}`);
|
|
125
|
+
}
|
|
126
|
+
async function readOwnerPid(dir) {
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(await readFile(join(dir, ownerFileName), 'utf8'));
|
|
129
|
+
return typeof parsed.pid === 'number' ? parsed.pid : null;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export function isProcessAlive(pid) {
|
|
136
|
+
try {
|
|
137
|
+
process.kill(pid, 0);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return error.code === 'EPERM';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=process-reaper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-reaper.js","sourceRoot":"","sources":["../../src/dev/process-reaper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn a workerd V8 stack (frames point into the bundled `main.js`) into one
|
|
3
|
+
* that points at the author's `.ts` sources, without runtime wrapper frames.
|
|
4
|
+
*/
|
|
5
|
+
export type StackMapper = (stack: string) => string;
|
|
6
|
+
export declare function createStackMapper(input: {
|
|
7
|
+
/** esbuild source map JSON for the user module, sources relative to the package root. */
|
|
8
|
+
sourceMap: string | null;
|
|
9
|
+
/** Module name of the bundled user code inside workerd. */
|
|
10
|
+
userModule: string;
|
|
11
|
+
}): StackMapper;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn a workerd V8 stack (frames point into the bundled `main.js`) into one
|
|
3
|
+
* that points at the author's `.ts` sources, without runtime wrapper frames.
|
|
4
|
+
*/
|
|
5
|
+
import { originalPositionFor, TraceMap } from '@jridgewell/trace-mapping';
|
|
6
|
+
const internalModules = new Set([
|
|
7
|
+
'__kody_local_entry.js',
|
|
8
|
+
'kody:runtime',
|
|
9
|
+
'.__kody_runtime__.js',
|
|
10
|
+
'runtime.js',
|
|
11
|
+
'entry.js',
|
|
12
|
+
]);
|
|
13
|
+
const framePattern = /^(\s*at\s+)(?:(.*?)\s+\()?([^()\s]+?):(\d+):(\d+)\)?\s*$/;
|
|
14
|
+
export function createStackMapper(input) {
|
|
15
|
+
let map = null;
|
|
16
|
+
if (input.sourceMap) {
|
|
17
|
+
try {
|
|
18
|
+
map = new TraceMap(input.sourceMap);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
map = null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return (stack) => {
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const line of stack.split('\n')) {
|
|
27
|
+
if (!/^\s*at\s/.test(line)) {
|
|
28
|
+
out.push(line);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const match = framePattern.exec(line);
|
|
32
|
+
if (!match)
|
|
33
|
+
continue;
|
|
34
|
+
const [, lead, fn, file, lineText, colText] = match;
|
|
35
|
+
const module = file.replace(/^file:\/\/\//, '').replace(/^\//, '');
|
|
36
|
+
if (internalModules.has(module))
|
|
37
|
+
continue;
|
|
38
|
+
const fnPart = fn ? `${fn} (` : '';
|
|
39
|
+
const close = fn ? ')' : '';
|
|
40
|
+
if (map && module === input.userModule) {
|
|
41
|
+
const original = originalPositionFor(map, {
|
|
42
|
+
line: Number(lineText),
|
|
43
|
+
column: Number(colText) - 1,
|
|
44
|
+
});
|
|
45
|
+
if (original.source && original.line !== null) {
|
|
46
|
+
const name = fn ?? original.name;
|
|
47
|
+
out.push(`${lead}${name ? `${name} (` : ''}${original.source}:${original.line}:${(original.column ?? 0) + 1}${name ? ')' : ''}`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
out.push(`${lead}${fnPart}${file}:${lineText}:${colText}${close}`);
|
|
52
|
+
}
|
|
53
|
+
return out.join('\n');
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=stack-trace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stack-trace.js","sourceRoot":"","sources":["../../src/dev/stack-trace.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAEzE,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC/B,uBAAuB;IACvB,cAAc;IACd,sBAAsB;IACtB,YAAY;IACZ,UAAU;CACV,CAAC,CAAA;AAEF,MAAM,YAAY,GAAG,0DAA0D,CAAA;AAI/E,MAAM,UAAU,iBAAiB,CAAC,KAKjC;IACA,IAAI,GAAG,GAAoB,IAAI,CAAA;IAC/B,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACrB,IAAI,CAAC;YACJ,GAAG,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QACpC,CAAC;QAAC,MAAM,CAAC;YACR,GAAG,GAAG,IAAI,CAAA;QACX,CAAC;IACF,CAAC;IACD,OAAO,CAAC,KAAK,EAAE,EAAE;QAChB,MAAM,GAAG,GAAkB,EAAE,CAAA;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5B,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACd,SAAQ;YACT,CAAC;YACD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACrC,IAAI,CAAC,KAAK;gBAAE,SAAQ;YACpB,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,KAAK,CAAA;YACnD,MAAM,MAAM,GAAG,IAAK,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;YACnE,IAAI,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAQ;YACzC,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;YAClC,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;YAC3B,IAAI,GAAG,IAAI,MAAM,KAAK,KAAK,CAAC,UAAU,EAAE,CAAC;gBACxC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,EAAE;oBACzC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC;oBACtB,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;iBAC3B,CAAC,CAAA;gBACF,IAAI,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;oBAC/C,MAAM,IAAI,GAAG,EAAE,IAAI,QAAQ,CAAC,IAAI,CAAA;oBAChC,GAAG,CAAC,IAAI,CACP,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACtH,CAAA;oBACD,SAAQ;gBACT,CAAC;YACF,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,IAAI,QAAQ,IAAI,OAAO,GAAG,KAAK,EAAE,CAAC,CAAA;QACnE,CAAC;QACD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One workerd `serve` process for `kody dev`: spawn, wait for the control-fd
|
|
3
|
+
* `listen` event, stream stdout/stderr line by line, and stop it.
|
|
4
|
+
*/
|
|
5
|
+
export type WorkerdHandle = {
|
|
6
|
+
pid: number;
|
|
7
|
+
port: number;
|
|
8
|
+
/** Resolves with a short exit description once the process is gone. */
|
|
9
|
+
exited: Promise<string>;
|
|
10
|
+
kill: () => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
export declare function startWorkerd(input: {
|
|
13
|
+
workerdPath: string;
|
|
14
|
+
configPath: string;
|
|
15
|
+
cwd: string;
|
|
16
|
+
env: NodeJS.ProcessEnv;
|
|
17
|
+
/** Called synchronously after spawn so callers can track the pid before listen. */
|
|
18
|
+
onSpawn?: (pid: number) => void;
|
|
19
|
+
onStdoutLine: (line: string) => void;
|
|
20
|
+
onStderrLine: (line: string) => void;
|
|
21
|
+
}): Promise<WorkerdHandle>;
|
|
22
|
+
export declare function parseListenPort(line: string): number | null;
|
|
23
|
+
/**
|
|
24
|
+
* Make workerd stderr readable: drop native `stack: …workerd@0x…` address
|
|
25
|
+
* lines and the C++ source prefix on uncaught-exception lines. Returns null
|
|
26
|
+
* for lines that carry nothing for a JS developer.
|
|
27
|
+
*/
|
|
28
|
+
export declare function cleanWorkerdStderrLine(line: string): string | null;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One workerd `serve` process for `kody dev`: spawn, wait for the control-fd
|
|
3
|
+
* `listen` event, stream stdout/stderr line by line, and stop it.
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
7
|
+
import { redact } from '../redact.js';
|
|
8
|
+
const workerdStartTimeoutMs = 30_000;
|
|
9
|
+
export async function startWorkerd(input) {
|
|
10
|
+
const child = spawn(input.workerdPath, ['serve', input.configPath, '--control-fd=3'], {
|
|
11
|
+
cwd: input.cwd,
|
|
12
|
+
env: input.env,
|
|
13
|
+
stdio: ['ignore', 'pipe', 'pipe', 'pipe'],
|
|
14
|
+
});
|
|
15
|
+
if (child.pid !== undefined)
|
|
16
|
+
input.onSpawn?.(child.pid);
|
|
17
|
+
let startupStderr = '';
|
|
18
|
+
let listening = false;
|
|
19
|
+
forEachLine(child.stdout, input.onStdoutLine);
|
|
20
|
+
forEachLine(child.stderr, (line) => {
|
|
21
|
+
if (!listening)
|
|
22
|
+
startupStderr += `${line}\n`;
|
|
23
|
+
input.onStderrLine(line);
|
|
24
|
+
});
|
|
25
|
+
const exited = new Promise((resolve) => {
|
|
26
|
+
child.once('exit', (code, signal) => resolve(`workerd exit ${signal ?? code}`));
|
|
27
|
+
child.once('error', (error) => resolve(`workerd error: ${error.message}`));
|
|
28
|
+
});
|
|
29
|
+
const port = await waitForListen(child, exited, () => {
|
|
30
|
+
const trimmed = redact(startupStderr.trim());
|
|
31
|
+
return trimmed ? `\n${trimmed}` : '';
|
|
32
|
+
});
|
|
33
|
+
listening = true;
|
|
34
|
+
return {
|
|
35
|
+
pid: child.pid ?? -1,
|
|
36
|
+
port,
|
|
37
|
+
exited,
|
|
38
|
+
kill: async () => {
|
|
39
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
40
|
+
return;
|
|
41
|
+
child.kill('SIGKILL');
|
|
42
|
+
await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 1000))]);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function forEachLine(stream, onLine) {
|
|
47
|
+
if (!stream)
|
|
48
|
+
return;
|
|
49
|
+
const lines = createInterface({ input: stream, crlfDelay: Infinity });
|
|
50
|
+
lines.on('line', (line) => {
|
|
51
|
+
if (line.length > 0)
|
|
52
|
+
onLine(line);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function waitForListen(child, exited, stderr) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const control = child.stdio[3];
|
|
58
|
+
let buffered = '';
|
|
59
|
+
let settled = false;
|
|
60
|
+
const settle = (fn) => {
|
|
61
|
+
if (settled)
|
|
62
|
+
return;
|
|
63
|
+
settled = true;
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
fn();
|
|
66
|
+
};
|
|
67
|
+
const timer = setTimeout(() => {
|
|
68
|
+
settle(() => {
|
|
69
|
+
child.kill('SIGKILL');
|
|
70
|
+
reject(new Error(`workerd did not start within ${workerdStartTimeoutMs / 1000}s.${stderr()}`));
|
|
71
|
+
});
|
|
72
|
+
}, workerdStartTimeoutMs);
|
|
73
|
+
control?.on('data', (chunk) => {
|
|
74
|
+
buffered += chunk.toString('utf8');
|
|
75
|
+
const lines = buffered.split('\n');
|
|
76
|
+
buffered = lines.pop() ?? '';
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
const port = parseListenPort(line);
|
|
79
|
+
if (port !== null)
|
|
80
|
+
settle(() => resolve(port));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
void exited.then((detail) => {
|
|
84
|
+
settle(() => reject(new Error(`workerd failed to start (${detail})${stderr()}`)));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export function parseListenPort(line) {
|
|
89
|
+
if (!line.trim())
|
|
90
|
+
return null;
|
|
91
|
+
try {
|
|
92
|
+
const event = JSON.parse(line);
|
|
93
|
+
return event.event === 'listen' && event.socket === 'http' && typeof event.port === 'number'
|
|
94
|
+
? event.port
|
|
95
|
+
: null;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Make workerd stderr readable: drop native `stack: …workerd@0x…` address
|
|
103
|
+
* lines and the C++ source prefix on uncaught-exception lines. Returns null
|
|
104
|
+
* for lines that carry nothing for a JS developer.
|
|
105
|
+
*/
|
|
106
|
+
export function cleanWorkerdStderrLine(line) {
|
|
107
|
+
const trimmed = line.trim();
|
|
108
|
+
if (!trimmed)
|
|
109
|
+
return null;
|
|
110
|
+
if (/^stack:(\s+\S+@[0-9a-fx]+)+\s*$/i.test(trimmed))
|
|
111
|
+
return null;
|
|
112
|
+
const prefixed = /^workerd\/\S+:\d+: (?:error|warning|info|debug): (.*)$/.exec(trimmed);
|
|
113
|
+
const body = prefixed ? prefixed[1] : trimmed;
|
|
114
|
+
return body
|
|
115
|
+
.replace(/\S+\.(?:c\+\+|h):\d+: failed: /g, '')
|
|
116
|
+
.replace(/(?:remote\.)?jsg\.(\w*Error)(?=:)/g, '$1');
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=workerd-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workerd-process.js","sourceRoot":"","sources":["../../src/dev/workerd-process.ts"],"names":[],"mappings":"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"}
|