@kodycodes/cli 1.25.0 → 1.26.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.
Files changed (61) hide show
  1. package/README.md +32 -0
  2. package/dist/capability-proxy.d.ts +37 -0
  3. package/dist/capability-proxy.js +144 -0
  4. package/dist/capability-proxy.js.map +1 -1
  5. package/dist/cli.js +2 -2
  6. package/dist/cli.js.map +1 -1
  7. package/dist/dev/dev-assets.d.ts +40 -0
  8. package/dist/dev/dev-assets.js +202 -0
  9. package/dist/dev/dev-assets.js.map +1 -0
  10. package/dist/dev/dev-bundler.d.ts +3 -1
  11. package/dist/dev/dev-bundler.js +21 -7
  12. package/dist/dev/dev-bundler.js.map +1 -1
  13. package/dist/dev/dev-events.d.ts +1 -1
  14. package/dist/dev/dev-events.js +1 -1
  15. package/dist/dev/dev-events.js.map +1 -1
  16. package/dist/dev/dev-port.d.ts +22 -0
  17. package/dist/dev/dev-port.js +80 -0
  18. package/dist/dev/dev-port.js.map +1 -0
  19. package/dist/dev/prebuild.d.ts +17 -0
  20. package/dist/dev/prebuild.js +102 -0
  21. package/dist/dev/prebuild.js.map +1 -0
  22. package/dist/dev/process-reaper.d.ts +3 -0
  23. package/dist/dev/process-reaper.js +8 -1
  24. package/dist/dev/process-reaper.js.map +1 -1
  25. package/dist/dev-command.d.ts +1 -0
  26. package/dist/dev-command.js +65 -29
  27. package/dist/dev-command.js.map +1 -1
  28. package/dist/help.js +7 -0
  29. package/dist/help.js.map +1 -1
  30. package/dist/local-execute.js +14 -50
  31. package/dist/local-execute.js.map +1 -1
  32. package/dist/local-gateway-fetch-forward.d.ts +16 -0
  33. package/dist/local-gateway-fetch-forward.js +135 -0
  34. package/dist/local-gateway-fetch-forward.js.map +1 -0
  35. package/dist/local-host-runtime-surface.d.ts +55 -0
  36. package/dist/local-host-runtime-surface.js +75 -0
  37. package/dist/local-host-runtime-surface.js.map +1 -0
  38. package/dist/local-package-graph.js +23 -1
  39. package/dist/local-package-graph.js.map +1 -1
  40. package/dist/local-package-run.js +14 -50
  41. package/dist/local-package-run.js.map +1 -1
  42. package/dist/local-runtime-bridge.d.ts +36 -0
  43. package/dist/local-runtime-bridge.js +128 -0
  44. package/dist/local-runtime-bridge.js.map +1 -0
  45. package/dist/local-runtime-source.d.ts +5 -0
  46. package/dist/local-runtime-source.js +158 -16
  47. package/dist/local-runtime-source.js.map +1 -1
  48. package/dist/mock/mock-supported.d.ts +1 -1
  49. package/dist/mock/mock-supported.js +1 -0
  50. package/dist/mock/mock-supported.js.map +1 -1
  51. package/dist/package-app-dev.d.ts +16 -0
  52. package/dist/package-app-dev.js +166 -70
  53. package/dist/package-app-dev.js.map +1 -1
  54. package/dist/package-local-runtime-source.js +44 -47
  55. package/dist/package-local-runtime-source.js.map +1 -1
  56. package/dist/package-manifest.d.ts +11 -0
  57. package/dist/package-manifest.js +66 -1
  58. package/dist/package-manifest.js.map +1 -1
  59. package/dist/structured-error.d.ts +1 -1
  60. package/dist/structured-error.js.map +1 -1
  61. package/package.json +1 -1
@@ -42,21 +42,136 @@ export function createRuntimeFlatAliasSource() {
42
42
  const runtime = JSON.stringify(localWorkerModuleNames.runtime);
43
43
  return `export * from ${runtime};\nexport { default } from ${runtime};\n`;
44
44
  }
45
- export function createLocalRuntimeModuleSource() {
45
+ /** Workerd-side `kody.gatewayFetchStream` + bridge install helpers (shared shape). */
46
+ export function localGatewayFetchStreamRuntimeSource(options) {
46
47
  return `
47
- import { AsyncLocalStorage } from 'node:async_hooks';
48
-
49
48
  let __kodyBridge = null;
50
49
 
51
50
  export function __kodyInstallLocalBridge(bridge) {
52
- __kodyBridge = bridge;
51
+ // Object form: { call, gatewayFetch }. Legacy function form still works for /call.
52
+ __kodyBridge =
53
+ typeof bridge === 'function' ? { call: bridge, gatewayFetch: null } : bridge;
53
54
  }
54
55
 
55
56
  function __kodyCall(path, args) {
56
- if (__kodyBridge === null) {
57
- throw new Error('kody:runtime is only available while an execute --local run is in progress.');
57
+ if (__kodyBridge === null || typeof __kodyBridge.call !== 'function') {
58
+ throw new Error(${JSON.stringify(options.callUnavailableMessage)});
58
59
  }
59
- return __kodyBridge(path, args);
60
+ return __kodyBridge.call(path, args);
61
+ }
62
+
63
+ const __kodyGatewayNullBodyStatuses = new Set([101, 103, 204, 205, 304]);
64
+ const __kodySecretResolutionHeader = 'x-kody-secret-resolution';
65
+
66
+ /**
67
+ * Streaming CapabilityProxy hop (POST /v1/capability-proxy/gateway-fetch via the
68
+ * loopback bridge). Returns a real Response — not the JSON gatewayFetch envelope.
69
+ * See kentcdodds/kody docs/contributing/contracts/cli-streaming-gateway-fetch.md.
70
+ */
71
+ async function gatewayFetchStream(input) {
72
+ if (__kodyBridge === null || typeof __kodyBridge.gatewayFetch !== 'function') {
73
+ throw new Error(
74
+ 'Local execute needs kody.gatewayFetchStream on the host runtime to stream a secret-aware fetch body (multipart, large, or ReadableStream). Upgrade @kodycodes/cli, or buffer the body under 4 MiB for the JSON gatewayFetch hop.',
75
+ );
76
+ }
77
+ const record = input != null && typeof input === 'object' && !Array.isArray(input) ? input : null;
78
+ if (!record) {
79
+ throw new Error('gatewayFetchStream requires a single object argument: { request, packageId? }.');
80
+ }
81
+ const requestValue = record.request;
82
+ if (requestValue == null || typeof requestValue !== 'object' || Array.isArray(requestValue)) {
83
+ throw new Error('gatewayFetchStream requires request: { url, method?, headers?, body? }.');
84
+ }
85
+ const url = typeof requestValue.url === 'string' ? requestValue.url.trim() : '';
86
+ if (!url) {
87
+ throw new Error('gatewayFetchStream request.url must be a non-empty absolute URL.');
88
+ }
89
+ const method =
90
+ typeof requestValue.method === 'string' && requestValue.method.trim()
91
+ ? requestValue.method.trim()
92
+ : 'GET';
93
+ const packageId =
94
+ typeof record.packageId === 'string' && record.packageId.trim()
95
+ ? record.packageId.trim()
96
+ : undefined;
97
+ const rawHeaders =
98
+ requestValue.headers != null &&
99
+ typeof requestValue.headers === 'object' &&
100
+ !Array.isArray(requestValue.headers)
101
+ ? { ...requestValue.headers }
102
+ : {};
103
+ const outboundHeaders = {};
104
+ let contentType = null;
105
+ let secretResolution = null;
106
+ for (const [key, value] of Object.entries(rawHeaders)) {
107
+ if (typeof value !== 'string') continue;
108
+ const lower = key.toLowerCase();
109
+ if (lower === 'content-type') {
110
+ contentType = value;
111
+ continue;
112
+ }
113
+ if (lower === __kodySecretResolutionHeader) {
114
+ secretResolution = value;
115
+ continue;
116
+ }
117
+ outboundHeaders[key] = value;
118
+ }
119
+ const headers = {
120
+ 'X-Kody-Gateway-Url': url,
121
+ 'X-Kody-Gateway-Method': method,
122
+ };
123
+ if (packageId) headers['X-Kody-Gateway-Package-Id'] = packageId;
124
+ if (Object.keys(outboundHeaders).length > 0) {
125
+ headers['X-Kody-Gateway-Headers'] = JSON.stringify(outboundHeaders);
126
+ }
127
+ if (contentType) headers['Content-Type'] = contentType;
128
+ if (secretResolution) headers[__kodySecretResolutionHeader] = secretResolution;
129
+ const body = Object.hasOwn(requestValue, 'body') ? requestValue.body : null;
130
+ const init = {
131
+ method: 'POST',
132
+ headers,
133
+ };
134
+ const upper = method.toUpperCase();
135
+ if (body != null && upper !== 'GET' && upper !== 'HEAD') {
136
+ init.body = body;
137
+ init.duplex = 'half';
138
+ }
139
+ if (requestValue.signal != null) init.signal = requestValue.signal;
140
+ if (requestValue.redirect != null) init.redirect = requestValue.redirect;
141
+ const response = await __kodyBridge.gatewayFetch(init);
142
+ const cacheControl = response.headers.get('cache-control') ?? '';
143
+ const contentTypeHeader = response.headers.get('content-type') ?? '';
144
+ if (response.status === 413 && !contentTypeHeader.includes('application/json')) {
145
+ throw new Error(
146
+ 'Request body exceeds the Worker upload limit of 104857600 bytes (100 MiB).',
147
+ );
148
+ }
149
+ if (cacheControl.includes('no-store') && contentTypeHeader.includes('application/json')) {
150
+ const text = await response.clone().text();
151
+ try {
152
+ const parsed = JSON.parse(text);
153
+ if (parsed != null && typeof parsed === 'object' && parsed.error != null) {
154
+ const err = parsed.error;
155
+ const message =
156
+ typeof err === 'string'
157
+ ? err
158
+ : typeof err?.message === 'string'
159
+ ? err.message
160
+ : 'gatewayFetchStream failed.';
161
+ throw new Error(message);
162
+ }
163
+ } catch (error) {
164
+ if (!(error instanceof SyntaxError)) throw error;
165
+ }
166
+ }
167
+ if (__kodyGatewayNullBodyStatuses.has(response.status)) {
168
+ return new Response(null, {
169
+ status: response.status,
170
+ statusText: response.statusText,
171
+ headers: response.headers,
172
+ });
173
+ }
174
+ return response;
60
175
  }
61
176
 
62
177
  function __kodyNamespace(path) {
@@ -67,6 +182,13 @@ function __kodyNamespace(path) {
67
182
  return undefined;
68
183
  }
69
184
  if (property === 'toString' || property === 'inspect') return () => label;
185
+ if (
186
+ path.length === 1 &&
187
+ path[0] === 'kody' &&
188
+ property === 'gatewayFetchStream'
189
+ ) {
190
+ return gatewayFetchStream;
191
+ }
70
192
  return __kodyNamespace([...path, property]);
71
193
  },
72
194
  apply(_target, _thisArg, args) {
@@ -74,6 +196,15 @@ function __kodyNamespace(path) {
74
196
  },
75
197
  });
76
198
  }
199
+ `.trimStart();
200
+ }
201
+ export function createLocalRuntimeModuleSource() {
202
+ return `
203
+ import { AsyncLocalStorage } from 'node:async_hooks';
204
+
205
+ ${localGatewayFetchStreamRuntimeSource({
206
+ callUnavailableMessage: 'kody:runtime is only available while an execute --local run is in progress.',
207
+ })}
77
208
 
78
209
  export const kody = __kodyNamespace(['kody']);
79
210
 
@@ -303,15 +434,26 @@ export default {
303
434
  }
304
435
  __kodyClaimed = true;
305
436
  const { params } = await request.json();
306
- __kodyInstallLocalBridge(async (path, args) => {
307
- const response = await env.KODY_BRIDGE.fetch('http://kody-bridge/call', {
308
- method: 'POST',
309
- headers: { 'content-type': 'application/json', [__kodyRunSecretHeader]: secret },
310
- body: JSON.stringify({ path, args }),
311
- });
312
- const data = await response.json();
313
- if (data.error) throw new Error(data.error);
314
- return data.result;
437
+ __kodyInstallLocalBridge({
438
+ call: async (path, args) => {
439
+ const response = await env.KODY_BRIDGE.fetch('http://kody-bridge/call', {
440
+ method: 'POST',
441
+ headers: { 'content-type': 'application/json', [__kodyRunSecretHeader]: secret },
442
+ body: JSON.stringify({ path, args }),
443
+ });
444
+ const data = await response.json();
445
+ if (data.error) throw new Error(data.error);
446
+ return data.result;
447
+ },
448
+ gatewayFetch: async (init) => {
449
+ const headers = new Headers(init?.headers);
450
+ headers.set(__kodyRunSecretHeader, secret);
451
+ return await env.KODY_BRIDGE.fetch('http://kody-bridge/gateway-fetch', {
452
+ ...init,
453
+ method: 'POST',
454
+ headers,
455
+ });
456
+ },
315
457
  });
316
458
  const logs = [];
317
459
  const nativeConsole = globalThis.console;
@@ -1 +1 @@
1
- {"version":3,"file":"local-runtime-source.js","sourceRoot":"","sources":["../src/local-runtime-source.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-runtime-source.js","sourceRoot":"","sources":["../src/local-runtime-source.ts"],"names":[],"mappings":"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"}
@@ -15,4 +15,4 @@ export type MockCapabilityPath = (typeof mockCapabilityPaths)[number];
15
15
  export declare function isMockOpenApiOperation(id: string): id is MockOpenApiOperationId;
16
16
  export declare function isMockCapabilityPath(pathKey: string): pathKey is MockCapabilityPath;
17
17
  /** CapabilityProxy routes always present on every target. */
18
- export declare const mockCapabilityProxyRoutes: readonly ["GET /v1/capability-proxy/session", "POST /v1/capability-proxy/call", "POST /v1/local-execute/package-graph"];
18
+ export declare const mockCapabilityProxyRoutes: readonly ["GET /v1/capability-proxy/session", "POST /v1/capability-proxy/call", "POST /v1/capability-proxy/gateway-fetch", "POST /v1/local-execute/package-graph"];
@@ -57,6 +57,7 @@ export function isMockCapabilityPath(pathKey) {
57
57
  export const mockCapabilityProxyRoutes = [
58
58
  'GET /v1/capability-proxy/session',
59
59
  'POST /v1/capability-proxy/call',
60
+ 'POST /v1/capability-proxy/gateway-fetch',
60
61
  'POST /v1/local-execute/package-graph',
61
62
  ];
62
63
  //# sourceMappingURL=mock-supported.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock-supported.js","sourceRoot":"","sources":["../../src/mock/mock-supported.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,8CAA8C;AAC9C,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACtC,QAAQ;IACR,oBAAoB;IACpB,UAAU;CACD,CAAA;AAEV,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mBAAmB,GAAG;IAClC,yBAAyB;IACzB,aAAa;IACb,cAAc;IACd,eAAe;IACf,6EAA6E;IAC7E,gEAAgE;IAChE,8BAA8B;IAC9B,8BAA8B;IAC9B,iCAAiC;IACjC,+BAA+B;IAC/B,8BAA8B;IAC9B,yEAAyE;IACzE,8DAA8D;IAC9D,wBAAwB;IACxB,wBAAwB;IACxB,2BAA2B;IAC3B,yBAAyB;IACzB,wBAAwB;IACxB,wEAAwE;IACxE,2BAA2B;IAC3B,8BAA8B;IAC9B,4BAA4B;IAC5B,8BAA8B;IAC9B,yEAAyE;IACzE,4EAA4E;IAC5E,wEAAwE;IACxE,0DAA0D;IAC1D,yBAAyB;IACzB,mBAAmB;IACnB,yBAAyB;IACzB,8EAA8E;IAC9E,oBAAoB;CACX,CAAA;AAKV,MAAM,UAAU,sBAAsB,CAAC,EAAU;IAChD,OAAQ,uBAAiD,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AACvE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAe;IACnD,OAAQ,mBAA6C,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;AACxE,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACxC,kCAAkC;IAClC,gCAAgC;IAChC,sCAAsC;CAC7B,CAAA"}
1
+ {"version":3,"file":"mock-supported.js","sourceRoot":"","sources":["../../src/mock/mock-supported.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,8CAA8C;AAC9C,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACtC,QAAQ;IACR,oBAAoB;IACpB,UAAU;CACD,CAAA;AAEV,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mBAAmB,GAAG;IAClC,yBAAyB;IACzB,aAAa;IACb,cAAc;IACd,eAAe;IACf,6EAA6E;IAC7E,gEAAgE;IAChE,8BAA8B;IAC9B,8BAA8B;IAC9B,iCAAiC;IACjC,+BAA+B;IAC/B,8BAA8B;IAC9B,yEAAyE;IACzE,8DAA8D;IAC9D,wBAAwB;IACxB,wBAAwB;IACxB,2BAA2B;IAC3B,yBAAyB;IACzB,wBAAwB;IACxB,wEAAwE;IACxE,2BAA2B;IAC3B,8BAA8B;IAC9B,4BAA4B;IAC5B,8BAA8B;IAC9B,yEAAyE;IACzE,4EAA4E;IAC5E,wEAAwE;IACxE,0DAA0D;IAC1D,yBAAyB;IACzB,mBAAmB;IACnB,yBAAyB;IACzB,8EAA8E;IAC9E,oBAAoB;CACX,CAAA;AAKV,MAAM,UAAU,sBAAsB,CAAC,EAAU;IAChD,OAAQ,uBAAiD,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AACvE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAe;IACnD,OAAQ,mBAA6C,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;AACxE,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACxC,kCAAkC;IAClC,gCAAgC;IAChC,yCAAyC;IACzC,sCAAsC;CAC7B,CAAA"}
@@ -19,7 +19,13 @@ export type PackageAppDevOptions = {
19
19
  apiUrl: string;
20
20
  target: KodyTarget;
21
21
  host?: string;
22
+ /** Listen on exactly this port (0 = any free port); EADDRINUSE is an error. */
22
23
  port?: number;
24
+ /**
25
+ * Used when `port` is unset: try this port, then the next free ones above
26
+ * it. Without either, any free port.
27
+ */
28
+ defaultPort?: number;
23
29
  /** Public mount path the Node proxy strips (default ''). */
24
30
  appBasePath?: string;
25
31
  /** User-owned token org selection (`?org=`), same as execute --local. */
@@ -58,6 +64,16 @@ export type PackageAppDevOptions = {
58
64
  onError?: (message: string) => void;
59
65
  /** Rebuilds, build/runtime errors, and app logs. */
60
66
  onEvent?: (event: DevEvent) => void;
67
+ /**
68
+ * Fired as soon as the HTTP port is bound (before the first build). Used
69
+ * to advertise the server in the attach registry so a second `kody dev`
70
+ * cannot start during the initial build window.
71
+ */
72
+ onListening?: (info: {
73
+ port: number;
74
+ hostedUrl: string;
75
+ appBasePath: string;
76
+ }) => void | Promise<void>;
61
77
  };
62
78
  export type PackageAppDevServer = {
63
79
  url: string;
@@ -13,17 +13,22 @@ import { rmSync } from 'node:fs';
13
13
  import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
14
14
  import { createServer, request as httpRequest, } from 'node:http';
15
15
  import { tmpdir } from 'node:os';
16
- import { join, relative } from 'node:path';
16
+ import { join, relative, resolve, sep } from 'node:path';
17
17
  import { assertTokenSafeApiUrl, callCapabilityProxy, } from './capability-proxy.js';
18
18
  import { apiTokenEnvVar } from './defaults.js';
19
+ import { createDevClientBundler, parseDevAssetPath, serveDevAsset, } from './dev/dev-assets.js';
20
+ import { forwardLocalGatewayFetchStream } from './local-gateway-fetch-forward.js';
21
+ import { startLocalRuntimeBridge, } from './local-runtime-bridge.js';
19
22
  import { createDevBundler } from './dev/dev-bundler.js';
20
23
  import { parseWorkerdStdoutLine } from './dev/dev-events.js';
21
24
  import { devErrorHeader, devRequestIdHeader } from './dev/dev-protocol.js';
25
+ import { listenDevServer } from './dev/dev-port.js';
22
26
  import { devStatusPath } from './dev/dev-registry.js';
23
27
  import { createDevWatcher } from './dev/dev-watcher.js';
24
28
  import { sendDevError } from './dev/error-page.js';
25
29
  import { createLiveReloadHub, injectLiveReloadScript, liveReloadClientPath, liveReloadClientSource, liveReloadEventsPath, liveReloadScriptTag, shouldInjectLiveReload, } from './dev/live-reload.js';
26
30
  import { devWorkDirPrefix, startProcessReaper, sweepStaleDevDirs, } from './dev/process-reaper.js';
31
+ import { runPrebuild } from './dev/prebuild.js';
27
32
  import { createStackMapper } from './dev/stack-trace.js';
28
33
  import { cleanWorkerdStderrLine, startWorkerd, } from './dev/workerd-process.js';
29
34
  import { BundleError, formatBundleDiagnostic } from './local-bundle.js';
@@ -36,7 +41,9 @@ import { createPackageAppEntrySource, createPackageLocalRuntimeModuleSource, run
36
41
  import { resolvePackagePath } from './package-manifest.js';
37
42
  import { redact } from './redact.js';
38
43
  import { ensureWorkerdBinary } from './workerd-binary.js';
39
- const defaultDebounceMs = 120;
44
+ const defaultDebounceMs = 30;
45
+ /** Watcher events this soon after a prebuild finishes are its own writes, not edits. */
46
+ const prebuildSettleMs = 150;
40
47
  /** Longest a request waits for an in-progress rebuild before the old build answers. */
41
48
  const rebuildGateMs = 10_000;
42
49
  /** Longest a retired generation may keep draining in-flight requests. */
@@ -113,18 +120,48 @@ export async function startPackageAppDev(options) {
113
120
  const proxy = createServer((req, res) => {
114
121
  void handleRequest(req, res);
115
122
  });
116
- await new Promise((resolve, reject) => {
117
- proxy.once('error', reject);
118
- proxy.listen(options.port ?? 0, host, () => resolve());
119
- });
123
+ try {
124
+ await listenDevServer(proxy, {
125
+ host,
126
+ port: options.port ?? options.defaultPort ?? 0,
127
+ explicit: options.port !== undefined,
128
+ });
129
+ }
130
+ catch (error) {
131
+ process.off('exit', killAllSync);
132
+ reaper.close();
133
+ await rm(workDir, { recursive: true, force: true });
134
+ throw error;
135
+ }
120
136
  const address = proxy.address();
121
137
  const hostedUrl = `http://${host}:${address.port}`;
122
- const packageContext = buildLocalPackageContext({
138
+ try {
139
+ await options.onListening?.({
140
+ port: address.port,
141
+ hostedUrl,
142
+ appBasePath,
143
+ });
144
+ }
145
+ catch (error) {
146
+ // Port is bound but startPackageAppDev has not returned yet — close the
147
+ // listen handle now or the process stays alive after the throw.
148
+ process.off('exit', killAllSync);
149
+ reaper.close();
150
+ proxy.closeAllConnections();
151
+ await new Promise((resolve) => {
152
+ proxy.close(() => resolve());
153
+ });
154
+ await rm(workDir, { recursive: true, force: true });
155
+ throw error;
156
+ }
157
+ const contextFor = (client) => buildLocalPackageContext({
123
158
  kodyId: options.manifest.kodyId,
124
159
  packageId: `local:${options.manifest.kodyId}`,
125
160
  hostedUrl,
126
161
  appBasePath,
162
+ clientModuleUrl: client ? clientModuleUrlFor(client) : null,
127
163
  });
164
+ const clientModuleUrlFor = (client) => `${hostedUrl}${appBasePath}/_assets/${client.name}`;
128
165
  const mockFetchGateway = await resolveMockFetchGateway({
129
166
  target: options.target,
130
167
  packageRoot: root,
@@ -132,9 +169,9 @@ export async function startPackageAppDev(options) {
132
169
  allowPrivateNetwork: options.allowPrivateNetwork,
133
170
  onStatus: options.onStatus,
134
171
  });
135
- const bridge = await startBridge({
172
+ const bridge = await startLocalRuntimeBridge({
136
173
  runSecret,
137
- forward: async (call, signal) => {
174
+ forwardCall: async (call, signal) => {
138
175
  const mocked = tryHandleMockGatewayFetchCall({
139
176
  gateway: mockFetchGateway,
140
177
  path: call.path,
@@ -149,12 +186,36 @@ export async function startPackageAppDev(options) {
149
186
  signal,
150
187
  });
151
188
  },
189
+ forwardGatewayFetchStream: async ({ request, response, signal }) => {
190
+ await forwardLocalGatewayFetchStream({
191
+ request,
192
+ response,
193
+ signal,
194
+ client,
195
+ ...(mockFetchGateway ? { mockFetchGateway } : {}),
196
+ });
197
+ },
152
198
  });
153
- const entryPath = resolvePackagePath(options.manifest, options.manifest.app.entry);
199
+ const app = options.manifest.app;
200
+ const entryPath = resolvePackagePath(options.manifest, app.entry);
154
201
  const bundler = createDevBundler({ entryPath, root });
202
+ const clientBundler = app.client
203
+ ? createDevClientBundler({
204
+ entryPath: resolvePackagePath(options.manifest, app.client),
205
+ root,
206
+ externals: app.clientExternals,
207
+ })
208
+ : null;
209
+ const assetsDir = app.assets ? resolve(root, app.assets) : null;
210
+ const prebuild = options.manifest.dev?.prebuild;
211
+ let prebuildRunning = false;
212
+ let prebuildSettledAt = 0;
213
+ let prebuildAbort;
155
214
  const workerdEnv = { ...process.env, [runSecretEnvVar]: runSecret };
156
215
  delete workerdEnv[apiTokenEnvVar];
157
216
  function requestBuild(paths) {
217
+ if (closed)
218
+ return Promise.resolve();
158
219
  for (const path of paths)
159
220
  changedFiles.add(path);
160
221
  if (buildLoop) {
@@ -176,12 +237,17 @@ export async function startPackageAppDev(options) {
176
237
  async function buildOnce(files) {
177
238
  const started = performance.now();
178
239
  emit({ type: 'rebuild_start', files: files.map((file) => relative(root, file) || file) });
179
- let bundle;
240
+ const assetsChanged = assetsDir !== null &&
241
+ files.some((file) => file === assetsDir || file.startsWith(`${assetsDir}${sep}`));
180
242
  try {
181
- bundle = await bundler.build();
182
- const midBuildEdits = (await watcher?.track([...bundle.inputs, options.manifest.packageJsonPath], {
183
- ignoreChanges: files,
184
- })) ?? [];
243
+ if (prebuild) {
244
+ await runPrebuildStep(prebuild);
245
+ }
246
+ const [bundle, client] = await Promise.all([
247
+ bundler.build(),
248
+ clientBundler ? clientBundler.build() : Promise.resolve(null),
249
+ ]);
250
+ const midBuildEdits = (await watcher?.track([...bundle.inputs, ...(client?.inputs ?? []), options.manifest.packageJsonPath], { ignoreChanges: files })) ?? [];
185
251
  // Queue mid-build edits for the next loop iteration directly — do not
186
252
  // call onChange (debounced); chokidar may already have the same paths
187
253
  // pending and would double-schedule a rebuild after debounce.
@@ -190,7 +256,10 @@ export async function startPackageAppDev(options) {
190
256
  changedFiles.add(path);
191
257
  rebuildAgain = true;
192
258
  }
193
- if (active && !buildFailure && bundle.code === active.code) {
259
+ if (active &&
260
+ !buildFailure &&
261
+ bundle.code === active.code &&
262
+ client?.name === active.client?.name) {
194
263
  // Still stamp lastRebuild so agents polling lastRebuild.at see
195
264
  // the no-op rebuild finish (status already flipped building→ready).
196
265
  lastRebuild = { ms: elapsed(started), at: new Date().toISOString() };
@@ -200,9 +269,12 @@ export async function startPackageAppDev(options) {
200
269
  buildId: active.id,
201
270
  restarted: false,
202
271
  });
272
+ if (assetsChanged || prebuild) {
273
+ liveReloadHub.broadcast({ event: 'reload', data: { buildId: active.id } });
274
+ }
203
275
  return;
204
276
  }
205
- const generation = await startGeneration(bundle);
277
+ const generation = await startGeneration(bundle, client);
206
278
  if (closed) {
207
279
  await stopGeneration(generation);
208
280
  return;
@@ -233,13 +305,54 @@ export async function startPackageAppDev(options) {
233
305
  liveReloadHub.broadcast({ event: 'build_error', data: buildErrorView(buildFailure) });
234
306
  }
235
307
  }
236
- async function startGeneration(bundle) {
308
+ let clearWatcherPending = null;
309
+ async function runPrebuildStep(command) {
310
+ prebuildRunning = true;
311
+ prebuildAbort = new AbortController();
312
+ let failed = false;
313
+ try {
314
+ await runPrebuild({
315
+ command,
316
+ cwd: root,
317
+ signal: prebuildAbort.signal,
318
+ onSpawn: (pid) => reaper.trackGroup(pid),
319
+ onExit: (pid) => reaper.untrackGroup(pid),
320
+ onLine: (line, stream) => emit({
321
+ type: 'log',
322
+ source: 'prebuild',
323
+ level: stream === 'stderr' ? 'warn' : 'log',
324
+ message: redact(line),
325
+ request: null,
326
+ }),
327
+ });
328
+ }
329
+ catch (error) {
330
+ failed = true;
331
+ throw error;
332
+ }
333
+ finally {
334
+ prebuildAbort = undefined;
335
+ prebuildRunning = false;
336
+ prebuildSettledAt = performance.now();
337
+ // Absorb writes even when prebuild fails — otherwise the 1s poll
338
+ // treats partial/failed output as new edits and loops rebuilds.
339
+ await watcher?.rebaseline();
340
+ if (failed) {
341
+ // Drop chokidar echoes of the failed prebuild / trigger burst so
342
+ // they cannot restart the loop after buildLoop clears.
343
+ changedFiles.clear();
344
+ rebuildAgain = false;
345
+ clearWatcherPending?.();
346
+ }
347
+ }
348
+ }
349
+ async function startGeneration(bundle, client) {
237
350
  const id = ++generationCounter;
238
351
  const dir = join(workDir, `g${id}`);
239
352
  await writeWorkerFiles({
240
353
  workDir: dir,
241
354
  code: bundle.code,
242
- packageContext,
355
+ packageContext: contextFor(client),
243
356
  packageRoot: root,
244
357
  mockStorage: options.target === 'mock',
245
358
  bridgePort: bridge.port,
@@ -292,6 +405,7 @@ export async function startPackageAppDev(options) {
292
405
  dir,
293
406
  workerd,
294
407
  code: bundle.code,
408
+ client,
295
409
  mapStack: createStackMapper({
296
410
  sourceMap: bundle.sourceMap,
297
411
  userModule: localWorkerModuleNames.user,
@@ -413,6 +527,20 @@ export async function startPackageAppDev(options) {
413
527
  if (handleDevRoute(req, res))
414
528
  return;
415
529
  await waitForBuild();
530
+ const assetPath = parseDevAssetPath(stripMount(new URL(req.url ?? '/', hostedUrl).pathname, appBasePath));
531
+ if (assetPath !== null) {
532
+ const client = active?.client ?? null;
533
+ await serveDevAsset({
534
+ req,
535
+ res,
536
+ relativePath: assetPath,
537
+ assetsDir,
538
+ client,
539
+ clientModuleUrl: client ? clientModuleUrlFor(client) : null,
540
+ appBasePath,
541
+ });
542
+ return;
543
+ }
416
544
  if (buildFailure) {
417
545
  sendDevError(req, res, 500, buildErrorView(buildFailure), {
418
546
  htmlTail: errorPageTail,
@@ -551,11 +679,25 @@ export async function startPackageAppDev(options) {
551
679
  ? null
552
680
  : (() => {
553
681
  const pending = new Set();
682
+ clearWatcherPending = () => {
683
+ pending.clear();
684
+ clearTimeout(debounceTimer);
685
+ debouncePending = false;
686
+ const resolve = resolveDebounceFlush;
687
+ resolveDebounceFlush = null;
688
+ debounceFlush = null;
689
+ resolve?.();
690
+ };
554
691
  return createDevWatcher({
555
692
  root,
693
+ ignore: options.manifest.dev?.ignore,
556
694
  onChange: (path) => {
557
695
  if (closed)
558
696
  return;
697
+ if (prebuildRunning ||
698
+ performance.now() - prebuildSettledAt < prebuildSettleMs) {
699
+ return;
700
+ }
559
701
  // Fold mid-build watcher events into the active loop
560
702
  // (including another save of a file already building).
561
703
  // Debouncing them instead schedules a second rebuild
@@ -600,9 +742,11 @@ export async function startPackageAppDev(options) {
600
742
  resolveDebounceFlush = null;
601
743
  debounceFlush = null;
602
744
  resolve?.();
745
+ prebuildAbort?.abort();
603
746
  await watcher?.close();
604
747
  await buildLoop?.catch(() => { });
605
748
  await bundler.dispose();
749
+ await clientBundler?.dispose();
606
750
  await Promise.all([...generations].map((generation) => stopGeneration(generation)));
607
751
  reaper.close();
608
752
  await bridge.close();
@@ -649,7 +793,9 @@ export async function startPackageAppDev(options) {
649
793
  port: address.port,
650
794
  hostedUrl,
651
795
  appBasePath,
652
- packageContext,
796
+ get packageContext() {
797
+ return contextFor(active?.client ?? null);
798
+ },
653
799
  fetchPath,
654
800
  status,
655
801
  close,
@@ -748,54 +894,4 @@ function joinUrlPath(base, path) {
748
894
  return rest;
749
895
  return `${base}${rest === '/' ? '' : rest}` || base;
750
896
  }
751
- async function startBridge(input) {
752
- const inFlight = new AbortController();
753
- const server = createServer(async (request, response) => {
754
- const reply = (status, body) => {
755
- response.writeHead(status, { 'content-type': 'application/json' });
756
- response.end(JSON.stringify(body));
757
- };
758
- if (request.method !== 'POST' ||
759
- request.url !== '/call' ||
760
- request.headers[runSecretHeader] !== input.runSecret) {
761
- reply(403, { error: 'Forbidden' });
762
- return;
763
- }
764
- try {
765
- const parsed = JSON.parse(await readBody(request));
766
- if (!Array.isArray(parsed.path) ||
767
- parsed.path.length === 0 ||
768
- !parsed.path.every((segment) => typeof segment === 'string')) {
769
- throw new Error('Invalid kody:runtime call path.');
770
- }
771
- const result = await input.forward({
772
- path: parsed.path,
773
- args: Array.isArray(parsed.args) ? parsed.args : [],
774
- }, inFlight.signal);
775
- reply(200, { result });
776
- }
777
- catch (error) {
778
- reply(200, { error: redact(error instanceof Error ? error.message : String(error)) });
779
- }
780
- });
781
- await new Promise((resolve, reject) => {
782
- server.once('error', reject);
783
- server.listen(0, '127.0.0.1', () => resolve());
784
- });
785
- return {
786
- port: server.address().port,
787
- close: () => new Promise((resolve) => {
788
- inFlight.abort();
789
- server.closeAllConnections();
790
- server.close(() => resolve());
791
- }),
792
- };
793
- }
794
- async function readBody(stream) {
795
- const chunks = [];
796
- for await (const chunk of stream) {
797
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
798
- }
799
- return Buffer.concat(chunks).toString('utf8');
800
- }
801
897
  //# sourceMappingURL=package-app-dev.js.map