@akanjs/cli 3.0.0-beta.24 → 3.0.0-beta.25

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 (70) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-0dh9fzzg.js → CodeAgent-p65jja35.js} +2 -2
  3. package/{CodeTui-drwdvwg9.js → CodeTui-5xr8kxx4.js} +1 -1
  4. package/TunnelShare-cwh9ezsm.js +9 -0
  5. package/{agent.command-ttn09rx3.js → agent.command-rp505zxa.js} +7 -7
  6. package/akanCode.js +2 -2
  7. package/{application.command-z7a4bdrq.js → application.command-2x9jqy0k.js} +7 -7
  8. package/{applicationBuildRunner-n6xfexaa.js → applicationBuildRunner-tm0vba5t.js} +4 -4
  9. package/buildBatch.proc.js +77 -23
  10. package/{cloud.command-j0fcz44z.js → cloud.command-j6m607bp.js} +12 -12
  11. package/cloud.script-r5t7nhee.js +20 -0
  12. package/{code.command-1jgk14tc.js → code.command-jhrjttm1.js} +7 -7
  13. package/{context.command-4vpjmg22.js → context.command-xncdvv7n.js} +14 -14
  14. package/context.runner-pjy89k2s.js +21 -0
  15. package/{dependencyScanner-ye2e6pdz.js → dependencyScanner-06z2dmrr.js} +2 -2
  16. package/{fileSys-5ngz93ga.js → fileSys-wzzrntkw.js} +1 -1
  17. package/{guideline.command-7jk47fgq.js → guideline.command-d5qmxx29.js} +4 -4
  18. package/guidelines/conventions/conventions.instruction.md +7 -5
  19. package/incrementalBuilder.proc.js +544 -72
  20. package/{index-ga6hygxr.js → index-0n9q1f23.js} +3 -3
  21. package/{index-3t7g9e4r.js → index-2ahj896a.js} +1 -1
  22. package/{index-zabxte74.js → index-2wc41k5w.js} +1 -1
  23. package/{index-khpfpcsd.js → index-48k96p55.js} +27 -7
  24. package/{index-ne50hc2m.js → index-4mgbzgb5.js} +456 -1623
  25. package/{index-2gh9sndr.js → index-50rq2qms.js} +4 -4
  26. package/{index-5b6tavv3.js → index-52w0fvh8.js} +15 -1
  27. package/{index-zbc2q6a6.js → index-56xwsgyn.js} +3 -3
  28. package/{index-rmcw8yc8.js → index-58cyna2s.js} +2 -2
  29. package/{index-1db2cw0y.js → index-5mm3g24t.js} +2 -2
  30. package/{index-wp43xrjv.js → index-6dt6zft6.js} +4 -4
  31. package/{index-vj1cwwy5.js → index-7yxndzea.js} +7 -7
  32. package/{index-q44jbw48.js → index-860zjnr1.js} +1 -1
  33. package/{index-d4mfjgm0.js → index-8k93esfr.js} +6 -6
  34. package/{index-fzd22vjh.js → index-94s080qm.js} +3 -17
  35. package/{index-yy4s1j72.js → index-d65fzxvd.js} +1 -1
  36. package/{index-m0ms3n1c.js → index-da72pfdv.js} +7 -7
  37. package/index-dt1x62ec.js +4108 -0
  38. package/{index-4dn1p115.js → index-ggfhz9ab.js} +4 -4
  39. package/{index-9629w7ns.js → index-hzy4pe24.js} +3 -3
  40. package/{index-ymgtbmf1.js → index-pb10njz7.js} +191 -58
  41. package/{index-mgfx3mfs.js → index-per2kqy9.js} +4 -4
  42. package/{index-aaz7n8s2.js → index-qm4fppfk.js} +4 -4
  43. package/{index-ya5qbmyg.js → index-t02g6dpn.js} +3 -3
  44. package/{index-hb8sgepf.js → index-tpdn30ya.js} +1 -1
  45. package/{index-rnt66vym.js → index-v4hnkqz9.js} +1 -1
  46. package/{index-pp54p5zk.js → index-xfgdmm5d.js} +2 -2
  47. package/{index-tgjy8xf3.js → index-zazvn45w.js} +4 -4
  48. package/{index-0bq14vr1.js → index-zzvvc845.js} +1 -1
  49. package/index.js +22 -22
  50. package/{library.command-ccb1cp92.js → library.command-sv6g1h2g.js} +4 -4
  51. package/{localRegistry.command-gmj1k6q8.js → localRegistry.command-4kj56531.js} +11 -11
  52. package/{module.command-5k6h407d.js → module.command-ccdeder9.js} +6 -6
  53. package/{package.command-e6ntvpez.js → package.command-xmw605b1.js} +5 -5
  54. package/package.json +3 -3
  55. package/{page.command-v4f90wb8.js → page.command-ky7vafsn.js} +4 -4
  56. package/{primitive.command-g6km57q5.js → primitive.command-bh6j8nfs.js} +7 -7
  57. package/{prompter-ptaa3nk0.js → prompter-76k56b37.js} +2 -2
  58. package/{quality.command-de3p36pz.js → quality.command-rqqrpe98.js} +3 -3
  59. package/{repair.command-d3x1javq.js → repair.command-n9nsm9qe.js} +6 -6
  60. package/{scalar.command-7ezrac2b.js → scalar.command-t09gmck8.js} +5 -5
  61. package/{subspace.command-ksenprez.js → subspace.command-ygfdqpbj.js} +6 -6
  62. package/{tunnel.command-54947jck.js → tunnel.command-4g95vhsn.js} +5 -5
  63. package/{workflow.command-z2p32nmf.js → workflow.command-b91ph8sx.js} +11 -11
  64. package/{workspace.command-tkn8g0bn.js → workspace.command-p44d21qf.js} +24 -24
  65. package/README.ko.md +0 -72
  66. package/README.md +0 -88
  67. package/TunnelShare-pemaknmd.js +0 -9
  68. package/cloud.script-g2dcnr8c.js +0 -20
  69. package/context.runner-ehjs419v.js +0 -21
  70. package/index-fgq75haa.js +0 -2084
@@ -3,27 +3,33 @@ import {
3
3
  ApplicationBuildReporter,
4
4
  HmrWatcher,
5
5
  WatchRootResolver
6
- } from "./index-khpfpcsd.js";
6
+ } from "./index-48k96p55.js";
7
7
  import {
8
8
  CodegenLock,
9
9
  WorkspaceExecutor,
10
10
  AppExecutor
11
- } from "./index-ga6hygxr.js";
12
- import"./index-5b6tavv3.js";
11
+ } from "./index-0n9q1f23.js";
12
+ import"./index-52w0fvh8.js";
13
13
  import {
14
14
  GraphClientEntryDiscovery,
15
15
  RouteClientBuilder,
16
16
  AutoImportSync,
17
+ ServerGraphFile,
18
+ CsrDevPatcher,
19
+ CsrDevBundler,
17
20
  DevChangePlanner,
18
- DevGeneratedIndexSync
19
- } from "./index-fgq75haa.js";
21
+ DevGeneratedIndexSync,
22
+ SsrDevBundler
23
+ } from "./index-dt1x62ec.js";
20
24
  import {
21
25
  prepareDevWatchBatch
22
26
  } from "./index-a5vmm6x4.js";
23
27
 
24
28
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
29
+ import { rm } from "fs/promises";
25
30
  import path2 from "path";
26
31
  import { Logger as Logger2 } from "akanjs/common";
32
+ import { CSR_DEV_PATCHING_MARKER } from "akanjs/server/hmr/csrDevManifest";
27
33
 
28
34
  // pkgs/@akanjs/devkit/incrementalBuilder/buildBatchRunner.ts
29
35
  import path from "path";
@@ -61,7 +67,9 @@ class BuildBatchRunner {
61
67
  const started = Date.now();
62
68
  const entry = await this.#resolveEntry();
63
69
  let result = null;
64
- const proc = Bun.spawn(["bun", entry, JSON.stringify(request)], {
70
+ const reported = new Map;
71
+ const payload = request.trace ? { ...request, trace: { ...request.trace, spawnAt: Date.now() } } : request;
72
+ const proc = Bun.spawn(["bun", entry, JSON.stringify(payload)], {
65
73
  cwd: this.#cwd,
66
74
  env: process.env,
67
75
  stdio: ["ignore", "inherit", "inherit"],
@@ -71,8 +79,11 @@ class BuildBatchRunner {
71
79
  return;
72
80
  if (message.type === "build-batch-result")
73
81
  result = message.data;
74
- else
82
+ else {
83
+ if (message.type === "build-status")
84
+ reported.set(message.data.phase, message.data.message);
75
85
  onMessage(message);
86
+ }
76
87
  }
77
88
  });
78
89
  const exitCode = await proc.exited;
@@ -82,11 +93,14 @@ class BuildBatchRunner {
82
93
  }
83
94
  const message = proc.signalCode ? `build worker was killed by ${proc.signalCode} before reporting a result${proc.signalCode === "SIGKILL" ? " \u2014 most often the kernel OOM killer; check the sandbox's memory limit" : ""}` : `build worker exited with code ${exitCode} before reporting a result`;
84
95
  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
- };
96
+ const crashedNeeds = request.needs.filter((need) => !reported.has(need));
97
+ const failed = request.needs.flatMap((need) => {
98
+ if (!reported.has(need))
99
+ return [[need, message]];
100
+ const error = reported.get(need);
101
+ return error ? [[need, error]] : [];
102
+ });
103
+ return { generation: request.generation, errors: Object.fromEntries(failed), crashed: true, crashedNeeds };
90
104
  }
91
105
  }
92
106
 
@@ -123,6 +137,90 @@ class BuilderChannel {
123
137
  }
124
138
  }
125
139
 
140
+ // pkgs/@akanjs/devkit/incrementalBuilder/builderWorkQueue.ts
141
+ class BuilderWorkQueue {
142
+ #runBatch;
143
+ #onSettled;
144
+ #pending = [];
145
+ #running = null;
146
+ #idle = Promise.resolve();
147
+ #wakeIdle = null;
148
+ constructor({ runBatch, onSettled }) {
149
+ this.#runBatch = runBatch;
150
+ this.#onSettled = onSettled;
151
+ }
152
+ get size() {
153
+ return this.#pending.length + (this.#running ? 1 : 0);
154
+ }
155
+ enqueueBatch(batch) {
156
+ const tail = this.#pending.at(-1);
157
+ if (tail?.batch) {
158
+ tail.batch = BuilderWorkQueue.merge(tail.batch, batch);
159
+ return tail.settle.promise;
160
+ }
161
+ const job = this.#job("batch", batch, async () => {
162
+ return;
163
+ });
164
+ job.run = async () => await this.#runBatch(job.batch ?? batch);
165
+ return this.#push(job);
166
+ }
167
+ enqueue(label, run) {
168
+ let result;
169
+ const job = this.#job(label, null, async () => {
170
+ result = await run();
171
+ });
172
+ return this.#push(job).then(() => result);
173
+ }
174
+ async drain() {
175
+ while (this.size > 0)
176
+ await this.#idle;
177
+ }
178
+ static merge(into, next) {
179
+ const discovery = into.discovery || next.discovery ? {
180
+ files: [...new Set([...into.discovery?.files ?? [], ...next.discovery?.files ?? []])],
181
+ refresh: !!into.discovery?.refresh || !!next.discovery?.refresh,
182
+ generation: Math.max(into.discovery?.generation ?? 0, next.discovery?.generation ?? 0)
183
+ } : undefined;
184
+ return {
185
+ generation: Math.max(into.generation, next.generation),
186
+ needs: [...new Set([...into.needs, ...next.needs])],
187
+ changedFiles: [...new Set([...into.changedFiles, ...next.changedFiles])],
188
+ trace: next.trace ?? into.trace,
189
+ ...discovery ? { discovery } : {}
190
+ };
191
+ }
192
+ #job(label, batch, run) {
193
+ return { label, batch, run, settle: Promise.withResolvers() };
194
+ }
195
+ #push(job) {
196
+ if (this.size === 0)
197
+ this.#idle = new Promise((resolve) => {
198
+ this.#wakeIdle = resolve;
199
+ });
200
+ this.#pending.push(job);
201
+ if (!this.#running)
202
+ this.#pump();
203
+ return job.settle.promise;
204
+ }
205
+ async#pump() {
206
+ for (let job = this.#pending.shift();job; job = this.#pending.shift()) {
207
+ this.#running = job;
208
+ const started = Date.now();
209
+ try {
210
+ await job.run();
211
+ job.settle.resolve();
212
+ } catch (error) {
213
+ job.settle.reject(error);
214
+ } finally {
215
+ this.#running = null;
216
+ this.#onSettled?.(job.label, Date.now() - started);
217
+ }
218
+ }
219
+ this.#wakeIdle?.();
220
+ this.#wakeIdle = null;
221
+ }
222
+ }
223
+
126
224
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
127
225
  class IncrementalBuilder {
128
226
  #logger = new Logger2("IncrementalBuilder");
@@ -137,8 +235,29 @@ class IncrementalBuilder {
137
235
  #autoImportSync;
138
236
  #watcher = null;
139
237
  #generation = 0;
238
+ #discoveredThrough = 0;
140
239
  #csrActive = IncrementalBuilder.#csrArmedByEnv();
141
- #workQueue = Promise.resolve();
240
+ #csrBundler;
241
+ #patcher;
242
+ #csrGate = Promise.resolve();
243
+ #csrArming = false;
244
+ #csrArms = 0;
245
+ #crashRetryTimer = null;
246
+ #crashRetryEntries = new Set;
247
+ #csrWasActive = false;
248
+ #csrArmSucceeded = false;
249
+ #ssrBundler;
250
+ #ssrPatcher;
251
+ #ssrLock = Promise.resolve();
252
+ #delegations = new Map;
253
+ #ssrArming = null;
254
+ #ssrBroken = false;
255
+ #ssrCrashedAt = null;
256
+ static #crashRetryMs = 1e4;
257
+ #beside = new Set;
258
+ #barrelFailed = false;
259
+ #fastQueue = Promise.resolve();
260
+ #workQueue;
142
261
  #inFlight = 0;
143
262
  #workCount = 0;
144
263
  #shuttingDown = false;
@@ -152,6 +271,7 @@ class IncrementalBuilder {
152
271
  this.#optimizedFonts = options.optimizedFonts;
153
272
  this.#discovery = options.discovery;
154
273
  this.#generation = options.initialGeneration ?? 0;
274
+ this.#discoveredThrough = this.#generation;
155
275
  this.#batchRunner = new BuildBatchRunner({
156
276
  workspaceRoot: options.app.workspace.workspaceRoot,
157
277
  cwd: options.app.cwdPath
@@ -159,14 +279,27 @@ class IncrementalBuilder {
159
279
  this.#changePlanner = new DevChangePlanner({ workspaceRoot: options.app.workspace.workspaceRoot });
160
280
  this.#generatedIndexSync = new DevGeneratedIndexSync({ workspaceRoot: options.app.workspace.workspaceRoot });
161
281
  this.#autoImportSync = new AutoImportSync({ workspaceRoot: options.app.workspace.workspaceRoot });
282
+ this.#csrBundler = new CsrDevBundler(options.app);
283
+ this.#patcher = process.env.AKAN_DEV_CSR_PATCHER === "off" ? null : new CsrDevPatcher(this.#csrBundler, { resident: true });
284
+ this.#ssrBundler = new SsrDevBundler(options.app, { discovery: () => this.#discovery });
285
+ this.#ssrPatcher = process.env.AKAN_DEV_CSR_PATCHER === "off" ? null : new CsrDevPatcher(this.#ssrBundler, { resident: true });
286
+ this.#workQueue = new BuilderWorkQueue({
287
+ runBatch: async (batch) => await this.#runQueuedBatch(batch),
288
+ onSettled: (label, ms) => {
289
+ this.#workCount += 1;
290
+ this.#logger.verbose(`[work-queue] ${label} finished in ${ms}ms`);
291
+ this.#reportMetrics();
292
+ }
293
+ });
162
294
  }
163
295
  get #artifactDir() {
164
296
  return `${this.#app.cwdPath}/.akan/artifact`;
165
297
  }
166
298
  async handleBuildRoute(msg) {
167
- await this.#enqueueWork(`build-route:${msg.routeId}`, async () => BuilderChannel.send(await this.#handleBuildRoute(msg)));
299
+ await this.#workQueue.enqueue(`build-route:${msg.routeId}`, async () => BuilderChannel.send(await this.#handleBuildRoute(msg)));
168
300
  }
169
301
  async#handleBuildRoute(msg) {
302
+ const seenGeneration = this.#discoveredThrough;
170
303
  try {
171
304
  const delta = await new RouteClientBuilder({
172
305
  app: this.#app,
@@ -175,10 +308,12 @@ class IncrementalBuilder {
175
308
  graphSeeds: msg.graphSeeds,
176
309
  artifact: this.#artifact,
177
310
  knownEntries: new Set(msg.knownEntries),
178
- discovery: this.#discovery
311
+ discovery: this.#discovery,
312
+ browser: "registry"
179
313
  }).build();
314
+ await this.#ensureSsrEntries(delta.registryEntries ?? []);
180
315
  this.#logger.verbose(`build-route ok routeId=${msg.routeId} newEntries=${delta.newEntries.length}`);
181
- this.#sendBuildStatus("route", { generation: msg.generation, ok: true, files: msg.seeds });
316
+ this.#sendBuildStatus("route", { generation: this.#generation, ok: true, files: msg.seeds, scope: msg.routeId });
182
317
  return {
183
318
  type: "build-route-res",
184
319
  id: msg.id,
@@ -191,35 +326,42 @@ class IncrementalBuilder {
191
326
  clientDeps: delta.clientDeps,
192
327
  clientDepsByEntry: delta.clientDepsByEntry,
193
328
  routeId: msg.routeId,
194
- generation: msg.generation
329
+ generation: msg.generation,
330
+ seenGeneration
195
331
  }
196
332
  };
197
333
  } catch (err) {
198
334
  const errMsg = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
199
335
  this.#logger.error(`build-route failed routeId=${msg.routeId}: ${errMsg}`);
200
- this.#sendBuildStatus("route", { generation: msg.generation, ok: false, files: msg.seeds, message: errMsg });
336
+ this.#sendBuildStatus("route", {
337
+ generation: this.#generation,
338
+ ok: false,
339
+ files: msg.seeds,
340
+ message: errMsg,
341
+ scope: msg.routeId
342
+ });
201
343
  return { type: "build-route-res", id: msg.id, ok: false, error: errMsg };
202
344
  }
203
345
  }
204
- #sendBuildStatus(phase, { generation, ok, files, message }) {
346
+ #sendBuildStatus(phase, {
347
+ generation,
348
+ ok,
349
+ files,
350
+ message,
351
+ scope
352
+ }) {
205
353
  if (typeof generation !== "number")
206
354
  return;
207
355
  BuilderChannel.emit({
208
356
  type: "build-status",
209
- data: {
210
- generation,
211
- phase,
212
- ok,
213
- files: files ?? [],
214
- message
215
- }
357
+ data: { generation, phase, ok, files: files ?? [], message, ...scope !== undefined ? { scope } : {} }
216
358
  });
217
359
  }
218
- async#enqueueWork(label, fn) {
360
+ async#enqueueFast(label, fn) {
219
361
  const started = Date.now();
220
362
  this.#inFlight += 1;
221
- const run = this.#workQueue.then(fn, fn);
222
- this.#workQueue = run.then(() => {
363
+ const run = this.#fastQueue.then(fn, fn);
364
+ this.#fastQueue = run.then(() => {
223
365
  return;
224
366
  }).catch(() => {
225
367
  return;
@@ -229,12 +371,22 @@ class IncrementalBuilder {
229
371
  } finally {
230
372
  this.#inFlight -= 1;
231
373
  this.#workCount += 1;
232
- this.#logger.verbose(`[work-queue] ${label} finished in ${Date.now() - started}ms`);
374
+ this.#logger.verbose(`[fast-queue] ${label} finished in ${Date.now() - started}ms`);
233
375
  this.#reportMetrics();
234
376
  }
235
377
  }
236
378
  get #idle() {
237
- return this.#inFlight === 0 && this.#cssRebuildTimer === null;
379
+ return this.#inFlight === 0 && this.#workQueue.size === 0 && this.#cssRebuildTimer === null;
380
+ }
381
+ #runBeside(work) {
382
+ this.#inFlight += 1;
383
+ const tracked = work.finally(() => {
384
+ this.#inFlight -= 1;
385
+ this.#beside.delete(tracked);
386
+ this.#reportMetrics();
387
+ });
388
+ this.#beside.add(tracked);
389
+ return tracked;
238
390
  }
239
391
  #reportMetrics() {
240
392
  if (!this.#idle || this.#shuttingDown)
@@ -256,12 +408,17 @@ class IncrementalBuilder {
256
408
  this.#cssRebuildTimer = null;
257
409
  this.#pendingCssRebuild = null;
258
410
  }
259
- await this.#workQueue.catch(() => {
411
+ await this.#fastQueue.catch(() => {
260
412
  return;
261
413
  });
414
+ await this.#workQueue.drain();
262
415
  await this.#cssRebuildQueue.catch(() => {
263
416
  return;
264
417
  });
418
+ while (this.#beside.size > 0)
419
+ await Promise.all([...this.#beside]);
420
+ if (this.#delegations.size > 0)
421
+ this.#logger.verbose(`registry saves handed to build workers this session: ${[...this.#delegations].map(([reason, count]) => `${reason}=${count}`).join(", ")}`);
265
422
  const flushed = await BuilderChannel.drain();
266
423
  this.#logger.debug(`drained in ${Date.now() - started}ms${flushed ? ` after flushing ${flushed} ipc write(s)` : ""}; exiting for recycle`);
267
424
  process.exit(0);
@@ -340,7 +497,7 @@ class IncrementalBuilder {
340
497
  roots,
341
498
  logger: this.#logger,
342
499
  onBatch: async (batch) => {
343
- await this.#enqueueWork("hmr-batch", async () => this.#handleWatchBatch(batch));
500
+ await this.#enqueueFast("hmr-batch", async () => this.#handleWatchBatch(batch));
344
501
  }
345
502
  });
346
503
  await watcher.start();
@@ -352,6 +509,7 @@ class IncrementalBuilder {
352
509
  if (rawKinds.size === 0)
353
510
  return;
354
511
  const generation = ++this.#generation;
512
+ const trace = { ...batch.trace, batchAt: Date.now() };
355
513
  const [autoImport, indexSync] = await CodegenLock.run(this.#app.workspace.workspaceRoot, `hmr-batch:${this.#app.name}`, async () => {
356
514
  const auto = await this.#autoImportSync.syncForBatch(batch.files);
357
515
  return [auto, await this.#generatedIndexSync.syncForBatch(batch.files)];
@@ -370,22 +528,19 @@ class IncrementalBuilder {
370
528
  this.#logger.verbose(`[hmr] batch generation=${generation} kinds=${kinds.join(",")} files=${files.length} generated=${indexSync.changedFiles.length} roles=${devPlan.roles.join(",") || "(none)"} actions=${devPlan.actions.join(",") || "(none)"}`);
371
529
  for (const error of indexSync.errors)
372
530
  this.#logger.error(error);
373
- if (kinds.includes("code")) {
374
- const started = Date.now();
375
- if (kinds.includes("config"))
376
- this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
377
- else
378
- this.#discovery.invalidate?.(files);
379
- this.#logger.verbose(`client-entry-discovery ${kinds.includes("config") ? "refreshed" : "invalidated"} (${Date.now() - started}ms)`);
380
- }
531
+ const discovery = kinds.includes("code") ? { files, refresh: kinds.includes("config"), generation } : undefined;
381
532
  if (hasSyncErrors) {
533
+ this.#barrelFailed = true;
382
534
  this.#sendBuildStatus("barrel", { generation, ok: false, files, message: indexSync.errors.join(`
383
535
  `) });
384
536
  BuilderChannel.emit(event);
537
+ if (discovery)
538
+ this.#workQueue.enqueue("discovery", async () => await this.#refreshDiscovery(discovery)).catch(this.#slowLaneFailed("discovery"));
385
539
  return;
386
540
  }
387
- if (indexSync.changedFiles.length > 0)
541
+ if (indexSync.changedFiles.length > 0 || this.#barrelFailed)
388
542
  this.#sendBuildStatus("barrel", { generation, ok: true, files });
543
+ this.#barrelFailed = false;
389
544
  const rebuildClient = devPlan.actions.includes("rebuild-client");
390
545
  if (kinds.includes("code") && !rebuildClient) {
391
546
  this.#logger.verbose(`client rebuild skipped; devPlan actions=${devPlan.actions.join(",") || "(none)"}`);
@@ -398,23 +553,290 @@ class IncrementalBuilder {
398
553
  } else if (kinds.includes("code") && rebuildClient && this.batchTouchesPagesTree(pageRoots, expandedBatch)) {
399
554
  this.#logger.verbose("pageKeys refresh skipped; changed page source cannot add/remove a route key");
400
555
  }
401
- const needs = [];
556
+ BuilderChannel.emit(event);
402
557
  if (kinds.includes("code") && rebuildClient) {
403
- if (this.#csrActive)
404
- needs.push("csr");
405
- else
558
+ const needs = ["pages", "css"];
559
+ if (!this.#csrActive)
406
560
  this.#logger.verbose(`csr-rebundle skipped; request /__csr or ?csr=true (or set AKAN_DEV_CSR_REBUILD=1) to enable per-save CSR rebuilds`);
407
- needs.push("pages");
408
- needs.push("css");
561
+ const patchCsr = async () => this.#csrActive ? await this.#patchCsr(generation, files, trace) : false;
562
+ const patchSsr = async () => await this.#patchSsr(generation, files, trace);
563
+ let csrToWorker;
564
+ let ssrToWorker;
565
+ if (this.#csrArming) {
566
+ ssrToWorker = await patchSsr();
567
+ csrToWorker = await patchCsr();
568
+ } else {
569
+ csrToWorker = await patchCsr();
570
+ ssrToWorker = await patchSsr();
571
+ }
572
+ if (csrToWorker)
573
+ needs.unshift("csr");
574
+ if (ssrToWorker)
575
+ needs.unshift("ssr");
576
+ const batch = { generation, needs, changedFiles: files, trace, ...discovery ? { discovery } : {} };
577
+ if (needs.includes("csr") || needs.includes("ssr"))
578
+ await this.#workQueue.enqueueBatch(batch);
579
+ else
580
+ this.#workQueue.enqueueBatch(batch).catch(this.#slowLaneFailed("batch"));
581
+ return;
409
582
  }
410
- BuilderChannel.emit(event);
411
- if (needs.length > 0)
412
- await this.#runBatch({ generation, needs, changedFiles: files });
413
- else if (kinds.includes("css")) {
583
+ if (discovery)
584
+ this.#workQueue.enqueue("discovery", async () => await this.#refreshDiscovery(discovery)).catch(this.#slowLaneFailed("discovery"));
585
+ if (kinds.includes("css")) {
414
586
  this.scheduleCssRebuild({ generation, changedFiles: files });
415
587
  this.#logger.verbose(`css-rebuild scheduled generation=${generation}`);
416
588
  }
417
589
  }
590
+ async#patchCsr(generation, files, trace) {
591
+ const patcher = this.#patcher;
592
+ if (!patcher)
593
+ return true;
594
+ await this.#csrGate;
595
+ if (!this.#csrActive)
596
+ return false;
597
+ const started = Date.now();
598
+ const marker = path2.join(this.#csrBundler.outDir, CSR_DEV_PATCHING_MARKER);
599
+ await Bun.write(marker, String(process.pid));
600
+ try {
601
+ const result = await patcher.update(files, {
602
+ announce: (update) => {
603
+ const now = Date.now();
604
+ BuilderChannel.emit({
605
+ type: "csr-updated",
606
+ data: {
607
+ generation: update.generation,
608
+ mode: "registry",
609
+ reload: update.reload,
610
+ reason: update.reason,
611
+ patchUrl: update.patchUrl,
612
+ changedIds: update.changedIds,
613
+ trace: { ...trace, patchAt: now, sentAt: now }
614
+ }
615
+ });
616
+ }
617
+ });
618
+ if (result.kind === "delegate") {
619
+ this.#logger.verbose(`csr-patch handed to a build worker (${this.#countDelegation("csr", result.reason)}): ${result.reason}`);
620
+ return true;
621
+ }
622
+ this.#sendBuildStatus("csr", { generation, ok: true, files });
623
+ this.#logger.verbose(result.kind === "unchanged" ? `csr-patch unchanged (${Date.now() - started}ms)` : `csr-patch generation=${result.update.generation} ${result.update.reload ? `reload (${result.update.reason})` : `patch modules=${result.update.changedIds.length}`} (${Date.now() - started}ms)`);
624
+ return false;
625
+ } catch (err) {
626
+ const message = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
627
+ if (IncrementalBuilder.#isCompileError(err)) {
628
+ this.#sendBuildStatus("csr", { generation, ok: false, files, message });
629
+ return false;
630
+ }
631
+ this.#logger.verbose(`csr-patch threw; a build worker takes this save: ${message}`);
632
+ patcher.forget();
633
+ return true;
634
+ } finally {
635
+ await rm(marker, { force: true });
636
+ }
637
+ }
638
+ async#patchSsr(generation, files, trace) {
639
+ const patcher = this.#ssrPatcher;
640
+ if (!patcher)
641
+ return true;
642
+ const hold = await ServerGraphFile.touches(await ServerGraphFile.read(this.#artifactDir), files, (file) => ServerGraphFile.clientExportsOf(file));
643
+ this.#discovery.invalidate?.(files);
644
+ return await this.#withSsrLock(async () => await this.#runSsrPatcher(patcher, files, { trace, hold, batchGeneration: generation }));
645
+ }
646
+ async#ensureSsrEntries(entries, { crashRetry = false } = {}) {
647
+ if (entries.length === 0)
648
+ return;
649
+ const arming = this.#ssrArming;
650
+ if (arming) {
651
+ this.#runBeside(arming.then(async () => await this.#ensureSsrEntries(entries, { crashRetry })).catch(this.#slowLaneFailed("ssr-ensure")));
652
+ return;
653
+ }
654
+ await this.#withSsrLock(async () => {
655
+ const patcher = this.#ssrPatcher;
656
+ const generation = this.#generation;
657
+ if (patcher && !await this.#runSsrPatcher(patcher, [], { roots: entries, onlyRoots: true, batchGeneration: generation }))
658
+ return;
659
+ if (!patcher && await this.#ssrBundler.holds(entries))
660
+ return;
661
+ if (this.#ssrBroken)
662
+ return;
663
+ if (this.#ssrCrashedAt !== null && Date.now() - this.#ssrCrashedAt < IncrementalBuilder.#crashRetryMs) {
664
+ patcher?.releaseHandedOver();
665
+ this.#retryAfterCrash(this.#ssrCrashedAt, entries);
666
+ return;
667
+ }
668
+ await this.#runSsrWorker(generation).catch((err) => {
669
+ this.#logger.error(`ssr-registry could not take a route's entries; the save that fixes it rebuilds it: ${ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot)}`);
670
+ });
671
+ if (this.#ssrCrashedAt !== null && !crashRetry)
672
+ this.#retryAfterCrash(this.#ssrCrashedAt, entries);
673
+ });
674
+ }
675
+ #retryAfterCrash(crashedAt, entries) {
676
+ for (const entry of entries)
677
+ this.#crashRetryEntries.add(entry);
678
+ if (this.#crashRetryTimer)
679
+ return;
680
+ const timer = setTimeout(() => {
681
+ this.#crashRetryTimer = null;
682
+ const retried = [...this.#crashRetryEntries];
683
+ this.#crashRetryEntries.clear();
684
+ if (this.shuttingDown || retried.length === 0)
685
+ return;
686
+ this.#runBeside(this.#ensureSsrEntries(retried, { crashRetry: true })).catch(this.#slowLaneFailed("ssr-crash-retry"));
687
+ }, Math.max(0, crashedAt + IncrementalBuilder.#crashRetryMs - Date.now()));
688
+ timer.unref?.();
689
+ this.#crashRetryTimer = timer;
690
+ }
691
+ async armSsrRegistry() {
692
+ const delayMs = Number(process.env.AKAN_DEV_SSR_ARM_DELAY_MS);
693
+ if (Number.isInteger(delayMs) && delayMs > 0)
694
+ await Bun.sleep(delayMs);
695
+ const arm = async () => await this.#withSsrLock(async () => {
696
+ const generation = this.#generation;
697
+ if (this.#ssrPatcher && !await this.#runSsrPatcher(this.#ssrPatcher, [], { batchGeneration: generation }))
698
+ return;
699
+ await this.#runSsrWorker(generation);
700
+ }).catch((err) => {
701
+ this.#logger.error(`ssr-registry boot build failed; the next save retries it: ${ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot)}`);
702
+ });
703
+ const arming = this.#runBeside((async () => {
704
+ await arm();
705
+ const crashedAt = this.#ssrCrashedAt;
706
+ if (crashedAt === null)
707
+ return;
708
+ while (!this.shuttingDown && Date.now() < crashedAt + IncrementalBuilder.#crashRetryMs)
709
+ await Bun.sleep(250);
710
+ if (!this.shuttingDown)
711
+ await arm();
712
+ })());
713
+ this.#ssrArming = arming;
714
+ await arming;
715
+ this.#ssrArming = null;
716
+ }
717
+ static #isCompileError(error) {
718
+ return error instanceof AggregateError;
719
+ }
720
+ #slowLaneFailed(label) {
721
+ return (error) => this.#logger.error(`${label} failed in the slow lane: ${ApplicationBuildReporter.formatError(error, this.#app.workspace.workspaceRoot)}`);
722
+ }
723
+ #countDelegation(registry, reason) {
724
+ const key = `${registry}:${reason.split(":")[0]}`;
725
+ const count = (this.#delegations.get(key) ?? 0) + 1;
726
+ this.#delegations.set(key, count);
727
+ return `#${count} for this reason`;
728
+ }
729
+ async#withSsrLock(fn) {
730
+ const run = this.#ssrLock.then(fn, fn);
731
+ this.#ssrLock = run.then(() => {
732
+ return;
733
+ }, () => {
734
+ return;
735
+ });
736
+ return await run;
737
+ }
738
+ async#runSsrWorker(generation) {
739
+ let result;
740
+ try {
741
+ result = await this.#runBatch({ generation, needs: ["ssr"], changedFiles: [] });
742
+ } finally {
743
+ this.#ssrPatcher?.forget();
744
+ }
745
+ this.#noteSsrWorker(result);
746
+ if (result.errors.ssr)
747
+ throw new Error(result.errors.ssr);
748
+ }
749
+ #noteSsrWorker(result) {
750
+ const crashed = !!result.crashed && (result.crashedNeeds ?? ["ssr"]).includes("ssr");
751
+ this.#ssrBroken = !!result.errors.ssr && !crashed;
752
+ this.#ssrCrashedAt = crashed ? Date.now() : null;
753
+ }
754
+ async#runSsrPatcher(patcher, files, {
755
+ roots,
756
+ onlyRoots,
757
+ trace,
758
+ hold,
759
+ batchGeneration
760
+ } = {}) {
761
+ const started = Date.now();
762
+ const marker = path2.join(this.#ssrBundler.outDir, CSR_DEV_PATCHING_MARKER);
763
+ await Bun.write(marker, String(process.pid));
764
+ try {
765
+ const result = await patcher.update(files, {
766
+ roots,
767
+ onlyRoots,
768
+ announce: (update) => {
769
+ const now = Date.now();
770
+ BuilderChannel.emit({
771
+ type: "ssr-updated",
772
+ data: {
773
+ generation: update.generation,
774
+ reload: update.reload,
775
+ reason: update.reason,
776
+ patchUrl: update.patchUrl,
777
+ changedIds: update.changedIds,
778
+ ...trace ? { trace: { ...trace, patchAt: now, sentAt: now } } : {},
779
+ ...hold ? { hold } : {},
780
+ ...batchGeneration !== undefined ? { batchGeneration } : {},
781
+ ...update.epoch !== undefined ? { epoch: update.epoch } : {},
782
+ ...update.first ? { first: true } : {}
783
+ }
784
+ });
785
+ }
786
+ });
787
+ if (result.kind === "delegate") {
788
+ this.#logger.verbose(`ssr-patch handed to a build worker (${this.#countDelegation("ssr", result.reason)}): ${result.reason}`);
789
+ return true;
790
+ }
791
+ if (!onlyRoots) {
792
+ this.#ssrBroken = false;
793
+ this.#ssrCrashedAt = null;
794
+ this.#sendBuildStatus("ssr", { generation: batchGeneration, ok: true, files });
795
+ }
796
+ this.#logger.verbose(result.kind === "unchanged" ? `ssr-patch unchanged (${Date.now() - started}ms)` : `ssr-patch generation=${result.update.generation} ${result.update.reload ? `reload (${result.update.reason})` : `patch modules=${result.update.changedIds.length}`} (${Date.now() - started}ms)`);
797
+ return false;
798
+ } catch (err) {
799
+ const message = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
800
+ if (IncrementalBuilder.#isCompileError(err)) {
801
+ this.#sendBuildStatus("ssr", { generation: batchGeneration, ok: false, files, message });
802
+ return false;
803
+ }
804
+ this.#logger.verbose(`ssr-patch threw; a build worker takes it: ${message}`);
805
+ patcher.forget();
806
+ return true;
807
+ } finally {
808
+ await rm(marker, { force: true });
809
+ }
810
+ }
811
+ async#runQueuedBatch({ discovery, ...work }) {
812
+ if (discovery)
813
+ await this.#refreshDiscovery(discovery);
814
+ this.#discoveredThrough = Math.max(this.#discoveredThrough, work.generation);
815
+ try {
816
+ if (work.needs.includes("ssr"))
817
+ await this.#withSsrLock(async () => {
818
+ try {
819
+ this.#noteSsrWorker(await this.#runBatch(work));
820
+ } finally {
821
+ this.#ssrPatcher?.forget();
822
+ }
823
+ });
824
+ else
825
+ await this.#runBatch(work);
826
+ } finally {
827
+ if (work.needs.includes("csr"))
828
+ this.#patcher?.forget();
829
+ }
830
+ }
831
+ async#refreshDiscovery({ files, refresh, generation }) {
832
+ const started = Date.now();
833
+ if (refresh)
834
+ this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
835
+ else
836
+ this.#discovery.invalidate?.(files);
837
+ this.#discoveredThrough = Math.max(this.#discoveredThrough, generation);
838
+ this.#logger.verbose(`client-entry-discovery ${refresh ? "refreshed" : "invalidated"} (${Date.now() - started}ms)`);
839
+ }
418
840
  async#runBatch(work) {
419
841
  const { generation, needs, changedFiles } = work;
420
842
  const started = Date.now();
@@ -424,15 +846,25 @@ class IncrementalBuilder {
424
846
  if (result.cssAssets)
425
847
  this.#artifact = { ...this.#artifact, cssAssets: result.cssAssets };
426
848
  if (result.crashed) {
427
- for (const need of needs)
849
+ const crashedNeeds = result.crashedNeeds ?? needs;
850
+ if (crashedNeeds.includes("csr"))
851
+ this.#patcher?.releaseHandedOver();
852
+ if (crashedNeeds.includes("ssr"))
853
+ this.#ssrPatcher?.releaseHandedOver();
854
+ for (const need of crashedNeeds)
428
855
  if (need !== "base")
429
- this.#sendBuildStatus(need, { generation, ok: false, files: changedFiles, message: result.errors[need] });
856
+ this.#sendBuildStatus(need, {
857
+ generation,
858
+ ok: false,
859
+ files: changedFiles,
860
+ message: `${result.errors[need] ?? "the build worker crashed"}; the next save builds it again`
861
+ });
430
862
  }
431
863
  if (needs.includes("css"))
432
864
  this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
433
865
  return result;
434
866
  }
435
- async#batchRequest({ generation, needs, changedFiles }) {
867
+ async#batchRequest({ generation, needs, changedFiles, trace }) {
436
868
  return {
437
869
  appName: this.#app.name,
438
870
  workspaceRoot: this.#app.workspace.workspaceRoot,
@@ -443,7 +875,8 @@ class IncrementalBuilder {
443
875
  pageKeys: await this.#app.getPageKeys(),
444
876
  optimizedFonts: this.#optimizedFonts,
445
877
  cssAssets: this.#artifact.cssAssets ?? null,
446
- artifactDir: path2.resolve(this.#artifactDir)
878
+ artifactDir: path2.resolve(this.#artifactDir),
879
+ ...trace ? { trace } : {}
447
880
  };
448
881
  }
449
882
  async boot() {
@@ -454,7 +887,7 @@ class IncrementalBuilder {
454
887
  }
455
888
  async announceRecoveredState(changedFiles) {
456
889
  const generation = ++this.#generation;
457
- await this.#enqueueWork("boot-recovered", async () => {
890
+ await this.#workQueue.enqueue("boot-recovered", async () => {
458
891
  await this.#runBatch({ generation, needs: ["pages", "css"], changedFiles });
459
892
  });
460
893
  }
@@ -483,19 +916,47 @@ class IncrementalBuilder {
483
916
  this.#logger.verbose(`announced boot state after recycle generation=${generation}`);
484
917
  }
485
918
  async handleBuildCsr(msg) {
486
- await this.#enqueueWork("build-csr", async () => {
919
+ this.#beginCsrArm();
920
+ this.#csrActive = true;
921
+ const armed = this.#workQueue.enqueue("build-csr", async () => {
487
922
  const started = Date.now();
488
- const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }));
489
- const error = result.errors.csr;
923
+ let error;
924
+ try {
925
+ const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }));
926
+ error = result.errors.csr;
927
+ } catch (err) {
928
+ error = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
929
+ }
490
930
  if (error) {
491
931
  this.#logger.error(`csr-build failed: ${error}`);
492
932
  await BuilderChannel.send({ type: "build-csr-res", id: msg.id, ok: false, error });
493
933
  return;
494
934
  }
495
- this.#csrActive = true;
935
+ this.#csrArmSucceeded = true;
936
+ this.#patcher?.forget();
496
937
  this.#logger.info(`csr-build ok on demand (${Date.now() - started}ms); rebuilding CSR on every save now`);
497
938
  await BuilderChannel.send({ type: "build-csr-res", id: msg.id, ok: true });
498
939
  });
940
+ this.#csrGate = this.#armingCsr(armed);
941
+ await armed;
942
+ }
943
+ #beginCsrArm() {
944
+ if (this.#csrArms === 0) {
945
+ this.#csrWasActive = this.#csrActive;
946
+ this.#csrArmSucceeded = false;
947
+ }
948
+ this.#csrArms += 1;
949
+ this.#csrArming = true;
950
+ }
951
+ #armingCsr(armed) {
952
+ const settle = () => {
953
+ this.#csrArms -= 1;
954
+ if (this.#csrArms > 0)
955
+ return;
956
+ this.#csrArming = false;
957
+ this.#csrActive = this.#csrWasActive || this.#csrArmSucceeded;
958
+ };
959
+ return armed.then(settle, settle);
499
960
  }
500
961
  static #csrArmedByEnv() {
501
962
  return process.env.AKAN_DEV_CSR_REBUILD === "1";
@@ -523,19 +984,24 @@ class IncrementalBuilder {
523
984
  async rearmCsrFromEnv() {
524
985
  if (!IncrementalBuilder.#csrArmedByEnv())
525
986
  return;
987
+ this.#beginCsrArm();
526
988
  this.#csrActive = true;
527
- await this.#enqueueWork("build-csr-rearm", async () => {
528
- const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }));
989
+ this.#csrArmSucceeded = true;
990
+ const rearmed = this.#workQueue.enqueue("build-csr-rearm", async () => {
991
+ const result = await this.#batchRunner.run(await this.#batchRequest({ generation: this.#generation, needs: ["csr"], changedFiles: [] }), (msg) => BuilderChannel.emit(msg));
992
+ this.#patcher?.forget();
529
993
  if (result.errors.csr)
530
994
  this.#logger.error(`csr-rearm failed: ${result.errors.csr}`);
531
995
  else
532
996
  this.#logger.verbose("csr-rearm ok; this session had CSR armed before the builder restarted");
533
997
  });
998
+ this.#csrGate = this.#armingCsr(rearmed);
999
+ await rearmed.catch((err) => this.#logger.error(`csr-rearm failed: ${ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot)}`));
534
1000
  }
535
- static #recoverBoot(app, bootError, logger, runner) {
1001
+ static #recoverBoot(app, bootError, logger, runner, initialGeneration) {
536
1002
  const firstMessage = bootError instanceof Error ? bootError.message : String(bootError);
537
1003
  logger.error(`boot build failed; entering degraded watch mode until the error is fixed: ${firstMessage}`);
538
- let generation = 0;
1004
+ let generation = initialGeneration;
539
1005
  const sendFailure = (files, message) => {
540
1006
  BuilderChannel.emit({
541
1007
  type: "build-status",
@@ -544,6 +1010,7 @@ class IncrementalBuilder {
544
1010
  };
545
1011
  sendFailure([], firstMessage);
546
1012
  BuilderChannel.emit({ type: "builder-ready" });
1013
+ BuilderChannel.emit({ type: "boot-armed" });
547
1014
  return new Promise((resolve, reject) => {
548
1015
  (async () => {
549
1016
  const roots = await new WatchRootResolver(app).resolve();
@@ -602,10 +1069,8 @@ class IncrementalBuilder {
602
1069
  BuilderChannel.emit({ type: `${msg.type}-res`, id: msg.id, ok: false, error });
603
1070
  return;
604
1071
  }
605
- if (msg.type === "build-route")
606
- builder.handleBuildRoute(msg);
607
- else
608
- builder.handleBuildCsr(msg);
1072
+ const handled = msg.type === "build-route" ? builder.handleBuildRoute(msg) : builder.handleBuildCsr(msg);
1073
+ handled.catch((err) => logger.error(`${msg.type} failed: ${err instanceof Error ? err.message : String(err)}`));
609
1074
  });
610
1075
  process.on("disconnect", () => {
611
1076
  logger.warn("host IPC channel closed; exiting builder");
@@ -613,12 +1078,14 @@ class IncrementalBuilder {
613
1078
  });
614
1079
  let recoveredFiles = null;
615
1080
  const bootRunner = new BuildBatchRunner({ workspaceRoot, cwd: app.cwdPath });
1081
+ const initialGeneration = IncrementalBuilder.#initialGeneration();
616
1082
  try {
617
- builder = new IncrementalBuilder({ app, watch, ...await IncrementalBuilder.#buildBootDeps(app, bootRunner) });
1083
+ const deps = await IncrementalBuilder.#buildBootDeps(app, bootRunner);
1084
+ builder = new IncrementalBuilder({ app, watch, initialGeneration, ...deps });
618
1085
  } catch (err) {
619
1086
  if (!watch)
620
1087
  throw err;
621
- const recovered = await IncrementalBuilder.#recoverBoot(app, err, logger, bootRunner);
1088
+ const recovered = await IncrementalBuilder.#recoverBoot(app, err, logger, bootRunner, initialGeneration);
622
1089
  builder = recovered.builder;
623
1090
  recoveredFiles = recovered.changedFiles;
624
1091
  }
@@ -627,7 +1094,12 @@ class IncrementalBuilder {
627
1094
  await builder.announceRecoveredState(recoveredFiles);
628
1095
  else if (process.env.AKAN_BUILDER_ANNOUNCE_BOOT === "1")
629
1096
  await builder.announceBootState();
630
- await builder.rearmCsrFromEnv();
1097
+ await Promise.all([builder.rearmCsrFromEnv(), builder.armSsrRegistry()]);
1098
+ BuilderChannel.emit({ type: "boot-armed" });
1099
+ }
1100
+ static #initialGeneration() {
1101
+ const generation = Number(process.env.AKAN_BUILDER_INITIAL_GENERATION);
1102
+ return Number.isInteger(generation) && generation > 0 ? generation + 1 : 0;
631
1103
  }
632
1104
  }
633
1105
  await IncrementalBuilder.main().catch((err) => {