@akanjs/cli 3.0.0-alpha.9 → 3.0.0-alpha.90

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 (135) hide show
  1. package/.build-stamp +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/{abstractCompactor-e5yq60qz.js → abstractCompactor-cefxzggj.js} +3 -5
  5. package/{agent.command-f0gqkfgz.js → agent.command-r93wmq7m.js} +11 -13
  6. package/{application.command-x903m2qq.js → application.command-5nqfqgdn.js} +62 -38
  7. package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-b21m07fs.js} +23 -20
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-3wh58n2m.js} +8 -9
  9. package/buildBatch.proc.js +12 -14
  10. package/capacitorApp-gzcx6txb.js +12 -0
  11. package/{cloud.command-zs6kyf83.js → cloud.command-g0pk0k60.js} +15 -18
  12. package/commandManifest.json +1 -1
  13. package/{context.command-fmepgjzg.js → context.command-qd8kqrnh.js} +19 -23
  14. package/dependencyScanner-shs82ynp.js +8 -0
  15. package/devTui-2xf0meaq.js +552 -0
  16. package/devTuiView-7jy8f30g.js +9 -0
  17. package/getDirname-dr3jz1xg.js +7 -0
  18. package/{guideline.command-n6sw0a0c.js → guideline.command-f38x8jhw.js} +16 -18
  19. package/guidelines/agentRule/agentRule.generate.json +31 -0
  20. package/guidelines/agentRule/agentRule.instruction.md +533 -0
  21. package/guidelines/conventions/conventions.instruction.md +406 -197
  22. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  23. package/guidelines/framework/framework.generate.json +3 -2
  24. package/guidelines/framework/framework.instruction.md +1 -9
  25. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  26. package/guidelines/mcpRule/mcpRule.instruction.md +217 -0
  27. package/guidelines/modelStore/modelStore.instruction.md +12 -0
  28. package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
  29. package/guidelines/queryRule/queryRule.generate.json +31 -0
  30. package/guidelines/queryRule/queryRule.instruction.md +143 -0
  31. package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
  32. package/guidelines/runtimeRule/runtimeRule.instruction.md +213 -0
  33. package/guidelines/ssrRule/ssrRule.instruction.md +49 -5
  34. package/guidelines/transportRule/transportRule.generate.json +31 -0
  35. package/guidelines/transportRule/transportRule.instruction.md +60 -0
  36. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +51 -760
  37. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
  38. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
  39. package/incrementalBuilder.proc.js +26 -25
  40. package/{index-774gcx29.js → index-0emb9eat.js} +41 -34
  41. package/index-3d9y066c.js +76 -0
  42. package/{index-3vta1tqa.js → index-3vvbv974.js} +32 -32
  43. package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
  44. package/{index-y4vkyhvv.js → index-5xdqxbn9.js} +14 -14
  45. package/{index-1577bej2.js → index-6wwzs1bc.js} +2 -2
  46. package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
  47. package/{index-jhpa13zj.js → index-9bwhxrqf.js} +57 -57
  48. package/{index-bjpxzr6s.js → index-9wtbw98m.js} +6 -3
  49. package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
  50. package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
  51. package/{index-kdh76j26.js → index-cezy7yb3.js} +100 -32
  52. package/{index-62955kn5.js → index-d61rcfa2.js} +758 -127
  53. package/{index-82jrh4hj.js → index-deekbtkj.js} +13 -13
  54. package/{index-rxkpk7n4.js → index-e82dyeqy.js} +4 -4
  55. package/{index-qzdjd4ct.js → index-h759vjfa.js} +18 -18
  56. package/{index-j37qq1f2.js → index-h7yw2z9n.js} +7 -7
  57. package/{index-qb61h0k7.js → index-jydtyzva.js} +1098 -1360
  58. package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
  59. package/{index-4m0bphac.js → index-kfnr6c19.js} +31 -30
  60. package/{index-xmmzf150.js → index-mpmyqm1y.js} +1 -1
  61. package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
  62. package/{index-0cj2zxbm.js → index-n85fer99.js} +12 -11
  63. package/{index-779ske1y.js → index-pgsswppj.js} +27 -27
  64. package/index-pvb4vdwr.js +159 -0
  65. package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
  66. package/{index-z8ptfkzv.js → index-q8sn827n.js} +969 -240
  67. package/{index-5sn5w2mt.js → index-r50qen0z.js} +94 -61
  68. package/{index-eyyf2149.js → index-rntcdss1.js} +36 -37
  69. package/{index-gcv8ncfh.js → index-sjak5v5b.js} +16 -12
  70. package/{index-j9cxndc0.js → index-w8kmx1b6.js} +20 -11
  71. package/{index-jf2ya7r2.js → index-y74k5anj.js} +8 -8
  72. package/{index-d43t8af2.js → index-zkvpaggp.js} +58 -25
  73. package/index.js +22 -25
  74. package/{library.command-cv9gdbmg.js → library.command-em103wtn.js} +11 -10
  75. package/{localRegistry.command-d8fg36zc.js → localRegistry.command-gz8apn4y.js} +20 -23
  76. package/{module.command-qyr1s9cn.js → module.command-451pztkc.js} +13 -16
  77. package/{package.command-wsjx0k47.js → package.command-rfhyf38t.js} +8 -10
  78. package/package.json +5 -7
  79. package/{page.command-rdzg65rm.js → page.command-kt19da5g.js} +7 -9
  80. package/{primitive.command-89g38b53.js → primitive.command-pgy69ere.js} +13 -16
  81. package/{quality.command-0cs2z92d.js → quality.command-gpe2vqb5.js} +226 -105
  82. package/recipeScanner-fpagtgc1.js +11 -0
  83. package/{repair.command-6fphxfwj.js → repair.command-2sbrz187.js} +12 -14
  84. package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-ze6x894c.js} +0 -2
  85. package/{scalar.command-xfer69s6.js → scalar.command-jcf4e5sv.js} +11 -14
  86. package/subspace.command-kqzqtk0p.js +755 -0
  87. package/templates/app/env/env.client.debug.ts.template +1 -5
  88. package/templates/app/env/env.client.develop.ts.template +1 -5
  89. package/templates/app/env/env.client.local.ts.template +1 -5
  90. package/templates/app/env/env.client.main.ts.template +1 -5
  91. package/templates/app/env/env.client.testing.ts.template +1 -5
  92. package/templates/app/env/env.client.type.ts +2 -6
  93. package/templates/app/env/env.server.debug.ts.template +1 -5
  94. package/templates/app/env/env.server.develop.ts.template +1 -5
  95. package/templates/app/env/env.server.local.ts.template +1 -5
  96. package/templates/app/env/env.server.main.ts.template +1 -5
  97. package/templates/app/main.ts +3 -1
  98. package/templates/appSample/lib/task/task.signal.ts +11 -6
  99. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  100. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  101. package/templates/crudPages/[__model__Id]/edit/page.tsx +5 -5
  102. package/templates/crudPages/[__model__Id]/page.tsx +10 -13
  103. package/templates/crudPages/new/page.tsx +5 -5
  104. package/templates/crudPages/page.tsx +9 -9
  105. package/templates/crudSinglePage/page.tsx +11 -7
  106. package/templates/env/_env.server.type.ts +7 -5
  107. package/templates/lib/db.ts +1 -1
  108. package/templates/lib/dict.ts +1 -1
  109. package/templates/lib/sig.ts +1 -1
  110. package/templates/lib/srv.ts +6 -2
  111. package/templates/module/__model__.dictionary.ts +4 -1
  112. package/templates/module/__model__.signal.ts +2 -3
  113. package/templates/module/__model__.store.ts +1 -2
  114. package/templates/workspaceRoot/.gitignore.template +0 -1
  115. package/templates/workspaceRoot/.husky/pre-commit.template +16 -0
  116. package/templates/workspaceRoot/.vscode/settings.json.template +1 -1
  117. package/templates/workspaceRoot/AGENTS.md.template +4 -0
  118. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  119. package/templates/workspaceRoot/biome.json.template +2 -1
  120. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
  121. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  122. package/templates/workspaceRoot/package.json.template +2 -4
  123. package/typeChecker-3xgrzc2v.js +7 -0
  124. package/typecheck.proc.js +4 -5
  125. package/{workflow.command-2ygfqt4f.js → workflow.command-m4z25a2p.js} +22 -25
  126. package/{workspace.command-88m0zs64.js → workspace.command-dqgj9zbk.js} +96 -81
  127. package/capacitorApp-9xswcgx7.js +0 -61
  128. package/dependencyScanner-f6grrep3.js +0 -9
  129. package/getDirname-m9s0cmhq.js +0 -8
  130. package/index-mxvakhsm.js +0 -66
  131. package/index-r24hmh0q.js +0 -4
  132. package/recipeScanner-tghkzrrt.js +0 -12
  133. package/typeChecker-kravn7ns.js +0 -8
  134. /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
  135. /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
@@ -1,27 +1,38 @@
1
1
  // @bun
2
- import {
3
- LibraryScript
4
- } from "./index-d43t8af2.js";
5
- import {
6
- resolveMobileTargets
7
- } from "./index-76rn3g2c.js";
8
2
  import {
9
3
  runner,
10
4
  script
11
- } from "./index-kdh76j26.js";
5
+ } from "./index-cezy7yb3.js";
6
+ import {
7
+ WorkspaceExecutor,
8
+ AppExecutor,
9
+ LibExecutor,
10
+ PkgExecutor
11
+ } from "./index-q8sn827n.js";
12
+ import {
13
+ LibraryScript
14
+ } from "./index-zkvpaggp.js";
12
15
  import {
13
16
  HmrWatcher,
14
17
  WatchRootResolver
15
- } from "./index-bjpxzr6s.js";
18
+ } from "./index-9wtbw98m.js";
16
19
  import {
17
- AppExecutor,
18
- LibExecutor,
19
- PkgExecutor,
20
- WorkspaceExecutor
21
- } from "./index-z8ptfkzv.js";
20
+ SlicePlanner,
21
+ formatSlicePlan
22
+ } from "./index-pvb4vdwr.js";
22
23
  import {
23
- __require
24
- } from "./index-r24hmh0q.js";
24
+ resolveMobileTargets
25
+ } from "./index-47pj3aee.js";
26
+
27
+ // pkgs/@akanjs/cli/openBrowser.ts
28
+ import { spawn } from "child_process";
29
+ function openBrowser(url) {
30
+ const command = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
31
+ const child = spawn(command[0], command.slice(1), { detached: true, stdio: "ignore" });
32
+ child.on("error", () => {});
33
+ child.unref();
34
+ return Promise.resolve();
35
+ }
25
36
 
26
37
  // pkgs/@akanjs/devkit/applicationBuildReporter.ts
27
38
  import { Logger } from "akanjs/common";
@@ -70,11 +81,12 @@ Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
70
81
 
71
82
  // pkgs/@akanjs/cli/application/application.script.ts
72
83
  import { confirm } from "@inquirer/prompts";
73
- import { Logger as Logger5 } from "akanjs/common";
84
+ import { Logger as Logger6 } from "akanjs/common";
85
+
86
+ // pkgs/@akanjs/cli/application/application.runner.ts
87
+ import path5 from "path";
74
88
 
75
89
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
76
- import { stat } from "fs/promises";
77
- import path2 from "path";
78
90
  import { Logger as Logger3 } from "akanjs/common";
79
91
 
80
92
  // pkgs/@akanjs/devkit/sshTunnel.ts
@@ -237,6 +249,8 @@ class IncrementalBuilderHost {
237
249
  app;
238
250
  ready = false;
239
251
  #onMessage;
252
+ #stdio;
253
+ #onOutput;
240
254
  #proc = null;
241
255
  #status = "stopped";
242
256
  #restartAttempts = 0;
@@ -247,11 +261,13 @@ class IncrementalBuilderHost {
247
261
  #manualStop = false;
248
262
  #inFlight = new Map;
249
263
  #startOptions = {};
250
- constructor({ app, entry, env, onMessage }) {
264
+ constructor({ app, entry, env, stdio = "inherit", onMessage, onOutput }) {
251
265
  this.app = app;
252
266
  this.entry = entry;
253
267
  this.env = env;
268
+ this.#stdio = stdio;
254
269
  this.#onMessage = onMessage;
270
+ this.#onOutput = onOutput ?? null;
255
271
  }
256
272
  get status() {
257
273
  return this.#status;
@@ -277,7 +293,7 @@ class IncrementalBuilderHost {
277
293
  proc = Bun.spawn(["bun", this.entry], {
278
294
  cwd: this.app.cwdPath,
279
295
  env: { ...this.env, AKAN_WATCH: "1", ...afterRecycle ? { AKAN_BUILDER_ANNOUNCE_BOOT: "1" } : {} },
280
- stdio: ["ignore", "inherit", "inherit"],
296
+ stdio: ["ignore", this.#stdio, this.#stdio],
281
297
  ipc: (msg) => {
282
298
  if (this.#proc !== proc)
283
299
  return;
@@ -325,8 +341,24 @@ class IncrementalBuilderHost {
325
341
  }
326
342
  });
327
343
  this.#proc = proc;
344
+ if (this.#stdio === "pipe" && this.#onOutput) {
345
+ this.#drain(proc.stdout, "stdout");
346
+ this.#drain(proc.stderr, "stderr");
347
+ }
328
348
  this.logger.verbose(`builder spawned pid=${proc.pid} entry=${this.entry}${isRestart ? " restart=1" : ""}`);
329
349
  }
350
+ async#drain(stream, kind) {
351
+ if (!stream)
352
+ return;
353
+ const decoder = new TextDecoder;
354
+ try {
355
+ for await (const chunk of stream) {
356
+ const text = decoder.decode(chunk, { stream: true });
357
+ if (text)
358
+ this.#onOutput?.(kind, text);
359
+ }
360
+ } catch {}
361
+ }
330
362
  #scheduleRestart() {
331
363
  if (this.#manualStop || this.#restartTimer)
332
364
  return;
@@ -350,7 +382,7 @@ class IncrementalBuilderHost {
350
382
  return false;
351
383
  this.#recycleRequested = true;
352
384
  this.#status = "recycling";
353
- this.logger.info(`recycling builder pid=${proc.pid} (${reason})`);
385
+ this.logger.debug(`recycling builder pid=${proc.pid} (${reason})`);
354
386
  this.#recycleTimer = setTimeout(() => {
355
387
  this.#recycleTimer = null;
356
388
  if (this.#proc !== proc)
@@ -422,7 +454,10 @@ class IncrementalBuilderHost {
422
454
  this.ready = false;
423
455
  this.#status = "stopped";
424
456
  }
425
- static async create(app, env, onMessage) {
457
+ static async create(app, env, onMessage, {
458
+ stdio = "inherit",
459
+ onOutput
460
+ } = {}) {
426
461
  const candidates = [
427
462
  path.join(app.workspace.workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts"),
428
463
  path.join(app.workspace.workspaceRoot, "node_modules/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts"),
@@ -431,7 +466,7 @@ class IncrementalBuilderHost {
431
466
  ];
432
467
  for (const c of candidates)
433
468
  if (await Bun.file(c).exists())
434
- return new IncrementalBuilderHost({ app, entry: c, env, onMessage });
469
+ return new IncrementalBuilderHost({ app, entry: c, env, stdio, onMessage, onOutput });
435
470
  throw new Error(`[cli] frontend builder entry not found; looked in: ${candidates.join(", ")}`);
436
471
  }
437
472
  }
@@ -470,38 +505,23 @@ class BuilderRequestRouter {
470
505
  }
471
506
  }
472
507
 
473
- // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
474
- var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
475
- var BACKEND_RESTART_DEBOUNCE_MS = 120;
476
- var BACKEND_GRACEFUL_TIMEOUT_MS = 8000;
477
- var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
478
- var BACKEND_RECOVERY_MAX_DELAY_MS = 30000;
508
+ // pkgs/@akanjs/devkit/akanApp/BackendImportGraph.ts
509
+ import { stat } from "fs/promises";
510
+ import path3 from "path";
511
+
512
+ // pkgs/@akanjs/devkit/akanApp/devHostPolicy.ts
513
+ import path2 from "path";
479
514
  var BACKEND_RECOVERY_MAX_ATTEMPTS = 5;
480
- var BACKEND_STDERR_TAIL_LIMIT = 40;
481
- var BUILDER_READY_TIMEOUT_MS = 150000;
482
- var BUILDER_START_MAX_ATTEMPTS = 3;
483
515
  var HELD_BUILDER_REQUEST_LIMIT = 64;
484
- var BUILDER_RSS_RECYCLE_QUIET_MS = 750;
485
516
  var BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS = 30000;
486
517
  var BUILDER_TIGHT_RSS_REPORT_LIMIT = 2;
487
- var BUILDER_RSS_SETTLE_MS = 20000;
488
- var PS_RSS_TIMEOUT_MS = 2000;
489
518
  var BUILDER_RSS_HARD_MULTIPLE = 1.5;
490
519
  var DEV_IDLE_SUSPEND_MS = 300000;
491
520
  var DEV_IDLE_MIN_UPTIME_MS = 30000;
492
- var BUILDER_RECOVERY_BASE_DELAY_MS = 2000;
493
- var BUILDER_RECOVERY_MAX_DELAY_MS = 60000;
494
521
  var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
495
- var NON_SOURCE_EXT_RE = /\.(css|scss|sass|less|json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav|html)$/i;
496
522
  var SERVER_SUFFIXES = [".service.ts", ".document.ts"];
497
523
  var SHARED_SUFFIXES = [".constant.ts", ".dictionary.ts", ".signal.ts"];
498
524
  var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
499
- var GRAPH_IMPORT_KINDS = new Set([
500
- "import-statement",
501
- "require-call",
502
- "require-resolve",
503
- "dynamic-import"
504
- ]);
505
525
  var shouldRestartBackendByDevPlan = (message) => {
506
526
  if (!message.devPlan)
507
527
  return null;
@@ -515,6 +535,15 @@ var shouldRestartBuilderByDevPlan = (message) => message.devPlan?.actions.includ
515
535
  var shouldAbandonBackendRecovery = (attempts, maxAttempts = BACKEND_RECOVERY_MAX_ATTEMPTS) => attempts >= maxAttempts;
516
536
  var normalizeBackendReportedGeneration = (generation) => generation >= 0 ? generation : undefined;
517
537
  var shouldRestartDevHostByDevPlan = (message) => message.devPlan?.actions.includes("restart-dev-host") ?? message.kinds.includes("config");
538
+ var devHostStateByBackendState = {
539
+ starting: "starting",
540
+ ready: "ready",
541
+ "restart-pending": "restarting",
542
+ recovering: "recovering",
543
+ stopping: "stopped",
544
+ stopped: "stopped"
545
+ };
546
+ var devHostStateOf = (state, gaveUp) => state === "stopped" && gaveUp ? "failed" : devHostStateByBackendState[state];
518
547
  var RESTART_ROLE_ORDER = ["server", "shared", "barrel", "config"];
519
548
  var generationValue = (generation) => generation ?? -1;
520
549
  var isLegacyBackendFallbackFile = (file, workspaceRoot) => {
@@ -682,6 +711,15 @@ var mergeInvalidateMessages = (current, next) => {
682
711
  var buildStatusReplaySequence = (pendingReplay, latestByPhase) => [...pendingReplay, ...latestByPhase.values()];
683
712
  var filesChangedSince = (before, after) => [...before].filter(([file, stamp]) => after.get(file) !== stamp).map(([file]) => file);
684
713
 
714
+ // pkgs/@akanjs/devkit/akanApp/BackendImportGraph.ts
715
+ var NON_SOURCE_EXT_RE = /\.(css|scss|sass|less|json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav|html)$/i;
716
+ var GRAPH_IMPORT_KINDS = new Set([
717
+ "import-statement",
718
+ "require-call",
719
+ "require-resolve",
720
+ "dynamic-import"
721
+ ]);
722
+
685
723
  class BackendImportGraph {
686
724
  #app;
687
725
  #logger;
@@ -704,7 +742,7 @@ class BackendImportGraph {
704
742
  return this.#lastRefreshSucceeded;
705
743
  }
706
744
  has(file) {
707
- return this.#files.has(path2.resolve(file));
745
+ return this.#files.has(path3.resolve(file));
708
746
  }
709
747
  async fingerprint() {
710
748
  const stamps = await Promise.all([...this.#files].map(async (file) => {
@@ -732,9 +770,9 @@ class BackendImportGraph {
732
770
  const roots = await this.#entrypoints();
733
771
  const files = new Set;
734
772
  const queue = [...roots];
735
- const workspaceRoot = path2.resolve(this.#app.workspace.workspaceRoot);
773
+ const workspaceRoot = path3.resolve(this.#app.workspace.workspaceRoot);
736
774
  while (queue.length > 0) {
737
- const current = path2.resolve(queue.pop());
775
+ const current = path3.resolve(queue.pop());
738
776
  if (files.has(current))
739
777
  continue;
740
778
  if (!this.#isWorkspaceSource(current, workspaceRoot))
@@ -743,7 +781,7 @@ class BackendImportGraph {
743
781
  if (!imports)
744
782
  continue;
745
783
  files.add(current);
746
- const importerDir = path2.dirname(current);
784
+ const importerDir = path3.dirname(current);
747
785
  for (const imp of imports) {
748
786
  if (!GRAPH_IMPORT_KINDS.has(imp.kind) || !imp.path || NON_SOURCE_EXT_RE.test(imp.path))
749
787
  continue;
@@ -774,7 +812,7 @@ class BackendImportGraph {
774
812
  const roots = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
775
813
  const existing = [];
776
814
  for (const root of roots) {
777
- const abs = path2.resolve(root);
815
+ const abs = path3.resolve(root);
778
816
  if (await Bun.file(abs).exists())
779
817
  existing.push(abs);
780
818
  }
@@ -783,25 +821,25 @@ class BackendImportGraph {
783
821
  #resolve(specifier, importerDir) {
784
822
  try {
785
823
  const resolved = Bun.resolveSync(specifier, importerDir);
786
- if (!path2.isAbsolute(resolved))
824
+ if (!path3.isAbsolute(resolved))
787
825
  return null;
788
- if (!SOURCE_EXTS.has(path2.extname(resolved).toLowerCase()))
826
+ if (!SOURCE_EXTS.has(path3.extname(resolved).toLowerCase()))
789
827
  return null;
790
- return path2.resolve(resolved);
828
+ return path3.resolve(resolved);
791
829
  } catch {
792
830
  return null;
793
831
  }
794
832
  }
795
833
  #isWorkspaceSource(file, workspaceRoot) {
796
- const rel = path2.relative(workspaceRoot, file);
797
- if (rel.startsWith("..") || path2.isAbsolute(rel))
834
+ const rel = path3.relative(workspaceRoot, file);
835
+ if (rel.startsWith("..") || path3.isAbsolute(rel))
798
836
  return false;
799
- if (rel.includes(`${path2.sep}node_modules${path2.sep}`) || rel.includes(`${path2.sep}.akan${path2.sep}`))
837
+ if (rel.includes(`${path3.sep}node_modules${path3.sep}`) || rel.includes(`${path3.sep}.akan${path3.sep}`))
800
838
  return false;
801
- return SOURCE_EXTS.has(path2.extname(file).toLowerCase());
839
+ return SOURCE_EXTS.has(path3.extname(file).toLowerCase());
802
840
  }
803
841
  #scanImports(file, source) {
804
- const ext = path2.extname(file).toLowerCase();
842
+ const ext = path3.extname(file).toLowerCase();
805
843
  if (ext === ".tsx")
806
844
  return this.#tsxTranspiler.scanImports(source);
807
845
  if (ext === ".jsx")
@@ -812,11 +850,28 @@ class BackendImportGraph {
812
850
  }
813
851
  }
814
852
 
853
+ // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
854
+ var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
855
+ var BACKEND_RESTART_DEBOUNCE_MS = 120;
856
+ var BACKEND_GRACEFUL_TIMEOUT_MS = 8000;
857
+ var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
858
+ var BACKEND_RECOVERY_MAX_DELAY_MS = 30000;
859
+ var BACKEND_STDERR_TAIL_LIMIT = 40;
860
+ var BUILDER_READY_TIMEOUT_MS = 150000;
861
+ var BUILDER_START_MAX_ATTEMPTS = 3;
862
+ var BUILDER_RSS_RECYCLE_QUIET_MS = 750;
863
+ var BUILDER_RSS_SETTLE_MS = 20000;
864
+ var PS_RSS_TIMEOUT_MS = 2000;
865
+ var BUILDER_RECOVERY_BASE_DELAY_MS = 2000;
866
+ var BUILDER_RECOVERY_MAX_DELAY_MS = 60000;
867
+
815
868
  class AkanAppHost {
816
869
  app;
817
870
  logger = new Logger3("AkanAppHost");
818
- withInk;
871
+ stdio;
819
872
  env;
873
+ #onDevEvent;
874
+ #lastDevState = null;
820
875
  #backend = null;
821
876
  #builder = null;
822
877
  #backendReady = false;
@@ -855,12 +910,23 @@ class AkanAppHost {
855
910
  #builderGapStamp = null;
856
911
  #pendingBuilderMessages = [];
857
912
  #builderRequests = new BuilderRequestRouter;
858
- constructor(app, { env, withInk = false }) {
913
+ constructor(app, {
914
+ env,
915
+ stdio = "inherit",
916
+ onDevEvent
917
+ }) {
859
918
  this.app = app;
860
919
  this.env = env;
861
- this.withInk = withInk;
920
+ this.stdio = stdio;
921
+ this.#onDevEvent = onDevEvent ?? null;
862
922
  this.#backendGraph = new BackendImportGraph(app, this.logger);
863
923
  }
924
+ #emitDevEvent(state, detail) {
925
+ if (!this.#onDevEvent || state === this.#lastDevState)
926
+ return;
927
+ this.#lastDevState = state;
928
+ this.#onDevEvent({ app: this.app.name, state, ...detail ? { detail } : {} });
929
+ }
864
930
  async start() {
865
931
  if (this.#backend)
866
932
  await this.#stopBackend();
@@ -907,6 +973,7 @@ class AkanAppHost {
907
973
  }
908
974
  #startBackend(startStatus = null) {
909
975
  this.#builderRequests.startGeneration();
976
+ this.#discardPendingBuilderMessages("the backend restarted while the builder was away");
910
977
  this.#backendStartStatus = startStatus;
911
978
  this.#backendGaveUp = false;
912
979
  this.#setBackendLifecycleState("starting");
@@ -914,7 +981,7 @@ class AkanAppHost {
914
981
  this.#backendStderrTail = [];
915
982
  const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
916
983
  cwd: this.app.workspace.workspaceRoot,
917
- stdio: this.withInk ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
984
+ stdio: this.stdio === "pipe" ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
918
985
  env: this.env,
919
986
  ipc: (msg) => {
920
987
  if (!msg || typeof msg !== "object")
@@ -955,7 +1022,7 @@ class AkanAppHost {
955
1022
  });
956
1023
  this.#backend = backend;
957
1024
  this.logger.verbose(`backend spawned pid=${backend.pid}`);
958
- if (this.withInk) {
1025
+ if (this.stdio === "pipe") {
959
1026
  this.#forwardBackendStream(backend.stderr, "stderr");
960
1027
  this.#forwardBackendStream(backend.stdout, "stdout");
961
1028
  }
@@ -976,14 +1043,13 @@ class AkanAppHost {
976
1043
  try {
977
1044
  for await (const chunk of stream) {
978
1045
  const text = decoder.decode(chunk, { stream: true });
979
- if (!text.trim())
1046
+ if (!text)
980
1047
  continue;
981
1048
  if (kind === "stderr") {
982
1049
  this.#recordBackendStderr(text);
983
- this.logger.warn(`[backend] ${text.trimEnd()}`);
984
- } else {
985
- this.logger.verbose(`[backend] ${text.trimEnd()}`);
986
- }
1050
+ process.stderr.write(text);
1051
+ } else
1052
+ process.stdout.write(text);
987
1053
  }
988
1054
  } catch {}
989
1055
  }
@@ -1030,6 +1096,7 @@ class AkanAppHost {
1030
1096
  const prev = this.#backendLifecycleState;
1031
1097
  this.#backendLifecycleState = next;
1032
1098
  this.logger.verbose(`[backend-lifecycle] ${prev} -> ${next}${detail ? ` ${detail}` : ""}`);
1099
+ this.#emitDevEvent(devHostStateOf(next, this.#backendGaveUp), detail);
1033
1100
  }
1034
1101
  #sendToBackend(message) {
1035
1102
  if (!this.#backend || !this.#backendReady) {
@@ -1359,6 +1426,7 @@ ${this.#backendStderrTail.join(`
1359
1426
  return;
1360
1427
  }
1361
1428
  this.#suspended = true;
1429
+ this.#emitDevEvent("suspended", `idle ${Math.round(idleMs / 1000)}s`);
1362
1430
  this.#stopBuilder();
1363
1431
  this.#openBuilderGap("idle suspend");
1364
1432
  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`);
@@ -1423,6 +1491,7 @@ ${this.#backendStderrTail.join(`
1423
1491
  this.#wokeAtMono = performance.now();
1424
1492
  this.#flushPendingBuilderMessages();
1425
1493
  this.#armIdleSuspend();
1494
+ this.#emitDevEvent(devHostStateOf(this.#backendLifecycleState, this.#backendGaveUp));
1426
1495
  }
1427
1496
  }
1428
1497
  async#applyIdleWake(batch) {
@@ -1498,6 +1567,12 @@ ${this.#backendStderrTail.join(`
1498
1567
  this.#pendingBuilderMessages.push(message);
1499
1568
  this.logger.verbose(`[builder] holding ${message.type} until the builder is ready (${this.#pendingBuilderMessages.length} waiting)`);
1500
1569
  }
1570
+ #discardPendingBuilderMessages(reason) {
1571
+ const held = this.#pendingBuilderMessages.splice(0);
1572
+ if (held.length === 0)
1573
+ return;
1574
+ this.logger.verbose(`[builder] dropped ${held.length} held builder request(s): ${reason}`);
1575
+ }
1501
1576
  #failPendingBuilderMessages(reason) {
1502
1577
  const held = this.#pendingBuilderMessages.splice(0);
1503
1578
  if (held.length === 0)
@@ -1753,6 +1828,9 @@ ${this.#backendStderrTail.join(`
1753
1828
  for (let attempt = 1;attempt <= BUILDER_START_MAX_ATTEMPTS; attempt++) {
1754
1829
  this.#builder = await IncrementalBuilderHost.create(this.app, this.env, (msg) => {
1755
1830
  this.#enqueueBuilderMessage(msg);
1831
+ }, {
1832
+ stdio: this.stdio,
1833
+ onOutput: (kind, text) => void (kind === "stderr" ? process.stderr : process.stdout).write(text)
1756
1834
  });
1757
1835
  try {
1758
1836
  await this.#waitForBuilderReady(attempt, { announceBootState });
@@ -1858,20 +1936,20 @@ ${this.#backendStderrTail.join(`
1858
1936
  }
1859
1937
  }
1860
1938
  // pkgs/@akanjs/devkit/applicationTestPreload.ts
1861
- import path3 from "path";
1939
+ import path4 from "path";
1862
1940
  var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
1863
1941
  async function resolveSignalTestPreloadPath(target) {
1864
1942
  const candidates = [];
1865
1943
  const addResolvedPackageCandidate = (basePath) => {
1866
1944
  try {
1867
- candidates.push(path3.join(path3.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
1945
+ candidates.push(path4.join(path4.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
1868
1946
  } catch {}
1869
1947
  };
1870
1948
  addResolvedPackageCandidate(target.cwdPath);
1871
1949
  addResolvedPackageCandidate(process.cwd());
1872
- addResolvedPackageCandidate(path3.dirname(Bun.main));
1950
+ addResolvedPackageCandidate(path4.dirname(Bun.main));
1873
1951
  addResolvedPackageCandidate(import.meta.dir);
1874
- 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));
1952
+ 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));
1875
1953
  const uniqueCandidates = [...new Set(candidates)];
1876
1954
  for (const candidate of uniqueCandidates) {
1877
1955
  if (await Bun.file(candidate).exists())
@@ -1886,22 +1964,10 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
1886
1964
  // pkgs/@akanjs/cli/application/application.runner.ts
1887
1965
  import { Logger as Logger4 } from "akanjs/common";
1888
1966
  import ora from "ora";
1889
-
1890
- // pkgs/@akanjs/cli/openBrowser.ts
1891
- import { spawn } from "child_process";
1892
- function openBrowser(url) {
1893
- const command = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
1894
- const child = spawn(command[0], command.slice(1), { detached: true, stdio: "ignore" });
1895
- child.on("error", () => {});
1896
- child.unref();
1897
- return Promise.resolve();
1898
- }
1899
-
1900
- // pkgs/@akanjs/cli/application/application.runner.ts
1901
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-ha3y8ew8.js")).ApplicationBuildRunner;
1902
- var loadReleasePackager = async () => (await import("./applicationReleasePackager-gg8ccbeh.js")).ApplicationReleasePackager;
1903
- var loadCapacitorApp = async () => (await import("./capacitorApp-9xswcgx7.js")).CapacitorApp;
1904
- var loadAbstractCompactor = async () => (await import("./abstractCompactor-e5yq60qz.js")).AbstractCompactor;
1967
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-b21m07fs.js")).ApplicationBuildRunner;
1968
+ var loadReleasePackager = async () => (await import("./applicationReleasePackager-3wh58n2m.js")).ApplicationReleasePackager;
1969
+ var loadCapacitorApp = async () => (await import("./capacitorApp-gzcx6txb.js")).CapacitorApp;
1970
+ var loadAbstractCompactor = async () => (await import("./abstractCompactor-cefxzggj.js")).AbstractCompactor;
1905
1971
  var loadPrompts = async () => await import("@inquirer/prompts");
1906
1972
 
1907
1973
  class ApplicationRunner extends runner("application") {
@@ -1914,8 +1980,11 @@ class ApplicationRunner extends runner("application") {
1914
1980
  });
1915
1981
  return AppExecutor.from(workspace, appName);
1916
1982
  }
1983
+ async planSlice(app) {
1984
+ return await new SlicePlanner(app).plan();
1985
+ }
1917
1986
  async removeApplication(app) {
1918
- await app.workspace.exec(`rm -rf apps/${app.name}`);
1987
+ await app.workspace.removeDir(`apps/${app.name}`);
1919
1988
  }
1920
1989
  async getConfig(app) {
1921
1990
  return await app.getConfig();
@@ -1945,10 +2014,69 @@ class ApplicationRunner extends runner("application") {
1945
2014
  stdio: "inherit"
1946
2015
  });
1947
2016
  }
2017
+ #runtimeDirOf(app, override) {
2018
+ return path5.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path5.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
2019
+ }
2020
+ async runLogs(app, options) {
2021
+ const { LogControlUnavailableError, LogTailClient } = await import("akanjs/server/logging/logTailClient");
2022
+ const { LogQueryMatcher } = await import("akanjs/server/logging/logQuery");
2023
+ const runtimeDir = this.#runtimeDirOf(app, options.runtimeDir);
2024
+ const socketPath = LogTailClient.socketPath(runtimeDir);
2025
+ const { level, grep, endpoint, trace, child, role, origin, since } = options;
2026
+ const query = Object.fromEntries(Object.entries({ level, grep, endpoint, trace, child, role, origin, since }).filter(([, value]) => value !== undefined && value !== null && value !== ""));
2027
+ const print = (record) => process.stdout.write(options.json ? `${JSON.stringify(record)}
2028
+ ` : Logger4.render(record));
2029
+ const note = (text) => process.stderr.write(`[akan logs] ${text}
2030
+ `);
2031
+ let finish = null;
2032
+ const closed = new Promise((resolve) => {
2033
+ finish = resolve;
2034
+ });
2035
+ let client;
2036
+ try {
2037
+ client = await LogTailClient.connect(socketPath, {
2038
+ onRecord: (entry) => print(entry.record),
2039
+ onEvent: (response) => {
2040
+ if (response.type === "dropped")
2041
+ note(`${response.count} records dropped (reader too slow)`);
2042
+ },
2043
+ onClose: () => finish?.()
2044
+ });
2045
+ } catch (error) {
2046
+ if (error instanceof LogControlUnavailableError)
2047
+ 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.`);
2048
+ throw error;
2049
+ }
2050
+ const sinceMs = since ? LogQueryMatcher.parseSince(since) : undefined;
2051
+ const noteCoverage = (coverage) => {
2052
+ if (sinceMs !== undefined && coverage.from !== null && sinceMs < coverage.from)
2053
+ note(`${LogTailClient.describeCoverage(coverage)}; older records are not retained`);
2054
+ };
2055
+ if (endpoint)
2056
+ note("requests served by the primitive query fast path carry no endpoint and are not shown");
2057
+ if (options.follow === false) {
2058
+ const { entries, coverage } = await client.history({
2059
+ ...query,
2060
+ ...options.replay ? { limit: options.replay } : {}
2061
+ });
2062
+ for (const entry of entries)
2063
+ print(entry.record);
2064
+ noteCoverage(coverage);
2065
+ client.close();
2066
+ return;
2067
+ }
2068
+ const subscribed = await client.subscribe(query, { replay: options.replay });
2069
+ noteCoverage(subscribed.coverage);
2070
+ const stop = () => client.close();
2071
+ process.once("SIGINT", stop);
2072
+ process.once("SIGTERM", stop);
2073
+ await closed;
2074
+ }
1948
2075
  async runConsole(app) {
1949
2076
  const serverPath = `${app.cwdPath}/server.ts`;
1950
2077
  if (!await app.exists("server.ts"))
1951
2078
  throw new Error(`Server file not found: apps/${app.name}/server.ts`);
2079
+ const runtimeDir = this.#runtimeDirOf(app);
1952
2080
  const code = `
1953
2081
  const serverModule = await import(${JSON.stringify(serverPath)});
1954
2082
  const { assertAkanConsoleAllowed, startAkanConsole } = await import("akanjs/server");
@@ -1958,6 +2086,7 @@ assertAkanConsoleAllowed(server.env);
1958
2086
  await server.start({ listen: false, web: false });
1959
2087
  try {
1960
2088
  await startAkanConsole(server, {
2089
+ runtimeDir: ${JSON.stringify(runtimeDir)},
1961
2090
  globals: {
1962
2091
  srv: serverModule.srv,
1963
2092
  sig: serverModule.sig,
@@ -2005,9 +2134,14 @@ try {
2005
2134
  } = {}) {
2006
2135
  return new (await loadBuildRunner())(app, { fast, reporter }).build({ spinner });
2007
2136
  }
2008
- async start(app, { open = false, onStart, withInk = false } = {}) {
2137
+ async start(app, {
2138
+ open = false,
2139
+ onStart,
2140
+ onDevEvent,
2141
+ stdio = "inherit"
2142
+ } = {}) {
2009
2143
  const { env } = await app.prepareCommand("start");
2010
- const appHost = await new AkanAppHost(app, { env, withInk }).start();
2144
+ const appHost = await new AkanAppHost(app, { env, stdio, onDevEvent }).start();
2011
2145
  onStart?.();
2012
2146
  if (open)
2013
2147
  setTimeout(() => openBrowser(`http://localhost:${env.AKAN_PUBLIC_CLIENT_PORT ?? env.PORT ?? "8282"}`), 3000);
@@ -2239,6 +2373,444 @@ ${detail}`
2239
2373
  }
2240
2374
  }
2241
2375
 
2376
+ // pkgs/@akanjs/cli/application/devPortReclaimer.ts
2377
+ class DevPortReclaimer {
2378
+ static lsofTimeoutMs = 3000;
2379
+ static releaseTimeoutMs = 8000;
2380
+ static maxAncestorDepth = 8;
2381
+ static #releasePollMs = 100;
2382
+ static wsPortOffset = 1e4;
2383
+ static portsFor(ports) {
2384
+ return [...new Set(ports.flatMap((port) => [port, port + DevPortReclaimer.wsPortOffset]))].sort((left, right) => left - right);
2385
+ }
2386
+ static isAkanCommand(command) {
2387
+ return /(^|\/)akan(\s|$)/.test(command) || command.includes("@akanjs/cli") || command.includes("akanjs/server") || /\bapps\/[^/\s]+\/main\.ts\b/.test(command) || command.includes("incrementalBuilder.proc") || command.includes("rscWorker");
2388
+ }
2389
+ async reclaim(ports) {
2390
+ const killed = [];
2391
+ const foreign = [];
2392
+ const seen = new Set;
2393
+ for (const port of DevPortReclaimer.portsFor(ports)) {
2394
+ for (const holder of await this.holdersOf(port)) {
2395
+ if (seen.has(holder.pid))
2396
+ continue;
2397
+ seen.add(holder.pid);
2398
+ if (!holder.akan) {
2399
+ foreign.push(holder);
2400
+ continue;
2401
+ }
2402
+ if (await this.#terminate(holder.pid))
2403
+ killed.push(holder);
2404
+ else
2405
+ foreign.push(holder);
2406
+ }
2407
+ }
2408
+ return { killed, foreign };
2409
+ }
2410
+ async holdersOf(port) {
2411
+ const holders = [];
2412
+ for (const listenerPid of await this.#pidsOn(port)) {
2413
+ if (listenerPid === process.pid || listenerPid === process.ppid)
2414
+ continue;
2415
+ const command = await this.#commandOf(listenerPid);
2416
+ if (!command)
2417
+ continue;
2418
+ if (!DevPortReclaimer.isAkanCommand(command)) {
2419
+ holders.push({ pid: listenerPid, command, port, listenerPid, akan: false });
2420
+ continue;
2421
+ }
2422
+ const root = await this.#akanRootOf(listenerPid, command);
2423
+ holders.push({ pid: root.pid, command: root.command, port, listenerPid, akan: true });
2424
+ }
2425
+ return holders;
2426
+ }
2427
+ async#akanRootOf(pid, command) {
2428
+ let root = { pid, command };
2429
+ for (let depth = 0;depth < DevPortReclaimer.maxAncestorDepth; depth += 1) {
2430
+ const parentPid = await this.#ppidOf(root.pid);
2431
+ if (parentPid === null || parentPid <= 1 || parentPid === process.pid || parentPid === process.ppid)
2432
+ break;
2433
+ const parentCommand = await this.#commandOf(parentPid);
2434
+ if (!parentCommand || !DevPortReclaimer.isAkanCommand(parentCommand))
2435
+ break;
2436
+ root = { pid: parentPid, command: parentCommand };
2437
+ }
2438
+ return root;
2439
+ }
2440
+ async#ppidOf(pid) {
2441
+ const output = await this.#run(["ps", "-p", String(pid), "-o", "ppid="]);
2442
+ const parsed = Number(output.trim());
2443
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
2444
+ }
2445
+ async#pidsOn(port) {
2446
+ const output = await this.#run(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
2447
+ return [
2448
+ ...new Set(output.split(`
2449
+ `).map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0))
2450
+ ];
2451
+ }
2452
+ async#commandOf(pid) {
2453
+ const output = await this.#run(["ps", "-p", String(pid), "-o", "command="]);
2454
+ return output.trim() || null;
2455
+ }
2456
+ async#terminate(pid) {
2457
+ if (!this.#signal(pid, "SIGTERM"))
2458
+ return !this.#alive(pid);
2459
+ const deadline = Date.now() + DevPortReclaimer.releaseTimeoutMs;
2460
+ while (Date.now() < deadline) {
2461
+ if (!this.#alive(pid))
2462
+ return true;
2463
+ await Bun.sleep(DevPortReclaimer.#releasePollMs);
2464
+ }
2465
+ this.#signal(pid, "SIGKILL");
2466
+ await Bun.sleep(DevPortReclaimer.#releasePollMs);
2467
+ return !this.#alive(pid);
2468
+ }
2469
+ #signal(pid, signal) {
2470
+ try {
2471
+ process.kill(pid, signal);
2472
+ return true;
2473
+ } catch {
2474
+ return false;
2475
+ }
2476
+ }
2477
+ #alive(pid) {
2478
+ try {
2479
+ process.kill(pid, 0);
2480
+ return true;
2481
+ } catch (error) {
2482
+ return error.code === "EPERM";
2483
+ }
2484
+ }
2485
+ async#run(command) {
2486
+ const proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"] });
2487
+ const timer = setTimeout(() => proc.kill("SIGKILL"), DevPortReclaimer.lsofTimeoutMs);
2488
+ try {
2489
+ return await new Response(proc.stdout).text();
2490
+ } catch {
2491
+ return "";
2492
+ } finally {
2493
+ clearTimeout(timer);
2494
+ }
2495
+ }
2496
+ }
2497
+
2498
+ // pkgs/@akanjs/cli/application/devStreamView.ts
2499
+ import { Logger as Logger5 } from "akanjs/common";
2500
+ import chalk from "chalk";
2501
+ var appColors = ["cyan", "magenta", "yellow", "green", "blue", "red"];
2502
+
2503
+ class DevStreamView {
2504
+ #logger = new Logger5("akan start");
2505
+ #partial = new Map;
2506
+ #lastState = new Map;
2507
+ #color = new Map;
2508
+ #width = 0;
2509
+ constructor(appNames) {
2510
+ this.#width = appNames.reduce((max, name) => Math.max(max, name.length), 0);
2511
+ appNames.forEach((name, idx) => {
2512
+ this.#color.set(name, appColors[idx % appColors.length] ?? "cyan");
2513
+ });
2514
+ }
2515
+ onOutput = (app, kind, text) => {
2516
+ const key = `${app}:${kind}`;
2517
+ const pending = `${this.#partial.get(key) ?? ""}${text}`;
2518
+ const lines = pending.split(`
2519
+ `);
2520
+ this.#partial.set(key, lines.pop() ?? "");
2521
+ if (lines.length === 0)
2522
+ return;
2523
+ const prefixed = lines.map((line) => `${this.#prefix(app)}${line}`).join(`
2524
+ `);
2525
+ (kind === "stderr" ? process.stderr : process.stdout).write(`${prefixed}
2526
+ `);
2527
+ };
2528
+ onStatus = (statuses) => {
2529
+ for (const status of statuses) {
2530
+ if (this.#lastState.get(status.name) === status.state)
2531
+ continue;
2532
+ this.#lastState.set(status.name, status.state);
2533
+ const detail = status.detail ? ` (${status.detail})` : "";
2534
+ const where = status.state === "ready" ? ` \u2014 ${status.url}` : "";
2535
+ const line = `${status.name} ${status.state}${detail}${where}`;
2536
+ if (status.state === "failed")
2537
+ this.#logger.error(line);
2538
+ else if (status.state === "stopped")
2539
+ this.#logger.warn(line);
2540
+ else
2541
+ this.#logger.info(line);
2542
+ }
2543
+ };
2544
+ onNote = (text, level) => {
2545
+ if (level === "warn")
2546
+ this.#logger.warn(text);
2547
+ else
2548
+ this.#logger.info(text);
2549
+ };
2550
+ waitForExit = () => new Promise(() => {
2551
+ return;
2552
+ });
2553
+ close = () => {
2554
+ for (const [key, remainder] of this.#partial) {
2555
+ if (!remainder)
2556
+ continue;
2557
+ const app = key.slice(0, key.lastIndexOf(":"));
2558
+ process.stdout.write(`${this.#prefix(app)}${remainder}
2559
+ `);
2560
+ }
2561
+ this.#partial.clear();
2562
+ };
2563
+ #prefix(app) {
2564
+ const color = this.#color.get(app) ?? "cyan";
2565
+ return `${chalk[color](app.padEnd(this.#width))} ${chalk.dim("\u2502")} `;
2566
+ }
2567
+ }
2568
+
2569
+ // pkgs/@akanjs/cli/application/devSupervisor.ts
2570
+ class DevSupervisor {
2571
+ static supervisedEnvKey = "AKAN_DEV_SUPERVISED";
2572
+ static childHooks() {
2573
+ if (process.env[DevSupervisor.supervisedEnvKey] !== "1" || !process.send)
2574
+ return {};
2575
+ const send = process.send.bind(process);
2576
+ return {
2577
+ stdio: "pipe",
2578
+ onDevEvent: (event) => {
2579
+ try {
2580
+ send(event);
2581
+ } catch {}
2582
+ }
2583
+ };
2584
+ }
2585
+ static readyTimeoutMs = 180000;
2586
+ static shutdownGraceMs = 12000;
2587
+ #options;
2588
+ #children = new Map;
2589
+ #view = null;
2590
+ #stopping = false;
2591
+ constructor(options) {
2592
+ this.#options = options;
2593
+ }
2594
+ get statuses() {
2595
+ return [...this.#children.values()].map((child) => child.status);
2596
+ }
2597
+ async run(view) {
2598
+ this.#view = view;
2599
+ const ports = await this.#assignPorts();
2600
+ for (const app of this.#options.apps)
2601
+ this.#children.set(app.name, this.#makeChild(app, ports.get(app.name) ?? 0));
2602
+ this.#publishStatus();
2603
+ const stopped = this.#installSignalHandlers();
2604
+ this.#bootInOrder();
2605
+ await Promise.race([view.waitForExit(), stopped]);
2606
+ await this.stop();
2607
+ view.close?.();
2608
+ }
2609
+ async#assignPorts() {
2610
+ const taken = new Set;
2611
+ const ports = new Map;
2612
+ for (const app of this.#options.apps) {
2613
+ let port = await app.getDevPort();
2614
+ while (taken.has(port))
2615
+ port += 1;
2616
+ taken.add(port);
2617
+ ports.set(app.name, port);
2618
+ }
2619
+ return ports;
2620
+ }
2621
+ #makeChild(app, port) {
2622
+ let markReady;
2623
+ const ready = new Promise((resolve) => {
2624
+ markReady = resolve;
2625
+ });
2626
+ return {
2627
+ status: {
2628
+ app,
2629
+ name: app.name,
2630
+ port,
2631
+ url: `http://localhost:${port}`,
2632
+ state: "starting",
2633
+ detail: "queued",
2634
+ exitCode: null
2635
+ },
2636
+ proc: null,
2637
+ ready,
2638
+ markReady,
2639
+ opened: false
2640
+ };
2641
+ }
2642
+ async#bootInOrder() {
2643
+ const concurrency = Math.max(1, Math.trunc(this.#options.concurrency ?? 1));
2644
+ const queue = [...this.#children.values()];
2645
+ const waves = [];
2646
+ for (let idx = 0;idx < queue.length; idx += concurrency)
2647
+ waves.push(queue.slice(idx, idx + concurrency));
2648
+ for (const wave of waves) {
2649
+ if (this.#stopping)
2650
+ return;
2651
+ for (const child of wave)
2652
+ this.#spawnChild(child);
2653
+ this.#publishStatus();
2654
+ await Promise.all(wave.map(async (child) => await this.#waitForReady(child)));
2655
+ }
2656
+ }
2657
+ async#waitForReady(child) {
2658
+ let timer = null;
2659
+ const expired = new Promise((resolve) => {
2660
+ timer = setTimeout(() => resolve("expired"), DevSupervisor.readyTimeoutMs);
2661
+ });
2662
+ try {
2663
+ if (await Promise.race([child.ready.then(() => "ready"), expired]) === "expired")
2664
+ this.#note(`${child.status.name} has not reported ready after ${Math.round(DevSupervisor.readyTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
2665
+ } finally {
2666
+ if (timer)
2667
+ clearTimeout(timer);
2668
+ }
2669
+ }
2670
+ static childArgs(name, { write = true } = {}) {
2671
+ return [
2672
+ "start",
2673
+ name,
2674
+ "--plain",
2675
+ "--dbup",
2676
+ "false",
2677
+ "--write",
2678
+ String(write)
2679
+ ];
2680
+ }
2681
+ #spawnChild(child) {
2682
+ const { app, name, port } = child.status;
2683
+ const args = DevSupervisor.childArgs(name, { write: this.#options.write ?? true });
2684
+ const proc = Bun.spawn([process.execPath, Bun.main, ...args], {
2685
+ cwd: app.workspace.workspaceRoot,
2686
+ env: {
2687
+ ...process.env,
2688
+ [DevSupervisor.supervisedEnvKey]: "1",
2689
+ AKAN_PUBLIC_APP_NAME: name,
2690
+ AKAN_DEV_PORT: String(port)
2691
+ },
2692
+ stdio: ["ignore", "pipe", "pipe"],
2693
+ ipc: (message) => {
2694
+ if (!message || typeof message !== "object" || typeof message.state !== "string")
2695
+ return;
2696
+ this.#applyChildEvent(child, message);
2697
+ },
2698
+ serialization: "advanced",
2699
+ onExit: (_proc, exitCode, signalCode) => {
2700
+ child.proc = null;
2701
+ child.status.exitCode = exitCode;
2702
+ if (this.#stopping)
2703
+ return;
2704
+ child.status.state = "stopped";
2705
+ child.status.detail = signalCode ? `killed by ${signalCode}` : `exited with ${exitCode}`;
2706
+ child.markReady();
2707
+ this.#publishStatus();
2708
+ }
2709
+ });
2710
+ child.proc = proc;
2711
+ child.status.state = "starting";
2712
+ child.status.detail = `pid ${proc.pid}`;
2713
+ this.#drain(proc.stdout, name, "stdout");
2714
+ this.#drain(proc.stderr, name, "stderr");
2715
+ }
2716
+ #applyChildEvent(child, event) {
2717
+ child.status.state = event.state;
2718
+ child.status.detail = event.detail ?? "";
2719
+ if (event.state === "ready") {
2720
+ child.markReady();
2721
+ if (this.#options.open && !child.opened) {
2722
+ child.opened = true;
2723
+ openBrowser(child.status.url);
2724
+ }
2725
+ }
2726
+ if (event.state === "failed")
2727
+ child.markReady();
2728
+ this.#publishStatus();
2729
+ }
2730
+ async#drain(stream, name, kind) {
2731
+ const decoder = new TextDecoder;
2732
+ try {
2733
+ for await (const chunk of stream) {
2734
+ const text = decoder.decode(chunk, { stream: true });
2735
+ if (text)
2736
+ this.#view?.onOutput(name, kind, text);
2737
+ }
2738
+ } catch {}
2739
+ }
2740
+ #publishStatus() {
2741
+ this.#view?.onStatus(this.statuses);
2742
+ }
2743
+ #note(text, level) {
2744
+ this.#view?.onNote(text, level);
2745
+ }
2746
+ #installSignalHandlers() {
2747
+ return new Promise((resolve) => {
2748
+ let asked = false;
2749
+ const onSignal = () => {
2750
+ if (asked) {
2751
+ this.#note("abandoning the wait; children may be left running", "warn");
2752
+ process.exit(130);
2753
+ }
2754
+ asked = true;
2755
+ resolve();
2756
+ };
2757
+ process.on("SIGINT", onSignal);
2758
+ process.on("SIGTERM", onSignal);
2759
+ });
2760
+ }
2761
+ async restart(name) {
2762
+ const child = this.#children.get(name);
2763
+ if (!child || this.#stopping)
2764
+ return;
2765
+ const running = child.proc;
2766
+ if (running) {
2767
+ running.kill("SIGTERM");
2768
+ await running.exited;
2769
+ }
2770
+ child.status.state = "starting";
2771
+ child.status.detail = "restarting";
2772
+ child.status.exitCode = null;
2773
+ this.#publishStatus();
2774
+ this.#spawnChild(child);
2775
+ this.#publishStatus();
2776
+ }
2777
+ async stop() {
2778
+ if (this.#stopping)
2779
+ return;
2780
+ this.#stopping = true;
2781
+ const running = [...this.#children.values()].filter((child) => Boolean(child.proc));
2782
+ for (const child of running)
2783
+ child.proc.kill("SIGTERM");
2784
+ const exited = Promise.all(running.map(async (child) => await child.proc.exited));
2785
+ let timer = null;
2786
+ const expired = new Promise((resolve) => {
2787
+ timer = setTimeout(() => resolve("expired"), DevSupervisor.shutdownGraceMs);
2788
+ });
2789
+ try {
2790
+ if (await Promise.race([exited.then(() => "exited"), expired]) === "expired") {
2791
+ for (const child of running) {
2792
+ if (!child.proc.killed) {
2793
+ this.#note(`${child.status.name} did not exit in time; killing it`, "warn");
2794
+ child.proc.kill("SIGKILL");
2795
+ }
2796
+ }
2797
+ }
2798
+ } finally {
2799
+ if (timer)
2800
+ clearTimeout(timer);
2801
+ }
2802
+ }
2803
+ }
2804
+
2805
+ // pkgs/@akanjs/cli/application/devUiMode.ts
2806
+ var resolveDevUi = (plain, { isTty = !!process.stdout.isTTY, columns = process.stdout.columns ?? 0 } = {}) => {
2807
+ if (plain)
2808
+ return { mode: "stream", downgraded: false };
2809
+ if (!isTty || columns <= 0)
2810
+ return { mode: "stream", downgraded: true };
2811
+ return { mode: "tui", downgraded: false };
2812
+ };
2813
+
2242
2814
  // pkgs/@akanjs/cli/application/application.script.ts
2243
2815
  class ApplicationScript extends script("application", [ApplicationRunner, LibraryScript]) {
2244
2816
  static dbShutdownTimeoutMs = 20000;
@@ -2322,13 +2894,19 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2322
2894
  spinner.succeed(`Application created in apps/${app.name}`);
2323
2895
  await app.scanSync();
2324
2896
  if (start)
2325
- await this.start(app, { open: true });
2897
+ await this.startOne(app, { open: true });
2326
2898
  }
2327
2899
  async removeApplication(app) {
2328
2900
  const spinner = app.spinning("Removing application...");
2329
2901
  await this.applicationRunner.removeApplication(app);
2330
2902
  spinner.succeed(`Application ${app.name} (apps/${app.name}) removed`);
2331
2903
  }
2904
+ async planSlice(app, format = "text") {
2905
+ const spinner = app.spinning("Planning distributable slice...");
2906
+ const plan = await this.applicationRunner.planSlice(app);
2907
+ spinner.succeed(`Slice planned (${plan.libs.length} libs, ${plan.warnings.length} warnings)`);
2908
+ Logger6.rawLog(format === "json" ? JSON.stringify(plan, null, 2) : formatSlicePlan(plan));
2909
+ }
2332
2910
  async sync(sys) {
2333
2911
  if (sys.type === "app")
2334
2912
  await sys.scanSync();
@@ -2338,13 +2916,13 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2338
2916
  async compact(sys, { module = null, minLines, interactive = false } = {}) {
2339
2917
  const { scanned, reports } = await this.applicationRunner.compact(sys, { module, minLines, interactive });
2340
2918
  if (!reports.length) {
2341
- Logger5.rawLog(`No abstract file long enough to compact in ${sys.name} (${scanned} scanned)`);
2919
+ Logger6.rawLog(`No abstract file long enough to compact in ${sys.name} (${scanned} scanned)`);
2342
2920
  return;
2343
2921
  }
2344
2922
  for (const report of reports)
2345
- Logger5.rawLog(`${report.status}: ${report.path} (${report.beforeLines} -> ${report.afterLines} lines)`);
2923
+ Logger6.rawLog(`${report.status}: ${report.path} (${report.beforeLines} -> ${report.afterLines} lines)`);
2346
2924
  const compactedNum = reports.filter((report) => report.status === "compacted").length;
2347
- Logger5.rawLog(`Compacted ${compactedNum}/${reports.length} abstract files in ${sys.name}`);
2925
+ Logger6.rawLog(`Compacted ${compactedNum}/${reports.length} abstract files in ${sys.name}`);
2348
2926
  }
2349
2927
  async script(app, filename) {
2350
2928
  const scriptFilename = filename ?? await this.applicationRunner.getScriptFilename(app);
@@ -2355,21 +2933,24 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2355
2933
  await app.scanSync();
2356
2934
  await this.applicationRunner.runConsole(app);
2357
2935
  }
2936
+ async logs(app, options) {
2937
+ await this.applicationRunner.runLogs(app, options);
2938
+ }
2358
2939
  async build(app, { write = true, fast = false, quiet = false } = {}) {
2359
2940
  await app.scanSync({ write });
2360
2941
  if (!quiet)
2361
- Logger5.rawLog(`Creating an optimized production build for ${app.name}...`);
2942
+ Logger6.rawLog(`Creating an optimized production build for ${app.name}...`);
2362
2943
  try {
2363
2944
  const result = await this.applicationRunner.build(app, {
2364
2945
  fast,
2365
2946
  spinner: !quiet
2366
2947
  });
2367
- Logger5.rawLog(`${app.name} built in dist/apps/${app.name}`);
2948
+ Logger6.rawLog(`${app.name} built in dist/apps/${app.name}`);
2368
2949
  if (!quiet)
2369
2950
  ApplicationBuildReporter.printSummary(result);
2370
2951
  } catch (error) {
2371
- Logger5.rawLog(`${app.name} build failed in dist/apps/${app.name}`, undefined, "error");
2372
- Logger5.rawLog(ApplicationBuildReporter.formatError(error), undefined, "error");
2952
+ Logger6.rawLog(`${app.name} build failed in dist/apps/${app.name}`, undefined, "error");
2953
+ Logger6.rawLog(ApplicationBuildReporter.formatError(error), undefined, "error");
2373
2954
  throw error;
2374
2955
  }
2375
2956
  }
@@ -2381,15 +2962,15 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2381
2962
  spinner.succeed(`${app.name} typechecked`);
2382
2963
  } catch (error) {
2383
2964
  spinner.fail(`${app.name} typecheck failed`);
2384
- Logger5.rawLog(ApplicationBuildReporter.formatError(error), undefined, "error");
2965
+ Logger6.rawLog(ApplicationBuildReporter.formatError(error), undefined, "error");
2385
2966
  throw error;
2386
2967
  }
2387
2968
  }
2388
2969
  async test(exec, { write = true } = {}) {
2389
2970
  if (exec instanceof LibExecutor) {
2390
2971
  await this.libraryScript.syncLibrary(exec);
2391
- const spinner2 = exec.spinning(`Preparing ${exec.name}...`);
2392
- spinner2.succeed(`${exec.name} prepared`);
2972
+ const spinner = exec.spinning(`Preparing ${exec.name}...`);
2973
+ spinner.succeed(`${exec.name} prepared`);
2393
2974
  await this.applicationRunner.test(exec);
2394
2975
  return;
2395
2976
  }
@@ -2406,12 +2987,20 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2406
2987
  }
2407
2988
  await this.applicationRunner.test(exec);
2408
2989
  }
2409
- async start(app, {
2410
- open = false,
2411
- dbup = true,
2412
- withInk = false,
2413
- write = true
2414
- } = {}) {
2990
+ async start(apps, { open = false, dbup = true, write = true, plain = false, kill = false, concurrency = 1 } = {}) {
2991
+ const first = apps[0];
2992
+ if (!first)
2993
+ throw new Error("No app selected to start");
2994
+ const { mode, downgraded } = resolveDevUi(plain);
2995
+ if (downgraded)
2996
+ first.workspace.log("no sized terminal to draw on; printing prefixed lines instead");
2997
+ if (kill)
2998
+ await this.reclaimDevPorts(apps);
2999
+ if (mode === "stream" && apps.length === 1)
3000
+ return await this.startOne(first, { open, dbup, write, ...DevSupervisor.childHooks() });
3001
+ await this.#startMany(apps, { open, dbup, write, mode, concurrency });
3002
+ }
3003
+ async startOne(app, { open = false, dbup = true, stdio = "inherit", write = true, onDevEvent } = {}) {
2415
3004
  await app.scanSync({ write });
2416
3005
  const akanConfig = await app.getConfig();
2417
3006
  const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
@@ -2427,10 +3016,55 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2427
3016
  onStart: () => {
2428
3017
  spinner.succeed(`${app.name} prepared, ready to start`);
2429
3018
  },
2430
- withInk
3019
+ onDevEvent,
3020
+ stdio
2431
3021
  });
2432
3022
  return akanAppHost;
2433
3023
  }
3024
+ async reclaimDevPorts(apps) {
3025
+ const workspace = apps[0]?.workspace;
3026
+ if (!workspace)
3027
+ return;
3028
+ const ports = await Promise.all(apps.map(async (app) => await app.getDevPort()));
3029
+ const { killed, foreign } = await new DevPortReclaimer().reclaim(ports);
3030
+ for (const holder of killed)
3031
+ workspace.log(`freed port ${holder.port} \u2014 killed pid ${holder.pid} (${holder.command})`);
3032
+ for (const holder of foreign)
3033
+ Logger6.rawLog(`port ${holder.port} is held by pid ${holder.pid} and was left alone: ${holder.command}`, undefined, "error");
3034
+ if (killed.length === 0 && foreign.length === 0)
3035
+ workspace.log("dev ports are free; nothing to kill");
3036
+ }
3037
+ async#startMany(apps, { open, dbup, write, mode, concurrency }) {
3038
+ const workspace = apps[0]?.workspace;
3039
+ if (!workspace)
3040
+ throw new Error("No app selected to start");
3041
+ const startedDatabase = dbup ? await this.#prepareSharedDatabase(apps) : false;
3042
+ const supervisor = new DevSupervisor({ apps, mode, concurrency, open, write });
3043
+ const view = mode === "tui" ? await (await import("./devTuiView-7jy8f30g.js")).createDevTuiView(supervisor) : new DevStreamView(apps.map((app) => app.name));
3044
+ await supervisor.run(view);
3045
+ if (startedDatabase)
3046
+ await this.#stopDatabase(workspace);
3047
+ }
3048
+ async#prepareSharedDatabase(apps) {
3049
+ const workspace = apps[0]?.workspace;
3050
+ if (!workspace)
3051
+ return false;
3052
+ const modes = new Set;
3053
+ for (const app of apps) {
3054
+ if (app.getEnv() !== "local")
3055
+ continue;
3056
+ const akanConfig = await app.getConfig();
3057
+ const mode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
3058
+ await this.syncDatabaseModeDependencies(app, akanConfig, mode);
3059
+ if (mode !== "single")
3060
+ modes.add(mode);
3061
+ }
3062
+ let started = false;
3063
+ for (const mode of modes)
3064
+ if (!await this.dbup(workspace, mode))
3065
+ started = true;
3066
+ return started;
3067
+ }
2434
3068
  async buildIos(app, { write = true, target, env = "debug", regenerate = false } = {}) {
2435
3069
  await app.scanSync({ write });
2436
3070
  await this.applicationRunner.buildIos(app, { target, env, regenerate });
@@ -2528,7 +3162,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2528
3162
  let stopping = false;
2529
3163
  process.on("SIGINT", () => {
2530
3164
  if (stopping) {
2531
- Logger5.rawLog("Abandoning the local database teardown; containers are left running.", undefined, "warn");
3165
+ Logger6.rawLog("Abandoning the local database teardown; containers are left running.", undefined, "error");
2532
3166
  process.exit(130);
2533
3167
  }
2534
3168
  stopping = true;
@@ -2536,24 +3170,21 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2536
3170
  });
2537
3171
  }
2538
3172
  async#stopDatabase(workspace) {
2539
- const timeout = new Promise((resolve) => setTimeout(() => resolve("timeout"), ApplicationScript.dbShutdownTimeoutMs));
2540
- const result = await Promise.race([this.dbdown(workspace).catch(() => {
2541
- return;
2542
- }), timeout]);
2543
- if (result !== "timeout")
2544
- return;
2545
- Logger5.rawLog(`Local database did not stop within ${ApplicationScript.dbShutdownTimeoutMs}ms; run \`akan dbdown\` once Docker responds.`, undefined, "warn");
2546
- }
2547
- async testSys(sys) {
2548
- if (sys.type === "app")
2549
- await this.testApplication(sys);
2550
- else
2551
- await this.libraryScript.testLibrary(sys);
2552
- }
2553
- async testApplication(app) {
2554
- const spinner = app.spinning("Testing application...");
2555
- await this.applicationRunner.test(app);
2556
- spinner.succeed(`Application ${app.name} (apps/${app.name}) test is successful`);
3173
+ let timer = null;
3174
+ const timeout = new Promise((resolve) => {
3175
+ timer = setTimeout(() => resolve("timeout"), ApplicationScript.dbShutdownTimeoutMs);
3176
+ });
3177
+ try {
3178
+ const result = await Promise.race([this.dbdown(workspace).catch(() => {
3179
+ return;
3180
+ }), timeout]);
3181
+ if (result !== "timeout")
3182
+ return;
3183
+ Logger6.rawLog(`Local database did not stop within ${ApplicationScript.dbShutdownTimeoutMs}ms; run \`akan dbdown\` once Docker responds.`, undefined, "error");
3184
+ } finally {
3185
+ if (timer)
3186
+ clearTimeout(timer);
3187
+ }
2557
3188
  }
2558
3189
  }
2559
3190