@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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-rmcw8yc8.js";
5
+ } from "./index-58cyna2s.js";
6
6
  import {
7
7
  AGENT_BLOCK_START,
8
8
  AGENT_BLOCK_END,
@@ -13,17 +13,17 @@ import {
13
13
  CLAUDE_COMMENT_RULE,
14
14
  AppExecutor,
15
15
  LibExecutor
16
- } from "./index-ga6hygxr.js";
16
+ } from "./index-0n9q1f23.js";
17
17
  import {
18
18
  collectRecipeSources2,
19
19
  scanRecipes2
20
20
  } from "./index-hj6w4ka1.js";
21
21
  import {
22
22
  AkanContextAnalyzer
23
- } from "./index-9629w7ns.js";
23
+ } from "./index-hzy4pe24.js";
24
24
  import {
25
25
  Prompter2
26
- } from "./index-0bq14vr1.js";
26
+ } from "./index-zzvvc845.js";
27
27
 
28
28
  // pkgs/@akanjs/cli/agent/agent.script.ts
29
29
  import { Logger as Logger2 } from "akanjs/common";
@@ -8,10 +8,10 @@ import {
8
8
  isScannedRootEntry,
9
9
  AppExecutor,
10
10
  LibExecutor
11
- } from "./index-ga6hygxr.js";
11
+ } from "./index-0n9q1f23.js";
12
12
  import {
13
13
  FileSys2
14
- } from "./index-5b6tavv3.js";
14
+ } from "./index-52w0fvh8.js";
15
15
  import {
16
16
  collectRecipeSources2,
17
17
  scanRecipes2,
@@ -21,7 +21,7 @@ import {
21
21
  workflowPathsForPlan,
22
22
  workflowRunArtifactPath,
23
23
  workflowSyncDir
24
- } from "./index-yy4s1j72.js";
24
+ } from "./index-d65fzxvd.js";
25
25
 
26
26
  // pkgs/@akanjs/devkit/akanContext.ts
27
27
  import { readdir } from "fs/promises";
@@ -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,6 +240,7 @@ 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 = {};
@@ -265,10 +272,16 @@ class IncrementalBuilderHost {
265
272
  this.ready = false;
266
273
  const afterRecycle = this.#spawnAfterRecycle;
267
274
  this.#spawnAfterRecycle = false;
275
+ this.#checkPatchingMarker(isRestart && !afterRecycle);
268
276
  let proc;
269
277
  proc = Bun.spawn(["bun", this.entry], {
270
278
  cwd: this.app.cwdPath,
271
- env: { ...this.env, AKAN_WATCH: "1", ...afterRecycle ? { AKAN_BUILDER_ANNOUNCE_BOOT: "1" } : {} },
279
+ env: {
280
+ ...this.env,
281
+ AKAN_WATCH: "1",
282
+ ...afterRecycle ? { AKAN_BUILDER_ANNOUNCE_BOOT: "1" } : {},
283
+ ...this.#patcherOff ? { AKAN_DEV_CSR_PATCHER: "off" } : {}
284
+ },
272
285
  stdio: ["ignore", this.#stdio, this.#stdio],
273
286
  ipc: (msg) => {
274
287
  if (this.#proc !== proc)
@@ -323,6 +336,17 @@ class IncrementalBuilderHost {
323
336
  }
324
337
  this.logger.verbose(`builder spawned pid=${proc.pid} entry=${this.entry}${isRestart ? " restart=1" : ""}`);
325
338
  }
339
+ #checkPatchingMarker(afterCrash) {
340
+ 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));
341
+ if (markers.length === 0)
342
+ return;
343
+ if (afterCrash && !this.#patcherOff) {
344
+ this.#patcherOff = true;
345
+ 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)");
346
+ }
347
+ for (const marker of markers)
348
+ fs.rmSync(marker, { force: true });
349
+ }
326
350
  async#drain(stream, kind) {
327
351
  if (!stream)
328
352
  return;
@@ -618,6 +642,41 @@ class BackendImportGraph {
618
642
  }
619
643
  }
620
644
 
645
+ // pkgs/@akanjs/devkit/akanApp/devBootLatch.ts
646
+ class DevBootLatch {
647
+ #onBooted;
648
+ #waitMs;
649
+ #ready = false;
650
+ #armed = false;
651
+ #booted = false;
652
+ #timer = null;
653
+ constructor(onBooted, { waitMs = 30000 } = {}) {
654
+ this.#onBooted = onBooted;
655
+ this.#waitMs = waitMs;
656
+ }
657
+ ready() {
658
+ this.#ready = true;
659
+ if (!this.#armed && !this.#timer) {
660
+ this.#timer = setTimeout(() => this.armed(), this.#waitMs);
661
+ this.#timer.unref?.();
662
+ }
663
+ this.#release();
664
+ }
665
+ armed() {
666
+ this.#armed = true;
667
+ this.#release();
668
+ }
669
+ #release() {
670
+ if (this.#booted || !this.#ready || !this.#armed)
671
+ return;
672
+ this.#booted = true;
673
+ if (this.#timer)
674
+ clearTimeout(this.#timer);
675
+ this.#timer = null;
676
+ this.#onBooted();
677
+ }
678
+ }
679
+
621
680
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
622
681
  var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
623
682
  var asMib = (bytes) => Math.round(bytes / 1024 / 1024);
@@ -641,6 +700,7 @@ class AkanAppHost {
641
700
  env;
642
701
  #onDevEvent;
643
702
  #lastDevState = null;
703
+ #bootLatch = new DevBootLatch(() => this.#onDevEvent?.({ app: this.app.name, booted: true }));
644
704
  #backend = null;
645
705
  #builder = null;
646
706
  #backendReady = false;
@@ -652,6 +712,8 @@ class AkanAppHost {
652
712
  #backendLifecycleState = "stopped";
653
713
  #pendingRestartReason = null;
654
714
  #pendingRecycle = null;
715
+ #restartFailedAfterApply = null;
716
+ #stopping = false;
655
717
  #builderRecoveryTimer = null;
656
718
  #builderRecoveryAttempts = 0;
657
719
  #backendStartStatus = null;
@@ -679,6 +741,7 @@ class AkanAppHost {
679
741
  #builderGapStamp = null;
680
742
  #pendingBuilderMessages = [];
681
743
  #builderRequests = new BuilderRequestRouter;
744
+ #builderGeneration = 0;
682
745
  constructor(app, {
683
746
  env,
684
747
  stdio = "inherit",
@@ -695,6 +758,8 @@ class AkanAppHost {
695
758
  return;
696
759
  this.#lastDevState = state;
697
760
  this.#onDevEvent({ app: this.app.name, state, ...detail ? { detail } : {} });
761
+ if (state === "ready")
762
+ this.#bootLatch.ready();
698
763
  }
699
764
  async start() {
700
765
  if (this.#backend)
@@ -712,6 +777,8 @@ class AkanAppHost {
712
777
  return this;
713
778
  }
714
779
  async stop() {
780
+ this.#stopping = true;
781
+ this.#pendingRecycle = null;
715
782
  this.#cancelIdleSuspend();
716
783
  this.#stopIdleWatcher();
717
784
  this.#clearRestartTimers();
@@ -756,6 +823,7 @@ class AkanAppHost {
756
823
  this.#setBackendLifecycleState("ready", `pid=${msg.pid}`);
757
824
  this.#recordBackendReadyStatus();
758
825
  this.logger.verbose(`backend ready pid=${msg.pid}`);
826
+ this.#forgetRouteBuildStatus();
759
827
  this.#replayBuilderState();
760
828
  return;
761
829
  }
@@ -976,17 +1044,26 @@ ${this.#backendStderrTail.join(`
976
1044
  });
977
1045
  }
978
1046
  async#handleBuilderMessage(message) {
1047
+ if (this.#stopping)
1048
+ return;
979
1049
  this.#markDevActivity();
1050
+ this.#trackBuilderGeneration(message);
980
1051
  if (message.type === "build-status") {
981
1052
  this.#recordBuildStatus(message.data);
982
1053
  this.#sendOrQueueBuildStatus(message.data);
983
1054
  this.#reviveBackendAfterGreenBuild(message.data);
1055
+ await this.#resumeFailedRecycle(message.data);
984
1056
  return;
985
1057
  }
986
1058
  if (message.type === "builder-metrics") {
987
1059
  this.#handleBuilderMetrics(message.data);
988
1060
  return;
989
1061
  }
1062
+ if (message.type === "boot-armed") {
1063
+ this.logger.verbose("[builder] boot builds settled");
1064
+ this.#bootLatch.armed();
1065
+ return;
1066
+ }
990
1067
  if (message.type === "pages-updated" || message.type === "css-updated") {
991
1068
  const recycled = message.data.reason === "builder-recycle";
992
1069
  if (recycled && !this.#shouldRelayRecycledState(message))
@@ -1008,6 +1085,22 @@ ${this.#backendStderrTail.join(`
1008
1085
  }
1009
1086
  this.#sendToBackend(message);
1010
1087
  }
1088
+ #trackBuilderGeneration(message) {
1089
+ const generation = AkanAppHost.#builderGenerationOf(message);
1090
+ if (generation === undefined || generation <= this.#builderGeneration)
1091
+ return;
1092
+ this.#builderGeneration = generation;
1093
+ Object.assign(this.env, { AKAN_BUILDER_INITIAL_GENERATION: String(generation) });
1094
+ }
1095
+ static #builderGenerationOf(message) {
1096
+ if (message.type === "invalidate")
1097
+ return message.generation;
1098
+ if (message.type === "build-status" || message.type === "builder-metrics")
1099
+ return message.data.generation;
1100
+ if (message.type === "pages-updated" || message.type === "css-updated")
1101
+ return message.data.generation;
1102
+ return;
1103
+ }
1011
1104
  #shouldRelayRecycledState(message) {
1012
1105
  const current = message.type === "pages-updated" ? this.#lastGoodFrontend.pages : this.#lastGoodFrontend.css;
1013
1106
  if (shouldRelayRecycledFrontendState(current, message)) {
@@ -1266,9 +1359,7 @@ ${this.#backendStderrTail.join(`
1266
1359
  if (shouldRefreshConfigOnIdleWake(batch)) {
1267
1360
  this.logger.verbose("[idle-suspend] config changed while suspended; restarting the dev host");
1268
1361
  this.#discardBuilderGap("config change replaces the backend anyway");
1269
- await this.#recycleDevChildren({ type: "invalidate", kinds: [...batch?.kinds ?? []], files }, {
1270
- refreshConfig: true
1271
- });
1362
+ await this.#applyRecycle({ type: "invalidate", kinds: [...batch?.kinds ?? []], files }, true);
1272
1363
  return;
1273
1364
  }
1274
1365
  if (files.length > 0)
@@ -1369,7 +1460,8 @@ ${this.#backendStderrTail.join(`
1369
1460
  this.#armRssRecycle(this.#rssRecycleReason);
1370
1461
  const wantsDevHostRestart = shouldRestartDevHostByDevPlan(message);
1371
1462
  const pending = this.#pendingRecycle;
1372
- if (wantsDevHostRestart || shouldRestartBuilderByDevPlan(message) || pending && message.kinds.includes("code")) {
1463
+ const resumes = !!pending && message.kinds.includes("code") && this.#touchesFailedRecycle(message.files);
1464
+ if (wantsDevHostRestart || shouldRestartBuilderByDevPlan(message) || resumes) {
1373
1465
  const refreshConfig = wantsDevHostRestart || (pending?.refreshConfig ?? false);
1374
1466
  const merged = pending ? mergeInvalidateMessages(pending.message, message) : message;
1375
1467
  const generation = message.devPlan?.generation ?? message.generation;
@@ -1377,16 +1469,7 @@ ${this.#backendStderrTail.join(`
1377
1469
  this.#deferRecycle(merged, { refreshConfig, generation });
1378
1470
  return;
1379
1471
  }
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
- }
1472
+ await this.#applyRecycle(merged, refreshConfig);
1390
1473
  return;
1391
1474
  }
1392
1475
  if (await this.#shouldRestartBackend(message)) {
@@ -1395,6 +1478,41 @@ ${this.#backendStderrTail.join(`
1395
1478
  }
1396
1479
  this.#sendToBackend(message);
1397
1480
  }
1481
+ async#applyRecycle(message, refreshConfig) {
1482
+ if (this.#stopping)
1483
+ return;
1484
+ this.#pendingRecycle = null;
1485
+ const progress = { applied: !refreshConfig };
1486
+ try {
1487
+ if (refreshConfig)
1488
+ await this.#restartDevHost(message, progress);
1489
+ else
1490
+ await this.#restartDevChildren(message);
1491
+ } catch (err) {
1492
+ const kind = refreshConfig ? "Config" : "Runtime metadata";
1493
+ const generation = this.#recordDevHostRestartFailure(message, err, kind, { applied: progress.applied });
1494
+ if (progress.applied)
1495
+ this.#restartFailedAfterApply = generation;
1496
+ else
1497
+ this.#pendingRecycle = { message, refreshConfig, failed: true, failedGeneration: generation };
1498
+ this.#resurrectDevChildren(message);
1499
+ }
1500
+ }
1501
+ async#resumeFailedRecycle(status) {
1502
+ const pending = this.#pendingRecycle;
1503
+ if (!pending?.failed || !status.ok || status.phase !== "pages" || !this.#touchesFailedRecycle(status.files))
1504
+ return;
1505
+ if (status.generation <= (pending.failedGeneration ?? -1))
1506
+ return;
1507
+ await this.#applyRecycle(pending.message, pending.refreshConfig);
1508
+ }
1509
+ #touchesFailedRecycle(files) {
1510
+ const pending = this.#pendingRecycle;
1511
+ if (!pending?.failed)
1512
+ return true;
1513
+ const changed = new Set(pending.message.files);
1514
+ return files.some((file) => changed.has(file));
1515
+ }
1398
1516
  #deferRecycle(message, { refreshConfig, generation }) {
1399
1517
  this.#pendingRecycle = { message, refreshConfig };
1400
1518
  const kind = refreshConfig ? "Config" : "Runtime metadata";
@@ -1440,8 +1558,10 @@ ${this.#backendStderrTail.join(`
1440
1558
  this.#builderRecoveryAttempts = 0;
1441
1559
  this.logger.info("[builder-recovery] builder recovered");
1442
1560
  this.#restartBackendForGapChanges();
1561
+ const failedAfterApply = this.#restartFailedAfterApply;
1562
+ this.#restartFailedAfterApply = null;
1443
1563
  const status = {
1444
- generation: reason.generation ?? this.#nextBackendBuildStatusGeneration(),
1564
+ generation: failedAfterApply !== null ? this.#nextBackendBuildStatusGeneration(failedAfterApply + 1) : reason.generation ?? this.#nextBackendBuildStatusGeneration(),
1445
1565
  phase: "scan",
1446
1566
  ok: true,
1447
1567
  files: reason.files,
@@ -1465,12 +1585,12 @@ ${this.#backendStderrTail.join(`
1465
1585
  this.logger.warn(`[dev-host] recycling builder/backend for runtime metadata generation=${generation ?? "(unknown)"} files=${message.files.length}`);
1466
1586
  await this.#recycleDevChildren(message);
1467
1587
  }
1468
- async#restartDevHost(message) {
1588
+ async#restartDevHost(message, progress) {
1469
1589
  const generation = message.devPlan?.generation ?? message.generation;
1470
1590
  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 });
1591
+ await this.#recycleDevChildren(message, { refreshConfig: true, progress });
1472
1592
  }
1473
- async#recycleDevChildren(message, { refreshConfig = false } = {}) {
1593
+ async#recycleDevChildren(message, { refreshConfig = false, progress } = {}) {
1474
1594
  const generation = message.devPlan?.generation ?? message.generation;
1475
1595
  this.#clearRestartTimers();
1476
1596
  this.#builderRecoveryAttempts = 0;
@@ -1484,6 +1604,8 @@ ${this.#backendStderrTail.join(`
1484
1604
  await this.app.getConfig({ refresh: true });
1485
1605
  const { env } = await this.app.prepareCommand("start");
1486
1606
  Object.assign(this.env, env);
1607
+ if (progress)
1608
+ progress.applied = true;
1487
1609
  }
1488
1610
  await this.#backendGraph.refresh();
1489
1611
  await this.#startBuilder();
@@ -1502,22 +1624,27 @@ ${this.#backendStderrTail.join(`
1502
1624
  this.#lastGoodFrontend.css = message;
1503
1625
  this.logger.verbose(`[last-good] css generation=${message.data.generation ?? "(unknown)"} assets=${Object.keys(message.data.cssAssets).length}`);
1504
1626
  }
1505
- #recordDevHostRestartFailure(message, err, kind) {
1627
+ #recordDevHostRestartFailure(message, err, kind, { applied }) {
1506
1628
  const generation = message.devPlan?.generation ?? message.generation ?? this.#nextBackendBuildStatusGeneration();
1507
1629
  const detail = err instanceof Error ? err.message : String(err);
1508
1630
  this.logger.warn(`[dev-host] ${kind.toLowerCase()} restart failed generation=${generation}: ${detail}`);
1631
+ const names = message.files.slice(0, 3).map((file) => path3.basename(file));
1632
+ const saved = names.length > 0 ? names.join(", ") : "the changed file";
1509
1633
  const status = {
1510
1634
  generation,
1511
1635
  phase: "scan",
1512
1636
  ok: false,
1513
1637
  files: message.files,
1514
- message: `${kind} change failed to apply; recovering the dev server automatically: ${detail}`
1638
+ 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
1639
  };
1516
1640
  this.#recordBuildStatus(status);
1517
1641
  this.#sendOrQueueBuildStatus(status);
1642
+ return generation;
1518
1643
  }
1519
1644
  #recordBuildStatus(status) {
1520
1645
  const recovered = shouldMarkBuildPhaseRecovered(this.#buildStatusByPhase, status);
1646
+ if (shouldKeepBuildFailure(this.#buildStatusByPhase, status))
1647
+ return;
1521
1648
  this.#buildStatusByPhase.set(status.phase, status);
1522
1649
  const label = `[build-status] generation=${status.generation} phase=${status.phase} ok=${status.ok} files=${status.files.length}`;
1523
1650
  if (status.ok)
@@ -1533,6 +1660,10 @@ ${this.#backendStderrTail.join(`
1533
1660
  }
1534
1661
  this.#sendToBackend({ type: "build-status", data: status });
1535
1662
  }
1663
+ #forgetRouteBuildStatus() {
1664
+ this.#buildStatusByPhase.delete("route");
1665
+ this.#pendingBuildStatusReplay = this.#pendingBuildStatusReplay.filter((status) => status.phase !== "route");
1666
+ }
1536
1667
  #replayBuilderState() {
1537
1668
  if (!this.#backendReady)
1538
1669
  return;
@@ -1675,16 +1806,16 @@ ${this.#backendStderrTail.join(`
1675
1806
  }
1676
1807
  }
1677
1808
  // pkgs/@akanjs/devkit/applicationTestPreload.ts
1678
- import path3 from "path";
1809
+ import path4 from "path";
1679
1810
  var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
1680
1811
  async function resolveSignalTestPreloadPath(target) {
1681
1812
  const candidates = [];
1682
- for (const basePath of [target.cwdPath, process.cwd(), path3.dirname(Bun.main), import.meta.dir]) {
1813
+ for (const basePath of [target.cwdPath, process.cwd(), path4.dirname(Bun.main), import.meta.dir]) {
1683
1814
  try {
1684
- candidates.push(path3.join(path3.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
1815
+ candidates.push(path4.join(path4.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
1685
1816
  } catch {}
1686
1817
  }
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));
1818
+ 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
1819
  const uniqueCandidates = [...new Set(candidates)];
1689
1820
  for (const candidate of uniqueCandidates)
1690
1821
  if (await Bun.file(candidate).exists())
@@ -1697,7 +1828,7 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
1697
1828
 
1698
1829
  // pkgs/@akanjs/cli/application/application.runner.ts
1699
1830
  import { Logger as Logger3 } from "akanjs/common";
1700
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-n6xfexaa.js")).ApplicationBuildRunner;
1831
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-tm0vba5t.js")).ApplicationBuildRunner;
1701
1832
  var loadPrompts = async () => await import("@inquirer/prompts");
1702
1833
 
1703
1834
  class ApplicationRunner extends runner("application") {
@@ -1742,7 +1873,7 @@ class ApplicationRunner extends runner("application") {
1742
1873
  });
1743
1874
  }
1744
1875
  #runtimeDirOf(app, override) {
1745
- return path4.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path4.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
1876
+ return path5.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path5.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
1746
1877
  }
1747
1878
  async runLogs(app, options) {
1748
1879
  const { LogControlUnavailableError, LogTailClient } = await import("akanjs/server/logging/logTailClient");
@@ -1768,7 +1899,7 @@ class ApplicationRunner extends runner("application") {
1768
1899
  });
1769
1900
  } catch (error) {
1770
1901
  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.`);
1902
+ 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
1903
  throw error;
1773
1904
  }
1774
1905
  const sinceMs = since ? LogQueryMatcher.parseSince(since) : undefined;
@@ -1833,7 +1964,7 @@ try {
1833
1964
  const serverPath = `${app.cwdPath}/server.ts`;
1834
1965
  if (!await app.exists("server.ts"))
1835
1966
  throw new Error(`Server file not found: apps/${app.name}/server.ts`);
1836
- const target = path4.resolve(app.workspace.workspaceRoot, dir);
1967
+ const target = path5.resolve(app.workspace.workspaceRoot, dir);
1837
1968
  const code = `
1838
1969
  const { server } = await import(${JSON.stringify(serverPath)});
1839
1970
  const { DatabaseAdaptorRole, DocumentTransfer } = await import("akanjs/service");
@@ -2406,7 +2537,7 @@ import os from "os";
2406
2537
  import { MemoryLimit as MemoryLimit2 } from "akanjs/server/memoryLimit";
2407
2538
 
2408
2539
  class DevBootConcurrency {
2409
- static perAppBytes = 900 * 1024 * 1024;
2540
+ static perAppBytes = 1800 * 1024 * 1024;
2410
2541
  static memoryShare = 0.5;
2411
2542
  static coresPerApp = 4;
2412
2543
  static budget() {
@@ -2440,7 +2571,7 @@ class DevBootConcurrency {
2440
2571
 
2441
2572
  // pkgs/@akanjs/cli/application/devSessionLog.ts
2442
2573
  import { mkdir, rename } from "fs/promises";
2443
- import path5 from "path";
2574
+ import path6 from "path";
2444
2575
  import dayjs from "dayjs";
2445
2576
 
2446
2577
  class DevSessionLog {
@@ -2464,19 +2595,19 @@ class DevSessionLog {
2464
2595
  return [...this.#appNames];
2465
2596
  }
2466
2597
  pathOf(app) {
2467
- return path5.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2598
+ return path6.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2468
2599
  }
2469
2600
  previousPathOf(app) {
2470
- return path5.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2601
+ return path6.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2471
2602
  }
2472
2603
  relativePathOf(app) {
2473
- return path5.relative(this.#workspaceRoot, this.pathOf(app));
2604
+ return path6.relative(this.#workspaceRoot, this.pathOf(app));
2474
2605
  }
2475
2606
  async open() {
2476
2607
  const stamp = dayjs(this.#now()).format("YYYY-MM-DD HH:mm:ss");
2477
2608
  for (const app of this.#appNames) {
2478
2609
  const file = this.pathOf(app);
2479
- await mkdir(path5.dirname(file), { recursive: true });
2610
+ await mkdir(path6.dirname(file), { recursive: true });
2480
2611
  await rename(file, this.previousPathOf(app)).catch(() => {
2481
2612
  return;
2482
2613
  });
@@ -2520,7 +2651,7 @@ class DevSessionLog {
2520
2651
  this.#writers.clear();
2521
2652
  }
2522
2653
  #runtimeDirOf(app) {
2523
- return path5.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2654
+ return path6.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2524
2655
  }
2525
2656
  #writeLine(app, text) {
2526
2657
  const writer = this.#writers.get(app);
@@ -2567,7 +2698,7 @@ class DevSupervisor {
2567
2698
  }
2568
2699
  };
2569
2700
  }
2570
- static readyTimeoutMs = 180000;
2701
+ static bootTimeoutMs = 180000;
2571
2702
  static shutdownGraceMs = 12000;
2572
2703
  #options;
2573
2704
  #children = new Map;
@@ -2616,7 +2747,7 @@ class DevSupervisor {
2616
2747
  return ports;
2617
2748
  }
2618
2749
  #makeChild(app, port) {
2619
- const { promise: ready, resolve: markReady } = Promise.withResolvers();
2750
+ const { promise: booted, resolve: markBooted } = Promise.withResolvers();
2620
2751
  return {
2621
2752
  status: {
2622
2753
  app,
@@ -2629,8 +2760,8 @@ class DevSupervisor {
2629
2760
  exitCode: null
2630
2761
  },
2631
2762
  proc: null,
2632
- ready,
2633
- markReady,
2763
+ booted,
2764
+ markBooted,
2634
2765
  opened: false
2635
2766
  };
2636
2767
  }
@@ -2649,12 +2780,12 @@ class DevSupervisor {
2649
2780
  for (const child of wave)
2650
2781
  this.#spawnChild(child);
2651
2782
  this.#publishStatus();
2652
- await Promise.all(wave.map(async (child) => await this.#waitForReady(child)));
2783
+ await Promise.all(wave.map(async (child) => await this.#waitForBoot(child)));
2653
2784
  }
2654
2785
  }
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");
2786
+ async#waitForBoot(child) {
2787
+ if (await DevSupervisor.timesOut(child.booted, DevSupervisor.bootTimeoutMs))
2788
+ this.#note(`${child.status.name} has not finished booting after ${Math.round(DevSupervisor.bootTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
2658
2789
  }
2659
2790
  static async timesOut(work, ms) {
2660
2791
  let timer;
@@ -2691,9 +2822,12 @@ class DevSupervisor {
2691
2822
  },
2692
2823
  stdio: ["ignore", "pipe", "pipe"],
2693
2824
  ipc: (message) => {
2694
- if (!message || typeof message !== "object" || typeof message.state !== "string")
2825
+ if (!message || typeof message !== "object")
2695
2826
  return;
2696
- this.#applyChildEvent(child, message);
2827
+ if ("booted" in message)
2828
+ child.markBooted();
2829
+ else if (typeof message.state === "string")
2830
+ this.#applyChildEvent(child, message);
2697
2831
  },
2698
2832
  serialization: "advanced",
2699
2833
  onExit: (_proc, exitCode, signalCode) => {
@@ -2703,7 +2837,7 @@ class DevSupervisor {
2703
2837
  return;
2704
2838
  child.status.state = "stopped";
2705
2839
  child.status.detail = signalCode ? `killed by ${signalCode}` : `exited with ${exitCode}`;
2706
- child.markReady();
2840
+ child.markBooted();
2707
2841
  this.#publishStatus();
2708
2842
  }
2709
2843
  });
@@ -2717,14 +2851,13 @@ class DevSupervisor {
2717
2851
  child.status.state = event.state;
2718
2852
  child.status.detail = event.detail ?? "";
2719
2853
  if (event.state === "ready") {
2720
- child.markReady();
2721
2854
  if (this.#options.open && !child.opened) {
2722
2855
  child.opened = true;
2723
2856
  openBrowser(child.status.url);
2724
2857
  }
2725
2858
  }
2726
2859
  if (event.state === "failed")
2727
- child.markReady();
2860
+ child.markBooted();
2728
2861
  this.#publishStatus();
2729
2862
  }
2730
2863
  async#drain(stream, name, kind) {
@@ -3125,7 +3258,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3125
3258
  await Promise.all(shares.map(async (share) => await share.close()));
3126
3259
  }, "Abandoning the tunnel release; the shares expire on their own.");
3127
3260
  await Promise.all(apps.map(async (app) => {
3128
- const { TunnelShare } = await import("./TunnelShare-pemaknmd.js");
3261
+ const { TunnelShare } = await import("./TunnelShare-cwh9ezsm.js");
3129
3262
  const share = await TunnelShare.open(app, { workspace: app.workspace });
3130
3263
  shares.push(share);
3131
3264
  urls.set(app.name, share.url);