@kodycodes/cli 1.23.1 → 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 +26 -0
- 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 +5 -0
- 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
package/README.md
CHANGED
|
@@ -240,6 +240,32 @@ kody execute --local --invoke kody:@scope/pkg/export --params '{}'
|
|
|
240
240
|
kody search --target mock local
|
|
241
241
|
```
|
|
242
242
|
|
|
243
|
+
### What `kody dev` does while it runs
|
|
244
|
+
|
|
245
|
+
- **Rebuilds never drop requests.** A save starts a new workerd, and the proxy
|
|
246
|
+
switches to it only once it is listening. The old one finishes its in-flight
|
|
247
|
+
requests and then stops. Requests that arrive mid-rebuild wait for it, so you
|
|
248
|
+
never get a `503` or the previous bundle after a save.
|
|
249
|
+
- **Watching survives editors and scripts.** chokidar watches the package
|
|
250
|
+
(skipping `node_modules`, dotfiles, and `*.map`). A 1s stat poll over the
|
|
251
|
+
files in the current bundle graph backs it up, so replaced files (`sed -i`,
|
|
252
|
+
`cp`, atomic saves) are always picked up.
|
|
253
|
+
- **Build errors** print `file:line:col: message` with a code frame. Until the
|
|
254
|
+
next good build, every request gets a `500` error page with the same text
|
|
255
|
+
(HTML for browsers, plain text otherwise, header `x-kody-dev-error: build`).
|
|
256
|
+
The last good build does not keep answering `200`.
|
|
257
|
+
- **Runtime errors**: an uncaught throw from the fetch handler returns a
|
|
258
|
+
dev-only `500` page with the stack mapped back to your `.ts` sources
|
|
259
|
+
(`x-kody-dev-error: runtime`). Runtime wrapper and native frames are
|
|
260
|
+
dropped. The terminal prints the same stack.
|
|
261
|
+
- **Logs**: app `console.*` output streams to the terminal tagged with the
|
|
262
|
+
request, e.g. `[GET /scores #12] saved 3 rows`. `warn` and `error` go to
|
|
263
|
+
stderr.
|
|
264
|
+
- **Cleanup**: Ctrl-C, `SIGTERM`, and `SIGHUP` stop workerd and remove the
|
|
265
|
+
`kody-dev-*` temp dir. If the CLI is killed outright, a small reaper process
|
|
266
|
+
does the same. Each start also sweeps dirs and orphaned workerd processes
|
|
267
|
+
left behind by dead CLI runs, including older CLI versions.
|
|
268
|
+
|
|
243
269
|
## Local execute
|
|
244
270
|
|
|
245
271
|
`kody execute --local` runs the same execute module (default export called
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kody dev` bundles of `kody.app.entry`: same esbuild options as one-shot
|
|
3
|
+
* local bundles, plus an in-memory source map (for mapping workerd stacks back
|
|
4
|
+
* to `.ts`) and the list of input files (for the watcher's stat poll).
|
|
5
|
+
*/
|
|
6
|
+
import * as esbuild from 'esbuild';
|
|
7
|
+
export type DevBundle = {
|
|
8
|
+
code: string;
|
|
9
|
+
/** Source map JSON with sources relative to the package root. */
|
|
10
|
+
sourceMap: string | null;
|
|
11
|
+
/** Absolute paths of every non-node_modules input in the graph. */
|
|
12
|
+
inputs: Array<string>;
|
|
13
|
+
};
|
|
14
|
+
export type DevBundler = {
|
|
15
|
+
build: () => Promise<DevBundle>;
|
|
16
|
+
dispose: () => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
export declare function createDevBundler(input: {
|
|
19
|
+
entryPath: string;
|
|
20
|
+
root: string;
|
|
21
|
+
}): DevBundler;
|
|
22
|
+
export declare function readBundle(result: esbuild.BuildResult<{
|
|
23
|
+
metafile: true;
|
|
24
|
+
write: false;
|
|
25
|
+
}>, workingDir: string, entryPath: string): DevBundle;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kody dev` bundles of `kody.app.entry`: same esbuild options as one-shot
|
|
3
|
+
* local bundles, plus an in-memory source map (for mapping workerd stacks back
|
|
4
|
+
* to `.ts`) and the list of input files (for the watcher's stat poll).
|
|
5
|
+
*/
|
|
6
|
+
import * as esbuild from 'esbuild';
|
|
7
|
+
import { join, resolve } from 'node:path';
|
|
8
|
+
import { BundleError, createBundleError, localBundleOptions } from '../local-bundle.js';
|
|
9
|
+
export function createDevBundler(input) {
|
|
10
|
+
const entry = resolve(input.entryPath);
|
|
11
|
+
const base = localBundleOptions(entry);
|
|
12
|
+
const options = {
|
|
13
|
+
...base,
|
|
14
|
+
sourcemap: 'external',
|
|
15
|
+
sourcesContent: false,
|
|
16
|
+
metafile: true,
|
|
17
|
+
// Never written (write: false); only anchors source map paths at the root.
|
|
18
|
+
outfile: join(input.root, '__kody_dev_bundle.js'),
|
|
19
|
+
};
|
|
20
|
+
const paths = { workingDir: base.absWorkingDir, displayRoot: input.root };
|
|
21
|
+
return {
|
|
22
|
+
build: async () => {
|
|
23
|
+
let result;
|
|
24
|
+
try {
|
|
25
|
+
result = await esbuild.build(options);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw createBundleError(input.entryPath, error, paths);
|
|
29
|
+
}
|
|
30
|
+
return readBundle(result, base.absWorkingDir, input.entryPath);
|
|
31
|
+
},
|
|
32
|
+
dispose: async () => { },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function readBundle(result, workingDir, entryPath) {
|
|
36
|
+
const outputs = result.outputFiles ?? [];
|
|
37
|
+
const js = outputs.find((file) => file.path.endsWith('.js'));
|
|
38
|
+
const map = outputs.find((file) => file.path.endsWith('.js.map'));
|
|
39
|
+
if (!js) {
|
|
40
|
+
throw new BundleError(`Could not bundle ${entryPath}: esbuild produced no output.`, []);
|
|
41
|
+
}
|
|
42
|
+
const inputs = Object.keys(result.metafile?.inputs ?? {})
|
|
43
|
+
.filter((path) => !path.includes('node_modules') && !path.includes(':'))
|
|
44
|
+
.map((path) => resolve(workingDir, path));
|
|
45
|
+
return {
|
|
46
|
+
code: js.text.replace(/\n\/\/# sourceMappingURL=\S+\s*$/, '\n'),
|
|
47
|
+
sourceMap: map?.text ?? null,
|
|
48
|
+
inputs,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=dev-bundler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-bundler.js","sourceRoot":"","sources":["../../src/dev/dev-bundler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAevF,MAAM,UAAU,gBAAgB,CAAC,KAA0C;IAC1E,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACtC,MAAM,OAAO,GAAG;QACf,GAAG,IAAI;QACP,SAAS,EAAE,UAAU;QACrB,cAAc,EAAE,KAAK;QACrB,QAAQ,EAAE,IAAI;QACd,2EAA2E;QAC3E,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,sBAAsB,CAAC;KAClB,CAAA;IAChC,MAAM,KAAK,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,KAAK,CAAC,IAAI,EAAE,CAAA;IAEzE,OAAO;QACN,KAAK,EAAE,KAAK,IAAI,EAAE;YACjB,IAAI,MAA2C,CAAA;YAC/C,IAAI,CAAC;gBACJ,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YACtC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,MAAM,iBAAiB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;YACvD,CAAC;YACD,OAAO,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/D,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;KACvB,CAAA;AACF,CAAC;AAED,MAAM,UAAU,UAAU,CACzB,MAA6D,EAC7D,UAAkB,EAClB,SAAiB;IAEjB,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAA;IACxC,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;IAC5D,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;IACjE,IAAI,CAAC,EAAE,EAAE,CAAC;QACT,MAAM,IAAI,WAAW,CAAC,oBAAoB,SAAS,+BAA+B,EAAE,EAAE,CAAC,CAAA;IACxF,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,CAAC;SACvD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;SACvE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAA;IAC1C,OAAO;QACN,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,kCAAkC,EAAE,IAAI,CAAC;QAC/D,SAAS,EAAE,GAAG,EAAE,IAAI,IAAI,IAAI;QAC5B,MAAM;KACN,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events a running `kody dev` server reports: rebuilds, build and runtime
|
|
3
|
+
* errors, and app logs. Humans get {@link formatDevEvent}; agents get the
|
|
4
|
+
* same objects as NDJSON.
|
|
5
|
+
*/
|
|
6
|
+
import { type BundleDiagnostic } from '../local-bundle.js';
|
|
7
|
+
export type DevRequestTag = {
|
|
8
|
+
id: string;
|
|
9
|
+
method: string;
|
|
10
|
+
path: string;
|
|
11
|
+
};
|
|
12
|
+
export type DevLogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
13
|
+
export type DevEvent = {
|
|
14
|
+
type: 'rebuild_start';
|
|
15
|
+
files: Array<string>;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'rebuild_ok';
|
|
18
|
+
ms: number;
|
|
19
|
+
buildId: number;
|
|
20
|
+
restarted: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'build_error';
|
|
23
|
+
ms: number;
|
|
24
|
+
message: string;
|
|
25
|
+
file: string | null;
|
|
26
|
+
line: number | null;
|
|
27
|
+
col: number | null;
|
|
28
|
+
diagnostics: Array<BundleDiagnostic>;
|
|
29
|
+
} | {
|
|
30
|
+
type: 'runtime_error';
|
|
31
|
+
message: string;
|
|
32
|
+
stack: string;
|
|
33
|
+
request: DevRequestTag | null;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'log';
|
|
36
|
+
source: 'app' | 'workerd';
|
|
37
|
+
level: DevLogLevel;
|
|
38
|
+
message: string;
|
|
39
|
+
request: DevRequestTag | null;
|
|
40
|
+
};
|
|
41
|
+
/** Parse one workerd stdout line; plain lines become untagged `log` events. */
|
|
42
|
+
export declare function parseWorkerdStdoutLine(line: string): Extract<DevEvent, {
|
|
43
|
+
type: 'log';
|
|
44
|
+
}>;
|
|
45
|
+
export declare function formatRequestTag(request: DevRequestTag | null): string;
|
|
46
|
+
/** One human-readable block for terminals. */
|
|
47
|
+
export declare function formatDevEvent(event: DevEvent): {
|
|
48
|
+
stream: 'stdout' | 'stderr';
|
|
49
|
+
text: string;
|
|
50
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events a running `kody dev` server reports: rebuilds, build and runtime
|
|
3
|
+
* errors, and app logs. Humans get {@link formatDevEvent}; agents get the
|
|
4
|
+
* same objects as NDJSON.
|
|
5
|
+
*/
|
|
6
|
+
import { formatBundleDiagnostic } from '../local-bundle.js';
|
|
7
|
+
import { appLogMarker } from './dev-protocol.js';
|
|
8
|
+
const logLevels = new Set(['log', 'info', 'warn', 'error', 'debug']);
|
|
9
|
+
/** Parse one workerd stdout line; plain lines become untagged `log` events. */
|
|
10
|
+
export function parseWorkerdStdoutLine(line) {
|
|
11
|
+
if (line.startsWith(appLogMarker)) {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(line.slice(appLogMarker.length));
|
|
14
|
+
const level = typeof parsed.level === 'string' && logLevels.has(parsed.level)
|
|
15
|
+
? parsed.level
|
|
16
|
+
: 'log';
|
|
17
|
+
return {
|
|
18
|
+
type: 'log',
|
|
19
|
+
source: 'app',
|
|
20
|
+
level,
|
|
21
|
+
message: typeof parsed.message === 'string' ? parsed.message : String(parsed.message),
|
|
22
|
+
request: readRequestTag(parsed.request),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch { }
|
|
26
|
+
}
|
|
27
|
+
return { type: 'log', source: 'app', level: 'log', message: line, request: null };
|
|
28
|
+
}
|
|
29
|
+
function readRequestTag(value) {
|
|
30
|
+
if (!value || typeof value !== 'object')
|
|
31
|
+
return null;
|
|
32
|
+
const record = value;
|
|
33
|
+
if (typeof record.id !== 'string')
|
|
34
|
+
return null;
|
|
35
|
+
return {
|
|
36
|
+
id: record.id,
|
|
37
|
+
method: typeof record.method === 'string' ? record.method : 'GET',
|
|
38
|
+
path: typeof record.path === 'string' ? record.path : '/',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function formatRequestTag(request) {
|
|
42
|
+
return request ? `[${request.method} ${request.path} #${request.id}]` : '[app]';
|
|
43
|
+
}
|
|
44
|
+
/** One human-readable block for terminals. */
|
|
45
|
+
export function formatDevEvent(event) {
|
|
46
|
+
switch (event.type) {
|
|
47
|
+
case 'rebuild_start':
|
|
48
|
+
return {
|
|
49
|
+
stream: 'stderr',
|
|
50
|
+
text: event.files.length > 0 ? `Rebuilding (${event.files.join(', ')})…` : 'Building…',
|
|
51
|
+
};
|
|
52
|
+
case 'rebuild_ok':
|
|
53
|
+
return {
|
|
54
|
+
stream: 'stderr',
|
|
55
|
+
text: event.restarted
|
|
56
|
+
? `Ready in ${event.ms}ms (build ${event.buildId})`
|
|
57
|
+
: `No bundle changes (${event.ms}ms)`,
|
|
58
|
+
};
|
|
59
|
+
case 'build_error':
|
|
60
|
+
return {
|
|
61
|
+
stream: 'stderr',
|
|
62
|
+
text: `Build failed${event.diagnostics.length > 0 ? `:\n${event.diagnostics.map(formatBundleDiagnostic).join('\n')}` : `: ${event.message}`}\nThe last good build is paused until this is fixed; requests get the error page.`,
|
|
63
|
+
};
|
|
64
|
+
case 'runtime_error':
|
|
65
|
+
return {
|
|
66
|
+
stream: 'stderr',
|
|
67
|
+
text: `${formatRequestTag(event.request)} Uncaught ${event.stack || event.message}`,
|
|
68
|
+
};
|
|
69
|
+
case 'log': {
|
|
70
|
+
const tag = event.source === 'workerd' ? '[workerd]' : formatRequestTag(event.request);
|
|
71
|
+
const level = event.level === 'log' || event.level === 'info' ? '' : `${event.level}: `;
|
|
72
|
+
const stream = event.level === 'error' || event.level === 'warn' ? 'stderr' : 'stdout';
|
|
73
|
+
return { stream, text: `${tag} ${level}${event.message}` };
|
|
74
|
+
}
|
|
75
|
+
default: {
|
|
76
|
+
const exhaustive = event;
|
|
77
|
+
throw new Error(`Unhandled dev event: ${JSON.stringify(exhaustive)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=dev-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-events.js","sourceRoot":"","sources":["../../src/dev/dev-events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire constants shared by the `kody dev` proxy (Node) and the dev entry that
|
|
3
|
+
* runs inside workerd.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Prefix the dev entry puts on every captured `console.*` line it writes to
|
|
7
|
+
* workerd stdout. Control characters keep it from colliding with app output.
|
|
8
|
+
*/
|
|
9
|
+
export declare const appLogMarker = "\u001Fkody-dev-log\u001F";
|
|
10
|
+
/** Proxy → workerd: short request id used to tag logs and errors. */
|
|
11
|
+
export declare const devRequestIdHeader = "x-kody-dev-request";
|
|
12
|
+
/** workerd → proxy: the response carries a serialized uncaught handler error. */
|
|
13
|
+
export declare const devErrorHeader = "x-kody-dev-error";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire constants shared by the `kody dev` proxy (Node) and the dev entry that
|
|
3
|
+
* runs inside workerd.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Prefix the dev entry puts on every captured `console.*` line it writes to
|
|
7
|
+
* workerd stdout. Control characters keep it from colliding with app output.
|
|
8
|
+
*/
|
|
9
|
+
export const appLogMarker = '\u001fkody-dev-log\u001f';
|
|
10
|
+
/** Proxy → workerd: short request id used to tag logs and errors. */
|
|
11
|
+
export const devRequestIdHeader = 'x-kody-dev-request';
|
|
12
|
+
/** workerd → proxy: the response carries a serialized uncaught handler error. */
|
|
13
|
+
export const devErrorHeader = 'x-kody-dev-error';
|
|
14
|
+
//# sourceMappingURL=dev-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-protocol.js","sourceRoot":"","sources":["../../src/dev/dev-protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,0BAA0B,CAAA;AAEtD,qEAAqE;AACrE,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAA;AAEtD,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,CAAA"}
|
|
@@ -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 {};
|