@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.
- package/.build-stamp +1 -1
- package/{agent.command-t4346579.js → agent.command-sa12ccz3.js} +2 -2
- package/{application.command-zkmeemdt.js → application.command-jv63vv7z.js} +4 -4
- package/{applicationBuildRunner-5w8j9yp7.js → applicationBuildRunner-vzjdgv1g.js} +3 -2
- package/buildBatch.proc.js +3 -2
- package/{cloud.command-5ztqxppm.js → cloud.command-8z2nc6b2.js} +7 -7
- package/{context.command-smwxwgwf.js → context.command-0m854xme.js} +8 -8
- package/{guideline.command-1r0esdjs.js → guideline.command-dsdpae45.js} +1 -1
- package/incrementalBuilder.proc.js +3 -3
- package/{index-3sd2vape.js → index-1b9f27pm.js} +1 -1
- package/{index-fh990y67.js → index-3a4vntr0.js} +1 -1
- package/{index-jbtn8h1y.js → index-81epkybm.js} +1 -1
- package/{index-0fn1r7gg.js → index-bjpxzr6s.js} +31 -4
- package/{index-ahpcr9ss.js → index-d2e2y85b.js} +1 -1
- package/{index-0jwvs8vp.js → index-e3b8ms64.js} +6 -6
- package/{index-s9yajzkj.js → index-eja0sbm6.js} +4 -4
- package/{index-wh201a76.js → index-h6pevatb.js} +2 -2
- package/{index-2v6a4wd5.js → index-m04as81s.js} +2 -2
- package/index-m0vg9n8z.js +2219 -0
- package/{index-n5y2gbf2.js → index-n2mp0f48.js} +200 -19
- package/{index-nv7et4t3.js → index-p9f1rpbp.js} +1 -1
- package/{index-qpz4csbs.js → index-phj3ewxv.js} +7 -1
- package/{index-pya1h7wx.js → index-q4sc46ec.js} +1 -1
- package/{index-y4kg682s.js → index-qacv44mm.js} +1 -1
- package/index-rh16j3c7.js +2166 -0
- package/{index-gr4cjv99.js → index-zw4aqvwh.js} +2 -2
- package/index.js +17 -17
- package/{library.command-jrew04m6.js → library.command-fhx5ce8n.js} +2 -2
- package/{localRegistry.command-m08dkm1b.js → localRegistry.command-18f61945.js} +6 -6
- package/{module.command-p98t7522.js → module.command-29wvnb56.js} +3 -3
- package/{package.command-57dyrfmn.js → package.command-46r1dncm.js} +2 -2
- package/package.json +2 -2
- package/{page.command-tas6f3na.js → page.command-vxvqdzc0.js} +2 -2
- package/{primitive.command-bjrbsakw.js → primitive.command-yk622wmt.js} +4 -4
- package/{quality.command-zavaafbw.js → quality.command-p0mb5xwt.js} +1 -1
- package/{repair.command-je57wx67.js → repair.command-nptckp6w.js} +2 -2
- package/{scalar.command-gqqyy1kr.js → scalar.command-bss14hez.js} +2 -2
- package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +19 -0
- package/{workflow.command-d6mrc47s.js → workflow.command-7fhqb33d.js} +6 -6
- package/{workspace.command-bbg96z8k.js → workspace.command-xkcec7ky.js} +15 -15
- 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-
|
|
7
|
+
} from "./index-3a4vntr0.js";
|
|
8
8
|
import {
|
|
9
9
|
runner,
|
|
10
10
|
script
|
|
11
|
-
} from "./index-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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.#
|
|
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.#
|
|
1141
|
-
if (!
|
|
1211
|
+
this.#rssCeilingTightReports += 1;
|
|
1212
|
+
if (this.#rssCeilingTightWarned || !shouldWarnBuilderRssCeilingTight(this.#rssCeilingTightReports))
|
|
1142
1213
|
return;
|
|
1143
|
-
this.#
|
|
1144
|
-
this.logger.
|
|
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
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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
|
|
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(`[
|
|
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 (
|
|
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-
|
|
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");
|
|
@@ -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)
|