@akanjs/cli 2.4.1-rc.2 → 2.4.1-rc.4
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 -0
- package/{agent.command-h4afc69n.js → agent.command-t4346579.js} +6 -6
- package/{application.command-47mj9qsy.js → application.command-zkmeemdt.js} +5 -4
- package/{applicationBuildRunner-yz144508.js → applicationBuildRunner-5w8j9yp7.js} +2 -1
- package/buildBatch.proc.js +176 -0
- package/{capacitorApp-y0h6cgft.js → capacitorApp-wnxzrppx.js} +2 -2
- package/{cloud.command-hxcgsf80.js → cloud.command-5ztqxppm.js} +9 -8
- package/{context.command-nqbtak4f.js → context.command-smwxwgwf.js} +14 -14
- package/{guideline.command-ya0dh44f.js → guideline.command-1r0esdjs.js} +3 -3
- package/incrementalBuilder.proc.js +281 -160
- package/index-0fn1r7gg.js +466 -0
- package/{index-qhtr07v8.js → index-0jwvs8vp.js} +19 -19
- package/{index-a6sbyy0b.js → index-0t7pwff2.js} +3 -0
- package/{index-h6ca6qg0.js → index-2v10yr8g.js} +1 -1
- package/{index-73pr2cmy.js → index-2v6a4wd5.js} +4 -4
- package/{index-45aj5ry0.js → index-3sd2vape.js} +3 -3
- package/{index-y3hdhy4p.js → index-ahpcr9ss.js} +5 -5
- package/{index-8rc0bm04.js → index-eay4t1te.js} +2 -2
- package/{index-qaq13qk3.js → index-eqxmxan6.js} +1 -1
- package/{index-pmm9e2jf.js → index-fh990y67.js} +2 -2
- package/{index-sgmas1fc.js → index-gr4cjv99.js} +5 -5
- package/{index-1xdrsbry.js → index-hk58kr1m.js} +1 -1
- package/{index-85msc0wg.js → index-jbtn8h1y.js} +3 -3
- package/{index-a5rmdgy4.js → index-khzzttv1.js} +1362 -1447
- package/{index-77crfweb.js → index-n5y2gbf2.js} +478 -20
- package/{index-n6h3482q.js → index-nv7et4t3.js} +2 -2
- package/{index-hwzpw9c1.js → index-pya1h7wx.js} +3 -3
- package/{index-hdqztm58.js → index-qpz4csbs.js} +1 -1
- package/{index-mq6ns0f9.js → index-s9yajzkj.js} +9 -9
- package/{index-8pkbzj26.js → index-wh201a76.js} +7 -7
- package/{index-swf4bmbg.js → index-y4kg682s.js} +1 -1
- package/index.js +18 -18
- package/{library.command-r15zdqvp.js → library.command-jrew04m6.js} +3 -3
- package/{localRegistry.command-6z4s13mj.js → localRegistry.command-m08dkm1b.js} +8 -7
- package/{module.command-qrj3kmyz.js → module.command-p98t7522.js} +6 -6
- package/{package.command-5x5m0ej1.js → package.command-57dyrfmn.js} +3 -3
- package/package.json +2 -2
- package/{page.command-c6xdx0xm.js → page.command-tas6f3na.js} +3 -3
- package/{primitive.command-pv9ssmtf.js → primitive.command-bjrbsakw.js} +7 -7
- package/{quality.command-es67wvdp.js → quality.command-zavaafbw.js} +2 -2
- package/{repair.command-677675vw.js → repair.command-je57wx67.js} +5 -5
- package/{scalar.command-kabkd6wd.js → scalar.command-gqqyy1kr.js} +5 -5
- package/{workflow.command-64r6cw0w.js → workflow.command-d6mrc47s.js} +10 -10
- package/{workspace.command-875aj35r.js → workspace.command-bbg96z8k.js} +24 -23
|
@@ -4,17 +4,21 @@ import {
|
|
|
4
4
|
} from "./index-76rn3g2c.js";
|
|
5
5
|
import {
|
|
6
6
|
LibraryScript
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-fh990y67.js";
|
|
8
8
|
import {
|
|
9
9
|
runner,
|
|
10
10
|
script
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-qpz4csbs.js";
|
|
12
12
|
import {
|
|
13
13
|
AppExecutor,
|
|
14
14
|
LibExecutor,
|
|
15
15
|
PkgExecutor,
|
|
16
16
|
WorkspaceExecutor
|
|
17
|
-
} from "./index-
|
|
17
|
+
} from "./index-0t7pwff2.js";
|
|
18
|
+
import {
|
|
19
|
+
HmrWatcher,
|
|
20
|
+
WatchRootResolver
|
|
21
|
+
} from "./index-0fn1r7gg.js";
|
|
18
22
|
import {
|
|
19
23
|
__require
|
|
20
24
|
} from "./index-r24hmh0q.js";
|
|
@@ -69,6 +73,7 @@ import { confirm } from "@inquirer/prompts";
|
|
|
69
73
|
import { Logger as Logger5 } from "akanjs/common";
|
|
70
74
|
|
|
71
75
|
// pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
|
|
76
|
+
import { stat } from "fs/promises";
|
|
72
77
|
import path2 from "path";
|
|
73
78
|
import { Logger as Logger3 } from "akanjs/common";
|
|
74
79
|
|
|
@@ -209,6 +214,7 @@ var createTunnel = async (service, { app, environment, port = service === "postg
|
|
|
209
214
|
// pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.host.ts
|
|
210
215
|
import path from "path";
|
|
211
216
|
import { Logger as Logger2 } from "akanjs/common";
|
|
217
|
+
import { MemoryLimit } from "akanjs/server/memoryLimit";
|
|
212
218
|
var builderMsgTypeSet = new Set([
|
|
213
219
|
"build-route-res",
|
|
214
220
|
"build-csr-res",
|
|
@@ -216,12 +222,15 @@ var builderMsgTypeSet = new Set([
|
|
|
216
222
|
"invalidate",
|
|
217
223
|
"css-updated",
|
|
218
224
|
"pages-updated",
|
|
219
|
-
"build-status"
|
|
225
|
+
"build-status",
|
|
226
|
+
"builder-metrics"
|
|
220
227
|
]);
|
|
221
228
|
|
|
222
229
|
class IncrementalBuilderHost {
|
|
223
230
|
static #restartBaseDelayMs = 1000;
|
|
224
231
|
static #restartMaxDelayMs = 30000;
|
|
232
|
+
static #recycleDrainTimeoutMs = 30000;
|
|
233
|
+
static #devMaxRssBytes = 1200 * 1024 * 1024;
|
|
225
234
|
logger = new Logger2("IncrementalBuilderHost");
|
|
226
235
|
entry;
|
|
227
236
|
env;
|
|
@@ -232,7 +241,11 @@ class IncrementalBuilderHost {
|
|
|
232
241
|
#status = "stopped";
|
|
233
242
|
#restartAttempts = 0;
|
|
234
243
|
#restartTimer = null;
|
|
244
|
+
#recycleTimer = null;
|
|
245
|
+
#recycleRequested = false;
|
|
246
|
+
#spawnAfterRecycle = false;
|
|
235
247
|
#manualStop = false;
|
|
248
|
+
#inFlight = new Map;
|
|
236
249
|
#startOptions = {};
|
|
237
250
|
constructor({ app, entry, env, onMessage }) {
|
|
238
251
|
this.app = app;
|
|
@@ -243,27 +256,35 @@ class IncrementalBuilderHost {
|
|
|
243
256
|
get status() {
|
|
244
257
|
return this.#status;
|
|
245
258
|
}
|
|
259
|
+
get pid() {
|
|
260
|
+
return this.#proc?.pid ?? null;
|
|
261
|
+
}
|
|
246
262
|
start(options = {}) {
|
|
247
263
|
if (this.#proc)
|
|
248
264
|
this.stop();
|
|
249
265
|
this.#manualStop = false;
|
|
250
266
|
this.#startOptions = options;
|
|
267
|
+
this.#spawnAfterRecycle = options.announceBootState ?? false;
|
|
251
268
|
this.#spawn(false);
|
|
252
269
|
return this;
|
|
253
270
|
}
|
|
254
271
|
#spawn(isRestart) {
|
|
255
272
|
this.#status = isRestart ? "restarting" : "starting";
|
|
256
273
|
this.ready = false;
|
|
274
|
+
const afterRecycle = this.#spawnAfterRecycle;
|
|
275
|
+
this.#spawnAfterRecycle = false;
|
|
257
276
|
let proc;
|
|
258
277
|
proc = Bun.spawn(["bun", this.entry], {
|
|
259
278
|
cwd: this.app.cwdPath,
|
|
260
|
-
env: { ...this.env, AKAN_WATCH: "1" },
|
|
279
|
+
env: { ...this.env, AKAN_WATCH: "1", ...afterRecycle ? { AKAN_BUILDER_ANNOUNCE_BOOT: "1" } : {} },
|
|
261
280
|
stdio: ["ignore", "inherit", "inherit"],
|
|
262
281
|
ipc: (msg) => {
|
|
263
282
|
if (this.#proc !== proc)
|
|
264
283
|
return;
|
|
265
284
|
if (!msg || typeof msg !== "object")
|
|
266
285
|
return;
|
|
286
|
+
if (msg.type === "build-route-res" || msg.type === "build-csr-res")
|
|
287
|
+
this.#inFlight.delete(msg.id);
|
|
267
288
|
if (builderMsgTypeSet.has(msg.type))
|
|
268
289
|
this.#onMessage(msg);
|
|
269
290
|
if (msg.type === "builder-ready" && !this.ready) {
|
|
@@ -282,7 +303,10 @@ class IncrementalBuilderHost {
|
|
|
282
303
|
return;
|
|
283
304
|
this.#proc = null;
|
|
284
305
|
const wasReady = this.ready;
|
|
306
|
+
const wasRecycle = this.#recycleRequested;
|
|
307
|
+
this.#clearRecycle();
|
|
285
308
|
this.ready = false;
|
|
309
|
+
this.#failInFlight(wasRecycle ? "builder exited to release bundler memory before answering; reload to retry" : "builder exited unexpectedly before answering; reload once it is back");
|
|
286
310
|
if (this.#manualStop || this.#status === "stopped")
|
|
287
311
|
return;
|
|
288
312
|
if (!wasReady) {
|
|
@@ -290,6 +314,12 @@ class IncrementalBuilderHost {
|
|
|
290
314
|
this.#startOptions.onExit?.();
|
|
291
315
|
return;
|
|
292
316
|
}
|
|
317
|
+
if (wasRecycle) {
|
|
318
|
+
this.logger.verbose("builder exited for a recycle; spawning its replacement now");
|
|
319
|
+
this.#spawnAfterRecycle = true;
|
|
320
|
+
this.#spawn(true);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
293
323
|
this.#scheduleRestart();
|
|
294
324
|
}
|
|
295
325
|
});
|
|
@@ -311,6 +341,40 @@ class IncrementalBuilderHost {
|
|
|
311
341
|
this.#spawn(true);
|
|
312
342
|
}, delay);
|
|
313
343
|
}
|
|
344
|
+
recycle(reason) {
|
|
345
|
+
if (!this.#proc || this.#status !== "ready" || this.#recycleRequested)
|
|
346
|
+
return false;
|
|
347
|
+
const proc = this.#proc;
|
|
348
|
+
if (!this.send({ type: "builder-shutdown", reason }))
|
|
349
|
+
return false;
|
|
350
|
+
this.#recycleRequested = true;
|
|
351
|
+
this.logger.info(`recycling builder pid=${proc.pid} (${reason})`);
|
|
352
|
+
this.#recycleTimer = setTimeout(() => {
|
|
353
|
+
this.#recycleTimer = null;
|
|
354
|
+
if (this.#proc !== proc)
|
|
355
|
+
return;
|
|
356
|
+
this.logger.warn(`builder pid=${proc.pid} did not exit within ${IncrementalBuilderHost.#recycleDrainTimeoutMs}ms of the recycle request; killing it`);
|
|
357
|
+
proc.kill();
|
|
358
|
+
}, IncrementalBuilderHost.#recycleDrainTimeoutMs);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
#clearRecycle() {
|
|
362
|
+
this.#recycleRequested = false;
|
|
363
|
+
if (!this.#recycleTimer)
|
|
364
|
+
return;
|
|
365
|
+
clearTimeout(this.#recycleTimer);
|
|
366
|
+
this.#recycleTimer = null;
|
|
367
|
+
}
|
|
368
|
+
static maxRssBytes() {
|
|
369
|
+
if (process.env.AKAN_BUILDER_MAX_RSS_MB === "0")
|
|
370
|
+
return null;
|
|
371
|
+
return MemoryLimit.resolveMaxRssBytes({
|
|
372
|
+
megabytesEnv: "AKAN_BUILDER_MAX_RSS_MB",
|
|
373
|
+
bytesEnv: "AKAN_BUILDER_MAX_RSS",
|
|
374
|
+
limitFraction: 0.35,
|
|
375
|
+
fallbackBytes: IncrementalBuilderHost.#devMaxRssBytes
|
|
376
|
+
});
|
|
377
|
+
}
|
|
314
378
|
send(message) {
|
|
315
379
|
if (!this.#proc || this.#status !== "ready") {
|
|
316
380
|
this.logger.warn(`incrementalBuilderHost is ${this.#status}; cannot send ${message.type}`);
|
|
@@ -318,14 +382,31 @@ class IncrementalBuilderHost {
|
|
|
318
382
|
}
|
|
319
383
|
try {
|
|
320
384
|
this.#proc.send(message);
|
|
385
|
+
if (message.type === "build-route" || message.type === "build-csr")
|
|
386
|
+
this.#inFlight.set(message.id, message.type);
|
|
321
387
|
return true;
|
|
322
388
|
} catch (error) {
|
|
323
389
|
this.logger.warn(`failed to send ${message.type} to builder: ${error instanceof Error ? error.message : String(error)}`);
|
|
324
390
|
return false;
|
|
325
391
|
}
|
|
326
392
|
}
|
|
393
|
+
#failInFlight(reason) {
|
|
394
|
+
if (!this.#inFlight.size)
|
|
395
|
+
return;
|
|
396
|
+
const lost = [...this.#inFlight];
|
|
397
|
+
this.#inFlight.clear();
|
|
398
|
+
this.logger.warn(`failing ${lost.length} unanswered builder request(s): ${reason}`);
|
|
399
|
+
for (const [id, type] of lost) {
|
|
400
|
+
if (type === "build-route")
|
|
401
|
+
this.#onMessage({ type: "build-route-res", id, ok: false, error: reason });
|
|
402
|
+
else
|
|
403
|
+
this.#onMessage({ type: "build-csr-res", id, ok: false, error: reason });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
327
406
|
stop() {
|
|
328
407
|
this.#manualStop = true;
|
|
408
|
+
this.#clearRecycle();
|
|
409
|
+
this.#failInFlight("builder was stopped before answering");
|
|
329
410
|
if (this.#restartTimer) {
|
|
330
411
|
clearTimeout(this.#restartTimer);
|
|
331
412
|
this.#restartTimer = null;
|
|
@@ -359,6 +440,13 @@ var BACKEND_RECOVERY_MAX_ATTEMPTS = 5;
|
|
|
359
440
|
var BACKEND_STDERR_TAIL_LIMIT = 40;
|
|
360
441
|
var BUILDER_READY_TIMEOUT_MS = 150000;
|
|
361
442
|
var BUILDER_START_MAX_ATTEMPTS = 3;
|
|
443
|
+
var BUILDER_RSS_RECYCLE_QUIET_MS = 750;
|
|
444
|
+
var BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS = 30000;
|
|
445
|
+
var BUILDER_INEFFECTIVE_RSS_RECYCLE_LIMIT = 2;
|
|
446
|
+
var BUILDER_RSS_SETTLE_MS = 20000;
|
|
447
|
+
var BUILDER_RSS_HARD_MULTIPLE = 1.5;
|
|
448
|
+
var DEV_IDLE_SUSPEND_MS = 300000;
|
|
449
|
+
var DEV_IDLE_MIN_UPTIME_MS = 30000;
|
|
362
450
|
var BUILDER_RECOVERY_BASE_DELAY_MS = 2000;
|
|
363
451
|
var BUILDER_RECOVERY_MAX_DELAY_MS = 60000;
|
|
364
452
|
var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
@@ -446,6 +534,74 @@ var hasBuildFailureForGeneration = (statusByPhase, generation) => {
|
|
|
446
534
|
}
|
|
447
535
|
return false;
|
|
448
536
|
};
|
|
537
|
+
var decideBuilderRssRecycle = ({
|
|
538
|
+
rssBytes,
|
|
539
|
+
ceilingBytes,
|
|
540
|
+
buildFailed,
|
|
541
|
+
msSinceLastRecycle,
|
|
542
|
+
minIntervalMs = BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS
|
|
543
|
+
}) => {
|
|
544
|
+
if (!ceilingBytes)
|
|
545
|
+
return "unbounded";
|
|
546
|
+
if (rssBytes < ceilingBytes)
|
|
547
|
+
return "below-ceiling";
|
|
548
|
+
if (buildFailed)
|
|
549
|
+
return "build-failed";
|
|
550
|
+
if (msSinceLastRecycle !== null && msSinceLastRecycle < minIntervalMs)
|
|
551
|
+
return "too-soon";
|
|
552
|
+
return "recycle";
|
|
553
|
+
};
|
|
554
|
+
var decideBuilderRssSettle = ({
|
|
555
|
+
rssBytes,
|
|
556
|
+
ceilingBytes,
|
|
557
|
+
hardMultiple = BUILDER_RSS_HARD_MULTIPLE
|
|
558
|
+
}) => rssBytes >= ceilingBytes * hardMultiple ? "recycle-now" : "wait-and-recheck";
|
|
559
|
+
var decideIdleSuspend = ({
|
|
560
|
+
enabled,
|
|
561
|
+
suspended,
|
|
562
|
+
builderReady,
|
|
563
|
+
backendReady,
|
|
564
|
+
buildFailed,
|
|
565
|
+
restartPending,
|
|
566
|
+
msSinceWake,
|
|
567
|
+
minUptimeMs = DEV_IDLE_MIN_UPTIME_MS
|
|
568
|
+
}) => {
|
|
569
|
+
if (!enabled)
|
|
570
|
+
return "disabled";
|
|
571
|
+
if (suspended)
|
|
572
|
+
return "already-suspended";
|
|
573
|
+
if (!builderReady)
|
|
574
|
+
return "builder-not-ready";
|
|
575
|
+
if (!backendReady)
|
|
576
|
+
return "backend-not-ready";
|
|
577
|
+
if (buildFailed)
|
|
578
|
+
return "build-failed";
|
|
579
|
+
if (restartPending)
|
|
580
|
+
return "restart-pending";
|
|
581
|
+
if (msSinceWake !== null && msSinceWake < minUptimeMs)
|
|
582
|
+
return "too-soon";
|
|
583
|
+
return "suspend";
|
|
584
|
+
};
|
|
585
|
+
var resolveIdleSuspendMs = (raw) => {
|
|
586
|
+
if (raw === undefined || raw === "")
|
|
587
|
+
return DEV_IDLE_SUSPEND_MS;
|
|
588
|
+
const parsed = Number(raw);
|
|
589
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
590
|
+
return null;
|
|
591
|
+
return Math.round(parsed);
|
|
592
|
+
};
|
|
593
|
+
var hasAnyBuildFailure = (statusByPhase) => [...statusByPhase.values()].some((status) => !status.ok);
|
|
594
|
+
var shouldRefreshConfigOnIdleWake = (batch) => !!batch && batch.kinds.has("config");
|
|
595
|
+
var shouldAbandonBuilderRssCeiling = (ineffectiveRecycles, limit = BUILDER_INEFFECTIVE_RSS_RECYCLE_LIMIT) => ineffectiveRecycles >= limit;
|
|
596
|
+
var shouldRelayRecycledFrontendState = (current, next) => {
|
|
597
|
+
if (!current || current.type !== next.type)
|
|
598
|
+
return true;
|
|
599
|
+
if (current.type === "pages-updated" && next.type === "pages-updated")
|
|
600
|
+
return current.data.bundlePath !== next.data.bundlePath;
|
|
601
|
+
if (current.type === "css-updated" && next.type === "css-updated")
|
|
602
|
+
return JSON.stringify(current.data.cssAssets) !== JSON.stringify(next.data.cssAssets);
|
|
603
|
+
return true;
|
|
604
|
+
};
|
|
449
605
|
var mergeDevPlans = (current, next) => {
|
|
450
606
|
if (!current)
|
|
451
607
|
return next;
|
|
@@ -484,6 +640,7 @@ class BackendImportGraph {
|
|
|
484
640
|
#jsTranspiler = new Bun.Transpiler({ loader: "js" });
|
|
485
641
|
#jsxTranspiler = new Bun.Transpiler({ loader: "jsx" });
|
|
486
642
|
#files = new Set;
|
|
643
|
+
#scanCache = new Map;
|
|
487
644
|
#ready = false;
|
|
488
645
|
#lastRefreshSucceeded = false;
|
|
489
646
|
constructor(app, logger) {
|
|
@@ -525,11 +682,10 @@ class BackendImportGraph {
|
|
|
525
682
|
continue;
|
|
526
683
|
if (!this.#isWorkspaceSource(current, workspaceRoot))
|
|
527
684
|
continue;
|
|
528
|
-
|
|
685
|
+
const imports = await this.#importsOf(current);
|
|
686
|
+
if (!imports)
|
|
529
687
|
continue;
|
|
530
688
|
files.add(current);
|
|
531
|
-
const source = await Bun.file(current).text();
|
|
532
|
-
const imports = this.#scanImports(current, source);
|
|
533
689
|
const importerDir = path2.dirname(current);
|
|
534
690
|
for (const imp of imports) {
|
|
535
691
|
if (!GRAPH_IMPORT_KINDS.has(imp.kind) || !imp.path || NON_SOURCE_EXT_RE.test(imp.path))
|
|
@@ -540,8 +696,23 @@ class BackendImportGraph {
|
|
|
540
696
|
queue.push(resolved);
|
|
541
697
|
}
|
|
542
698
|
}
|
|
699
|
+
for (const cached of this.#scanCache.keys())
|
|
700
|
+
if (!files.has(cached))
|
|
701
|
+
this.#scanCache.delete(cached);
|
|
543
702
|
return files;
|
|
544
703
|
}
|
|
704
|
+
async#importsOf(file) {
|
|
705
|
+
const stats = await stat(file).catch(() => null);
|
|
706
|
+
if (!stats?.isFile())
|
|
707
|
+
return null;
|
|
708
|
+
const mtimeMs = Math.round(stats.mtimeMs);
|
|
709
|
+
const cached = this.#scanCache.get(file);
|
|
710
|
+
if (cached && cached.mtimeMs === mtimeMs && cached.size === stats.size)
|
|
711
|
+
return cached.specifiers;
|
|
712
|
+
const specifiers = this.#scanImports(file, await Bun.file(file).text());
|
|
713
|
+
this.#scanCache.set(file, { mtimeMs, size: stats.size, specifiers });
|
|
714
|
+
return specifiers;
|
|
715
|
+
}
|
|
545
716
|
async#entrypoints() {
|
|
546
717
|
const roots = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
|
|
547
718
|
const existing = [];
|
|
@@ -606,10 +777,24 @@ class AkanAppHost {
|
|
|
606
777
|
#backendBuildStatusGeneration = 0;
|
|
607
778
|
#backendStderrTail = [];
|
|
608
779
|
#lastGoodFrontend = {};
|
|
780
|
+
#rssRecycleTimer = null;
|
|
781
|
+
#rssRecycleReason = null;
|
|
782
|
+
#lastRssRecycleAtMono = null;
|
|
783
|
+
#rssCeilingIneffective = 0;
|
|
784
|
+
#rssSettleToken = 0;
|
|
785
|
+
#rssRecycleOver = null;
|
|
786
|
+
#rssCeilingAbandoned = false;
|
|
609
787
|
#buildStatusByPhase = new Map;
|
|
610
788
|
#pendingBuildStatusReplay = [];
|
|
611
789
|
#builderMessageQueue = Promise.resolve();
|
|
612
790
|
#backendGraph;
|
|
791
|
+
#idleSuspendTimer = null;
|
|
792
|
+
#suspended = false;
|
|
793
|
+
#waking = false;
|
|
794
|
+
#wokeAtMono = null;
|
|
795
|
+
#idleWatcher = null;
|
|
796
|
+
#suspendedChanges = null;
|
|
797
|
+
#pendingBuilderMessages = [];
|
|
613
798
|
constructor(app, { env, withInk = false }) {
|
|
614
799
|
this.app = app;
|
|
615
800
|
this.env = env;
|
|
@@ -628,9 +813,12 @@ class AkanAppHost {
|
|
|
628
813
|
]);
|
|
629
814
|
Object.assign(this.env, { REDIS_HOST: redisHost });
|
|
630
815
|
this.#startBackend();
|
|
816
|
+
this.#armIdleSuspend();
|
|
631
817
|
return this;
|
|
632
818
|
}
|
|
633
819
|
async stop() {
|
|
820
|
+
this.#cancelIdleSuspend();
|
|
821
|
+
this.#stopIdleWatcher();
|
|
634
822
|
if (this.#restartTimer) {
|
|
635
823
|
clearTimeout(this.#restartTimer);
|
|
636
824
|
this.#restartTimer = null;
|
|
@@ -895,24 +1083,284 @@ ${this.#backendStderrTail.join(`
|
|
|
895
1083
|
});
|
|
896
1084
|
}
|
|
897
1085
|
async#handleBuilderMessage(message) {
|
|
1086
|
+
this.#markDevActivity();
|
|
898
1087
|
if (message.type === "build-status") {
|
|
899
1088
|
this.#recordBuildStatus(message.data);
|
|
900
1089
|
this.#sendOrQueueBuildStatus(message.data);
|
|
901
1090
|
this.#reviveBackendAfterGreenBuild(message.data);
|
|
902
1091
|
return;
|
|
903
1092
|
}
|
|
904
|
-
if (message.type === "
|
|
905
|
-
this.#
|
|
906
|
-
|
|
907
|
-
|
|
1093
|
+
if (message.type === "builder-metrics") {
|
|
1094
|
+
this.#handleBuilderMetrics(message.data);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (message.type === "pages-updated" || message.type === "css-updated") {
|
|
1098
|
+
const recycled = message.data.reason === "builder-recycle";
|
|
1099
|
+
if (recycled && !this.#shouldRelayRecycledState(message))
|
|
1100
|
+
return;
|
|
1101
|
+
this.#recordLastGood(message, { supersede: recycled });
|
|
1102
|
+
}
|
|
908
1103
|
if (message.type === "invalidate") {
|
|
909
1104
|
await this.#handleInvalidate(message);
|
|
910
1105
|
return;
|
|
911
1106
|
}
|
|
912
1107
|
this.#sendToBackend(message);
|
|
913
1108
|
}
|
|
1109
|
+
#shouldRelayRecycledState(message) {
|
|
1110
|
+
const current = message.type === "pages-updated" ? this.#lastGoodFrontend.pages : this.#lastGoodFrontend.css;
|
|
1111
|
+
if (shouldRelayRecycledFrontendState(current, message)) {
|
|
1112
|
+
this.logger.verbose(`[builder-recycle] ${message.type} moved during the recycle; pushing it to the backend`);
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1115
|
+
this.logger.verbose(`[builder-recycle] ${message.type} unchanged after the recycle; backend left as is`);
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
#handleBuilderMetrics(metrics) {
|
|
1119
|
+
if (this.#rssCeilingAbandoned)
|
|
1120
|
+
return;
|
|
1121
|
+
const ceilingBytes = IncrementalBuilderHost.maxRssBytes();
|
|
1122
|
+
const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
1123
|
+
const decision = decideBuilderRssRecycle({
|
|
1124
|
+
rssBytes: metrics.rssBytes,
|
|
1125
|
+
ceilingBytes,
|
|
1126
|
+
buildFailed: hasBuildFailureForGeneration(this.#buildStatusByPhase, metrics.generation),
|
|
1127
|
+
msSinceLastRecycle: this.#lastRssRecycleAtMono === null ? null : performance.now() - this.#lastRssRecycleAtMono
|
|
1128
|
+
});
|
|
1129
|
+
if (decision === "below-ceiling") {
|
|
1130
|
+
this.#rssCeilingIneffective = 0;
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
if (decision === "unbounded")
|
|
1134
|
+
return;
|
|
1135
|
+
if (decision === "build-failed") {
|
|
1136
|
+
this.logger.verbose(`[builder-recycle] deferred: generation=${metrics.generation} has a failing build, so a replacement would hit the same error`);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
if (decision === "too-soon") {
|
|
1140
|
+
this.#rssCeilingIneffective += 1;
|
|
1141
|
+
if (!shouldAbandonBuilderRssCeiling(this.#rssCeilingIneffective))
|
|
1142
|
+
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.`);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
this.#armRssRecycle(`rss=${asMib(metrics.rssBytes)}MiB>=${asMib(ceilingBytes ?? 0)}MiB after ${metrics.workCount} build(s)`, { rssBytes: metrics.rssBytes, ceilingBytes: ceilingBytes ?? 0 });
|
|
1148
|
+
}
|
|
1149
|
+
#armRssRecycle(reason, over) {
|
|
1150
|
+
if (this.#rssRecycleReason !== reason)
|
|
1151
|
+
this.logger.verbose(`[builder-recycle] armed (${reason}); replacing the builder once it stays quiet`);
|
|
1152
|
+
this.#rssRecycleReason = reason;
|
|
1153
|
+
if (over)
|
|
1154
|
+
this.#rssRecycleOver = over;
|
|
1155
|
+
if (this.#rssRecycleTimer)
|
|
1156
|
+
clearTimeout(this.#rssRecycleTimer);
|
|
1157
|
+
this.#rssRecycleTimer = setTimeout(() => {
|
|
1158
|
+
this.#rssRecycleTimer = null;
|
|
1159
|
+
const pendingReason = this.#rssRecycleReason;
|
|
1160
|
+
const pendingOver = this.#rssRecycleOver;
|
|
1161
|
+
this.#rssRecycleReason = null;
|
|
1162
|
+
this.#rssRecycleOver = null;
|
|
1163
|
+
if (!pendingReason)
|
|
1164
|
+
return;
|
|
1165
|
+
if (!pendingOver) {
|
|
1166
|
+
this.#recycleBuilderForRss(pendingReason);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
this.#recycleBuilderForRssWhenStillOver(pendingReason, pendingOver);
|
|
1170
|
+
}, BUILDER_RSS_RECYCLE_QUIET_MS);
|
|
1171
|
+
}
|
|
1172
|
+
async#recycleBuilderForRssWhenStillOver(reason, { rssBytes, ceilingBytes }) {
|
|
1173
|
+
const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
|
|
1174
|
+
if (decideBuilderRssSettle({ rssBytes, ceilingBytes }) === "recycle-now") {
|
|
1175
|
+
this.#recycleBuilderForRss(reason);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const pid = this.#builder?.pid;
|
|
1179
|
+
if (!pid) {
|
|
1180
|
+
this.#recycleBuilderForRss(reason);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
this.#rssSettleToken += 1;
|
|
1184
|
+
const token = this.#rssSettleToken;
|
|
1185
|
+
this.logger.verbose(`[builder-recycle] holding ${Math.round(BUILDER_RSS_SETTLE_MS / 1000)}s to see whether the allocator returns it (${reason})`);
|
|
1186
|
+
await Bun.sleep(BUILDER_RSS_SETTLE_MS);
|
|
1187
|
+
if (token !== this.#rssSettleToken || this.#builder?.pid !== pid || this.#suspended || this.#waking) {
|
|
1188
|
+
this.logger.verbose("[builder-recycle] settle check abandoned; the builder moved on");
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
const settledBytes = await AkanAppHost.readProcessRssBytes(pid);
|
|
1192
|
+
if (settledBytes !== null && settledBytes < ceilingBytes) {
|
|
1193
|
+
this.logger.info(`[builder-recycle] skipped: the builder fell to ${asMib(settledBytes)}MiB (ceiling ${asMib(ceilingBytes)}MiB) on its own, so a recycle would have cost a boot build for nothing`);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
this.#recycleBuilderForRss(settledBytes === null ? reason : `${reason}; still ${asMib(settledBytes)}MiB after settling`);
|
|
1197
|
+
}
|
|
1198
|
+
static async readProcessRssBytes(pid) {
|
|
1199
|
+
const status = await Bun.file(`/proc/${pid}/status`).text().catch(() => null);
|
|
1200
|
+
const vmRssKb = status === null ? null : /VmRSS:\s+(\d+) kB/.exec(status)?.[1];
|
|
1201
|
+
if (vmRssKb)
|
|
1202
|
+
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;
|
|
1206
|
+
}
|
|
1207
|
+
#cancelRssRecycle() {
|
|
1208
|
+
this.#rssRecycleReason = null;
|
|
1209
|
+
this.#rssRecycleOver = null;
|
|
1210
|
+
this.#rssSettleToken += 1;
|
|
1211
|
+
if (!this.#rssRecycleTimer)
|
|
1212
|
+
return;
|
|
1213
|
+
clearTimeout(this.#rssRecycleTimer);
|
|
1214
|
+
this.#rssRecycleTimer = null;
|
|
1215
|
+
}
|
|
1216
|
+
static idleSuspendMs() {
|
|
1217
|
+
return resolveIdleSuspendMs(process.env.AKAN_DEV_IDLE_SUSPEND_MS);
|
|
1218
|
+
}
|
|
1219
|
+
#markDevActivity() {
|
|
1220
|
+
if (this.#suspended || this.#waking)
|
|
1221
|
+
return;
|
|
1222
|
+
this.#armIdleSuspend();
|
|
1223
|
+
}
|
|
1224
|
+
#armIdleSuspend() {
|
|
1225
|
+
const idleMs = AkanAppHost.idleSuspendMs();
|
|
1226
|
+
this.#cancelIdleSuspend();
|
|
1227
|
+
if (idleMs === null)
|
|
1228
|
+
return;
|
|
1229
|
+
this.#idleSuspendTimer = setTimeout(() => {
|
|
1230
|
+
this.#idleSuspendTimer = null;
|
|
1231
|
+
this.#suspendWhenIdle(idleMs);
|
|
1232
|
+
}, idleMs);
|
|
1233
|
+
}
|
|
1234
|
+
#cancelIdleSuspend() {
|
|
1235
|
+
if (!this.#idleSuspendTimer)
|
|
1236
|
+
return;
|
|
1237
|
+
clearTimeout(this.#idleSuspendTimer);
|
|
1238
|
+
this.#idleSuspendTimer = null;
|
|
1239
|
+
}
|
|
1240
|
+
async#suspendWhenIdle(idleMs) {
|
|
1241
|
+
const decision = decideIdleSuspend({
|
|
1242
|
+
enabled: true,
|
|
1243
|
+
suspended: this.#suspended,
|
|
1244
|
+
builderReady: this.#builder?.status === "ready",
|
|
1245
|
+
backendReady: this.#backendReady,
|
|
1246
|
+
buildFailed: hasAnyBuildFailure(this.#buildStatusByPhase),
|
|
1247
|
+
restartPending: this.#restartPending,
|
|
1248
|
+
msSinceWake: this.#wokeAtMono === null ? null : performance.now() - this.#wokeAtMono
|
|
1249
|
+
});
|
|
1250
|
+
if (decision !== "suspend") {
|
|
1251
|
+
this.logger.verbose(`[idle-suspend] skipped (${decision}); re-arming`);
|
|
1252
|
+
this.#armIdleSuspend();
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
if (!await this.#startIdleWatcher()) {
|
|
1256
|
+
this.#armIdleSuspend();
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
this.#suspended = true;
|
|
1260
|
+
this.#stopBuilder();
|
|
1261
|
+
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
|
+
}
|
|
1263
|
+
get #restartPending() {
|
|
1264
|
+
return !!(this.#pendingRecycle || this.#restartTimer || this.#backendRecoveryTimer || this.#builderRecoveryTimer || this.#rssRecycleReason);
|
|
1265
|
+
}
|
|
1266
|
+
async#startIdleWatcher() {
|
|
1267
|
+
try {
|
|
1268
|
+
const roots = await new WatchRootResolver(this.app).resolve();
|
|
1269
|
+
const watcher = new HmrWatcher({
|
|
1270
|
+
roots,
|
|
1271
|
+
logger: this.logger,
|
|
1272
|
+
onBatch: (batch) => {
|
|
1273
|
+
this.#recordSuspendedChange(batch);
|
|
1274
|
+
this.#wakeFromIdle(`${batch.files.length} file(s) changed`);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
await watcher.start();
|
|
1278
|
+
this.#idleWatcher = watcher;
|
|
1279
|
+
return true;
|
|
1280
|
+
} catch (err) {
|
|
1281
|
+
this.logger.warn(`[idle-suspend] could not install the idle watcher; staying awake: ${err instanceof Error ? err.message : String(err)}`);
|
|
1282
|
+
this.#stopIdleWatcher();
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
#stopIdleWatcher() {
|
|
1287
|
+
this.#idleWatcher?.stop();
|
|
1288
|
+
this.#idleWatcher = null;
|
|
1289
|
+
}
|
|
1290
|
+
#recordSuspendedChange(batch) {
|
|
1291
|
+
const current = this.#suspendedChanges;
|
|
1292
|
+
if (!current) {
|
|
1293
|
+
this.#suspendedChanges = { files: [...batch.files], kinds: new Set(batch.kinds) };
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
this.#suspendedChanges = {
|
|
1297
|
+
files: [...new Set([...current.files, ...batch.files])],
|
|
1298
|
+
kinds: new Set([...current.kinds, ...batch.kinds])
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
async#wakeFromIdle(reason) {
|
|
1302
|
+
if (!this.#suspended || this.#waking)
|
|
1303
|
+
return;
|
|
1304
|
+
this.#waking = true;
|
|
1305
|
+
this.#cancelIdleSuspend();
|
|
1306
|
+
this.#stopIdleWatcher();
|
|
1307
|
+
const batch = this.#suspendedChanges;
|
|
1308
|
+
this.#suspendedChanges = null;
|
|
1309
|
+
const startedAtMono = performance.now();
|
|
1310
|
+
this.logger.info(`[idle-suspend] waking (${reason})`);
|
|
1311
|
+
try {
|
|
1312
|
+
await this.#applyIdleWake(batch);
|
|
1313
|
+
this.logger.info(`[idle-suspend] awake in ${Math.round(performance.now() - startedAtMono)}ms`);
|
|
1314
|
+
} catch (err) {
|
|
1315
|
+
this.logger.error(`[idle-suspend] wake failed; recovering the builder: ${err instanceof Error ? err.message : String(err)}`);
|
|
1316
|
+
this.#scheduleBuilderRecovery({ files: batch?.files ?? [] });
|
|
1317
|
+
} finally {
|
|
1318
|
+
this.#suspended = false;
|
|
1319
|
+
this.#waking = false;
|
|
1320
|
+
this.#wokeAtMono = performance.now();
|
|
1321
|
+
this.#flushPendingBuilderMessages();
|
|
1322
|
+
this.#armIdleSuspend();
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
async#applyIdleWake(batch) {
|
|
1326
|
+
const files = batch?.files ?? [];
|
|
1327
|
+
if (shouldRefreshConfigOnIdleWake(batch)) {
|
|
1328
|
+
this.logger.verbose("[idle-suspend] config changed while suspended; restarting the dev host");
|
|
1329
|
+
await this.#recycleDevChildren({ type: "invalidate", kinds: [...batch?.kinds ?? []], files }, {
|
|
1330
|
+
refreshConfig: true
|
|
1331
|
+
});
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
if (files.length > 0)
|
|
1335
|
+
await this.#backendGraph.refresh();
|
|
1336
|
+
await this.#startBuilder({ announceBootState: true });
|
|
1337
|
+
const backendFiles = files.filter((file) => this.#isBackendFile(file));
|
|
1338
|
+
if (backendFiles.length === 0)
|
|
1339
|
+
return;
|
|
1340
|
+
this.logger.verbose(`[idle-suspend] ${backendFiles.length} backend file(s) changed while suspended`);
|
|
1341
|
+
this.#scheduleBackendRestart({ files: backendFiles, roles: [] });
|
|
1342
|
+
}
|
|
1343
|
+
#flushPendingBuilderMessages() {
|
|
1344
|
+
const pending = this.#pendingBuilderMessages.splice(0);
|
|
1345
|
+
if (pending.length === 0)
|
|
1346
|
+
return;
|
|
1347
|
+
this.logger.verbose(`[idle-suspend] replaying ${pending.length} request(s) held during the wake`);
|
|
1348
|
+
for (const message of pending)
|
|
1349
|
+
this.#sendToBuilder(message);
|
|
1350
|
+
}
|
|
1351
|
+
#recycleBuilderForRss(reason) {
|
|
1352
|
+
if (this.#pendingRecycle || this.#restartTimer) {
|
|
1353
|
+
this.logger.verbose(`[builder-recycle] skipped (${reason}); a dev restart is already pending`);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
if (!this.#builder?.recycle(reason))
|
|
1357
|
+
return;
|
|
1358
|
+
this.#lastRssRecycleAtMono = performance.now();
|
|
1359
|
+
}
|
|
914
1360
|
async#handleInvalidate(message) {
|
|
915
1361
|
this.#logDevPlan(message);
|
|
1362
|
+
if (this.#rssRecycleReason)
|
|
1363
|
+
this.#armRssRecycle(this.#rssRecycleReason);
|
|
916
1364
|
const wantsDevHostRestart = shouldRestartDevHostByDevPlan(message);
|
|
917
1365
|
const pending = this.#pendingRecycle;
|
|
918
1366
|
if (wantsDevHostRestart || shouldRestartBuilderByDevPlan(message) || pending && message.kinds.includes("code")) {
|
|
@@ -1045,15 +1493,15 @@ ${this.#backendStderrTail.join(`
|
|
|
1045
1493
|
await this.#startBuilder();
|
|
1046
1494
|
this.#startBackend({ generation, files: message.files });
|
|
1047
1495
|
}
|
|
1048
|
-
#recordLastGood(message) {
|
|
1496
|
+
#recordLastGood(message, { supersede = false } = {}) {
|
|
1049
1497
|
if (message.type === "pages-updated") {
|
|
1050
|
-
if (!shouldReplaceLastGoodMessage(this.#lastGoodFrontend.pages, message))
|
|
1498
|
+
if (!supersede && !shouldReplaceLastGoodMessage(this.#lastGoodFrontend.pages, message))
|
|
1051
1499
|
return;
|
|
1052
1500
|
this.#lastGoodFrontend.pages = message;
|
|
1053
1501
|
this.logger.verbose(`[last-good] pages generation=${message.data.generation ?? "(unknown)"} buildId=${message.data.buildId}`);
|
|
1054
1502
|
return;
|
|
1055
1503
|
}
|
|
1056
|
-
if (!shouldReplaceLastGoodMessage(this.#lastGoodFrontend.css, message))
|
|
1504
|
+
if (!supersede && !shouldReplaceLastGoodMessage(this.#lastGoodFrontend.css, message))
|
|
1057
1505
|
return;
|
|
1058
1506
|
this.#lastGoodFrontend.css = message;
|
|
1059
1507
|
this.logger.verbose(`[last-good] css generation=${message.data.generation ?? "(unknown)"} assets=${Object.keys(message.data.cssAssets).length}`);
|
|
@@ -1132,7 +1580,9 @@ ${this.#backendStderrTail.join(`
|
|
|
1132
1580
|
#isBackendFile(file) {
|
|
1133
1581
|
return this.#backendGraph.has(file);
|
|
1134
1582
|
}
|
|
1135
|
-
async#startBuilder(
|
|
1583
|
+
async#startBuilder({
|
|
1584
|
+
announceBootState = false
|
|
1585
|
+
} = {}) {
|
|
1136
1586
|
const startTime = Date.now();
|
|
1137
1587
|
this.app.verbose(`[cli] waiting for builder to complete initial base build\u2026`);
|
|
1138
1588
|
let lastError;
|
|
@@ -1141,7 +1591,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1141
1591
|
this.#enqueueBuilderMessage(msg);
|
|
1142
1592
|
});
|
|
1143
1593
|
try {
|
|
1144
|
-
await this.#waitForBuilderReady(attempt);
|
|
1594
|
+
await this.#waitForBuilderReady(attempt, { announceBootState });
|
|
1145
1595
|
this.app.verbose(`[cli] base build ready in ${Date.now() - startTime}ms \u2014 starting backend`);
|
|
1146
1596
|
return this.#builder;
|
|
1147
1597
|
} catch (err) {
|
|
@@ -1154,7 +1604,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1154
1604
|
}
|
|
1155
1605
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
1156
1606
|
}
|
|
1157
|
-
#waitForBuilderReady(attempt) {
|
|
1607
|
+
#waitForBuilderReady(attempt, { announceBootState = false } = {}) {
|
|
1158
1608
|
return new Promise((resolve, reject) => {
|
|
1159
1609
|
if (!this.#builder)
|
|
1160
1610
|
throw new Error("Builder Not Found");
|
|
@@ -1170,6 +1620,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1170
1620
|
settle(() => reject(new Error("[cli] builder timed out before emitting builder-ready")));
|
|
1171
1621
|
}, BUILDER_READY_TIMEOUT_MS);
|
|
1172
1622
|
this.#builder.start({
|
|
1623
|
+
announceBootState,
|
|
1173
1624
|
onExit: () => {
|
|
1174
1625
|
settle(() => reject(new Error(`[cli] builder exited before emitting builder-ready (attempt ${attempt})`)));
|
|
1175
1626
|
},
|
|
@@ -1184,6 +1635,12 @@ ${this.#backendStderrTail.join(`
|
|
|
1184
1635
|
});
|
|
1185
1636
|
}
|
|
1186
1637
|
#sendToBuilder(message) {
|
|
1638
|
+
this.#markDevActivity();
|
|
1639
|
+
if (this.#suspended || this.#waking) {
|
|
1640
|
+
this.#pendingBuilderMessages.push(message);
|
|
1641
|
+
this.#wakeFromIdle(`${message.type} arrived while suspended`);
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1187
1644
|
if (message.type === "build-csr" && this.env.AKAN_DEV_CSR_REBUILD !== "1") {
|
|
1188
1645
|
Object.assign(this.env, { AKAN_DEV_CSR_REBUILD: "1" });
|
|
1189
1646
|
this.logger.verbose(`[csr] armed dev CSR rebuilds (${message.reason})`);
|
|
@@ -1212,6 +1669,7 @@ ${this.#backendStderrTail.join(`
|
|
|
1212
1669
|
this.logger.warn("akanAppHost builder is not running");
|
|
1213
1670
|
}
|
|
1214
1671
|
#stopBuilder() {
|
|
1672
|
+
this.#cancelRssRecycle();
|
|
1215
1673
|
if (!this.#builder)
|
|
1216
1674
|
return;
|
|
1217
1675
|
this.#builder.stop();
|
|
@@ -1259,9 +1717,9 @@ function openBrowser(url) {
|
|
|
1259
1717
|
}
|
|
1260
1718
|
|
|
1261
1719
|
// pkgs/@akanjs/cli/application/application.runner.ts
|
|
1262
|
-
var loadBuildRunner = async () => (await import("./applicationBuildRunner-
|
|
1720
|
+
var loadBuildRunner = async () => (await import("./applicationBuildRunner-5w8j9yp7.js")).ApplicationBuildRunner;
|
|
1263
1721
|
var loadReleasePackager = async () => (await import("./applicationReleasePackager-brvth6rs.js")).ApplicationReleasePackager;
|
|
1264
|
-
var loadCapacitorApp = async () => (await import("./capacitorApp-
|
|
1722
|
+
var loadCapacitorApp = async () => (await import("./capacitorApp-wnxzrppx.js")).CapacitorApp;
|
|
1265
1723
|
var loadPrompts = async () => await import("@inquirer/prompts");
|
|
1266
1724
|
|
|
1267
1725
|
class ApplicationRunner extends runner("application") {
|