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

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 (68) 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-afajz1m5.js} +7 -7
  8. package/{applicationBuildRunner-n6xfexaa.js → applicationBuildRunner-412y0345.js} +4 -4
  9. package/buildBatch.proc.js +77 -23
  10. package/{cloud.command-j0fcz44z.js → cloud.command-yz0h8a2e.js} +12 -12
  11. package/cloud.script-zez2b8am.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-2gh9sndr.js → index-50rq2qms.js} +4 -4
  25. package/{index-5b6tavv3.js → index-52w0fvh8.js} +15 -1
  26. package/{index-zbc2q6a6.js → index-56xwsgyn.js} +3 -3
  27. package/{index-rmcw8yc8.js → index-58cyna2s.js} +2 -2
  28. package/{index-1db2cw0y.js → index-5mm3g24t.js} +2 -2
  29. package/{index-wp43xrjv.js → index-6dt6zft6.js} +4 -4
  30. package/{index-vj1cwwy5.js → index-7yxndzea.js} +7 -7
  31. package/{index-q44jbw48.js → index-860zjnr1.js} +1 -1
  32. package/{index-fzd22vjh.js → index-94s080qm.js} +3 -17
  33. package/index-cd9qt2t6.js +4110 -0
  34. package/{index-ymgtbmf1.js → index-cfj73ck1.js} +213 -62
  35. package/{index-yy4s1j72.js → index-d65fzxvd.js} +1 -1
  36. package/{index-m0ms3n1c.js → index-da72pfdv.js} +7 -7
  37. package/{index-4dn1p115.js → index-ggfhz9ab.js} +4 -4
  38. package/{index-9629w7ns.js → index-hzy4pe24.js} +3 -3
  39. package/{index-d4mfjgm0.js → index-mq74nzf8.js} +6 -6
  40. package/{index-mgfx3mfs.js → index-per2kqy9.js} +4 -4
  41. package/{index-aaz7n8s2.js → index-qm4fppfk.js} +4 -4
  42. package/{index-ya5qbmyg.js → index-t02g6dpn.js} +3 -3
  43. package/{index-hb8sgepf.js → index-tpdn30ya.js} +1 -1
  44. package/{index-rnt66vym.js → index-v4hnkqz9.js} +1 -1
  45. package/{index-ne50hc2m.js → index-wvebzf0y.js} +456 -1623
  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-fm41vdtw.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 +2 -2
  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-y1p56wyv.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-d8he10tv.js} +24 -24
  65. package/TunnelShare-pemaknmd.js +0 -9
  66. package/cloud.script-g2dcnr8c.js +0 -20
  67. package/context.runner-ehjs419v.js +0 -21
  68. package/index-fgq75haa.js +0 -2084
@@ -11,6 +11,7 @@ import {
11
11
  devHostStateOf,
12
12
  isLegacyBackendFallbackFile,
13
13
  shouldMarkBuildPhaseRecovered,
14
+ shouldKeepBuildFailure,
14
15
  createBackendBuildStatus,
15
16
  backendRestartReasonFromMessage,
16
17
  mergeBackendRestartReasons,
@@ -32,20 +33,20 @@ import {
32
33
  filesChangedSince,
33
34
  HmrWatcher,
34
35
  WatchRootResolver
35
- } from "./index-khpfpcsd.js";
36
+ } from "./index-48k96p55.js";
36
37
  import {
37
38
  runner,
38
39
  script
39
- } from "./index-rmcw8yc8.js";
40
+ } from "./index-58cyna2s.js";
40
41
  import {
41
42
  WorkspaceExecutor,
42
43
  AppExecutor,
43
44
  LibExecutor,
44
45
  PkgExecutor
45
- } from "./index-ga6hygxr.js";
46
+ } from "./index-0n9q1f23.js";
46
47
  import {
47
48
  LibraryScript
48
- } from "./index-1db2cw0y.js";
49
+ } from "./index-5mm3g24t.js";
49
50
  import {
50
51
  resolveMobileTargets,
51
52
  NativeApp
@@ -56,7 +57,7 @@ import {
56
57
  import {
57
58
  SlicePlanner,
58
59
  formatSlicePlan
59
- } from "./index-q44jbw48.js";
60
+ } from "./index-860zjnr1.js";
60
61
  import {
61
62
  openBrowser
62
63
  } from "./index-9dp031by.js";
@@ -66,9 +67,10 @@ import { confirm } from "@inquirer/prompts";
66
67
  import { Logger as Logger6 } from "akanjs/common";
67
68
 
68
69
  // pkgs/@akanjs/cli/application/application.runner.ts
69
- import path4 from "path";
70
+ import path5 from "path";
70
71
 
71
72
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
73
+ import path3 from "path";
72
74
  import { Logger as Logger2 } from "akanjs/common";
73
75
 
74
76
  // pkgs/@akanjs/devkit/sshTunnel.ts
@@ -199,8 +201,10 @@ var createTunnel = async (service, { app, environment, port = service === "postg
199
201
  return `localhost:${port}`;
200
202
  };
201
203
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.host.ts
204
+ import fs from "fs";
202
205
  import path from "path";
203
206
  import { Logger } from "akanjs/common";
207
+ import { CSR_DEV_DIRNAME, CSR_DEV_PATCHING_MARKER, SSR_DEV_DIRNAME } from "akanjs/server/hmr/csrDevManifest";
204
208
  import { MemoryLimit } from "akanjs/server/memoryLimit";
205
209
  var builderMsgTypeSet = new Set([
206
210
  "build-route-res",
@@ -210,15 +214,17 @@ var builderMsgTypeSet = new Set([
210
214
  "css-updated",
211
215
  "pages-updated",
212
216
  "csr-updated",
217
+ "ssr-updated",
213
218
  "build-status",
214
- "builder-metrics"
219
+ "builder-metrics",
220
+ "boot-armed"
215
221
  ]);
216
222
 
217
223
  class IncrementalBuilderHost {
218
224
  static #restartBaseDelayMs = 1000;
219
225
  static #restartMaxDelayMs = 30000;
220
226
  static #recycleDrainTimeoutMs = 30000;
221
- static #devMaxRssBytes = 1200 * 1024 * 1024;
227
+ static #devMaxRssBytes = 2048 * 1024 * 1024;
222
228
  logger = new Logger("IncrementalBuilderHost");
223
229
  entry;
224
230
  env;
@@ -234,20 +240,33 @@ class IncrementalBuilderHost {
234
240
  #recycleTimer = null;
235
241
  #recycleRequested = false;
236
242
  #spawnAfterRecycle = false;
243
+ #patcherOff = false;
237
244
  #manualStop = false;
238
245
  #inFlight = new Map;
239
246
  #startOptions = {};
240
- constructor({ app, entry, env, stdio = "inherit", onMessage, onOutput }) {
247
+ constructor({
248
+ app,
249
+ entry,
250
+ env,
251
+ stdio = "inherit",
252
+ onMessage,
253
+ onOutput,
254
+ patcherOff = false
255
+ }) {
241
256
  this.app = app;
242
257
  this.entry = entry;
243
258
  this.env = env;
244
259
  this.#stdio = stdio;
245
260
  this.#onMessage = onMessage;
246
261
  this.#onOutput = onOutput ?? null;
262
+ this.#patcherOff = patcherOff;
247
263
  }
248
264
  get status() {
249
265
  return this.#status;
250
266
  }
267
+ get patcherOff() {
268
+ return this.#patcherOff;
269
+ }
251
270
  get pid() {
252
271
  return this.#proc?.pid ?? null;
253
272
  }
@@ -265,10 +284,16 @@ class IncrementalBuilderHost {
265
284
  this.ready = false;
266
285
  const afterRecycle = this.#spawnAfterRecycle;
267
286
  this.#spawnAfterRecycle = false;
287
+ this.#checkPatchingMarker(isRestart && !afterRecycle);
268
288
  let proc;
269
289
  proc = Bun.spawn(["bun", this.entry], {
270
290
  cwd: this.app.cwdPath,
271
- env: { ...this.env, AKAN_WATCH: "1", ...afterRecycle ? { AKAN_BUILDER_ANNOUNCE_BOOT: "1" } : {} },
291
+ env: {
292
+ ...this.env,
293
+ AKAN_WATCH: "1",
294
+ ...afterRecycle ? { AKAN_BUILDER_ANNOUNCE_BOOT: "1" } : {},
295
+ ...this.#patcherOff ? { AKAN_DEV_CSR_PATCHER: "off" } : {}
296
+ },
272
297
  stdio: ["ignore", this.#stdio, this.#stdio],
273
298
  ipc: (msg) => {
274
299
  if (this.#proc !== proc)
@@ -323,6 +348,17 @@ class IncrementalBuilderHost {
323
348
  }
324
349
  this.logger.verbose(`builder spawned pid=${proc.pid} entry=${this.entry}${isRestart ? " restart=1" : ""}`);
325
350
  }
351
+ #checkPatchingMarker(afterCrash) {
352
+ const markers = [CSR_DEV_DIRNAME, SSR_DEV_DIRNAME].map((dirName) => path.join(this.app.cwdPath, ".akan/artifact", dirName, CSR_DEV_PATCHING_MARKER)).filter((marker) => fs.existsSync(marker));
353
+ if (markers.length === 0)
354
+ return;
355
+ if (afterCrash && !this.#patcherOff) {
356
+ this.#patcherOff = true;
357
+ this.logger.warn("the builder died while patching a dev module registry; registry saves go to a build worker until the next config or metadata restart (AKAN_DEV_CSR_PATCHER=off)");
358
+ }
359
+ for (const marker of markers)
360
+ fs.rmSync(marker, { force: true });
361
+ }
326
362
  async#drain(stream, kind) {
327
363
  if (!stream)
328
364
  return;
@@ -428,7 +464,8 @@ class IncrementalBuilderHost {
428
464
  }
429
465
  static async create(app, env, onMessage, {
430
466
  stdio = "inherit",
431
- onOutput
467
+ onOutput,
468
+ patcherOff = false
432
469
  } = {}) {
433
470
  const candidates = [
434
471
  path.join(app.workspace.workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts"),
@@ -438,7 +475,7 @@ class IncrementalBuilderHost {
438
475
  ];
439
476
  for (const c of candidates)
440
477
  if (await Bun.file(c).exists())
441
- return new IncrementalBuilderHost({ app, entry: c, env, stdio, onMessage, onOutput });
478
+ return new IncrementalBuilderHost({ app, entry: c, env, stdio, onMessage, onOutput, patcherOff });
442
479
  throw new Error(`[cli] frontend builder entry not found; looked in: ${candidates.join(", ")}`);
443
480
  }
444
481
  }
@@ -618,6 +655,41 @@ class BackendImportGraph {
618
655
  }
619
656
  }
620
657
 
658
+ // pkgs/@akanjs/devkit/akanApp/devBootLatch.ts
659
+ class DevBootLatch {
660
+ #onBooted;
661
+ #waitMs;
662
+ #ready = false;
663
+ #armed = false;
664
+ #booted = false;
665
+ #timer = null;
666
+ constructor(onBooted, { waitMs = 30000 } = {}) {
667
+ this.#onBooted = onBooted;
668
+ this.#waitMs = waitMs;
669
+ }
670
+ ready() {
671
+ this.#ready = true;
672
+ if (!this.#armed && !this.#timer) {
673
+ this.#timer = setTimeout(() => this.armed(), this.#waitMs);
674
+ this.#timer.unref?.();
675
+ }
676
+ this.#release();
677
+ }
678
+ armed() {
679
+ this.#armed = true;
680
+ this.#release();
681
+ }
682
+ #release() {
683
+ if (this.#booted || !this.#ready || !this.#armed)
684
+ return;
685
+ this.#booted = true;
686
+ if (this.#timer)
687
+ clearTimeout(this.#timer);
688
+ this.#timer = null;
689
+ this.#onBooted();
690
+ }
691
+ }
692
+
621
693
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
622
694
  var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
623
695
  var asMib = (bytes) => Math.round(bytes / 1024 / 1024);
@@ -641,8 +713,10 @@ class AkanAppHost {
641
713
  env;
642
714
  #onDevEvent;
643
715
  #lastDevState = null;
716
+ #bootLatch = new DevBootLatch(() => this.#onDevEvent?.({ app: this.app.name, booted: true }));
644
717
  #backend = null;
645
718
  #builder = null;
719
+ #patcherOff = false;
646
720
  #backendReady = false;
647
721
  #plannedBackendStops = new WeakSet;
648
722
  #restartTimer = null;
@@ -652,6 +726,8 @@ class AkanAppHost {
652
726
  #backendLifecycleState = "stopped";
653
727
  #pendingRestartReason = null;
654
728
  #pendingRecycle = null;
729
+ #restartFailedAfterApply = null;
730
+ #stopping = false;
655
731
  #builderRecoveryTimer = null;
656
732
  #builderRecoveryAttempts = 0;
657
733
  #backendStartStatus = null;
@@ -679,6 +755,7 @@ class AkanAppHost {
679
755
  #builderGapStamp = null;
680
756
  #pendingBuilderMessages = [];
681
757
  #builderRequests = new BuilderRequestRouter;
758
+ #builderGeneration = 0;
682
759
  constructor(app, {
683
760
  env,
684
761
  stdio = "inherit",
@@ -695,6 +772,8 @@ class AkanAppHost {
695
772
  return;
696
773
  this.#lastDevState = state;
697
774
  this.#onDevEvent({ app: this.app.name, state, ...detail ? { detail } : {} });
775
+ if (state === "ready")
776
+ this.#bootLatch.ready();
698
777
  }
699
778
  async start() {
700
779
  if (this.#backend)
@@ -712,6 +791,8 @@ class AkanAppHost {
712
791
  return this;
713
792
  }
714
793
  async stop() {
794
+ this.#stopping = true;
795
+ this.#pendingRecycle = null;
715
796
  this.#cancelIdleSuspend();
716
797
  this.#stopIdleWatcher();
717
798
  this.#clearRestartTimers();
@@ -756,6 +837,7 @@ class AkanAppHost {
756
837
  this.#setBackendLifecycleState("ready", `pid=${msg.pid}`);
757
838
  this.#recordBackendReadyStatus();
758
839
  this.logger.verbose(`backend ready pid=${msg.pid}`);
840
+ this.#forgetRouteBuildStatus();
759
841
  this.#replayBuilderState();
760
842
  return;
761
843
  }
@@ -976,17 +1058,26 @@ ${this.#backendStderrTail.join(`
976
1058
  });
977
1059
  }
978
1060
  async#handleBuilderMessage(message) {
1061
+ if (this.#stopping)
1062
+ return;
979
1063
  this.#markDevActivity();
1064
+ this.#trackBuilderGeneration(message);
980
1065
  if (message.type === "build-status") {
981
1066
  this.#recordBuildStatus(message.data);
982
1067
  this.#sendOrQueueBuildStatus(message.data);
983
1068
  this.#reviveBackendAfterGreenBuild(message.data);
1069
+ await this.#resumeFailedRecycle(message.data);
984
1070
  return;
985
1071
  }
986
1072
  if (message.type === "builder-metrics") {
987
1073
  this.#handleBuilderMetrics(message.data);
988
1074
  return;
989
1075
  }
1076
+ if (message.type === "boot-armed") {
1077
+ this.logger.verbose("[builder] boot builds settled");
1078
+ this.#bootLatch.armed();
1079
+ return;
1080
+ }
990
1081
  if (message.type === "pages-updated" || message.type === "css-updated") {
991
1082
  const recycled = message.data.reason === "builder-recycle";
992
1083
  if (recycled && !this.#shouldRelayRecycledState(message))
@@ -1008,6 +1099,22 @@ ${this.#backendStderrTail.join(`
1008
1099
  }
1009
1100
  this.#sendToBackend(message);
1010
1101
  }
1102
+ #trackBuilderGeneration(message) {
1103
+ const generation = AkanAppHost.#builderGenerationOf(message);
1104
+ if (generation === undefined || generation <= this.#builderGeneration)
1105
+ return;
1106
+ this.#builderGeneration = generation;
1107
+ Object.assign(this.env, { AKAN_BUILDER_INITIAL_GENERATION: String(generation) });
1108
+ }
1109
+ static #builderGenerationOf(message) {
1110
+ if (message.type === "invalidate")
1111
+ return message.generation;
1112
+ if (message.type === "build-status" || message.type === "builder-metrics")
1113
+ return message.data.generation;
1114
+ if (message.type === "pages-updated" || message.type === "css-updated")
1115
+ return message.data.generation;
1116
+ return;
1117
+ }
1011
1118
  #shouldRelayRecycledState(message) {
1012
1119
  const current = message.type === "pages-updated" ? this.#lastGoodFrontend.pages : this.#lastGoodFrontend.css;
1013
1120
  if (shouldRelayRecycledFrontendState(current, message)) {
@@ -1266,9 +1373,7 @@ ${this.#backendStderrTail.join(`
1266
1373
  if (shouldRefreshConfigOnIdleWake(batch)) {
1267
1374
  this.logger.verbose("[idle-suspend] config changed while suspended; restarting the dev host");
1268
1375
  this.#discardBuilderGap("config change replaces the backend anyway");
1269
- await this.#recycleDevChildren({ type: "invalidate", kinds: [...batch?.kinds ?? []], files }, {
1270
- refreshConfig: true
1271
- });
1376
+ await this.#applyRecycle({ type: "invalidate", kinds: [...batch?.kinds ?? []], files }, true);
1272
1377
  return;
1273
1378
  }
1274
1379
  if (files.length > 0)
@@ -1369,7 +1474,8 @@ ${this.#backendStderrTail.join(`
1369
1474
  this.#armRssRecycle(this.#rssRecycleReason);
1370
1475
  const wantsDevHostRestart = shouldRestartDevHostByDevPlan(message);
1371
1476
  const pending = this.#pendingRecycle;
1372
- if (wantsDevHostRestart || shouldRestartBuilderByDevPlan(message) || pending && message.kinds.includes("code")) {
1477
+ const resumes = !!pending && message.kinds.includes("code") && this.#touchesFailedRecycle(message.files);
1478
+ if (wantsDevHostRestart || shouldRestartBuilderByDevPlan(message) || resumes) {
1373
1479
  const refreshConfig = wantsDevHostRestart || (pending?.refreshConfig ?? false);
1374
1480
  const merged = pending ? mergeInvalidateMessages(pending.message, message) : message;
1375
1481
  const generation = message.devPlan?.generation ?? message.generation;
@@ -1377,16 +1483,7 @@ ${this.#backendStderrTail.join(`
1377
1483
  this.#deferRecycle(merged, { refreshConfig, generation });
1378
1484
  return;
1379
1485
  }
1380
- this.#pendingRecycle = null;
1381
- try {
1382
- if (refreshConfig)
1383
- await this.#restartDevHost(merged);
1384
- else
1385
- await this.#restartDevChildren(merged);
1386
- } catch (err) {
1387
- this.#recordDevHostRestartFailure(merged, err, refreshConfig ? "Config" : "Runtime metadata");
1388
- this.#resurrectDevChildren(merged);
1389
- }
1486
+ await this.#applyRecycle(merged, refreshConfig);
1390
1487
  return;
1391
1488
  }
1392
1489
  if (await this.#shouldRestartBackend(message)) {
@@ -1395,6 +1492,41 @@ ${this.#backendStderrTail.join(`
1395
1492
  }
1396
1493
  this.#sendToBackend(message);
1397
1494
  }
1495
+ async#applyRecycle(message, refreshConfig) {
1496
+ if (this.#stopping)
1497
+ return;
1498
+ this.#pendingRecycle = null;
1499
+ const progress = { applied: !refreshConfig };
1500
+ try {
1501
+ if (refreshConfig)
1502
+ await this.#restartDevHost(message, progress);
1503
+ else
1504
+ await this.#restartDevChildren(message);
1505
+ } catch (err) {
1506
+ const kind = refreshConfig ? "Config" : "Runtime metadata";
1507
+ const generation = this.#recordDevHostRestartFailure(message, err, kind, { applied: progress.applied });
1508
+ if (progress.applied)
1509
+ this.#restartFailedAfterApply = generation;
1510
+ else
1511
+ this.#pendingRecycle = { message, refreshConfig, failed: true, failedGeneration: generation };
1512
+ this.#resurrectDevChildren(message);
1513
+ }
1514
+ }
1515
+ async#resumeFailedRecycle(status) {
1516
+ const pending = this.#pendingRecycle;
1517
+ if (!pending?.failed || !status.ok || status.phase !== "pages" || !this.#touchesFailedRecycle(status.files))
1518
+ return;
1519
+ if (status.generation <= (pending.failedGeneration ?? -1))
1520
+ return;
1521
+ await this.#applyRecycle(pending.message, pending.refreshConfig);
1522
+ }
1523
+ #touchesFailedRecycle(files) {
1524
+ const pending = this.#pendingRecycle;
1525
+ if (!pending?.failed)
1526
+ return true;
1527
+ const changed = new Set(pending.message.files);
1528
+ return files.some((file) => changed.has(file));
1529
+ }
1398
1530
  #deferRecycle(message, { refreshConfig, generation }) {
1399
1531
  this.#pendingRecycle = { message, refreshConfig };
1400
1532
  const kind = refreshConfig ? "Config" : "Runtime metadata";
@@ -1440,8 +1572,10 @@ ${this.#backendStderrTail.join(`
1440
1572
  this.#builderRecoveryAttempts = 0;
1441
1573
  this.logger.info("[builder-recovery] builder recovered");
1442
1574
  this.#restartBackendForGapChanges();
1575
+ const failedAfterApply = this.#restartFailedAfterApply;
1576
+ this.#restartFailedAfterApply = null;
1443
1577
  const status = {
1444
- generation: reason.generation ?? this.#nextBackendBuildStatusGeneration(),
1578
+ generation: failedAfterApply !== null ? this.#nextBackendBuildStatusGeneration(failedAfterApply + 1) : reason.generation ?? this.#nextBackendBuildStatusGeneration(),
1445
1579
  phase: "scan",
1446
1580
  ok: true,
1447
1581
  files: reason.files,
@@ -1465,12 +1599,12 @@ ${this.#backendStderrTail.join(`
1465
1599
  this.logger.warn(`[dev-host] recycling builder/backend for runtime metadata generation=${generation ?? "(unknown)"} files=${message.files.length}`);
1466
1600
  await this.#recycleDevChildren(message);
1467
1601
  }
1468
- async#restartDevHost(message) {
1602
+ async#restartDevHost(message, progress) {
1469
1603
  const generation = message.devPlan?.generation ?? message.generation;
1470
1604
  this.logger.warn(`[dev-host] config change detected; restarting dev host generation=${generation ?? "(unknown)"} files=${message.files.length}`);
1471
- await this.#recycleDevChildren(message, { refreshConfig: true });
1605
+ await this.#recycleDevChildren(message, { refreshConfig: true, progress });
1472
1606
  }
1473
- async#recycleDevChildren(message, { refreshConfig = false } = {}) {
1607
+ async#recycleDevChildren(message, { refreshConfig = false, progress } = {}) {
1474
1608
  const generation = message.devPlan?.generation ?? message.generation;
1475
1609
  this.#clearRestartTimers();
1476
1610
  this.#builderRecoveryAttempts = 0;
@@ -1480,10 +1614,13 @@ ${this.#backendStderrTail.join(`
1480
1614
  this.#pendingBuildStatusReplay = [];
1481
1615
  await this.#stopBackend();
1482
1616
  this.#stopBuilder();
1617
+ this.#patcherOff = false;
1483
1618
  if (refreshConfig) {
1484
1619
  await this.app.getConfig({ refresh: true });
1485
1620
  const { env } = await this.app.prepareCommand("start");
1486
1621
  Object.assign(this.env, env);
1622
+ if (progress)
1623
+ progress.applied = true;
1487
1624
  }
1488
1625
  await this.#backendGraph.refresh();
1489
1626
  await this.#startBuilder();
@@ -1502,22 +1639,27 @@ ${this.#backendStderrTail.join(`
1502
1639
  this.#lastGoodFrontend.css = message;
1503
1640
  this.logger.verbose(`[last-good] css generation=${message.data.generation ?? "(unknown)"} assets=${Object.keys(message.data.cssAssets).length}`);
1504
1641
  }
1505
- #recordDevHostRestartFailure(message, err, kind) {
1642
+ #recordDevHostRestartFailure(message, err, kind, { applied }) {
1506
1643
  const generation = message.devPlan?.generation ?? message.generation ?? this.#nextBackendBuildStatusGeneration();
1507
1644
  const detail = err instanceof Error ? err.message : String(err);
1508
1645
  this.logger.warn(`[dev-host] ${kind.toLowerCase()} restart failed generation=${generation}: ${detail}`);
1646
+ const names = message.files.slice(0, 3).map((file) => path3.basename(file));
1647
+ const saved = names.length > 0 ? names.join(", ") : "the changed file";
1509
1648
  const status = {
1510
1649
  generation,
1511
1650
  phase: "scan",
1512
1651
  ok: false,
1513
1652
  files: message.files,
1514
- message: `${kind} change failed to apply; recovering the dev server automatically: ${detail}`
1653
+ message: applied ? `${kind} change applied, but the dev server failed to restart: ${detail}. It is recovering on its own.` : `${kind} change failed to apply: ${detail}. The dev server keeps running on the previous ${kind.toLowerCase()}; once it is fixed, save ${saved} again or restart the dev server to apply it.`
1515
1654
  };
1516
1655
  this.#recordBuildStatus(status);
1517
1656
  this.#sendOrQueueBuildStatus(status);
1657
+ return generation;
1518
1658
  }
1519
1659
  #recordBuildStatus(status) {
1520
1660
  const recovered = shouldMarkBuildPhaseRecovered(this.#buildStatusByPhase, status);
1661
+ if (shouldKeepBuildFailure(this.#buildStatusByPhase, status))
1662
+ return;
1521
1663
  this.#buildStatusByPhase.set(status.phase, status);
1522
1664
  const label = `[build-status] generation=${status.generation} phase=${status.phase} ok=${status.ok} files=${status.files.length}`;
1523
1665
  if (status.ok)
@@ -1533,6 +1675,10 @@ ${this.#backendStderrTail.join(`
1533
1675
  }
1534
1676
  this.#sendToBackend({ type: "build-status", data: status });
1535
1677
  }
1678
+ #forgetRouteBuildStatus() {
1679
+ this.#buildStatusByPhase.delete("route");
1680
+ this.#pendingBuildStatusReplay = this.#pendingBuildStatusReplay.filter((status) => status.phase !== "route");
1681
+ }
1536
1682
  #replayBuilderState() {
1537
1683
  if (!this.#backendReady)
1538
1684
  return;
@@ -1584,7 +1730,8 @@ ${this.#backendStderrTail.join(`
1584
1730
  this.#enqueueBuilderMessage(msg);
1585
1731
  }, {
1586
1732
  stdio: this.stdio,
1587
- onOutput: (kind, text) => void (kind === "stderr" ? process.stderr : process.stdout).write(text)
1733
+ onOutput: (kind, text) => void (kind === "stderr" ? process.stderr : process.stdout).write(text),
1734
+ patcherOff: this.#patcherOff
1588
1735
  });
1589
1736
  try {
1590
1737
  await this.#waitForBuilderReady(attempt, { announceBootState });
@@ -1670,21 +1817,23 @@ ${this.#backendStderrTail.join(`
1670
1817
  this.#cancelRssRecycle();
1671
1818
  if (!this.#builder)
1672
1819
  return;
1820
+ if (this.#builder.patcherOff)
1821
+ this.#patcherOff = true;
1673
1822
  this.#builder.stop();
1674
1823
  this.#builder = null;
1675
1824
  }
1676
1825
  }
1677
1826
  // pkgs/@akanjs/devkit/applicationTestPreload.ts
1678
- import path3 from "path";
1827
+ import path4 from "path";
1679
1828
  var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
1680
1829
  async function resolveSignalTestPreloadPath(target) {
1681
1830
  const candidates = [];
1682
- for (const basePath of [target.cwdPath, process.cwd(), path3.dirname(Bun.main), import.meta.dir]) {
1831
+ for (const basePath of [target.cwdPath, process.cwd(), path4.dirname(Bun.main), import.meta.dir]) {
1683
1832
  try {
1684
- candidates.push(path3.join(path3.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
1833
+ candidates.push(path4.join(path4.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
1685
1834
  } catch {}
1686
1835
  }
1687
- candidates.push(path3.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.join(path3.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path3.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
1836
+ candidates.push(path4.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.join(path4.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path4.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
1688
1837
  const uniqueCandidates = [...new Set(candidates)];
1689
1838
  for (const candidate of uniqueCandidates)
1690
1839
  if (await Bun.file(candidate).exists())
@@ -1697,7 +1846,7 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
1697
1846
 
1698
1847
  // pkgs/@akanjs/cli/application/application.runner.ts
1699
1848
  import { Logger as Logger3 } from "akanjs/common";
1700
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-n6xfexaa.js")).ApplicationBuildRunner;
1849
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-412y0345.js")).ApplicationBuildRunner;
1701
1850
  var loadPrompts = async () => await import("@inquirer/prompts");
1702
1851
 
1703
1852
  class ApplicationRunner extends runner("application") {
@@ -1742,7 +1891,7 @@ class ApplicationRunner extends runner("application") {
1742
1891
  });
1743
1892
  }
1744
1893
  #runtimeDirOf(app, override) {
1745
- return path4.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path4.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
1894
+ return path5.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path5.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
1746
1895
  }
1747
1896
  async runLogs(app, options) {
1748
1897
  const { LogControlUnavailableError, LogTailClient } = await import("akanjs/server/logging/logTailClient");
@@ -1768,7 +1917,7 @@ class ApplicationRunner extends runner("application") {
1768
1917
  });
1769
1918
  } catch (error) {
1770
1919
  if (error instanceof LogControlUnavailableError)
1771
- throw new Error(`${app.name} is not running (no ${path4.basename(socketPath)} in ${runtimeDir}). Start it with \`akan start ${app.name}\`, or pass --runtime-dir for a built app.`);
1920
+ throw new Error(`${app.name} is not running (no ${path5.basename(socketPath)} in ${runtimeDir}). Start it with \`akan start ${app.name}\`, or pass --runtime-dir for a built app.`);
1772
1921
  throw error;
1773
1922
  }
1774
1923
  const sinceMs = since ? LogQueryMatcher.parseSince(since) : undefined;
@@ -1833,7 +1982,7 @@ try {
1833
1982
  const serverPath = `${app.cwdPath}/server.ts`;
1834
1983
  if (!await app.exists("server.ts"))
1835
1984
  throw new Error(`Server file not found: apps/${app.name}/server.ts`);
1836
- const target = path4.resolve(app.workspace.workspaceRoot, dir);
1985
+ const target = path5.resolve(app.workspace.workspaceRoot, dir);
1837
1986
  const code = `
1838
1987
  const { server } = await import(${JSON.stringify(serverPath)});
1839
1988
  const { DatabaseAdaptorRole, DocumentTransfer } = await import("akanjs/service");
@@ -2406,7 +2555,7 @@ import os from "os";
2406
2555
  import { MemoryLimit as MemoryLimit2 } from "akanjs/server/memoryLimit";
2407
2556
 
2408
2557
  class DevBootConcurrency {
2409
- static perAppBytes = 900 * 1024 * 1024;
2558
+ static perAppBytes = 1800 * 1024 * 1024;
2410
2559
  static memoryShare = 0.5;
2411
2560
  static coresPerApp = 4;
2412
2561
  static budget() {
@@ -2440,7 +2589,7 @@ class DevBootConcurrency {
2440
2589
 
2441
2590
  // pkgs/@akanjs/cli/application/devSessionLog.ts
2442
2591
  import { mkdir, rename } from "fs/promises";
2443
- import path5 from "path";
2592
+ import path6 from "path";
2444
2593
  import dayjs from "dayjs";
2445
2594
 
2446
2595
  class DevSessionLog {
@@ -2464,19 +2613,19 @@ class DevSessionLog {
2464
2613
  return [...this.#appNames];
2465
2614
  }
2466
2615
  pathOf(app) {
2467
- return path5.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2616
+ return path6.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2468
2617
  }
2469
2618
  previousPathOf(app) {
2470
- return path5.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2619
+ return path6.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2471
2620
  }
2472
2621
  relativePathOf(app) {
2473
- return path5.relative(this.#workspaceRoot, this.pathOf(app));
2622
+ return path6.relative(this.#workspaceRoot, this.pathOf(app));
2474
2623
  }
2475
2624
  async open() {
2476
2625
  const stamp = dayjs(this.#now()).format("YYYY-MM-DD HH:mm:ss");
2477
2626
  for (const app of this.#appNames) {
2478
2627
  const file = this.pathOf(app);
2479
- await mkdir(path5.dirname(file), { recursive: true });
2628
+ await mkdir(path6.dirname(file), { recursive: true });
2480
2629
  await rename(file, this.previousPathOf(app)).catch(() => {
2481
2630
  return;
2482
2631
  });
@@ -2520,7 +2669,7 @@ class DevSessionLog {
2520
2669
  this.#writers.clear();
2521
2670
  }
2522
2671
  #runtimeDirOf(app) {
2523
- return path5.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2672
+ return path6.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2524
2673
  }
2525
2674
  #writeLine(app, text) {
2526
2675
  const writer = this.#writers.get(app);
@@ -2567,7 +2716,7 @@ class DevSupervisor {
2567
2716
  }
2568
2717
  };
2569
2718
  }
2570
- static readyTimeoutMs = 180000;
2719
+ static bootTimeoutMs = 180000;
2571
2720
  static shutdownGraceMs = 12000;
2572
2721
  #options;
2573
2722
  #children = new Map;
@@ -2616,7 +2765,7 @@ class DevSupervisor {
2616
2765
  return ports;
2617
2766
  }
2618
2767
  #makeChild(app, port) {
2619
- const { promise: ready, resolve: markReady } = Promise.withResolvers();
2768
+ const { promise: booted, resolve: markBooted } = Promise.withResolvers();
2620
2769
  return {
2621
2770
  status: {
2622
2771
  app,
@@ -2629,8 +2778,8 @@ class DevSupervisor {
2629
2778
  exitCode: null
2630
2779
  },
2631
2780
  proc: null,
2632
- ready,
2633
- markReady,
2781
+ booted,
2782
+ markBooted,
2634
2783
  opened: false
2635
2784
  };
2636
2785
  }
@@ -2649,12 +2798,12 @@ class DevSupervisor {
2649
2798
  for (const child of wave)
2650
2799
  this.#spawnChild(child);
2651
2800
  this.#publishStatus();
2652
- await Promise.all(wave.map(async (child) => await this.#waitForReady(child)));
2801
+ await Promise.all(wave.map(async (child) => await this.#waitForBoot(child)));
2653
2802
  }
2654
2803
  }
2655
- async#waitForReady(child) {
2656
- if (await DevSupervisor.timesOut(child.ready, DevSupervisor.readyTimeoutMs))
2657
- this.#note(`${child.status.name} has not reported ready after ${Math.round(DevSupervisor.readyTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
2804
+ async#waitForBoot(child) {
2805
+ if (await DevSupervisor.timesOut(child.booted, DevSupervisor.bootTimeoutMs))
2806
+ this.#note(`${child.status.name} has not finished booting after ${Math.round(DevSupervisor.bootTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
2658
2807
  }
2659
2808
  static async timesOut(work, ms) {
2660
2809
  let timer;
@@ -2691,9 +2840,12 @@ class DevSupervisor {
2691
2840
  },
2692
2841
  stdio: ["ignore", "pipe", "pipe"],
2693
2842
  ipc: (message) => {
2694
- if (!message || typeof message !== "object" || typeof message.state !== "string")
2843
+ if (!message || typeof message !== "object")
2695
2844
  return;
2696
- this.#applyChildEvent(child, message);
2845
+ if ("booted" in message)
2846
+ child.markBooted();
2847
+ else if (typeof message.state === "string")
2848
+ this.#applyChildEvent(child, message);
2697
2849
  },
2698
2850
  serialization: "advanced",
2699
2851
  onExit: (_proc, exitCode, signalCode) => {
@@ -2703,7 +2855,7 @@ class DevSupervisor {
2703
2855
  return;
2704
2856
  child.status.state = "stopped";
2705
2857
  child.status.detail = signalCode ? `killed by ${signalCode}` : `exited with ${exitCode}`;
2706
- child.markReady();
2858
+ child.markBooted();
2707
2859
  this.#publishStatus();
2708
2860
  }
2709
2861
  });
@@ -2717,14 +2869,13 @@ class DevSupervisor {
2717
2869
  child.status.state = event.state;
2718
2870
  child.status.detail = event.detail ?? "";
2719
2871
  if (event.state === "ready") {
2720
- child.markReady();
2721
2872
  if (this.#options.open && !child.opened) {
2722
2873
  child.opened = true;
2723
2874
  openBrowser(child.status.url);
2724
2875
  }
2725
2876
  }
2726
2877
  if (event.state === "failed")
2727
- child.markReady();
2878
+ child.markBooted();
2728
2879
  this.#publishStatus();
2729
2880
  }
2730
2881
  async#drain(stream, name, kind) {
@@ -3125,7 +3276,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3125
3276
  await Promise.all(shares.map(async (share) => await share.close()));
3126
3277
  }, "Abandoning the tunnel release; the shares expire on their own.");
3127
3278
  await Promise.all(apps.map(async (app) => {
3128
- const { TunnelShare } = await import("./TunnelShare-pemaknmd.js");
3279
+ const { TunnelShare } = await import("./TunnelShare-cwh9ezsm.js");
3129
3280
  const share = await TunnelShare.open(app, { workspace: app.workspace });
3130
3281
  shares.push(share);
3131
3282
  urls.set(app.name, share.url);