@akanjs/cli 2.4.1-rc.2 → 2.4.1-rc.3

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.
@@ -6,40 +6,108 @@ import {
6
6
  AppExecutor,
7
7
  WorkspaceExecutor
8
8
  } from "./index-a6sbyy0b.js";
9
+ import"./index-61keag0s.js";
9
10
  import {
10
11
  AutoImportSync,
11
- CsrArtifactBuilder,
12
12
  DevChangePlanner,
13
13
  DevGeneratedIndexSync,
14
- FontOptimizer,
15
14
  GraphClientEntryDiscovery,
16
15
  HmrWatcher,
17
- PagesBundleBuilder,
18
16
  RouteClientBuilder,
19
17
  SsrBaseArtifactBuilder,
20
18
  WatchRootResolver
21
- } from "./index-a5rmdgy4.js";
22
- import"./index-61keag0s.js";
19
+ } from "./index-q7zcac6n.js";
23
20
  import"./index-6pz1j0zj.js";
24
21
  import"./index-r24hmh0q.js";
25
22
 
26
23
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
24
+ import path2 from "path";
25
+ import { Logger as Logger2 } from "akanjs/common";
26
+
27
+ // pkgs/@akanjs/devkit/incrementalBuilder/buildBatchRunner.ts
27
28
  import path from "path";
28
29
  import { Logger } from "akanjs/common";
30
+
31
+ class BuildBatchRunner {
32
+ static #entryCandidates = (workspaceRoot) => [
33
+ path.join(workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
34
+ path.join(workspaceRoot, "node_modules/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
35
+ path.join(import.meta.dir, "buildBatch.proc.js"),
36
+ path.join(import.meta.dir, "buildBatch.proc.ts")
37
+ ];
38
+ #logger = new Logger("BuildBatchRunner");
39
+ #entry = null;
40
+ #workspaceRoot;
41
+ #cwd;
42
+ constructor({ workspaceRoot, cwd }) {
43
+ this.#workspaceRoot = workspaceRoot;
44
+ this.#cwd = cwd;
45
+ }
46
+ async#resolveEntry() {
47
+ if (this.#entry)
48
+ return this.#entry;
49
+ const candidates = BuildBatchRunner.#entryCandidates(this.#workspaceRoot);
50
+ for (const candidate of candidates) {
51
+ if (!await Bun.file(candidate).exists())
52
+ continue;
53
+ this.#entry = candidate;
54
+ return candidate;
55
+ }
56
+ throw new Error(`[build-batch] worker entry not found; looked in: ${candidates.join(", ")}`);
57
+ }
58
+ async run(request, onMessage = () => {
59
+ return;
60
+ }) {
61
+ const started = Date.now();
62
+ const entry = await this.#resolveEntry();
63
+ let result = null;
64
+ const proc = Bun.spawn(["bun", entry, JSON.stringify(request)], {
65
+ cwd: this.#cwd,
66
+ env: process.env,
67
+ stdio: ["ignore", "inherit", "inherit"],
68
+ serialization: "advanced",
69
+ ipc: (message2) => {
70
+ if (!message2 || typeof message2 !== "object")
71
+ return;
72
+ if (message2.type === "build-batch-result")
73
+ result = message2.data;
74
+ else
75
+ onMessage(message2);
76
+ }
77
+ });
78
+ const exitCode = await proc.exited;
79
+ if (result) {
80
+ this.#logger.verbose(`[build-batch] generation=${request.generation} needs=${request.needs.join(",")} done in ${Date.now() - started}ms`);
81
+ return result;
82
+ }
83
+ const message = `build worker exited with code ${exitCode} before reporting a result`;
84
+ this.#logger.error(`[build-batch] generation=${request.generation} ${message}`);
85
+ return {
86
+ generation: request.generation,
87
+ errors: Object.fromEntries(request.needs.map((need) => [need, message])),
88
+ crashed: true
89
+ };
90
+ }
91
+ }
92
+
93
+ // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
29
94
  class IncrementalBuilder {
30
- #logger = new Logger("IncrementalBuilder");
95
+ #logger = new Logger2("IncrementalBuilder");
31
96
  #app;
32
97
  #artifact;
33
98
  #watch;
34
- #cssCompiler;
35
99
  #optimizedFonts;
36
100
  #discovery;
101
+ #batchRunner;
37
102
  #changePlanner;
38
103
  #generatedIndexSync;
39
104
  #autoImportSync;
40
105
  #generation = 0;
41
106
  #csrActive = IncrementalBuilder.#csrArmedByEnv();
42
107
  #workQueue = Promise.resolve();
108
+ #inFlight = 0;
109
+ #workCount = 0;
110
+ #shuttingDown = false;
43
111
  #cssRebuildQueue = Promise.resolve();
44
112
  #cssRebuildTimer = null;
45
113
  #pendingCssRebuild = null;
@@ -47,10 +115,13 @@ class IncrementalBuilder {
47
115
  this.#app = options.app;
48
116
  this.#artifact = options.artifact;
49
117
  this.#watch = options.watch;
50
- this.#cssCompiler = options.cssCompiler;
51
118
  this.#optimizedFonts = options.optimizedFonts;
52
119
  this.#discovery = options.discovery;
53
120
  this.#generation = options.initialGeneration ?? 0;
121
+ this.#batchRunner = new BuildBatchRunner({
122
+ workspaceRoot: options.app.workspace.workspaceRoot,
123
+ cwd: options.app.cwdPath
124
+ });
54
125
  this.#changePlanner = new DevChangePlanner({ workspaceRoot: options.app.workspace.workspaceRoot });
55
126
  this.#generatedIndexSync = new DevGeneratedIndexSync({ workspaceRoot: options.app.workspace.workspaceRoot });
56
127
  this.#autoImportSync = new AutoImportSync({ workspaceRoot: options.app.workspace.workspaceRoot });
@@ -111,6 +182,7 @@ class IncrementalBuilder {
111
182
  }
112
183
  async#enqueueWork(label, fn) {
113
184
  const started = Date.now();
185
+ this.#inFlight += 1;
114
186
  const run = this.#workQueue.then(fn, fn);
115
187
  this.#workQueue = run.then(() => {
116
188
  return;
@@ -120,14 +192,51 @@ class IncrementalBuilder {
120
192
  try {
121
193
  return await run;
122
194
  } finally {
195
+ this.#inFlight -= 1;
196
+ this.#workCount += 1;
123
197
  this.#logger.verbose(`[work-queue] ${label} finished in ${Date.now() - started}ms`);
198
+ this.#reportMetrics();
124
199
  }
125
200
  }
201
+ get #idle() {
202
+ return this.#inFlight === 0 && this.#cssRebuildTimer === null;
203
+ }
204
+ #reportMetrics() {
205
+ if (!this.#idle || this.#shuttingDown)
206
+ return;
207
+ process.send?.({
208
+ type: "builder-metrics",
209
+ data: { rssBytes: process.memoryUsage.rss(), generation: this.#generation, workCount: this.#workCount }
210
+ });
211
+ }
212
+ async shutdown(reason) {
213
+ if (this.#shuttingDown)
214
+ return;
215
+ this.#shuttingDown = true;
216
+ const started = Date.now();
217
+ this.#logger.info(`shutdown requested (${reason}); draining ${this.#inFlight} work item(s)`);
218
+ if (this.#cssRebuildTimer) {
219
+ clearTimeout(this.#cssRebuildTimer);
220
+ this.#cssRebuildTimer = null;
221
+ this.#pendingCssRebuild = null;
222
+ }
223
+ await this.#workQueue.catch(() => {
224
+ return;
225
+ });
226
+ await this.#cssRebuildQueue.catch(() => {
227
+ return;
228
+ });
229
+ this.#logger.info(`drained in ${Date.now() - started}ms; exiting for recycle`);
230
+ process.exit(0);
231
+ }
232
+ get shuttingDown() {
233
+ return this.#shuttingDown;
234
+ }
126
235
  batchTouchesPagesTree(appDir, batch) {
127
- const absAppDir = path.resolve(appDir);
236
+ const absAppDir = path2.resolve(appDir);
128
237
  for (const f of batch.files) {
129
- const abs = path.resolve(f);
130
- if (!abs.startsWith(`${absAppDir}${path.sep}`) && abs !== absAppDir)
238
+ const abs = path2.resolve(f);
239
+ if (!abs.startsWith(`${absAppDir}${path2.sep}`) && abs !== absAppDir)
131
240
  continue;
132
241
  if (/\.(tsx|ts|jsx|js)$/.test(abs))
133
242
  return true;
@@ -135,67 +244,22 @@ class IncrementalBuilder {
135
244
  return false;
136
245
  }
137
246
  async batchMayChangePageKeys(appDir, batch) {
138
- const absAppDir = path.resolve(appDir);
139
- const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path.normalize(key)));
247
+ const absAppDir = path2.resolve(appDir);
248
+ const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path2.normalize(key)));
140
249
  for (const f of batch.files) {
141
- const abs = path.resolve(f);
142
- if (!abs.startsWith(`${absAppDir}${path.sep}`) && abs !== absAppDir)
250
+ const abs = path2.resolve(f);
251
+ if (!abs.startsWith(`${absAppDir}${path2.sep}`) && abs !== absAppDir)
143
252
  continue;
144
253
  if (!/\.(tsx|ts|jsx|js)$/.test(abs))
145
254
  continue;
146
- const rel = path.normalize(path.relative(absAppDir, abs));
255
+ const rel = path2.normalize(path2.relative(absAppDir, abs));
147
256
  if (!await Bun.file(abs).exists() || !pageKeys.has(rel))
148
257
  return true;
149
258
  }
150
259
  return false;
151
260
  }
152
- async rebuildCssArtifact(artifactDir, { refresh, generation, changedFiles }) {
153
- const cssStarted = Date.now();
154
- const cssByBasePathStarted = Date.now();
155
- const cssByBasePath = await this.#cssCompiler.getCssByBasePath({ refresh });
156
- this.#logger.verbose(`css-get-by-base-path ok (${Date.now() - cssByBasePathStarted}ms)`);
157
- const fontStarted = Date.now();
158
- const optimizedFonts = await this.#getOptimizedFonts(changedFiles ?? []);
159
- this.#logger.verbose(`font-assets ready (${Date.now() - fontStarted}ms)`);
160
- const cssAssetEntries = [];
161
- const cssBase64ByUrl = {};
162
- await Promise.all(Object.entries(cssByBasePath).flatMap(([basePath, baseCssText]) => {
163
- const cssText = [baseCssText, optimizedFonts.css].filter(Boolean).join(`
164
- `);
165
- if (!cssText)
166
- return [];
167
- return [
168
- (async () => {
169
- const cssAssetName = basePath || "root";
170
- const cssHash = Bun.hash(`${basePath}
171
- ${cssText}`).toString(36);
172
- const cssRelPath = `styles/${cssAssetName}-${cssHash}.css`;
173
- const cssUrl = `/_akan/styles/${cssAssetName}-${cssHash}.css`;
174
- await Bun.write(path.join(artifactDir, cssRelPath), cssText);
175
- cssAssetEntries.push([basePath, { cssUrl, cssRelPath }]);
176
- cssBase64ByUrl[cssUrl] = Buffer.from(new TextEncoder().encode(cssText)).toString("base64");
177
- })()
178
- ];
179
- }));
180
- const cssAssets = Object.fromEntries(cssAssetEntries);
181
- if (JSON.stringify(this.#artifact.cssAssets ?? {}) === JSON.stringify(cssAssets)) {
182
- this.#logger.verbose("css-rebuild unchanged assets; broadcast skipped");
183
- return;
184
- }
185
- this.#artifact = { ...this.#artifact, cssAssets };
186
- this.#logger.verbose(`css-compile ok assets=${Object.keys(cssAssets).length} (${Date.now() - cssStarted}ms)`);
187
- process.send?.({
188
- type: "css-updated",
189
- data: {
190
- cssAssets,
191
- cssBase64ByUrl,
192
- generation,
193
- changedFiles
194
- }
195
- });
196
- }
197
- scheduleCssRebuild(artifactDir, { refresh, generation, changedFiles }) {
198
- this.#pendingCssRebuild = { artifactDir, refresh, generation, changedFiles };
261
+ scheduleCssRebuild({ generation, changedFiles }) {
262
+ this.#pendingCssRebuild = { generation, changedFiles };
199
263
  if (this.#cssRebuildTimer)
200
264
  clearTimeout(this.#cssRebuildTimer);
201
265
  this.#cssRebuildTimer = setTimeout(() => {
@@ -204,15 +268,13 @@ ${cssText}`).toString(36);
204
268
  this.#pendingCssRebuild = null;
205
269
  if (!next)
206
270
  return;
271
+ this.#inFlight += 1;
207
272
  this.#cssRebuildQueue = this.#cssRebuildQueue.then(async () => {
208
- const started = Date.now();
209
- await this.rebuildCssArtifact(next.artifactDir, {
210
- refresh: next.refresh,
211
- generation: next.generation,
212
- changedFiles: next.changedFiles
273
+ await this.#runBatch({
274
+ generation: next.generation ?? this.#generation,
275
+ needs: ["css"],
276
+ changedFiles: next.changedFiles ?? []
213
277
  });
214
- this.#sendBuildStatus("css", { generation: next.generation, ok: true, files: next.changedFiles });
215
- this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
216
278
  }).catch((err) => {
217
279
  const message = err instanceof Error ? err.message : String(err);
218
280
  this.#logger.error(`css-rebuild failed: ${message}`);
@@ -222,29 +284,12 @@ ${cssText}`).toString(36);
222
284
  files: next.changedFiles,
223
285
  message
224
286
  });
287
+ }).finally(() => {
288
+ this.#inFlight -= 1;
289
+ this.#reportMetrics();
225
290
  });
226
291
  }, 150);
227
292
  }
228
- async#getOptimizedFonts(changedFiles) {
229
- if (!this.#shouldReoptimizeFonts(changedFiles)) {
230
- this.#logger.verbose(`font-optimize cached files=${this.#optimizedFonts.files.length}`);
231
- return this.#optimizedFonts;
232
- }
233
- const started = Date.now();
234
- this.#optimizedFonts = await new FontOptimizer(this.#app, "start").optimize();
235
- this.#logger.verbose(`font-optimize ok files=${this.#optimizedFonts.files.length} (${Date.now() - started}ms)`);
236
- return this.#optimizedFonts;
237
- }
238
- #shouldReoptimizeFonts(changedFiles) {
239
- if (changedFiles.length === 0)
240
- return false;
241
- return changedFiles.some((file) => {
242
- const normalized = path.resolve(file);
243
- if (/\.(woff2?|ttf|otf)$/i.test(normalized))
244
- return true;
245
- return this.#optimizedFonts.files.some((fontFile) => path.resolve(fontFile) === normalized);
246
- });
247
- }
248
293
  async installWatcher() {
249
294
  const [appDir, artifactDir] = [`${this.#app.cwdPath}/page`, this.#artifactDir];
250
295
  const roots = await new WatchRootResolver(this.#app).resolve();
@@ -306,42 +351,60 @@ ${cssText}`).toString(36);
306
351
  } else if (kinds.includes("code") && rebuildClient && this.batchTouchesPagesTree(appDir, expandedBatch)) {
307
352
  this.#logger.verbose("pageKeys refresh skipped; changed page source cannot add/remove a route key");
308
353
  }
309
- if (kinds.includes("code") && rebuildClient && this.#shouldRebuildCsr()) {
310
- try {
311
- const started = Date.now();
312
- await new CsrArtifactBuilder(this.#app).build();
313
- this.#sendBuildStatus("csr", { generation, ok: true, files });
314
- this.#logger.verbose(`csr-rebundle ok (${Date.now() - started}ms)`);
315
- } catch (err) {
316
- const message = err instanceof Error ? err.message : String(err);
317
- this.#logger.error(`csr-rebundle failed: ${message}`);
318
- this.#sendBuildStatus("csr", { generation, ok: false, files, message });
319
- }
320
- } else if (kinds.includes("code") && rebuildClient) {
321
- this.#logger.verbose(`csr-rebundle skipped; request /__csr or ?csr=true (or set AKAN_DEV_CSR_REBUILD=1) to enable per-save CSR rebuilds`);
322
- }
323
- process.send?.(event);
354
+ const needs = [];
324
355
  if (kinds.includes("code") && rebuildClient) {
325
- try {
326
- const started = Date.now();
327
- const next = await new PagesBundleBuilder(this.#app).build();
328
- process.send?.({
329
- type: "pages-updated",
330
- data: { bundlePath: next.bundlePath, buildId: next.buildId, generation, changedFiles: files }
331
- });
332
- this.#sendBuildStatus("pages", { generation, ok: true, files });
333
- this.#logger.verbose(`pages-rebundle ok buildId=${next.buildId} (${Date.now() - started}ms)`);
334
- } catch (err) {
335
- const message = err instanceof Error ? err.message : String(err);
336
- this.#logger.error(`pages-rebundle failed: ${message}`);
337
- this.#sendBuildStatus("pages", { generation, ok: false, files, message });
338
- }
356
+ if (this.#shouldRebuildCsr())
357
+ needs.push("csr");
358
+ else
359
+ this.#logger.verbose(`csr-rebundle skipped; request /__csr or ?csr=true (or set AKAN_DEV_CSR_REBUILD=1) to enable per-save CSR rebuilds`);
360
+ needs.push("pages");
361
+ needs.push("css");
339
362
  }
340
- if (kinds.includes("css") || kinds.includes("code") && rebuildClient) {
341
- this.scheduleCssRebuild(artifactDir, { refresh: true, generation, changedFiles: files });
363
+ process.send?.(event);
364
+ if (needs.length > 0)
365
+ await this.#runBatch({ generation, needs, changedFiles: files });
366
+ else if (kinds.includes("css")) {
367
+ this.scheduleCssRebuild({ generation, changedFiles: files });
342
368
  this.#logger.verbose(`css-rebuild scheduled generation=${generation}`);
343
369
  }
344
370
  }
371
+ async#runBatch({
372
+ generation,
373
+ needs,
374
+ changedFiles
375
+ }) {
376
+ const started = Date.now();
377
+ const result = await this.#batchRunner.run(await this.#batchRequest({ generation, needs, changedFiles }), (msg) => process.send?.(msg));
378
+ if (result.optimizedFonts)
379
+ this.#optimizedFonts = result.optimizedFonts;
380
+ if (result.cssAssets)
381
+ this.#artifact = { ...this.#artifact, cssAssets: result.cssAssets };
382
+ if (result.crashed) {
383
+ for (const need of needs)
384
+ this.#sendBuildStatus(need, { generation, ok: false, files: changedFiles, message: result.errors[need] });
385
+ }
386
+ if (needs.includes("css"))
387
+ this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
388
+ return result;
389
+ }
390
+ async#batchRequest({
391
+ generation,
392
+ needs,
393
+ changedFiles
394
+ }) {
395
+ return {
396
+ appName: this.#app.name,
397
+ workspaceRoot: this.#app.workspace.workspaceRoot,
398
+ repoName: this.#app.workspace.repoName,
399
+ generation,
400
+ needs,
401
+ changedFiles,
402
+ pageKeys: await this.#app.getPageKeys(),
403
+ optimizedFonts: this.#optimizedFonts,
404
+ cssAssets: this.#artifact.cssAssets ?? null,
405
+ artifactDir: path2.resolve(this.#artifactDir)
406
+ };
407
+ }
345
408
  async boot() {
346
409
  if (this.#watch)
347
410
  await this.installWatcher();
@@ -351,34 +414,45 @@ ${cssText}`).toString(36);
351
414
  async announceRecoveredState(changedFiles) {
352
415
  const generation = ++this.#generation;
353
416
  await this.#enqueueWork("boot-recovered", async () => {
354
- try {
355
- const next = await new PagesBundleBuilder(this.#app).build();
356
- process.send?.({
357
- type: "pages-updated",
358
- data: { bundlePath: next.bundlePath, buildId: next.buildId, generation, changedFiles }
359
- });
360
- this.#sendBuildStatus("pages", { generation, ok: true, files: changedFiles, message: "Boot build recovered" });
361
- } catch (err) {
362
- const message = err instanceof Error ? err.message : String(err);
363
- this.#logger.error(`recovered pages rebundle failed: ${message}`);
364
- this.#sendBuildStatus("pages", { generation, ok: false, files: changedFiles, message });
417
+ await this.#runBatch({ generation, needs: ["pages", "css"], changedFiles });
418
+ });
419
+ }
420
+ async announceBootState() {
421
+ const generation = ++this.#generation;
422
+ const reason = "builder-recycle";
423
+ process.send?.({
424
+ type: "pages-updated",
425
+ data: {
426
+ bundlePath: this.#artifact.pagesBundlePath,
427
+ buildId: this.#artifact.pagesBundleBuildId,
428
+ generation,
429
+ changedFiles: [],
430
+ reason
365
431
  }
366
- this.scheduleCssRebuild(this.#artifactDir, { refresh: true, generation, changedFiles });
367
432
  });
433
+ const cssAssets = this.#artifact.cssAssets ?? {};
434
+ const cssBase64ByUrl = Object.fromEntries(await Promise.all(Object.values(cssAssets).map(async ({ cssUrl, cssRelPath }) => [
435
+ cssUrl,
436
+ Buffer.from(await Bun.file(path2.join(this.#artifactDir, cssRelPath)).arrayBuffer()).toString("base64")
437
+ ])));
438
+ process.send?.({
439
+ type: "css-updated",
440
+ data: { cssAssets, cssBase64ByUrl, generation, changedFiles: [], reason }
441
+ });
442
+ this.#logger.verbose(`announced boot state after recycle generation=${generation}`);
368
443
  }
369
444
  async handleBuildCsr(msg) {
370
445
  return this.#enqueueWork("build-csr", async () => {
371
446
  const started = Date.now();
372
- try {
373
- await new CsrArtifactBuilder(this.#app).build();
374
- this.#csrActive = true;
375
- this.#logger.info(`csr-build ok on demand (${Date.now() - started}ms); rebuilding CSR on every save now`);
376
- return { type: "build-csr-res", id: msg.id, ok: true };
377
- } catch (err) {
378
- const message = err instanceof Error ? err.message : String(err);
379
- this.#logger.error(`csr-build failed: ${message}`);
380
- return { type: "build-csr-res", id: msg.id, ok: false, error: message };
447
+ const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }));
448
+ const error = result.errors.csr;
449
+ if (error) {
450
+ this.#logger.error(`csr-build failed: ${error}`);
451
+ return { type: "build-csr-res", id: msg.id, ok: false, error };
381
452
  }
453
+ this.#csrActive = true;
454
+ this.#logger.info(`csr-build ok on demand (${Date.now() - started}ms); rebuilding CSR on every save now`);
455
+ return { type: "build-csr-res", id: msg.id, ok: true };
382
456
  });
383
457
  }
384
458
  #shouldRebuildCsr() {
@@ -388,11 +462,21 @@ ${cssText}`).toString(36);
388
462
  return process.env.AKAN_DEV_CSR_REBUILD === "1";
389
463
  }
390
464
  static async#buildBootDeps(app) {
391
- const { artifact, cssCompiler, optimizedFonts } = await new SsrBaseArtifactBuilder(app).build();
392
- if (IncrementalBuilder.#csrArmedByEnv())
393
- await new CsrArtifactBuilder(app).build();
465
+ const { artifact, optimizedFonts } = await new SsrBaseArtifactBuilder(app).build();
394
466
  const discovery = await GraphClientEntryDiscovery.create(app);
395
- return { artifact, cssCompiler, optimizedFonts, discovery };
467
+ return { artifact, optimizedFonts, discovery };
468
+ }
469
+ async rearmCsrFromEnv() {
470
+ if (!IncrementalBuilder.#csrArmedByEnv())
471
+ return;
472
+ this.#csrActive = true;
473
+ await this.#enqueueWork("build-csr-rearm", async () => {
474
+ const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }));
475
+ if (result.errors.csr)
476
+ this.#logger.error(`csr-rearm failed: ${result.errors.csr}`);
477
+ else
478
+ this.#logger.verbose("csr-rearm ok; this session had CSR armed before the builder restarted");
479
+ });
396
480
  }
397
481
  static #recoverBoot(app, bootError, logger) {
398
482
  const firstMessage = bootError instanceof Error ? bootError.message : String(bootError);
@@ -436,7 +520,7 @@ ${cssText}`).toString(36);
436
520
  });
437
521
  }
438
522
  static async main() {
439
- const logger = new Logger("IncrementalBuilder");
523
+ const logger = new Logger2("IncrementalBuilder");
440
524
  const { appName, repoName, workspaceRoot } = WorkspaceExecutor.getBaseDevEnv();
441
525
  if (!workspaceRoot || !appName)
442
526
  throw new Error("AKAN_WORKSPACE_ROOT or AKAN_PUBLIC_APP_NAME is not set");
@@ -445,20 +529,31 @@ ${cssText}`).toString(36);
445
529
  const watch = process.env.AKAN_WATCH !== "0";
446
530
  let builder = null;
447
531
  const bootingError = "builder is recovering from a failed boot build; retry after the build error is fixed";
532
+ const recyclingError = "builder is recycling to release bundler memory; retry after it restarts";
448
533
  process.on("message", (msg) => {
449
534
  if (!msg || typeof msg !== "object")
450
535
  return;
451
- if (msg.type === "build-route") {
536
+ if (msg.type === "builder-shutdown") {
452
537
  if (!builder) {
453
- process.send?.({ type: "build-route-res", id: msg.id, ok: false, error: bootingError });
538
+ logger.warn(`ignoring shutdown request (${msg.reason}); builder is still recovering from a failed boot`);
539
+ return;
540
+ }
541
+ builder.shutdown(msg.reason);
542
+ return;
543
+ }
544
+ if (msg.type === "build-route") {
545
+ const error = builder?.shuttingDown ? recyclingError : bootingError;
546
+ if (!builder || builder.shuttingDown) {
547
+ process.send?.({ type: "build-route-res", id: msg.id, ok: false, error });
454
548
  return;
455
549
  }
456
550
  builder.handleBuildRoute(msg).then((res) => process.send?.(res));
457
551
  return;
458
552
  }
459
553
  if (msg.type === "build-csr") {
460
- if (!builder) {
461
- process.send?.({ type: "build-csr-res", id: msg.id, ok: false, error: bootingError });
554
+ const error = builder?.shuttingDown ? recyclingError : bootingError;
555
+ if (!builder || builder.shuttingDown) {
556
+ process.send?.({ type: "build-csr-res", id: msg.id, ok: false, error });
462
557
  return;
463
558
  }
464
559
  builder.handleBuildCsr(msg).then((res) => process.send?.(res));
@@ -481,6 +576,9 @@ ${cssText}`).toString(36);
481
576
  await builder.boot();
482
577
  if (recoveredFiles)
483
578
  await builder.announceRecoveredState(recoveredFiles);
579
+ else if (process.env.AKAN_BUILDER_RECYCLED === "1")
580
+ await builder.announceBootState();
581
+ await builder.rearmCsrFromEnv();
484
582
  }
485
583
  }
486
584
  IncrementalBuilder.main().catch((err) => {
@@ -1,18 +1,18 @@
1
1
  // @bun
2
2
  import {
3
3
  CloudRunner
4
- } from "./index-sgmas1fc.js";
5
- import {
6
- ApplicationScript
7
- } from "./index-77crfweb.js";
8
- import {
9
- PackageScript
10
- } from "./index-n6h3482q.js";
4
+ } from "./index-dynknvzd.js";
11
5
  import {
12
6
  AiSession,
13
7
  CloudApi,
14
8
  GlobalConfig
15
9
  } from "./index-5vvwc0cz.js";
10
+ import {
11
+ ApplicationScript
12
+ } from "./index-rgfxj6qc.js";
13
+ import {
14
+ PackageScript
15
+ } from "./index-n6h3482q.js";
16
16
  import {
17
17
  script
18
18
  } from "./index-hdqztm58.js";
@@ -1,16 +1,19 @@
1
1
  // @bun
2
+ import {
3
+ RepairRunner
4
+ } from "./index-85msc0wg.js";
2
5
  import {
3
6
  WorkflowRunner
4
7
  } from "./index-45aj5ry0.js";
5
8
  import {
6
9
  ScalarScript
7
- } from "./index-y3hdhy4p.js";
10
+ } from "./index-gb26e7z0.js";
8
11
  import {
9
12
  PrimitiveScript
10
- } from "./index-73pr2cmy.js";
13
+ } from "./index-m9pca6jv.js";
11
14
  import {
12
- RepairRunner
13
- } from "./index-85msc0wg.js";
15
+ ModuleScript
16
+ } from "./index-fmky5k3p.js";
14
17
  import {
15
18
  isPlaceholderAppId
16
19
  } from "./index-1xdrsbry.js";
@@ -25,15 +28,6 @@ import {
25
28
  resourceList,
26
29
  upsertCodexMcpServerBlock
27
30
  } from "./index-8rc0bm04.js";
28
- import {
29
- getMobileTargets
30
- } from "./index-76rn3g2c.js";
31
- import {
32
- ModuleScript
33
- } from "./index-8pkbzj26.js";
34
- import {
35
- Prompter
36
- } from "./index-qaq13qk3.js";
37
31
  import {
38
32
  buildAkanModuleContextIndex,
39
33
  createWorkflowBaselineSummary,
@@ -41,6 +35,12 @@ import {
41
35
  jsonText,
42
36
  toolingRolloutGate
43
37
  } from "./index-h6ca6qg0.js";
38
+ import {
39
+ Prompter
40
+ } from "./index-qaq13qk3.js";
41
+ import {
42
+ getMobileTargets
43
+ } from "./index-76rn3g2c.js";
44
44
  import {
45
45
  CommandContainer,
46
46
  runner,
@@ -1,13 +1,13 @@
1
1
  // @bun
2
- import {
3
- openBrowser
4
- } from "./index-77crfweb.js";
5
2
  import {
6
3
  AiSession,
7
4
  CloudApi,
8
5
  GlobalConfig,
9
6
  getDefaultHostConfig
10
7
  } from "./index-5vvwc0cz.js";
8
+ import {
9
+ openBrowser
10
+ } from "./index-rgfxj6qc.js";
11
11
  import {
12
12
  runner
13
13
  } from "./index-hdqztm58.js";
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  ModuleScript
4
- } from "./index-8pkbzj26.js";
4
+ } from "./index-fmky5k3p.js";
5
5
  import {
6
6
  addFieldUiPolicyForType,
7
7
  coerceFieldDefault,