@akanjs/cli 3.0.0-alpha.88 → 3.0.0-alpha.89

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 (58) hide show
  1. package/.build-stamp +1 -1
  2. package/{abstractCompactor-pwc6d6yk.js → abstractCompactor-cefxzggj.js} +2 -2
  3. package/{agent.command-180hh0ye.js → agent.command-r93wmq7m.js} +6 -6
  4. package/{application.command-v8dywdqh.js → application.command-5nqfqgdn.js} +9 -8
  5. package/{applicationBuildRunner-27wyznpv.js → applicationBuildRunner-b21m07fs.js} +4 -3
  6. package/{applicationReleasePackager-ktjt0erc.js → applicationReleasePackager-3wh58n2m.js} +2 -2
  7. package/buildBatch.proc.js +4 -4
  8. package/{capacitorApp-kwa67kzs.js → capacitorApp-gzcx6txb.js} +3 -3
  9. package/{cloud.command-12k4wy4x.js → cloud.command-g0pk0k60.js} +11 -11
  10. package/{context.command-dk2m2sjx.js → context.command-qd8kqrnh.js} +14 -14
  11. package/{dependencyScanner-c64m6fxp.js → dependencyScanner-shs82ynp.js} +2 -2
  12. package/devTui-2xf0meaq.js +552 -0
  13. package/devTuiView-7jy8f30g.js +9 -0
  14. package/{guideline.command-w6ddv13r.js → guideline.command-f38x8jhw.js} +4 -4
  15. package/guidelines/agentRule/agentRule.instruction.md +9 -1
  16. package/guidelines/conventions/conventions.instruction.md +30 -0
  17. package/guidelines/modelStore/modelStore.instruction.md +12 -0
  18. package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
  19. package/guidelines/runtimeRule/runtimeRule.instruction.md +37 -0
  20. package/incrementalBuilder.proc.js +8 -5
  21. package/{index-5y7qba1c.js → index-0emb9eat.js} +3 -3
  22. package/{index-cd5x18qh.js → index-3vvbv974.js} +4 -4
  23. package/{index-v0s24t8j.js → index-5xdqxbn9.js} +3 -3
  24. package/{index-k86zc21w.js → index-9bwhxrqf.js} +6 -6
  25. package/{index-ft1sfjz0.js → index-cezy7yb3.js} +66 -7
  26. package/{index-a6c5pc4p.js → index-d61rcfa2.js} +614 -62
  27. package/{index-gz0c482j.js → index-deekbtkj.js} +6 -6
  28. package/{index-saheq2bs.js → index-e82dyeqy.js} +1 -1
  29. package/{index-j9vnq8tr.js → index-h759vjfa.js} +3 -3
  30. package/{index-z3c9mmw4.js → index-jydtyzva.js} +2 -2
  31. package/{index-mfm35dhh.js → index-kcxmv48c.js} +1 -1
  32. package/{index-6c3ex9qw.js → index-kfnr6c19.js} +4 -4
  33. package/{index-1kzsn94d.js → index-mpmyqm1y.js} +1 -1
  34. package/{index-wet2ytnr.js → index-n85fer99.js} +2 -2
  35. package/{index-b2nt0w0k.js → index-pgsswppj.js} +1 -1
  36. package/{index-m9c7k0gm.js → index-pvb4vdwr.js} +1 -1
  37. package/{index-ayy5ecax.js → index-q0y2jq9j.js} +13 -1
  38. package/{index-k1n0h7dk.js → index-q8sn827n.js} +254 -126
  39. package/{index-dq6wq1tv.js → index-r50qen0z.js} +10 -10
  40. package/{index-5n0hf8r9.js → index-rntcdss1.js} +3 -3
  41. package/{index-a8h184wr.js → index-sjak5v5b.js} +6 -6
  42. package/{index-x23688ar.js → index-w8kmx1b6.js} +8 -4
  43. package/{index-qcracvc9.js → index-y74k5anj.js} +3 -3
  44. package/{index-xxe90kqy.js → index-zkvpaggp.js} +2 -2
  45. package/index.js +20 -20
  46. package/{library.command-wpedct6c.js → library.command-em103wtn.js} +4 -4
  47. package/{localRegistry.command-3cew8f75.js → localRegistry.command-gz8apn4y.js} +10 -10
  48. package/{module.command-8n4kct3e.js → module.command-451pztkc.js} +7 -7
  49. package/{package.command-fcbd0b1d.js → package.command-rfhyf38t.js} +5 -5
  50. package/package.json +2 -2
  51. package/{page.command-x4379grq.js → page.command-kt19da5g.js} +4 -4
  52. package/{primitive.command-8etmnqhq.js → primitive.command-pgy69ere.js} +8 -8
  53. package/{quality.command-hfx7z8yr.js → quality.command-gpe2vqb5.js} +3 -3
  54. package/{repair.command-j0fs5qkz.js → repair.command-2sbrz187.js} +6 -6
  55. package/{scalar.command-2emk89eh.js → scalar.command-jcf4e5sv.js} +6 -6
  56. package/{subspace.command-nr32nm35.js → subspace.command-kqzqtk0p.js} +6 -6
  57. package/{workflow.command-cvpmzwjg.js → workflow.command-m4z25a2p.js} +11 -11
  58. package/{workspace.command-rqnms515.js → workspace.command-dqgj9zbk.js} +22 -22
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-ft1sfjz0.js";
5
+ } from "./index-cezy7yb3.js";
6
6
  import {
7
7
  WorkspaceExecutor,
8
8
  AppExecutor,
9
9
  LibExecutor,
10
10
  PkgExecutor
11
- } from "./index-k1n0h7dk.js";
11
+ } from "./index-q8sn827n.js";
12
12
  import {
13
13
  LibraryScript
14
- } from "./index-xxe90kqy.js";
14
+ } from "./index-zkvpaggp.js";
15
15
  import {
16
16
  HmrWatcher,
17
17
  WatchRootResolver
@@ -19,11 +19,21 @@ import {
19
19
  import {
20
20
  SlicePlanner,
21
21
  formatSlicePlan
22
- } from "./index-m9c7k0gm.js";
22
+ } from "./index-pvb4vdwr.js";
23
23
  import {
24
24
  resolveMobileTargets
25
25
  } from "./index-47pj3aee.js";
26
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
+ }
36
+
27
37
  // pkgs/@akanjs/devkit/applicationBuildReporter.ts
28
38
  import { Logger } from "akanjs/common";
29
39
 
@@ -71,7 +81,7 @@ Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
71
81
 
72
82
  // pkgs/@akanjs/cli/application/application.script.ts
73
83
  import { confirm } from "@inquirer/prompts";
74
- import { Logger as Logger5 } from "akanjs/common";
84
+ import { Logger as Logger6 } from "akanjs/common";
75
85
 
76
86
  // pkgs/@akanjs/cli/application/application.runner.ts
77
87
  import path5 from "path";
@@ -239,6 +249,8 @@ class IncrementalBuilderHost {
239
249
  app;
240
250
  ready = false;
241
251
  #onMessage;
252
+ #stdio;
253
+ #onOutput;
242
254
  #proc = null;
243
255
  #status = "stopped";
244
256
  #restartAttempts = 0;
@@ -249,11 +261,13 @@ class IncrementalBuilderHost {
249
261
  #manualStop = false;
250
262
  #inFlight = new Map;
251
263
  #startOptions = {};
252
- constructor({ app, entry, env, onMessage }) {
264
+ constructor({ app, entry, env, stdio = "inherit", onMessage, onOutput }) {
253
265
  this.app = app;
254
266
  this.entry = entry;
255
267
  this.env = env;
268
+ this.#stdio = stdio;
256
269
  this.#onMessage = onMessage;
270
+ this.#onOutput = onOutput ?? null;
257
271
  }
258
272
  get status() {
259
273
  return this.#status;
@@ -279,7 +293,7 @@ class IncrementalBuilderHost {
279
293
  proc = Bun.spawn(["bun", this.entry], {
280
294
  cwd: this.app.cwdPath,
281
295
  env: { ...this.env, AKAN_WATCH: "1", ...afterRecycle ? { AKAN_BUILDER_ANNOUNCE_BOOT: "1" } : {} },
282
- stdio: ["ignore", "inherit", "inherit"],
296
+ stdio: ["ignore", this.#stdio, this.#stdio],
283
297
  ipc: (msg) => {
284
298
  if (this.#proc !== proc)
285
299
  return;
@@ -327,8 +341,24 @@ class IncrementalBuilderHost {
327
341
  }
328
342
  });
329
343
  this.#proc = proc;
344
+ if (this.#stdio === "pipe" && this.#onOutput) {
345
+ this.#drain(proc.stdout, "stdout");
346
+ this.#drain(proc.stderr, "stderr");
347
+ }
330
348
  this.logger.verbose(`builder spawned pid=${proc.pid} entry=${this.entry}${isRestart ? " restart=1" : ""}`);
331
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
+ }
332
362
  #scheduleRestart() {
333
363
  if (this.#manualStop || this.#restartTimer)
334
364
  return;
@@ -424,7 +454,10 @@ class IncrementalBuilderHost {
424
454
  this.ready = false;
425
455
  this.#status = "stopped";
426
456
  }
427
- static async create(app, env, onMessage) {
457
+ static async create(app, env, onMessage, {
458
+ stdio = "inherit",
459
+ onOutput
460
+ } = {}) {
428
461
  const candidates = [
429
462
  path.join(app.workspace.workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts"),
430
463
  path.join(app.workspace.workspaceRoot, "node_modules/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts"),
@@ -433,7 +466,7 @@ class IncrementalBuilderHost {
433
466
  ];
434
467
  for (const c of candidates)
435
468
  if (await Bun.file(c).exists())
436
- return new IncrementalBuilderHost({ app, entry: c, env, onMessage });
469
+ return new IncrementalBuilderHost({ app, entry: c, env, stdio, onMessage, onOutput });
437
470
  throw new Error(`[cli] frontend builder entry not found; looked in: ${candidates.join(", ")}`);
438
471
  }
439
472
  }
@@ -502,6 +535,15 @@ var shouldRestartBuilderByDevPlan = (message) => message.devPlan?.actions.includ
502
535
  var shouldAbandonBackendRecovery = (attempts, maxAttempts = BACKEND_RECOVERY_MAX_ATTEMPTS) => attempts >= maxAttempts;
503
536
  var normalizeBackendReportedGeneration = (generation) => generation >= 0 ? generation : undefined;
504
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];
505
547
  var RESTART_ROLE_ORDER = ["server", "shared", "barrel", "config"];
506
548
  var generationValue = (generation) => generation ?? -1;
507
549
  var isLegacyBackendFallbackFile = (file, workspaceRoot) => {
@@ -826,8 +868,10 @@ var BUILDER_RECOVERY_MAX_DELAY_MS = 60000;
826
868
  class AkanAppHost {
827
869
  app;
828
870
  logger = new Logger3("AkanAppHost");
829
- withInk;
871
+ stdio;
830
872
  env;
873
+ #onDevEvent;
874
+ #lastDevState = null;
831
875
  #backend = null;
832
876
  #builder = null;
833
877
  #backendReady = false;
@@ -866,12 +910,23 @@ class AkanAppHost {
866
910
  #builderGapStamp = null;
867
911
  #pendingBuilderMessages = [];
868
912
  #builderRequests = new BuilderRequestRouter;
869
- constructor(app, { env, withInk = false }) {
913
+ constructor(app, {
914
+ env,
915
+ stdio = "inherit",
916
+ onDevEvent
917
+ }) {
870
918
  this.app = app;
871
919
  this.env = env;
872
- this.withInk = withInk;
920
+ this.stdio = stdio;
921
+ this.#onDevEvent = onDevEvent ?? null;
873
922
  this.#backendGraph = new BackendImportGraph(app, this.logger);
874
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
+ }
875
930
  async start() {
876
931
  if (this.#backend)
877
932
  await this.#stopBackend();
@@ -926,7 +981,7 @@ class AkanAppHost {
926
981
  this.#backendStderrTail = [];
927
982
  const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
928
983
  cwd: this.app.workspace.workspaceRoot,
929
- stdio: this.withInk ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
984
+ stdio: this.stdio === "pipe" ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "inherit"],
930
985
  env: this.env,
931
986
  ipc: (msg) => {
932
987
  if (!msg || typeof msg !== "object")
@@ -967,7 +1022,7 @@ class AkanAppHost {
967
1022
  });
968
1023
  this.#backend = backend;
969
1024
  this.logger.verbose(`backend spawned pid=${backend.pid}`);
970
- if (this.withInk) {
1025
+ if (this.stdio === "pipe") {
971
1026
  this.#forwardBackendStream(backend.stderr, "stderr");
972
1027
  this.#forwardBackendStream(backend.stdout, "stdout");
973
1028
  }
@@ -988,14 +1043,13 @@ class AkanAppHost {
988
1043
  try {
989
1044
  for await (const chunk of stream) {
990
1045
  const text = decoder.decode(chunk, { stream: true });
991
- if (!text.trim())
1046
+ if (!text)
992
1047
  continue;
993
1048
  if (kind === "stderr") {
994
1049
  this.#recordBackendStderr(text);
995
- this.logger.warn(`[backend] ${text.trimEnd()}`);
996
- } else {
997
- this.logger.verbose(`[backend] ${text.trimEnd()}`);
998
- }
1050
+ process.stderr.write(text);
1051
+ } else
1052
+ process.stdout.write(text);
999
1053
  }
1000
1054
  } catch {}
1001
1055
  }
@@ -1042,6 +1096,7 @@ class AkanAppHost {
1042
1096
  const prev = this.#backendLifecycleState;
1043
1097
  this.#backendLifecycleState = next;
1044
1098
  this.logger.verbose(`[backend-lifecycle] ${prev} -> ${next}${detail ? ` ${detail}` : ""}`);
1099
+ this.#emitDevEvent(devHostStateOf(next, this.#backendGaveUp), detail);
1045
1100
  }
1046
1101
  #sendToBackend(message) {
1047
1102
  if (!this.#backend || !this.#backendReady) {
@@ -1371,6 +1426,7 @@ ${this.#backendStderrTail.join(`
1371
1426
  return;
1372
1427
  }
1373
1428
  this.#suspended = true;
1429
+ this.#emitDevEvent("suspended", `idle ${Math.round(idleMs / 1000)}s`);
1374
1430
  this.#stopBuilder();
1375
1431
  this.#openBuilderGap("idle suspend");
1376
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`);
@@ -1435,6 +1491,7 @@ ${this.#backendStderrTail.join(`
1435
1491
  this.#wokeAtMono = performance.now();
1436
1492
  this.#flushPendingBuilderMessages();
1437
1493
  this.#armIdleSuspend();
1494
+ this.#emitDevEvent(devHostStateOf(this.#backendLifecycleState, this.#backendGaveUp));
1438
1495
  }
1439
1496
  }
1440
1497
  async#applyIdleWake(batch) {
@@ -1771,6 +1828,9 @@ ${this.#backendStderrTail.join(`
1771
1828
  for (let attempt = 1;attempt <= BUILDER_START_MAX_ATTEMPTS; attempt++) {
1772
1829
  this.#builder = await IncrementalBuilderHost.create(this.app, this.env, (msg) => {
1773
1830
  this.#enqueueBuilderMessage(msg);
1831
+ }, {
1832
+ stdio: this.stdio,
1833
+ onOutput: (kind, text) => void (kind === "stderr" ? process.stderr : process.stdout).write(text)
1774
1834
  });
1775
1835
  try {
1776
1836
  await this.#waitForBuilderReady(attempt, { announceBootState });
@@ -1904,22 +1964,10 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
1904
1964
  // pkgs/@akanjs/cli/application/application.runner.ts
1905
1965
  import { Logger as Logger4 } from "akanjs/common";
1906
1966
  import ora from "ora";
1907
-
1908
- // pkgs/@akanjs/cli/openBrowser.ts
1909
- import { spawn } from "child_process";
1910
- function openBrowser(url) {
1911
- const command = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
1912
- const child = spawn(command[0], command.slice(1), { detached: true, stdio: "ignore" });
1913
- child.on("error", () => {});
1914
- child.unref();
1915
- return Promise.resolve();
1916
- }
1917
-
1918
- // pkgs/@akanjs/cli/application/application.runner.ts
1919
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-27wyznpv.js")).ApplicationBuildRunner;
1920
- var loadReleasePackager = async () => (await import("./applicationReleasePackager-ktjt0erc.js")).ApplicationReleasePackager;
1921
- var loadCapacitorApp = async () => (await import("./capacitorApp-kwa67kzs.js")).CapacitorApp;
1922
- var loadAbstractCompactor = async () => (await import("./abstractCompactor-pwc6d6yk.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;
1923
1971
  var loadPrompts = async () => await import("@inquirer/prompts");
1924
1972
 
1925
1973
  class ApplicationRunner extends runner("application") {
@@ -2086,9 +2134,14 @@ try {
2086
2134
  } = {}) {
2087
2135
  return new (await loadBuildRunner())(app, { fast, reporter }).build({ spinner });
2088
2136
  }
2089
- async start(app, { open = false, onStart, withInk = false } = {}) {
2137
+ async start(app, {
2138
+ open = false,
2139
+ onStart,
2140
+ onDevEvent,
2141
+ stdio = "inherit"
2142
+ } = {}) {
2090
2143
  const { env } = await app.prepareCommand("start");
2091
- const appHost = await new AkanAppHost(app, { env, withInk }).start();
2144
+ const appHost = await new AkanAppHost(app, { env, stdio, onDevEvent }).start();
2092
2145
  onStart?.();
2093
2146
  if (open)
2094
2147
  setTimeout(() => openBrowser(`http://localhost:${env.AKAN_PUBLIC_CLIENT_PORT ?? env.PORT ?? "8282"}`), 3000);
@@ -2320,6 +2373,444 @@ ${detail}`
2320
2373
  }
2321
2374
  }
2322
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
+
2323
2814
  // pkgs/@akanjs/cli/application/application.script.ts
2324
2815
  class ApplicationScript extends script("application", [ApplicationRunner, LibraryScript]) {
2325
2816
  static dbShutdownTimeoutMs = 20000;
@@ -2403,7 +2894,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2403
2894
  spinner.succeed(`Application created in apps/${app.name}`);
2404
2895
  await app.scanSync();
2405
2896
  if (start)
2406
- await this.start(app, { open: true });
2897
+ await this.startOne(app, { open: true });
2407
2898
  }
2408
2899
  async removeApplication(app) {
2409
2900
  const spinner = app.spinning("Removing application...");
@@ -2414,7 +2905,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2414
2905
  const spinner = app.spinning("Planning distributable slice...");
2415
2906
  const plan = await this.applicationRunner.planSlice(app);
2416
2907
  spinner.succeed(`Slice planned (${plan.libs.length} libs, ${plan.warnings.length} warnings)`);
2417
- Logger5.rawLog(format === "json" ? JSON.stringify(plan, null, 2) : formatSlicePlan(plan));
2908
+ Logger6.rawLog(format === "json" ? JSON.stringify(plan, null, 2) : formatSlicePlan(plan));
2418
2909
  }
2419
2910
  async sync(sys) {
2420
2911
  if (sys.type === "app")
@@ -2425,13 +2916,13 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2425
2916
  async compact(sys, { module = null, minLines, interactive = false } = {}) {
2426
2917
  const { scanned, reports } = await this.applicationRunner.compact(sys, { module, minLines, interactive });
2427
2918
  if (!reports.length) {
2428
- 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)`);
2429
2920
  return;
2430
2921
  }
2431
2922
  for (const report of reports)
2432
- Logger5.rawLog(`${report.status}: ${report.path} (${report.beforeLines} -> ${report.afterLines} lines)`);
2923
+ Logger6.rawLog(`${report.status}: ${report.path} (${report.beforeLines} -> ${report.afterLines} lines)`);
2433
2924
  const compactedNum = reports.filter((report) => report.status === "compacted").length;
2434
- Logger5.rawLog(`Compacted ${compactedNum}/${reports.length} abstract files in ${sys.name}`);
2925
+ Logger6.rawLog(`Compacted ${compactedNum}/${reports.length} abstract files in ${sys.name}`);
2435
2926
  }
2436
2927
  async script(app, filename) {
2437
2928
  const scriptFilename = filename ?? await this.applicationRunner.getScriptFilename(app);
@@ -2448,18 +2939,18 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2448
2939
  async build(app, { write = true, fast = false, quiet = false } = {}) {
2449
2940
  await app.scanSync({ write });
2450
2941
  if (!quiet)
2451
- Logger5.rawLog(`Creating an optimized production build for ${app.name}...`);
2942
+ Logger6.rawLog(`Creating an optimized production build for ${app.name}...`);
2452
2943
  try {
2453
2944
  const result = await this.applicationRunner.build(app, {
2454
2945
  fast,
2455
2946
  spinner: !quiet
2456
2947
  });
2457
- Logger5.rawLog(`${app.name} built in dist/apps/${app.name}`);
2948
+ Logger6.rawLog(`${app.name} built in dist/apps/${app.name}`);
2458
2949
  if (!quiet)
2459
2950
  ApplicationBuildReporter.printSummary(result);
2460
2951
  } catch (error) {
2461
- Logger5.rawLog(`${app.name} build failed in dist/apps/${app.name}`, undefined, "error");
2462
- 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");
2463
2954
  throw error;
2464
2955
  }
2465
2956
  }
@@ -2471,7 +2962,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2471
2962
  spinner.succeed(`${app.name} typechecked`);
2472
2963
  } catch (error) {
2473
2964
  spinner.fail(`${app.name} typecheck failed`);
2474
- Logger5.rawLog(ApplicationBuildReporter.formatError(error), undefined, "error");
2965
+ Logger6.rawLog(ApplicationBuildReporter.formatError(error), undefined, "error");
2475
2966
  throw error;
2476
2967
  }
2477
2968
  }
@@ -2496,12 +2987,20 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2496
2987
  }
2497
2988
  await this.applicationRunner.test(exec);
2498
2989
  }
2499
- async start(app, {
2500
- open = false,
2501
- dbup = true,
2502
- withInk = false,
2503
- write = true
2504
- } = {}) {
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 } = {}) {
2505
3004
  await app.scanSync({ write });
2506
3005
  const akanConfig = await app.getConfig();
2507
3006
  const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
@@ -2517,10 +3016,55 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2517
3016
  onStart: () => {
2518
3017
  spinner.succeed(`${app.name} prepared, ready to start`);
2519
3018
  },
2520
- withInk
3019
+ onDevEvent,
3020
+ stdio
2521
3021
  });
2522
3022
  return akanAppHost;
2523
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
+ }
2524
3068
  async buildIos(app, { write = true, target, env = "debug", regenerate = false } = {}) {
2525
3069
  await app.scanSync({ write });
2526
3070
  await this.applicationRunner.buildIos(app, { target, env, regenerate });
@@ -2618,7 +3162,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2618
3162
  let stopping = false;
2619
3163
  process.on("SIGINT", () => {
2620
3164
  if (stopping) {
2621
- Logger5.rawLog("Abandoning the local database teardown; containers are left running.", undefined, "error");
3165
+ Logger6.rawLog("Abandoning the local database teardown; containers are left running.", undefined, "error");
2622
3166
  process.exit(130);
2623
3167
  }
2624
3168
  stopping = true;
@@ -2626,13 +3170,21 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
2626
3170
  });
2627
3171
  }
2628
3172
  async#stopDatabase(workspace) {
2629
- const timeout = new Promise((resolve) => setTimeout(() => resolve("timeout"), ApplicationScript.dbShutdownTimeoutMs));
2630
- const result = await Promise.race([this.dbdown(workspace).catch(() => {
2631
- return;
2632
- }), timeout]);
2633
- if (result !== "timeout")
2634
- return;
2635
- Logger5.rawLog(`Local database did not stop within ${ApplicationScript.dbShutdownTimeoutMs}ms; run \`akan dbdown\` once Docker responds.`, undefined, "error");
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
+ }
2636
3188
  }
2637
3189
  }
2638
3190