@akanjs/cli 3.0.0-beta.23 → 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 (73) 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-b6rck8d6.js → application.command-2x9jqy0k.js} +7 -7
  8. package/{applicationBuildRunner-vh74vrhg.js → applicationBuildRunner-tm0vba5t.js} +4 -4
  9. package/buildBatch.proc.js +99 -12
  10. package/{cloud.command-gwdypy7k.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/agentRule/agentRule.instruction.md +7 -0
  19. package/guidelines/conventions/conventions.instruction.md +7 -5
  20. package/incrementalBuilder.proc.js +544 -72
  21. package/{index-ga6hygxr.js → index-0n9q1f23.js} +3 -3
  22. package/{index-3t7g9e4r.js → index-2ahj896a.js} +1 -1
  23. package/{index-smme4r47.js → index-2wc41k5w.js} +1 -1
  24. package/{index-khpfpcsd.js → index-48k96p55.js} +27 -7
  25. package/{index-776sswhv.js → index-4mgbzgb5.js} +281 -651
  26. package/{index-2gh9sndr.js → index-50rq2qms.js} +4 -4
  27. package/{index-5b6tavv3.js → index-52w0fvh8.js} +15 -1
  28. package/{index-yjkte9aj.js → index-56xwsgyn.js} +3 -3
  29. package/{index-rmcw8yc8.js → index-58cyna2s.js} +2 -2
  30. package/{index-1db2cw0y.js → index-5mm3g24t.js} +2 -2
  31. package/{index-wp43xrjv.js → index-6dt6zft6.js} +4 -4
  32. package/{index-vj1cwwy5.js → index-7yxndzea.js} +7 -7
  33. package/{index-q44jbw48.js → index-860zjnr1.js} +1 -1
  34. package/{index-jn07hxrr.js → index-8k93esfr.js} +6 -6
  35. package/{index-22d57v4c.js → index-94s080qm.js} +3 -3
  36. package/{index-yy4s1j72.js → index-d65fzxvd.js} +1 -1
  37. package/{index-m0ms3n1c.js → index-da72pfdv.js} +7 -7
  38. package/index-dt1x62ec.js +4108 -0
  39. package/{index-4dn1p115.js → index-ggfhz9ab.js} +4 -4
  40. package/{index-9629w7ns.js → index-hzy4pe24.js} +3 -3
  41. package/{index-jg5e15db.js → index-pb10njz7.js} +192 -58
  42. package/{index-mgfx3mfs.js → index-per2kqy9.js} +4 -4
  43. package/{index-aaz7n8s2.js → index-qm4fppfk.js} +4 -4
  44. package/{index-ya5qbmyg.js → index-t02g6dpn.js} +3 -3
  45. package/{index-hb8sgepf.js → index-tpdn30ya.js} +1 -1
  46. package/{index-rnt66vym.js → index-v4hnkqz9.js} +1 -1
  47. package/{index-pp54p5zk.js → index-xfgdmm5d.js} +2 -2
  48. package/{index-tgjy8xf3.js → index-zazvn45w.js} +4 -4
  49. package/{index-0bq14vr1.js → index-zzvvc845.js} +1 -1
  50. package/index.js +22 -22
  51. package/{library.command-ccb1cp92.js → library.command-sv6g1h2g.js} +4 -4
  52. package/{localRegistry.command-cxvgn7fr.js → localRegistry.command-4kj56531.js} +11 -11
  53. package/{module.command-5k6h407d.js → module.command-ccdeder9.js} +6 -6
  54. package/{package.command-e6ntvpez.js → package.command-xmw605b1.js} +5 -5
  55. package/package.json +4 -4
  56. package/{page.command-v4f90wb8.js → page.command-ky7vafsn.js} +4 -4
  57. package/{primitive.command-g6km57q5.js → primitive.command-bh6j8nfs.js} +7 -7
  58. package/{prompter-ptaa3nk0.js → prompter-76k56b37.js} +2 -2
  59. package/{quality.command-de3p36pz.js → quality.command-rqqrpe98.js} +3 -3
  60. package/{repair.command-d3x1javq.js → repair.command-n9nsm9qe.js} +6 -6
  61. package/{scalar.command-7ezrac2b.js → scalar.command-t09gmck8.js} +5 -5
  62. package/{subspace.command-0cep72ce.js → subspace.command-ygfdqpbj.js} +6 -6
  63. package/templates/workspaceRoot/package.json.template +1 -3
  64. package/{tunnel.command-hsrv2mny.js → tunnel.command-4g95vhsn.js} +5 -5
  65. package/{workflow.command-z2p32nmf.js → workflow.command-b91ph8sx.js} +11 -11
  66. package/{workspace.command-axwscbnn.js → workspace.command-p44d21qf.js} +24 -24
  67. package/README.ko.md +0 -72
  68. package/README.md +0 -88
  69. package/TunnelShare-scf4e4pd.js +0 -9
  70. package/cloud.script-at2beffs.js +0 -20
  71. package/context.runner-ehjs419v.js +0 -21
  72. package/index-fgq75haa.js +0 -2084
  73. package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
@@ -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",
@@ -209,15 +213,18 @@ var builderMsgTypeSet = new Set([
209
213
  "invalidate",
210
214
  "css-updated",
211
215
  "pages-updated",
216
+ "csr-updated",
217
+ "ssr-updated",
212
218
  "build-status",
213
- "builder-metrics"
219
+ "builder-metrics",
220
+ "boot-armed"
214
221
  ]);
215
222
 
216
223
  class IncrementalBuilderHost {
217
224
  static #restartBaseDelayMs = 1000;
218
225
  static #restartMaxDelayMs = 30000;
219
226
  static #recycleDrainTimeoutMs = 30000;
220
- static #devMaxRssBytes = 1200 * 1024 * 1024;
227
+ static #devMaxRssBytes = 2048 * 1024 * 1024;
221
228
  logger = new Logger("IncrementalBuilderHost");
222
229
  entry;
223
230
  env;
@@ -233,6 +240,7 @@ class IncrementalBuilderHost {
233
240
  #recycleTimer = null;
234
241
  #recycleRequested = false;
235
242
  #spawnAfterRecycle = false;
243
+ #patcherOff = false;
236
244
  #manualStop = false;
237
245
  #inFlight = new Map;
238
246
  #startOptions = {};
@@ -264,10 +272,16 @@ class IncrementalBuilderHost {
264
272
  this.ready = false;
265
273
  const afterRecycle = this.#spawnAfterRecycle;
266
274
  this.#spawnAfterRecycle = false;
275
+ this.#checkPatchingMarker(isRestart && !afterRecycle);
267
276
  let proc;
268
277
  proc = Bun.spawn(["bun", this.entry], {
269
278
  cwd: this.app.cwdPath,
270
- 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
+ },
271
285
  stdio: ["ignore", this.#stdio, this.#stdio],
272
286
  ipc: (msg) => {
273
287
  if (this.#proc !== proc)
@@ -322,6 +336,17 @@ class IncrementalBuilderHost {
322
336
  }
323
337
  this.logger.verbose(`builder spawned pid=${proc.pid} entry=${this.entry}${isRestart ? " restart=1" : ""}`);
324
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
+ }
325
350
  async#drain(stream, kind) {
326
351
  if (!stream)
327
352
  return;
@@ -617,6 +642,41 @@ class BackendImportGraph {
617
642
  }
618
643
  }
619
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
+
620
680
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
621
681
  var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
622
682
  var asMib = (bytes) => Math.round(bytes / 1024 / 1024);
@@ -640,6 +700,7 @@ class AkanAppHost {
640
700
  env;
641
701
  #onDevEvent;
642
702
  #lastDevState = null;
703
+ #bootLatch = new DevBootLatch(() => this.#onDevEvent?.({ app: this.app.name, booted: true }));
643
704
  #backend = null;
644
705
  #builder = null;
645
706
  #backendReady = false;
@@ -651,6 +712,8 @@ class AkanAppHost {
651
712
  #backendLifecycleState = "stopped";
652
713
  #pendingRestartReason = null;
653
714
  #pendingRecycle = null;
715
+ #restartFailedAfterApply = null;
716
+ #stopping = false;
654
717
  #builderRecoveryTimer = null;
655
718
  #builderRecoveryAttempts = 0;
656
719
  #backendStartStatus = null;
@@ -678,6 +741,7 @@ class AkanAppHost {
678
741
  #builderGapStamp = null;
679
742
  #pendingBuilderMessages = [];
680
743
  #builderRequests = new BuilderRequestRouter;
744
+ #builderGeneration = 0;
681
745
  constructor(app, {
682
746
  env,
683
747
  stdio = "inherit",
@@ -694,6 +758,8 @@ class AkanAppHost {
694
758
  return;
695
759
  this.#lastDevState = state;
696
760
  this.#onDevEvent({ app: this.app.name, state, ...detail ? { detail } : {} });
761
+ if (state === "ready")
762
+ this.#bootLatch.ready();
697
763
  }
698
764
  async start() {
699
765
  if (this.#backend)
@@ -711,6 +777,8 @@ class AkanAppHost {
711
777
  return this;
712
778
  }
713
779
  async stop() {
780
+ this.#stopping = true;
781
+ this.#pendingRecycle = null;
714
782
  this.#cancelIdleSuspend();
715
783
  this.#stopIdleWatcher();
716
784
  this.#clearRestartTimers();
@@ -755,6 +823,7 @@ class AkanAppHost {
755
823
  this.#setBackendLifecycleState("ready", `pid=${msg.pid}`);
756
824
  this.#recordBackendReadyStatus();
757
825
  this.logger.verbose(`backend ready pid=${msg.pid}`);
826
+ this.#forgetRouteBuildStatus();
758
827
  this.#replayBuilderState();
759
828
  return;
760
829
  }
@@ -975,17 +1044,26 @@ ${this.#backendStderrTail.join(`
975
1044
  });
976
1045
  }
977
1046
  async#handleBuilderMessage(message) {
1047
+ if (this.#stopping)
1048
+ return;
978
1049
  this.#markDevActivity();
1050
+ this.#trackBuilderGeneration(message);
979
1051
  if (message.type === "build-status") {
980
1052
  this.#recordBuildStatus(message.data);
981
1053
  this.#sendOrQueueBuildStatus(message.data);
982
1054
  this.#reviveBackendAfterGreenBuild(message.data);
1055
+ await this.#resumeFailedRecycle(message.data);
983
1056
  return;
984
1057
  }
985
1058
  if (message.type === "builder-metrics") {
986
1059
  this.#handleBuilderMetrics(message.data);
987
1060
  return;
988
1061
  }
1062
+ if (message.type === "boot-armed") {
1063
+ this.logger.verbose("[builder] boot builds settled");
1064
+ this.#bootLatch.armed();
1065
+ return;
1066
+ }
989
1067
  if (message.type === "pages-updated" || message.type === "css-updated") {
990
1068
  const recycled = message.data.reason === "builder-recycle";
991
1069
  if (recycled && !this.#shouldRelayRecycledState(message))
@@ -1007,6 +1085,22 @@ ${this.#backendStderrTail.join(`
1007
1085
  }
1008
1086
  this.#sendToBackend(message);
1009
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
+ }
1010
1104
  #shouldRelayRecycledState(message) {
1011
1105
  const current = message.type === "pages-updated" ? this.#lastGoodFrontend.pages : this.#lastGoodFrontend.css;
1012
1106
  if (shouldRelayRecycledFrontendState(current, message)) {
@@ -1265,9 +1359,7 @@ ${this.#backendStderrTail.join(`
1265
1359
  if (shouldRefreshConfigOnIdleWake(batch)) {
1266
1360
  this.logger.verbose("[idle-suspend] config changed while suspended; restarting the dev host");
1267
1361
  this.#discardBuilderGap("config change replaces the backend anyway");
1268
- await this.#recycleDevChildren({ type: "invalidate", kinds: [...batch?.kinds ?? []], files }, {
1269
- refreshConfig: true
1270
- });
1362
+ await this.#applyRecycle({ type: "invalidate", kinds: [...batch?.kinds ?? []], files }, true);
1271
1363
  return;
1272
1364
  }
1273
1365
  if (files.length > 0)
@@ -1368,7 +1460,8 @@ ${this.#backendStderrTail.join(`
1368
1460
  this.#armRssRecycle(this.#rssRecycleReason);
1369
1461
  const wantsDevHostRestart = shouldRestartDevHostByDevPlan(message);
1370
1462
  const pending = this.#pendingRecycle;
1371
- 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) {
1372
1465
  const refreshConfig = wantsDevHostRestart || (pending?.refreshConfig ?? false);
1373
1466
  const merged = pending ? mergeInvalidateMessages(pending.message, message) : message;
1374
1467
  const generation = message.devPlan?.generation ?? message.generation;
@@ -1376,16 +1469,7 @@ ${this.#backendStderrTail.join(`
1376
1469
  this.#deferRecycle(merged, { refreshConfig, generation });
1377
1470
  return;
1378
1471
  }
1379
- this.#pendingRecycle = null;
1380
- try {
1381
- if (refreshConfig)
1382
- await this.#restartDevHost(merged);
1383
- else
1384
- await this.#restartDevChildren(merged);
1385
- } catch (err) {
1386
- this.#recordDevHostRestartFailure(merged, err, refreshConfig ? "Config" : "Runtime metadata");
1387
- this.#resurrectDevChildren(merged);
1388
- }
1472
+ await this.#applyRecycle(merged, refreshConfig);
1389
1473
  return;
1390
1474
  }
1391
1475
  if (await this.#shouldRestartBackend(message)) {
@@ -1394,6 +1478,41 @@ ${this.#backendStderrTail.join(`
1394
1478
  }
1395
1479
  this.#sendToBackend(message);
1396
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
+ }
1397
1516
  #deferRecycle(message, { refreshConfig, generation }) {
1398
1517
  this.#pendingRecycle = { message, refreshConfig };
1399
1518
  const kind = refreshConfig ? "Config" : "Runtime metadata";
@@ -1439,8 +1558,10 @@ ${this.#backendStderrTail.join(`
1439
1558
  this.#builderRecoveryAttempts = 0;
1440
1559
  this.logger.info("[builder-recovery] builder recovered");
1441
1560
  this.#restartBackendForGapChanges();
1561
+ const failedAfterApply = this.#restartFailedAfterApply;
1562
+ this.#restartFailedAfterApply = null;
1442
1563
  const status = {
1443
- generation: reason.generation ?? this.#nextBackendBuildStatusGeneration(),
1564
+ generation: failedAfterApply !== null ? this.#nextBackendBuildStatusGeneration(failedAfterApply + 1) : reason.generation ?? this.#nextBackendBuildStatusGeneration(),
1444
1565
  phase: "scan",
1445
1566
  ok: true,
1446
1567
  files: reason.files,
@@ -1464,12 +1585,12 @@ ${this.#backendStderrTail.join(`
1464
1585
  this.logger.warn(`[dev-host] recycling builder/backend for runtime metadata generation=${generation ?? "(unknown)"} files=${message.files.length}`);
1465
1586
  await this.#recycleDevChildren(message);
1466
1587
  }
1467
- async#restartDevHost(message) {
1588
+ async#restartDevHost(message, progress) {
1468
1589
  const generation = message.devPlan?.generation ?? message.generation;
1469
1590
  this.logger.warn(`[dev-host] config change detected; restarting dev host generation=${generation ?? "(unknown)"} files=${message.files.length}`);
1470
- await this.#recycleDevChildren(message, { refreshConfig: true });
1591
+ await this.#recycleDevChildren(message, { refreshConfig: true, progress });
1471
1592
  }
1472
- async#recycleDevChildren(message, { refreshConfig = false } = {}) {
1593
+ async#recycleDevChildren(message, { refreshConfig = false, progress } = {}) {
1473
1594
  const generation = message.devPlan?.generation ?? message.generation;
1474
1595
  this.#clearRestartTimers();
1475
1596
  this.#builderRecoveryAttempts = 0;
@@ -1483,6 +1604,8 @@ ${this.#backendStderrTail.join(`
1483
1604
  await this.app.getConfig({ refresh: true });
1484
1605
  const { env } = await this.app.prepareCommand("start");
1485
1606
  Object.assign(this.env, env);
1607
+ if (progress)
1608
+ progress.applied = true;
1486
1609
  }
1487
1610
  await this.#backendGraph.refresh();
1488
1611
  await this.#startBuilder();
@@ -1501,22 +1624,27 @@ ${this.#backendStderrTail.join(`
1501
1624
  this.#lastGoodFrontend.css = message;
1502
1625
  this.logger.verbose(`[last-good] css generation=${message.data.generation ?? "(unknown)"} assets=${Object.keys(message.data.cssAssets).length}`);
1503
1626
  }
1504
- #recordDevHostRestartFailure(message, err, kind) {
1627
+ #recordDevHostRestartFailure(message, err, kind, { applied }) {
1505
1628
  const generation = message.devPlan?.generation ?? message.generation ?? this.#nextBackendBuildStatusGeneration();
1506
1629
  const detail = err instanceof Error ? err.message : String(err);
1507
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";
1508
1633
  const status = {
1509
1634
  generation,
1510
1635
  phase: "scan",
1511
1636
  ok: false,
1512
1637
  files: message.files,
1513
- 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.`
1514
1639
  };
1515
1640
  this.#recordBuildStatus(status);
1516
1641
  this.#sendOrQueueBuildStatus(status);
1642
+ return generation;
1517
1643
  }
1518
1644
  #recordBuildStatus(status) {
1519
1645
  const recovered = shouldMarkBuildPhaseRecovered(this.#buildStatusByPhase, status);
1646
+ if (shouldKeepBuildFailure(this.#buildStatusByPhase, status))
1647
+ return;
1520
1648
  this.#buildStatusByPhase.set(status.phase, status);
1521
1649
  const label = `[build-status] generation=${status.generation} phase=${status.phase} ok=${status.ok} files=${status.files.length}`;
1522
1650
  if (status.ok)
@@ -1532,6 +1660,10 @@ ${this.#backendStderrTail.join(`
1532
1660
  }
1533
1661
  this.#sendToBackend({ type: "build-status", data: status });
1534
1662
  }
1663
+ #forgetRouteBuildStatus() {
1664
+ this.#buildStatusByPhase.delete("route");
1665
+ this.#pendingBuildStatusReplay = this.#pendingBuildStatusReplay.filter((status) => status.phase !== "route");
1666
+ }
1535
1667
  #replayBuilderState() {
1536
1668
  if (!this.#backendReady)
1537
1669
  return;
@@ -1674,16 +1806,16 @@ ${this.#backendStderrTail.join(`
1674
1806
  }
1675
1807
  }
1676
1808
  // pkgs/@akanjs/devkit/applicationTestPreload.ts
1677
- import path3 from "path";
1809
+ import path4 from "path";
1678
1810
  var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
1679
1811
  async function resolveSignalTestPreloadPath(target) {
1680
1812
  const candidates = [];
1681
- 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]) {
1682
1814
  try {
1683
- 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));
1684
1816
  } catch {}
1685
1817
  }
1686
- 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));
1687
1819
  const uniqueCandidates = [...new Set(candidates)];
1688
1820
  for (const candidate of uniqueCandidates)
1689
1821
  if (await Bun.file(candidate).exists())
@@ -1696,7 +1828,7 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
1696
1828
 
1697
1829
  // pkgs/@akanjs/cli/application/application.runner.ts
1698
1830
  import { Logger as Logger3 } from "akanjs/common";
1699
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-vh74vrhg.js")).ApplicationBuildRunner;
1831
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-tm0vba5t.js")).ApplicationBuildRunner;
1700
1832
  var loadPrompts = async () => await import("@inquirer/prompts");
1701
1833
 
1702
1834
  class ApplicationRunner extends runner("application") {
@@ -1741,7 +1873,7 @@ class ApplicationRunner extends runner("application") {
1741
1873
  });
1742
1874
  }
1743
1875
  #runtimeDirOf(app, override) {
1744
- 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"));
1745
1877
  }
1746
1878
  async runLogs(app, options) {
1747
1879
  const { LogControlUnavailableError, LogTailClient } = await import("akanjs/server/logging/logTailClient");
@@ -1767,7 +1899,7 @@ class ApplicationRunner extends runner("application") {
1767
1899
  });
1768
1900
  } catch (error) {
1769
1901
  if (error instanceof LogControlUnavailableError)
1770
- 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.`);
1771
1903
  throw error;
1772
1904
  }
1773
1905
  const sinceMs = since ? LogQueryMatcher.parseSince(since) : undefined;
@@ -1832,7 +1964,7 @@ try {
1832
1964
  const serverPath = `${app.cwdPath}/server.ts`;
1833
1965
  if (!await app.exists("server.ts"))
1834
1966
  throw new Error(`Server file not found: apps/${app.name}/server.ts`);
1835
- const target = path4.resolve(app.workspace.workspaceRoot, dir);
1967
+ const target = path5.resolve(app.workspace.workspaceRoot, dir);
1836
1968
  const code = `
1837
1969
  const { server } = await import(${JSON.stringify(serverPath)});
1838
1970
  const { DatabaseAdaptorRole, DocumentTransfer } = await import("akanjs/service");
@@ -2405,7 +2537,7 @@ import os from "os";
2405
2537
  import { MemoryLimit as MemoryLimit2 } from "akanjs/server/memoryLimit";
2406
2538
 
2407
2539
  class DevBootConcurrency {
2408
- static perAppBytes = 900 * 1024 * 1024;
2540
+ static perAppBytes = 1800 * 1024 * 1024;
2409
2541
  static memoryShare = 0.5;
2410
2542
  static coresPerApp = 4;
2411
2543
  static budget() {
@@ -2439,7 +2571,7 @@ class DevBootConcurrency {
2439
2571
 
2440
2572
  // pkgs/@akanjs/cli/application/devSessionLog.ts
2441
2573
  import { mkdir, rename } from "fs/promises";
2442
- import path5 from "path";
2574
+ import path6 from "path";
2443
2575
  import dayjs from "dayjs";
2444
2576
 
2445
2577
  class DevSessionLog {
@@ -2463,19 +2595,19 @@ class DevSessionLog {
2463
2595
  return [...this.#appNames];
2464
2596
  }
2465
2597
  pathOf(app) {
2466
- return path5.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2598
+ return path6.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2467
2599
  }
2468
2600
  previousPathOf(app) {
2469
- return path5.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2601
+ return path6.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2470
2602
  }
2471
2603
  relativePathOf(app) {
2472
- return path5.relative(this.#workspaceRoot, this.pathOf(app));
2604
+ return path6.relative(this.#workspaceRoot, this.pathOf(app));
2473
2605
  }
2474
2606
  async open() {
2475
2607
  const stamp = dayjs(this.#now()).format("YYYY-MM-DD HH:mm:ss");
2476
2608
  for (const app of this.#appNames) {
2477
2609
  const file = this.pathOf(app);
2478
- await mkdir(path5.dirname(file), { recursive: true });
2610
+ await mkdir(path6.dirname(file), { recursive: true });
2479
2611
  await rename(file, this.previousPathOf(app)).catch(() => {
2480
2612
  return;
2481
2613
  });
@@ -2519,7 +2651,7 @@ class DevSessionLog {
2519
2651
  this.#writers.clear();
2520
2652
  }
2521
2653
  #runtimeDirOf(app) {
2522
- return path5.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2654
+ return path6.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2523
2655
  }
2524
2656
  #writeLine(app, text) {
2525
2657
  const writer = this.#writers.get(app);
@@ -2566,7 +2698,7 @@ class DevSupervisor {
2566
2698
  }
2567
2699
  };
2568
2700
  }
2569
- static readyTimeoutMs = 180000;
2701
+ static bootTimeoutMs = 180000;
2570
2702
  static shutdownGraceMs = 12000;
2571
2703
  #options;
2572
2704
  #children = new Map;
@@ -2615,7 +2747,7 @@ class DevSupervisor {
2615
2747
  return ports;
2616
2748
  }
2617
2749
  #makeChild(app, port) {
2618
- const { promise: ready, resolve: markReady } = Promise.withResolvers();
2750
+ const { promise: booted, resolve: markBooted } = Promise.withResolvers();
2619
2751
  return {
2620
2752
  status: {
2621
2753
  app,
@@ -2628,8 +2760,8 @@ class DevSupervisor {
2628
2760
  exitCode: null
2629
2761
  },
2630
2762
  proc: null,
2631
- ready,
2632
- markReady,
2763
+ booted,
2764
+ markBooted,
2633
2765
  opened: false
2634
2766
  };
2635
2767
  }
@@ -2648,12 +2780,12 @@ class DevSupervisor {
2648
2780
  for (const child of wave)
2649
2781
  this.#spawnChild(child);
2650
2782
  this.#publishStatus();
2651
- await Promise.all(wave.map(async (child) => await this.#waitForReady(child)));
2783
+ await Promise.all(wave.map(async (child) => await this.#waitForBoot(child)));
2652
2784
  }
2653
2785
  }
2654
- async#waitForReady(child) {
2655
- if (await DevSupervisor.timesOut(child.ready, DevSupervisor.readyTimeoutMs))
2656
- 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");
2657
2789
  }
2658
2790
  static async timesOut(work, ms) {
2659
2791
  let timer;
@@ -2690,9 +2822,12 @@ class DevSupervisor {
2690
2822
  },
2691
2823
  stdio: ["ignore", "pipe", "pipe"],
2692
2824
  ipc: (message) => {
2693
- if (!message || typeof message !== "object" || typeof message.state !== "string")
2825
+ if (!message || typeof message !== "object")
2694
2826
  return;
2695
- this.#applyChildEvent(child, message);
2827
+ if ("booted" in message)
2828
+ child.markBooted();
2829
+ else if (typeof message.state === "string")
2830
+ this.#applyChildEvent(child, message);
2696
2831
  },
2697
2832
  serialization: "advanced",
2698
2833
  onExit: (_proc, exitCode, signalCode) => {
@@ -2702,7 +2837,7 @@ class DevSupervisor {
2702
2837
  return;
2703
2838
  child.status.state = "stopped";
2704
2839
  child.status.detail = signalCode ? `killed by ${signalCode}` : `exited with ${exitCode}`;
2705
- child.markReady();
2840
+ child.markBooted();
2706
2841
  this.#publishStatus();
2707
2842
  }
2708
2843
  });
@@ -2716,14 +2851,13 @@ class DevSupervisor {
2716
2851
  child.status.state = event.state;
2717
2852
  child.status.detail = event.detail ?? "";
2718
2853
  if (event.state === "ready") {
2719
- child.markReady();
2720
2854
  if (this.#options.open && !child.opened) {
2721
2855
  child.opened = true;
2722
2856
  openBrowser(child.status.url);
2723
2857
  }
2724
2858
  }
2725
2859
  if (event.state === "failed")
2726
- child.markReady();
2860
+ child.markBooted();
2727
2861
  this.#publishStatus();
2728
2862
  }
2729
2863
  async#drain(stream, name, kind) {
@@ -3124,7 +3258,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3124
3258
  await Promise.all(shares.map(async (share) => await share.close()));
3125
3259
  }, "Abandoning the tunnel release; the shares expire on their own.");
3126
3260
  await Promise.all(apps.map(async (app) => {
3127
- const { TunnelShare } = await import("./TunnelShare-scf4e4pd.js");
3261
+ const { TunnelShare } = await import("./TunnelShare-cwh9ezsm.js");
3128
3262
  const share = await TunnelShare.open(app, { workspace: app.workspace });
3129
3263
  shares.push(share);
3130
3264
  urls.set(app.name, share.url);