@akanjs/cli 2.4.1-rc.7 → 2.4.1

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 (41) hide show
  1. package/.build-stamp +1 -1
  2. package/{agent.command-t4346579.js → agent.command-sa12ccz3.js} +2 -2
  3. package/{application.command-zkmeemdt.js → application.command-jv63vv7z.js} +4 -4
  4. package/{applicationBuildRunner-5w8j9yp7.js → applicationBuildRunner-vzjdgv1g.js} +3 -2
  5. package/buildBatch.proc.js +3 -2
  6. package/{cloud.command-5ztqxppm.js → cloud.command-8z2nc6b2.js} +7 -7
  7. package/{context.command-smwxwgwf.js → context.command-0m854xme.js} +8 -8
  8. package/{guideline.command-1r0esdjs.js → guideline.command-dsdpae45.js} +1 -1
  9. package/incrementalBuilder.proc.js +3 -3
  10. package/{index-3sd2vape.js → index-1b9f27pm.js} +1 -1
  11. package/{index-fh990y67.js → index-3a4vntr0.js} +1 -1
  12. package/{index-jbtn8h1y.js → index-81epkybm.js} +1 -1
  13. package/{index-0fn1r7gg.js → index-bjpxzr6s.js} +31 -4
  14. package/{index-ahpcr9ss.js → index-d2e2y85b.js} +1 -1
  15. package/{index-0jwvs8vp.js → index-e3b8ms64.js} +6 -6
  16. package/{index-s9yajzkj.js → index-eja0sbm6.js} +4 -4
  17. package/{index-wh201a76.js → index-h6pevatb.js} +2 -2
  18. package/{index-2v6a4wd5.js → index-m04as81s.js} +2 -2
  19. package/index-m0vg9n8z.js +2219 -0
  20. package/{index-n5y2gbf2.js → index-n2mp0f48.js} +200 -19
  21. package/{index-nv7et4t3.js → index-p9f1rpbp.js} +1 -1
  22. package/{index-qpz4csbs.js → index-phj3ewxv.js} +7 -1
  23. package/{index-pya1h7wx.js → index-q4sc46ec.js} +1 -1
  24. package/{index-y4kg682s.js → index-qacv44mm.js} +1 -1
  25. package/index-rh16j3c7.js +2166 -0
  26. package/{index-gr4cjv99.js → index-zw4aqvwh.js} +2 -2
  27. package/index.js +17 -17
  28. package/{library.command-jrew04m6.js → library.command-fhx5ce8n.js} +2 -2
  29. package/{localRegistry.command-m08dkm1b.js → localRegistry.command-18f61945.js} +6 -6
  30. package/{module.command-p98t7522.js → module.command-29wvnb56.js} +3 -3
  31. package/{package.command-57dyrfmn.js → package.command-46r1dncm.js} +2 -2
  32. package/package.json +2 -2
  33. package/{page.command-tas6f3na.js → page.command-vxvqdzc0.js} +2 -2
  34. package/{primitive.command-bjrbsakw.js → primitive.command-yk622wmt.js} +4 -4
  35. package/{quality.command-zavaafbw.js → quality.command-p0mb5xwt.js} +1 -1
  36. package/{repair.command-je57wx67.js → repair.command-nptckp6w.js} +2 -2
  37. package/{scalar.command-gqqyy1kr.js → scalar.command-bss14hez.js} +2 -2
  38. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +19 -0
  39. package/{workflow.command-d6mrc47s.js → workflow.command-7fhqb33d.js} +6 -6
  40. package/{workspace.command-bbg96z8k.js → workspace.command-xkcec7ky.js} +15 -15
  41. package/index-khzzttv1.js +0 -4274
@@ -4,11 +4,11 @@ import {
4
4
  } from "./index-76rn3g2c.js";
5
5
  import {
6
6
  LibraryScript
7
- } from "./index-fh990y67.js";
7
+ } from "./index-3a4vntr0.js";
8
8
  import {
9
9
  runner,
10
10
  script
11
- } from "./index-qpz4csbs.js";
11
+ } from "./index-phj3ewxv.js";
12
12
  import {
13
13
  AppExecutor,
14
14
  LibExecutor,
@@ -18,7 +18,7 @@ import {
18
18
  import {
19
19
  HmrWatcher,
20
20
  WatchRootResolver
21
- } from "./index-0fn1r7gg.js";
21
+ } from "./index-bjpxzr6s.js";
22
22
  import {
23
23
  __require
24
24
  } from "./index-r24hmh0q.js";
@@ -314,6 +314,7 @@ class IncrementalBuilderHost {
314
314
  this.#startOptions.onExit?.();
315
315
  return;
316
316
  }
317
+ this.#startOptions.onAway?.();
317
318
  if (wasRecycle) {
318
319
  this.logger.verbose("builder exited for a recycle; spawning its replacement now");
319
320
  this.#spawnAfterRecycle = true;
@@ -348,6 +349,7 @@ class IncrementalBuilderHost {
348
349
  if (!this.send({ type: "builder-shutdown", reason }))
349
350
  return false;
350
351
  this.#recycleRequested = true;
352
+ this.#status = "recycling";
351
353
  this.logger.info(`recycling builder pid=${proc.pid} (${reason})`);
352
354
  this.#recycleTimer = setTimeout(() => {
353
355
  this.#recycleTimer = null;
@@ -377,7 +379,10 @@ class IncrementalBuilderHost {
377
379
  }
378
380
  send(message) {
379
381
  if (!this.#proc || this.#status !== "ready") {
380
- this.logger.warn(`incrementalBuilderHost is ${this.#status}; cannot send ${message.type}`);
382
+ if (this.#status === "recycling" || this.#status === "restarting")
383
+ this.logger.verbose(`incrementalBuilderHost is ${this.#status}; ${message.type} is for the replacement`);
384
+ else
385
+ this.logger.warn(`incrementalBuilderHost is ${this.#status}; cannot send ${message.type}`);
381
386
  return false;
382
387
  }
383
388
  try {
@@ -430,6 +435,41 @@ class IncrementalBuilderHost {
430
435
  throw new Error(`[cli] frontend builder entry not found; looked in: ${candidates.join(", ")}`);
431
436
  }
432
437
  }
438
+ // pkgs/@akanjs/devkit/incrementalBuilder/builderRequestRouter.ts
439
+ class BuilderRequestRouter {
440
+ #generation = 0;
441
+ #nextId = 1;
442
+ #inFlight = new Map;
443
+ get generation() {
444
+ return this.#generation;
445
+ }
446
+ get inFlightCount() {
447
+ return this.#inFlight.size;
448
+ }
449
+ startGeneration() {
450
+ this.#generation += 1;
451
+ this.#inFlight.clear();
452
+ return this.#generation;
453
+ }
454
+ issue(message) {
455
+ const id = this.#nextId++;
456
+ this.#inFlight.set(id, { backendId: message.id, generation: this.#generation });
457
+ return { ...message, id };
458
+ }
459
+ withdraw(id) {
460
+ this.#inFlight.delete(id);
461
+ }
462
+ settle(message) {
463
+ const request = this.#inFlight.get(message.id);
464
+ if (!request)
465
+ return null;
466
+ this.#inFlight.delete(message.id);
467
+ if (request.generation !== this.#generation)
468
+ return null;
469
+ return { ...message, id: request.backendId };
470
+ }
471
+ }
472
+
433
473
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
434
474
  var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
435
475
  var BACKEND_RESTART_DEBOUNCE_MS = 120;
@@ -440,10 +480,12 @@ var BACKEND_RECOVERY_MAX_ATTEMPTS = 5;
440
480
  var BACKEND_STDERR_TAIL_LIMIT = 40;
441
481
  var BUILDER_READY_TIMEOUT_MS = 150000;
442
482
  var BUILDER_START_MAX_ATTEMPTS = 3;
483
+ var HELD_BUILDER_REQUEST_LIMIT = 64;
443
484
  var BUILDER_RSS_RECYCLE_QUIET_MS = 750;
444
485
  var BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS = 30000;
445
- var BUILDER_INEFFECTIVE_RSS_RECYCLE_LIMIT = 2;
486
+ var BUILDER_TIGHT_RSS_REPORT_LIMIT = 2;
446
487
  var BUILDER_RSS_SETTLE_MS = 20000;
488
+ var PS_RSS_TIMEOUT_MS = 2000;
447
489
  var BUILDER_RSS_HARD_MULTIPLE = 1.5;
448
490
  var DEV_IDLE_SUSPEND_MS = 300000;
449
491
  var DEV_IDLE_MIN_UPTIME_MS = 30000;
@@ -592,7 +634,14 @@ var resolveIdleSuspendMs = (raw) => {
592
634
  };
593
635
  var hasAnyBuildFailure = (statusByPhase) => [...statusByPhase.values()].some((status) => !status.ok);
594
636
  var shouldRefreshConfigOnIdleWake = (batch) => !!batch && batch.kinds.has("config");
595
- var shouldAbandonBuilderRssCeiling = (ineffectiveRecycles, limit = BUILDER_INEFFECTIVE_RSS_RECYCLE_LIMIT) => ineffectiveRecycles >= limit;
637
+ var RETURNING_BUILDER_STATUSES = new Set(["starting", "recycling", "restarting"]);
638
+ var shouldHoldForReturningBuilder = ({
639
+ status,
640
+ heldCount,
641
+ limit = HELD_BUILDER_REQUEST_LIMIT
642
+ }) => RETURNING_BUILDER_STATUSES.has(status) && heldCount < limit;
643
+ var shouldWarnBuilderRssCeilingTight = (reportsSinceRecycle, limit = BUILDER_TIGHT_RSS_REPORT_LIMIT) => reportsSinceRecycle >= limit;
644
+ var isRssCeilingUnreachable = (freshRssBytes, ceilingBytes) => freshRssBytes !== null && ceilingBytes !== null && freshRssBytes >= ceilingBytes;
596
645
  var shouldRelayRecycledFrontendState = (current, next) => {
597
646
  if (!current || current.type !== next.type)
598
647
  return true;
@@ -631,6 +680,7 @@ var mergeInvalidateMessages = (current, next) => {
631
680
  };
632
681
  };
633
682
  var buildStatusReplaySequence = (pendingReplay, latestByPhase) => [...pendingReplay, ...latestByPhase.values()];
683
+ var filesChangedSince = (before, after) => [...before].filter(([file, stamp]) => after.get(file) !== stamp).map(([file]) => file);
634
684
 
635
685
  class BackendImportGraph {
636
686
  #app;
@@ -656,6 +706,13 @@ class BackendImportGraph {
656
706
  has(file) {
657
707
  return this.#files.has(path2.resolve(file));
658
708
  }
709
+ async fingerprint() {
710
+ const stamps = await Promise.all([...this.#files].map(async (file) => {
711
+ const stats = await stat(file).catch(() => null);
712
+ return [file, stats ? `${Math.round(stats.mtimeMs)}:${stats.size}` : "(gone)"];
713
+ }));
714
+ return new Map(stamps);
715
+ }
659
716
  async refresh() {
660
717
  try {
661
718
  const files = await this.#build();
@@ -780,7 +837,8 @@ class AkanAppHost {
780
837
  #rssRecycleTimer = null;
781
838
  #rssRecycleReason = null;
782
839
  #lastRssRecycleAtMono = null;
783
- #rssCeilingIneffective = 0;
840
+ #rssCeilingTightReports = 0;
841
+ #rssCeilingTightWarned = false;
784
842
  #rssSettleToken = 0;
785
843
  #rssRecycleOver = null;
786
844
  #rssCeilingAbandoned = false;
@@ -794,7 +852,9 @@ class AkanAppHost {
794
852
  #wokeAtMono = null;
795
853
  #idleWatcher = null;
796
854
  #suspendedChanges = null;
855
+ #builderGapStamp = null;
797
856
  #pendingBuilderMessages = [];
857
+ #builderRequests = new BuilderRequestRouter;
798
858
  constructor(app, { env, withInk = false }) {
799
859
  this.app = app;
800
860
  this.env = env;
@@ -831,6 +891,7 @@ class AkanAppHost {
831
891
  clearTimeout(this.#builderRecoveryTimer);
832
892
  this.#builderRecoveryTimer = null;
833
893
  }
894
+ this.#failPendingBuilderMessages("dev server is shutting down");
834
895
  await this.#stopBackend();
835
896
  this.#stopBuilder();
836
897
  return this;
@@ -845,6 +906,7 @@ class AkanAppHost {
845
906
  return await createTunnel(type, { app: this.app, environment });
846
907
  }
847
908
  #startBackend(startStatus = null) {
909
+ this.#builderRequests.startGeneration();
848
910
  this.#backendStartStatus = startStatus;
849
911
  this.#backendGaveUp = false;
850
912
  this.#setBackendLifecycleState("starting");
@@ -1104,6 +1166,15 @@ ${this.#backendStderrTail.join(`
1104
1166
  await this.#handleInvalidate(message);
1105
1167
  return;
1106
1168
  }
1169
+ if (message.type === "build-route-res" || message.type === "build-csr-res") {
1170
+ const answer = this.#builderRequests.settle(message);
1171
+ if (!answer) {
1172
+ this.logger.verbose(`[builder] dropped a ${message.type} no live backend is waiting for (id=${message.id})`);
1173
+ return;
1174
+ }
1175
+ this.#sendToBackend(answer);
1176
+ return;
1177
+ }
1107
1178
  this.#sendToBackend(message);
1108
1179
  }
1109
1180
  #shouldRelayRecycledState(message) {
@@ -1127,7 +1198,7 @@ ${this.#backendStderrTail.join(`
1127
1198
  msSinceLastRecycle: this.#lastRssRecycleAtMono === null ? null : performance.now() - this.#lastRssRecycleAtMono
1128
1199
  });
1129
1200
  if (decision === "below-ceiling") {
1130
- this.#rssCeilingIneffective = 0;
1201
+ this.#rssCeilingTightReports = 0;
1131
1202
  return;
1132
1203
  }
1133
1204
  if (decision === "unbounded")
@@ -1137,15 +1208,29 @@ ${this.#backendStderrTail.join(`
1137
1208
  return;
1138
1209
  }
1139
1210
  if (decision === "too-soon") {
1140
- this.#rssCeilingIneffective += 1;
1141
- if (!shouldAbandonBuilderRssCeiling(this.#rssCeilingIneffective))
1211
+ this.#rssCeilingTightReports += 1;
1212
+ if (this.#rssCeilingTightWarned || !shouldWarnBuilderRssCeilingTight(this.#rssCeilingTightReports))
1142
1213
  return;
1143
- this.#rssCeilingAbandoned = true;
1144
- this.logger.error(`[builder-recycle] the builder is still at ${asMib(metrics.rssBytes)}MiB right after being recycled, so the ${asMib(ceilingBytes ?? 0)}MiB ceiling cannot be met for this app; no longer enforcing it this session. Raise AKAN_BUILDER_MAX_RSS_MB, or set it to 0 to leave the builder unbounded.`);
1214
+ this.#rssCeilingTightWarned = true;
1215
+ this.logger.warn(`[builder-recycle] the builder is back at ${asMib(metrics.rssBytes)}MiB within ${Math.round(BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS / 1000)}s of a recycle, so the ${asMib(ceilingBytes ?? 0)}MiB ceiling costs about one boot build per interval while you keep building. Raise AKAN_BUILDER_MAX_RSS_MB if that trade is wrong for this app, or set it to 0 to leave the builder unbounded.`);
1145
1216
  return;
1146
1217
  }
1147
1218
  this.#armRssRecycle(`rss=${asMib(metrics.rssBytes)}MiB>=${asMib(ceilingBytes ?? 0)}MiB after ${metrics.workCount} build(s)`, { rssBytes: metrics.rssBytes, ceilingBytes: ceilingBytes ?? 0 });
1148
1219
  }
1220
+ async#checkRecycledBuilderFloor() {
1221
+ if (this.#rssCeilingAbandoned || this.#lastRssRecycleAtMono === null)
1222
+ return;
1223
+ const ceilingBytes = IncrementalBuilderHost.maxRssBytes();
1224
+ const pid = this.#builder?.pid;
1225
+ if (!ceilingBytes || !pid)
1226
+ return;
1227
+ const freshRssBytes = await AkanAppHost.readProcessRssBytes(pid);
1228
+ if (!isRssCeilingUnreachable(freshRssBytes, ceilingBytes))
1229
+ return;
1230
+ this.#rssCeilingAbandoned = true;
1231
+ const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
1232
+ this.logger.error(`[builder-recycle] a freshly recycled builder is already at ${asMib(freshRssBytes ?? 0)}MiB with nothing built on demand, so the ${asMib(ceilingBytes)}MiB ceiling cannot be met for this app; no longer enforcing it this session. Raise AKAN_BUILDER_MAX_RSS_MB, or set it to 0 to leave the builder unbounded.`);
1233
+ }
1149
1234
  #armRssRecycle(reason, over) {
1150
1235
  if (this.#rssRecycleReason !== reason)
1151
1236
  this.logger.verbose(`[builder-recycle] armed (${reason}); replacing the builder once it stays quiet`);
@@ -1200,9 +1285,26 @@ ${this.#backendStderrTail.join(`
1200
1285
  const vmRssKb = status === null ? null : /VmRSS:\s+(\d+) kB/.exec(status)?.[1];
1201
1286
  if (vmRssKb)
1202
1287
  return Number(vmRssKb) * 1024;
1203
- const psOutput = await Bun.$`ps -o rss= -p ${pid}`.quiet().text().catch(() => "");
1204
- const rssKb = Number(psOutput.trim());
1205
- return Number.isFinite(rssKb) && rssKb > 0 ? rssKb * 1024 : null;
1288
+ return await AkanAppHost.#readRssViaPs(pid);
1289
+ }
1290
+ static async#readRssViaPs(pid, timeoutMs = PS_RSS_TIMEOUT_MS) {
1291
+ let proc;
1292
+ try {
1293
+ proc = Bun.spawn(["ps", "-o", "rss=", "-p", String(pid)], { stdio: ["ignore", "pipe", "ignore"] });
1294
+ } catch {
1295
+ return null;
1296
+ }
1297
+ const killer = setTimeout(() => proc.kill("SIGKILL"), timeoutMs);
1298
+ try {
1299
+ const output = await new Response(proc.stdout).text();
1300
+ await proc.exited;
1301
+ const rssKb = Number(output.trim());
1302
+ return Number.isFinite(rssKb) && rssKb > 0 ? rssKb * 1024 : null;
1303
+ } catch {
1304
+ return null;
1305
+ } finally {
1306
+ clearTimeout(killer);
1307
+ }
1206
1308
  }
1207
1309
  #cancelRssRecycle() {
1208
1310
  this.#rssRecycleReason = null;
@@ -1258,6 +1360,7 @@ ${this.#backendStderrTail.join(`
1258
1360
  }
1259
1361
  this.#suspended = true;
1260
1362
  this.#stopBuilder();
1363
+ this.#openBuilderGap("idle suspend");
1261
1364
  this.logger.info(`[idle-suspend] no build activity for ${Math.round(idleMs / 1000)}s; released the builder \u2014 the next edit or route request brings it back`);
1262
1365
  }
1263
1366
  get #restartPending() {
@@ -1326,6 +1429,7 @@ ${this.#backendStderrTail.join(`
1326
1429
  const files = batch?.files ?? [];
1327
1430
  if (shouldRefreshConfigOnIdleWake(batch)) {
1328
1431
  this.logger.verbose("[idle-suspend] config changed while suspended; restarting the dev host");
1432
+ this.#discardBuilderGap("config change replaces the backend anyway");
1329
1433
  await this.#recycleDevChildren({ type: "invalidate", kinds: [...batch?.kinds ?? []], files }, {
1330
1434
  refreshConfig: true
1331
1435
  });
@@ -1334,20 +1438,78 @@ ${this.#backendStderrTail.join(`
1334
1438
  if (files.length > 0)
1335
1439
  await this.#backendGraph.refresh();
1336
1440
  await this.#startBuilder({ announceBootState: true });
1337
- const backendFiles = files.filter((file) => this.#isBackendFile(file));
1441
+ const missed = await this.#takeBuilderGapChanges();
1442
+ const backendFiles = [...new Set([...files.filter((file) => this.#isBackendFile(file)), ...missed])];
1338
1443
  if (backendFiles.length === 0)
1339
1444
  return;
1340
1445
  this.logger.verbose(`[idle-suspend] ${backendFiles.length} backend file(s) changed while suspended`);
1341
1446
  this.#scheduleBackendRestart({ files: backendFiles, roles: [] });
1342
1447
  }
1448
+ #openBuilderGap(reason) {
1449
+ if (this.#builderGapStamp)
1450
+ return;
1451
+ if (!this.#backendGraph.ready) {
1452
+ this.logger.verbose(`[builder-gap] no backend graph yet; a save during this ${reason} goes unnoticed`);
1453
+ return;
1454
+ }
1455
+ this.#builderGapStamp = this.#backendGraph.fingerprint().then((stamps) => {
1456
+ this.logger.verbose(`[builder-gap] stamped ${stamps.size} backend file(s) (${reason})`);
1457
+ return stamps;
1458
+ }).catch((err) => {
1459
+ this.logger.warn(`[builder-gap] could not stamp the backend files: ${err instanceof Error ? err.message : String(err)}`);
1460
+ return null;
1461
+ });
1462
+ }
1463
+ async#takeBuilderGapChanges() {
1464
+ const stamping = this.#builderGapStamp;
1465
+ this.#builderGapStamp = null;
1466
+ const before = stamping ? await stamping : null;
1467
+ if (!before)
1468
+ return [];
1469
+ const moved = filesChangedSince(before, await this.#backendGraph.fingerprint());
1470
+ if (moved.length === 0) {
1471
+ this.logger.verbose(`[builder-gap] none of the ${before.size} stamped backend file(s) moved`);
1472
+ return moved;
1473
+ }
1474
+ this.logger.info(`[builder-gap] ${moved.length} backend file(s) changed while the builder was away; the backend is running the old ones`);
1475
+ return moved;
1476
+ }
1477
+ #discardBuilderGap(reason) {
1478
+ if (!this.#builderGapStamp)
1479
+ return;
1480
+ this.#builderGapStamp = null;
1481
+ this.logger.verbose(`[builder-gap] stamps dropped (${reason})`);
1482
+ }
1483
+ async#restartBackendForGapChanges() {
1484
+ const moved = await this.#takeBuilderGapChanges();
1485
+ if (moved.length === 0)
1486
+ return;
1487
+ this.#scheduleBackendRestart({ files: moved, roles: [] });
1488
+ }
1343
1489
  #flushPendingBuilderMessages() {
1344
1490
  const pending = this.#pendingBuilderMessages.splice(0);
1345
1491
  if (pending.length === 0)
1346
1492
  return;
1347
- this.logger.verbose(`[idle-suspend] replaying ${pending.length} request(s) held during the wake`);
1493
+ this.logger.verbose(`[builder] replaying ${pending.length} request(s) held while the builder was away`);
1348
1494
  for (const message of pending)
1349
1495
  this.#sendToBuilder(message);
1350
1496
  }
1497
+ #holdUntilBuilderReady(message) {
1498
+ this.#pendingBuilderMessages.push(message);
1499
+ this.logger.verbose(`[builder] holding ${message.type} until the builder is ready (${this.#pendingBuilderMessages.length} waiting)`);
1500
+ }
1501
+ #failPendingBuilderMessages(reason) {
1502
+ const held = this.#pendingBuilderMessages.splice(0);
1503
+ if (held.length === 0)
1504
+ return;
1505
+ this.logger.warn(`failing ${held.length} held builder request(s): ${reason}`);
1506
+ for (const message of held) {
1507
+ if (message.type === "build-route")
1508
+ this.#sendToBackend({ type: "build-route-res", id: message.id, ok: false, error: reason });
1509
+ else if (message.type === "build-csr")
1510
+ this.#sendToBackend({ type: "build-csr-res", id: message.id, ok: false, error: reason });
1511
+ }
1512
+ }
1351
1513
  #recycleBuilderForRss(reason) {
1352
1514
  if (this.#pendingRecycle || this.#restartTimer) {
1353
1515
  this.logger.verbose(`[builder-recycle] skipped (${reason}); a dev restart is already pending`);
@@ -1356,6 +1518,7 @@ ${this.#backendStderrTail.join(`
1356
1518
  if (!this.#builder?.recycle(reason))
1357
1519
  return;
1358
1520
  this.#lastRssRecycleAtMono = performance.now();
1521
+ this.#openBuilderGap("builder recycle");
1359
1522
  }
1360
1523
  async#handleInvalidate(message) {
1361
1524
  this.#logDevPlan(message);
@@ -1433,6 +1596,7 @@ ${this.#backendStderrTail.join(`
1433
1596
  }
1434
1597
  this.#builderRecoveryAttempts = 0;
1435
1598
  this.logger.info("[builder-recovery] builder recovered");
1599
+ this.#restartBackendForGapChanges();
1436
1600
  const status = {
1437
1601
  generation: reason.generation ?? this.#nextBackendBuildStatusGeneration(),
1438
1602
  phase: "scan",
@@ -1602,6 +1766,7 @@ ${this.#backendStderrTail.join(`
1602
1766
  this.app.verbose(`[cli] builder failed before ready; retrying (${attempt + 1}/${BUILDER_START_MAX_ATTEMPTS})`);
1603
1767
  }
1604
1768
  }
1769
+ this.#failPendingBuilderMessages("builder failed to start");
1605
1770
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
1606
1771
  }
1607
1772
  #waitForBuilderReady(attempt, { announceBootState = false } = {}) {
@@ -1624,12 +1789,19 @@ ${this.#backendStderrTail.join(`
1624
1789
  onExit: () => {
1625
1790
  settle(() => reject(new Error(`[cli] builder exited before emitting builder-ready (attempt ${attempt})`)));
1626
1791
  },
1792
+ onAway: () => {
1793
+ this.#openBuilderGap("builder replacement");
1794
+ },
1627
1795
  onReady: () => {
1628
1796
  settle(resolve);
1797
+ this.#flushPendingBuilderMessages();
1629
1798
  },
1630
1799
  onRestartReady: () => {
1631
1800
  this.logger.verbose("[builder-recovery] builder ready after restart; replaying latest state");
1632
1801
  this.#replayBuilderState();
1802
+ this.#flushPendingBuilderMessages();
1803
+ this.#restartBackendForGapChanges();
1804
+ this.#checkRecycledBuilderFloor();
1633
1805
  }
1634
1806
  });
1635
1807
  });
@@ -1645,9 +1817,18 @@ ${this.#backendStderrTail.join(`
1645
1817
  Object.assign(this.env, { AKAN_DEV_CSR_REBUILD: "1" });
1646
1818
  this.logger.verbose(`[csr] armed dev CSR rebuilds (${message.reason})`);
1647
1819
  }
1648
- if (this.#builder?.send(message))
1820
+ if (message.type === "build-route" || message.type === "build-csr") {
1821
+ const outgoing = this.#builderRequests.issue(message);
1822
+ if (this.#builder?.send(outgoing))
1823
+ return;
1824
+ this.#builderRequests.withdraw(outgoing.id);
1825
+ } else if (this.#builder?.send(message))
1649
1826
  return;
1650
1827
  const status = this.#builder?.status ?? "stopped";
1828
+ if (shouldHoldForReturningBuilder({ status, heldCount: this.#pendingBuilderMessages.length })) {
1829
+ this.#holdUntilBuilderReady(message);
1830
+ return;
1831
+ }
1651
1832
  if (message.type === "build-route") {
1652
1833
  this.#sendToBackend({
1653
1834
  type: "build-route-res",
@@ -1717,7 +1898,7 @@ function openBrowser(url) {
1717
1898
  }
1718
1899
 
1719
1900
  // pkgs/@akanjs/cli/application/application.runner.ts
1720
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-5w8j9yp7.js")).ApplicationBuildRunner;
1901
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-vzjdgv1g.js")).ApplicationBuildRunner;
1721
1902
  var loadReleasePackager = async () => (await import("./applicationReleasePackager-brvth6rs.js")).ApplicationReleasePackager;
1722
1903
  var loadCapacitorApp = async () => (await import("./capacitorApp-wnxzrppx.js")).CapacitorApp;
1723
1904
  var loadPrompts = async () => await import("@inquirer/prompts");
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-qpz4csbs.js";
5
+ } from "./index-phj3ewxv.js";
6
6
  import {
7
7
  PkgExecutor
8
8
  } from "./index-0t7pwff2.js";
@@ -11,6 +11,9 @@ import {
11
11
  import {
12
12
  FileSys
13
13
  } from "./index-61keag0s.js";
14
+ import {
15
+ __require
16
+ } from "./index-r24hmh0q.js";
14
17
 
15
18
  // pkgs/@akanjs/devkit/commandDecorators/targetMeta.ts
16
19
  var COMMAND_META = Symbol("akan.command.meta");
@@ -58,7 +61,6 @@ var Workspace = createInternalArgToken("Workspace");
58
61
 
59
62
  // pkgs/@akanjs/devkit/commandDecorators/command.ts
60
63
  import path from "path";
61
- import { confirm, input, select } from "@inquirer/prompts";
62
64
  import { Logger } from "akanjs/common";
63
65
  import chalk2 from "chalk";
64
66
  import { program } from "commander";
@@ -375,6 +377,10 @@ var convertArgValue = (value, type) => {
375
377
  return value === true || value === "true";
376
378
  };
377
379
  var normalizeEnumChoices = (enumChoices) => enumChoices.map((choice) => typeof choice === "object" ? { value: choice.value, name: choice.label } : { value: choice, name: choice.toString() });
380
+ var prompts = async () => await import("@inquirer/prompts");
381
+ var select = (config, context) => prompts().then((m) => m.select(config, context));
382
+ var confirm = (config, context) => prompts().then((m) => m.confirm(config, context));
383
+ var input = (config, context) => prompts().then((m) => m.input(config, context));
378
384
  var resolveEnumChoices = async (argMeta, context) => {
379
385
  const enumChoices = argMeta.argsOption.enum;
380
386
  if (!enumChoices)
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  runner,
10
10
  script
11
- } from "./index-qpz4csbs.js";
11
+ } from "./index-phj3ewxv.js";
12
12
 
13
13
  // pkgs/@akanjs/cli/agent/agent.script.ts
14
14
  import { Logger } from "akanjs/common";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-qpz4csbs.js";
5
+ } from "./index-phj3ewxv.js";
6
6
 
7
7
  // pkgs/@akanjs/cli/page/page.runner.ts
8
8
  class PageRunner extends runner("page") {