@akanjs/cli 3.0.0-beta.2 → 3.0.0-beta.21

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 (190) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-q2m47rqp.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-pdqhc3sn.js +1457 -0
  6. package/README.md +3 -0
  7. package/TunnelShare-scf4e4pd.js +9 -0
  8. package/{agent.command-d2gfxfck.js → agent.command-ttn09rx3.js} +8 -9
  9. package/akanCode.js +102 -0
  10. package/application.command-7gweenv7.js +179 -0
  11. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-38exmejd.js} +63 -64
  12. package/buildBatch.proc.js +26 -25
  13. package/cloud.command-bhztw7c2.js +95 -0
  14. package/cloud.script-aabyhgba.js +20 -0
  15. package/code.command-6s8c0k91.js +154 -0
  16. package/commandManifest.json +1 -1
  17. package/consoleToStderr-h5bryj94.js +7 -0
  18. package/context.command-phmgywt2.js +48 -0
  19. package/context.runner-yvyw5g2d.js +21 -0
  20. package/{dependencyScanner-shs82ynp.js → dependencyScanner-ye2e6pdz.js} +2 -2
  21. package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
  22. package/fileSys-5ngz93ga.js +9 -0
  23. package/guideline.command-7jk47fgq.js +42 -0
  24. package/guidelines/agentRule/agentRule.instruction.md +209 -28
  25. package/guidelines/conventions/conventions.instruction.md +96 -29
  26. package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
  27. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  28. package/guidelines/queryRule/queryRule.instruction.md +17 -7
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +77 -3
  30. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  31. package/guidelines/transportRule/transportRule.instruction.md +51 -0
  32. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  33. package/incrementalBuilder.proc.js +31 -48
  34. package/{index-h7yw2z9n.js → index-0bq14vr1.js} +8 -14
  35. package/{index-259dsnz3.js → index-1db2cw0y.js} +5 -8
  36. package/index-22d57v4c.js +266 -0
  37. package/index-3hx1z96c.js +87 -0
  38. package/{index-kcxmv48c.js → index-3t7g9e4r.js} +45 -79
  39. package/{index-ne2qy0nk.js → index-4dn1p115.js} +12 -12
  40. package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
  41. package/index-7cqjvraf.js +69 -0
  42. package/index-7md2g9vw.js +6 -0
  43. package/{index-zry5nz81.js → index-8kb5p54p.js} +51 -30
  44. package/{index-ktb5njgy.js → index-9629w7ns.js} +49 -99
  45. package/index-9dp031by.js +12 -0
  46. package/{index-a64esmtb.js → index-aadpbnt2.js} +172 -308
  47. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  48. package/index-c6eggbee.js +123 -0
  49. package/index-d0arnrm8.js +700 -0
  50. package/index-d2242ksy.js +99 -0
  51. package/{index-600zhnve.js → index-dg97qp24.js} +70 -200
  52. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  53. package/{index-w8kmx1b6.js → index-fgq75haa.js} +198 -290
  54. package/{index-2tv3fdx4.js → index-ga6hygxr.js} +944 -1333
  55. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  56. package/index-hr5n7t4x.js +465 -0
  57. package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
  58. package/{index-7737gxcp.js → index-m0ms3n1c.js} +107 -241
  59. package/{index-ws5e0j51.js → index-mgfx3mfs.js} +43 -26
  60. package/index-p6pbz9n5.js +25 -0
  61. package/index-pp54p5zk.js +53 -0
  62. package/{index-j9yhg404.js → index-q44jbw48.js} +4 -7
  63. package/{index-mak3cd28.js → index-rmcw8yc8.js} +137 -255
  64. package/{index-pnqbkf5p.js → index-rnt66vym.js} +6 -2
  65. package/index-smme4r47.js +49 -0
  66. package/index-tgjy8xf3.js +187 -0
  67. package/index-v5eayqqw.js +2729 -0
  68. package/index-wa5jbxj1.js +760 -0
  69. package/{index-4edzf1gr.js → index-wp43xrjv.js} +77 -150
  70. package/{index-fz5q3gd7.js → index-ya5qbmyg.js} +10 -17
  71. package/index-yjkte9aj.js +1119 -0
  72. package/{index-gyd220p2.js → index-yy4s1j72.js} +554 -577
  73. package/{index-v0qh106m.js → index-z50k041j.js} +580 -862
  74. package/index.js +23 -22
  75. package/{library.command-nbsky672.js → library.command-ccb1cp92.js} +4 -5
  76. package/{localRegistry.command-yeq57wjr.js → localRegistry.command-ajb9n87j.js} +23 -42
  77. package/{module.command-29vqzrh8.js → module.command-5k6h407d.js} +8 -11
  78. package/{package.command-axv0abmn.js → package.command-e6ntvpez.js} +6 -7
  79. package/package.json +14 -6
  80. package/{page.command-vs9s3zrh.js → page.command-v4f90wb8.js} +4 -5
  81. package/{primitive.command-cbxmqbtc.js → primitive.command-g6km57q5.js} +9 -22
  82. package/prompter-ptaa3nk0.js +8 -0
  83. package/{quality.command-gmbs7nws.js → quality.command-de3p36pz.js} +205 -377
  84. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  85. package/{repair.command-bqsjptsj.js → repair.command-d3x1javq.js} +11 -35
  86. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  87. package/{scalar.command-6k1vhe9s.js → scalar.command-7ezrac2b.js} +7 -10
  88. package/skills/akan-code/SKILL.md +130 -0
  89. package/skills/akan-debug/SKILL.md +42 -0
  90. package/skills/akan-deps/SKILL.md +50 -0
  91. package/skills/akan-err/SKILL.md +39 -0
  92. package/skills/akan-module/SKILL.md +45 -0
  93. package/skills/akan-scaffold/SKILL.md +76 -0
  94. package/skills/akan-scalar/SKILL.md +52 -0
  95. package/skills/akan-service/SKILL.md +34 -0
  96. package/skills/akan-store/SKILL.md +67 -0
  97. package/skills/akan-ui/SKILL.md +63 -0
  98. package/skills/akan-validate/SKILL.md +44 -0
  99. package/{subspace.command-fwjfcts7.js → subspace.command-ps71ynp6.js} +138 -67
  100. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  101. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  102. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  103. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  104. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  105. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  106. package/templates/appSample/lib/task/task.signal.ts +1 -1
  107. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  108. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  109. package/templates/client.ts +7 -30
  110. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  111. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  112. package/templates/crudPages/new/page.tsx +2 -1
  113. package/templates/crudPages/page.tsx +2 -1
  114. package/templates/crudSinglePage/page.tsx +2 -1
  115. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  116. package/templates/lib/__lib/lib.document.ts +0 -2
  117. package/templates/lib/sig.ts +8 -30
  118. package/templates/localDev/docker-compose.yaml.template +3 -14
  119. package/templates/module/__model__.signal.ts +12 -4
  120. package/templates/pkgRoot/tsconfig.json.template +2 -7
  121. package/templates/server.ts +0 -2
  122. package/templates/service/__model__.abstract.md.template +8 -0
  123. package/templates/workspaceRoot/.gitignore.template +0 -1
  124. package/tunnel.command-hsrv2mny.js +129 -0
  125. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  126. package/typecheck.proc.js +1 -1
  127. package/workflow.command-z2p32nmf.js +79 -0
  128. package/{workspace.command-8s050m0h.js → workspace.command-tmdqkrh0.js} +77 -109
  129. package/abstractCompactor-mjh5ppsq.js +0 -101
  130. package/application.command-pj2m7584.js +0 -204
  131. package/applicationReleasePackager-4yasxwv9.js +0 -244
  132. package/capacitorApp-36rbr858.js +0 -12
  133. package/cloud.command-jshwdq22.js +0 -93
  134. package/context.command-ez8a0zyz.js +0 -80
  135. package/devTuiView-c08ccbat.js +0 -9
  136. package/getDirname-dr3jz1xg.js +0 -7
  137. package/guideline.command-xmqwx8zm.js +0 -359
  138. package/guidelines/agentRule/agentRule.generate.json +0 -31
  139. package/guidelines/componentRule/componentRule.generate.json +0 -31
  140. package/guidelines/conventions/conventions.generate.json +0 -25
  141. package/guidelines/cssRule/cssRule.generate.json +0 -31
  142. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  143. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  144. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  145. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  146. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  147. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  148. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  149. package/guidelines/framework/framework.generate.json +0 -32
  150. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  151. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  152. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  153. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  154. package/guidelines/modelService/modelService.generate.json +0 -31
  155. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  156. package/guidelines/modelStore/modelStore.generate.json +0 -31
  157. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  158. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  159. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  160. package/guidelines/modelView/modelView.generate.json +0 -31
  161. package/guidelines/modelZone/modelZone.generate.json +0 -31
  162. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  163. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  164. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  165. package/guidelines/queryRule/queryRule.generate.json +0 -31
  166. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  167. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  168. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  169. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  170. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  171. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  172. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  173. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  174. package/guidelines/transportRule/transportRule.generate.json +0 -31
  175. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  176. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  177. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  178. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  179. package/index-2hq52jq3.js +0 -465
  180. package/index-47pj3aee.js +0 -76
  181. package/index-6wwzs1bc.js +0 -7
  182. package/index-8pm30m73.js +0 -841
  183. package/index-kn1h7p1x.js +0 -560
  184. package/index-meh558jy.js +0 -65
  185. package/index-rwg2aa4f.js +0 -229
  186. package/index-wt7qrtd4.js +0 -1516
  187. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  188. package/templates/module/__model__.abstract.md +0 -28
  189. package/templates/service/__model__.abstract.md +0 -28
  190. package/workflow.command-nwpcdc8p.js +0 -107
@@ -1,178 +1,75 @@
1
1
  // @bun
2
+ import {
3
+ ApplicationBuildReporter,
4
+ BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS,
5
+ SOURCE_EXTS,
6
+ shouldRestartBackendByDevPlan,
7
+ shouldRestartBuilderByDevPlan,
8
+ shouldAbandonBackendRecovery,
9
+ normalizeBackendReportedGeneration,
10
+ shouldRestartDevHostByDevPlan,
11
+ devHostStateOf,
12
+ isLegacyBackendFallbackFile,
13
+ shouldMarkBuildPhaseRecovered,
14
+ createBackendBuildStatus,
15
+ backendRestartReasonFromMessage,
16
+ mergeBackendRestartReasons,
17
+ shouldReplaceLastGoodMessage,
18
+ shouldQueueBuildStatusReplay,
19
+ hasBuildFailureForGeneration,
20
+ decideBuilderRssRecycle,
21
+ decideBuilderRssSettle,
22
+ decideIdleSuspend,
23
+ resolveIdleSuspendMs,
24
+ hasAnyBuildFailure,
25
+ shouldRefreshConfigOnIdleWake,
26
+ shouldHoldForReturningBuilder,
27
+ shouldWarnBuilderRssCeilingTight,
28
+ isRssCeilingUnreachable,
29
+ shouldRelayRecycledFrontendState,
30
+ mergeInvalidateMessages,
31
+ buildStatusReplaySequence,
32
+ filesChangedSince,
33
+ HmrWatcher,
34
+ WatchRootResolver
35
+ } from "./index-khpfpcsd.js";
2
36
  import {
3
37
  runner,
4
38
  script
5
- } from "./index-mak3cd28.js";
39
+ } from "./index-rmcw8yc8.js";
6
40
  import {
7
41
  WorkspaceExecutor,
8
42
  AppExecutor,
9
43
  LibExecutor,
10
44
  PkgExecutor
11
- } from "./index-2tv3fdx4.js";
45
+ } from "./index-ga6hygxr.js";
12
46
  import {
13
47
  LibraryScript
14
- } from "./index-259dsnz3.js";
48
+ } from "./index-1db2cw0y.js";
15
49
  import {
16
- HmrWatcher,
17
- WatchRootResolver
18
- } from "./index-hwkygn9s.js";
50
+ resolveMobileTargets,
51
+ NativeApp
52
+ } from "./index-hr5n7t4x.js";
53
+ import {
54
+ stripAnsi
55
+ } from "./index-7md2g9vw.js";
19
56
  import {
20
57
  SlicePlanner,
21
58
  formatSlicePlan
22
- } from "./index-j9yhg404.js";
59
+ } from "./index-q44jbw48.js";
23
60
  import {
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
- }
36
-
37
- // pkgs/@akanjs/cli/application/devLogBuffer.ts
38
- var ansi = new RegExp(`${String.fromCharCode(27)}(?:[@-Z\\\\-_]|\\[[\\s\\S]*?[@-~])`, "g");
39
- var stripAnsi = (text) => text.replace(ansi, "");
40
- var plainTextOf = (lines, { withApp = false } = {}) => {
41
- const width = withApp ? lines.reduce((max, line) => Math.max(max, line.app.length), 0) : 0;
42
- return lines.map((line) => `${withApp ? `${line.app.padEnd(width)} \u2502 ` : ""}${stripAnsi(line.text)}`).join(`
43
- `);
44
- };
45
- var HOST_SOURCE = "host";
46
- var childPrefix = /^\[child:(\d+) ([a-z-]+)\] \[(?:stdout|stderr)\] /;
47
- var sourceOf = (text) => {
48
- const match = childPrefix.exec(text);
49
- return match ? `#${match[1]} ${match[2]}` : HOST_SOURCE;
50
- };
51
-
52
- class DevLogBuffer {
53
- static defaultLimit = 5000;
54
- #limit;
55
- #lines = [];
56
- #partial = new Map;
57
- #sources = new Map;
58
- #seq = 0;
59
- constructor({ limit = DevLogBuffer.defaultLimit } = {}) {
60
- this.#limit = Math.max(1, limit);
61
- }
62
- get size() {
63
- return this.#lines.length;
64
- }
65
- sourcesOf(app) {
66
- return [...this.#sources.get(app) ?? []];
67
- }
68
- push(app, kind, chunk) {
69
- const key = `${app}:${kind}`;
70
- const parts = `${this.#partial.get(key) ?? ""}${chunk}`.split(`
71
- `);
72
- this.#partial.set(key, parts.pop() ?? "");
73
- return this.#append(app, kind, parts);
74
- }
75
- flushPartials() {
76
- const flushed = [];
77
- for (const [key, remainder] of [...this.#partial]) {
78
- this.#partial.set(key, "");
79
- if (!remainder)
80
- continue;
81
- const split = key.lastIndexOf(":");
82
- const app = key.slice(0, split);
83
- const kind = key.slice(split + 1) === "stderr" ? "stderr" : "stdout";
84
- flushed.push(...this.#append(app, kind, [remainder]));
85
- }
86
- return flushed;
87
- }
88
- select({ app = null, source = null, grep, errorsOnly = false } = {}) {
89
- const needle = grep?.trim().toLowerCase();
90
- return this.#lines.filter((line) => {
91
- if (app !== null && line.app !== app)
92
- return false;
93
- if (source !== null && line.source !== source)
94
- return false;
95
- if (errorsOnly && line.kind !== "stderr")
96
- return false;
97
- if (needle && !stripAnsi(line.text).toLowerCase().includes(needle))
98
- return false;
99
- return true;
100
- });
101
- }
102
- clear(app = null) {
103
- const kept = app === null ? [] : this.#lines.filter((line) => line.app !== app);
104
- this.#lines.length = 0;
105
- this.#lines.push(...kept);
106
- }
107
- #append(app, kind, texts) {
108
- const known = this.#sources.get(app) ?? new Set;
109
- this.#sources.set(app, known);
110
- const added = texts.map((text) => {
111
- const source = sourceOf(text);
112
- known.add(source);
113
- return { seq: ++this.#seq, app, source, kind, text };
114
- });
115
- this.#lines.push(...added);
116
- if (this.#lines.length > this.#limit)
117
- this.#lines.splice(0, this.#lines.length - this.#limit);
118
- return added;
119
- }
120
- }
121
-
122
- // pkgs/@akanjs/devkit/applicationBuildReporter.ts
123
- import { Logger } from "akanjs/common";
124
-
125
- class ApplicationBuildReporter {
126
- static create() {
127
- return {
128
- phaseDone: (phase) => Logger.rawLog(ApplicationBuildReporter.formatPhaseLine(phase))
129
- };
130
- }
131
- static printSummary(result) {
132
- Logger.rawLog("");
133
- Logger.rawLog(`Route artifacts: ${result.artifactDir}`);
134
- Logger.rawLog(`Server output: ${result.outputDir}`);
135
- Logger.rawLog(`Done in ${ApplicationBuildReporter.formatDuration(result.durationMs)}`);
136
- }
137
- static formatError(error) {
138
- if (error instanceof AggregateError) {
139
- const nestedMessages = error.errors.map((nestedError, index) => ApplicationBuildReporter.formatError(nestedError).trim() || `Unknown error ${index + 1}`).map((message) => message.replace(/^/gm, " ")).join(`
140
- `);
141
- return nestedMessages ? `${error.message}
142
- ${nestedMessages}` : error.message;
143
- }
144
- if (error instanceof Error) {
145
- const causeMessage = error.cause ? `
146
- Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
147
- return `${error.message}${causeMessage}`;
148
- }
149
- if (typeof error === "object" && error !== null && "message" in error)
150
- return String(error.message);
151
- return String(error);
152
- }
153
- static formatDuration(ms) {
154
- if (ms < 1000)
155
- return `${ms}ms`;
156
- if (ms < 60000)
157
- return `${Math.round(ms / 100) / 10}s`;
158
- const seconds = Math.floor(ms / 1000);
159
- return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
160
- }
161
- static formatPhaseLine(phase) {
162
- const summary = phase.summary ? `: ${phase.summary}` : "";
163
- return `\u2713 ${phase.label}${summary} (${ApplicationBuildReporter.formatDuration(phase.durationMs)})`;
164
- }
165
- }
61
+ openBrowser
62
+ } from "./index-9dp031by.js";
166
63
 
167
64
  // pkgs/@akanjs/cli/application/application.script.ts
168
65
  import { confirm } from "@inquirer/prompts";
169
66
  import { Logger as Logger6 } from "akanjs/common";
170
67
 
171
68
  // pkgs/@akanjs/cli/application/application.runner.ts
172
- import path5 from "path";
69
+ import path4 from "path";
173
70
 
174
71
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
175
- import { Logger as Logger3 } from "akanjs/common";
72
+ import { Logger as Logger2 } from "akanjs/common";
176
73
 
177
74
  // pkgs/@akanjs/devkit/sshTunnel.ts
178
75
  import { Client } from "ssh2";
@@ -261,7 +158,7 @@ var createSshTunnel = async (options) => {
261
158
  for (const socket of sockets)
262
159
  closeSocket(socket, error);
263
160
  closeQuietly(() => client.end());
264
- closeBunServer(server);
161
+ server.stop();
265
162
  };
266
163
  client.once("error", (error) => closeTunnel(error));
267
164
  client.once("close", () => closeTunnel());
@@ -281,22 +178,10 @@ var closeBunSocket = (socket, error) => {
281
178
  socket.data.stream?.end();
282
179
  closeQuietly(() => socket.end());
283
180
  };
284
- var closeBunServer = (server) => {
285
- server.stop();
286
- };
287
181
 
288
182
  // pkgs/@akanjs/devkit/createTunnel.ts
289
- var getSshTunnelOptions = (app, environment) => {
290
- const { serveDomain, repoName } = WorkspaceExecutor.getBaseDevEnv();
291
- return {
292
- host: `${app.name}-${environment}.${serveDomain}`,
293
- port: process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767,
294
- username: process.env.SSH_TUNNEL_USERNAME ?? "root",
295
- password: process.env.SSH_TUNNEL_PASSWORD ?? repoName
296
- };
297
- };
298
183
  var createTunnel = async (service, { app, environment, port = service === "postgres" ? 5432 : 6379 }) => {
299
- const sshOptions = getSshTunnelOptions(app, environment);
184
+ const { serveDomain, repoName } = WorkspaceExecutor.getBaseDevEnv();
300
185
  await createSshTunnel({
301
186
  localHost: "0.0.0.0",
302
187
  localPort: port,
@@ -304,13 +189,18 @@ var createTunnel = async (service, { app, environment, port = service === "postg
304
189
  srcPort: port,
305
190
  dstHost: `${service}-0.${service}-svc.${app.name}-${environment}.svc.cluster.local`,
306
191
  dstPort: service === "postgres" ? 5432 : 6379,
307
- sshOptions
192
+ sshOptions: {
193
+ host: `${app.name}-${environment}.${serveDomain}`,
194
+ port: process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767,
195
+ username: process.env.SSH_TUNNEL_USERNAME ?? "root",
196
+ password: process.env.SSH_TUNNEL_PASSWORD ?? repoName
197
+ }
308
198
  });
309
199
  return `localhost:${port}`;
310
200
  };
311
201
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.host.ts
312
202
  import path from "path";
313
- import { Logger as Logger2 } from "akanjs/common";
203
+ import { Logger } from "akanjs/common";
314
204
  import { MemoryLimit } from "akanjs/server/memoryLimit";
315
205
  var builderMsgTypeSet = new Set([
316
206
  "build-route-res",
@@ -328,7 +218,7 @@ class IncrementalBuilderHost {
328
218
  static #restartMaxDelayMs = 30000;
329
219
  static #recycleDrainTimeoutMs = 30000;
330
220
  static #devMaxRssBytes = 1200 * 1024 * 1024;
331
- logger = new Logger2("IncrementalBuilderHost");
221
+ logger = new Logger("IncrementalBuilderHost");
332
222
  entry;
333
223
  env;
334
224
  app;
@@ -518,12 +408,8 @@ class IncrementalBuilderHost {
518
408
  const lost = [...this.#inFlight];
519
409
  this.#inFlight.clear();
520
410
  this.logger.warn(`failing ${lost.length} unanswered builder request(s): ${reason}`);
521
- for (const [id, type] of lost) {
522
- if (type === "build-route")
523
- this.#onMessage({ type: "build-route-res", id, ok: false, error: reason });
524
- else
525
- this.#onMessage({ type: "build-csr-res", id, ok: false, error: reason });
526
- }
411
+ for (const [id, type] of lost)
412
+ this.#onMessage({ type: `${type}-res`, id, ok: false, error: reason });
527
413
  }
528
414
  stop() {
529
415
  this.#manualStop = true;
@@ -592,211 +478,7 @@ class BuilderRequestRouter {
592
478
 
593
479
  // pkgs/@akanjs/devkit/akanApp/BackendImportGraph.ts
594
480
  import { stat } from "fs/promises";
595
- import path3 from "path";
596
-
597
- // pkgs/@akanjs/devkit/akanApp/devHostPolicy.ts
598
481
  import path2 from "path";
599
- var BACKEND_RECOVERY_MAX_ATTEMPTS = 5;
600
- var HELD_BUILDER_REQUEST_LIMIT = 64;
601
- var BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS = 30000;
602
- var BUILDER_TIGHT_RSS_REPORT_LIMIT = 2;
603
- var BUILDER_RSS_HARD_MULTIPLE = 1.5;
604
- var DEV_IDLE_SUSPEND_MS = 300000;
605
- var DEV_IDLE_MIN_UPTIME_MS = 30000;
606
- var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
607
- var SERVER_SUFFIXES = [".service.ts", ".document.ts"];
608
- var SHARED_SUFFIXES = [".constant.ts", ".dictionary.ts", ".signal.ts"];
609
- var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
610
- var shouldRestartBackendByDevPlan = (message) => {
611
- if (!message.devPlan)
612
- return null;
613
- if (message.devPlan.actions.includes("report-error"))
614
- return false;
615
- if (message.devPlan.actions.includes("restart-builder"))
616
- return false;
617
- return message.devPlan.actions.includes("restart-backend");
618
- };
619
- var shouldRestartBuilderByDevPlan = (message) => message.devPlan?.actions.includes("restart-builder") ?? false;
620
- var shouldAbandonBackendRecovery = (attempts, maxAttempts = BACKEND_RECOVERY_MAX_ATTEMPTS) => attempts >= maxAttempts;
621
- var normalizeBackendReportedGeneration = (generation) => generation >= 0 ? generation : undefined;
622
- var shouldRestartDevHostByDevPlan = (message) => message.devPlan?.actions.includes("restart-dev-host") ?? message.kinds.includes("config");
623
- var devHostStateByBackendState = {
624
- starting: "starting",
625
- ready: "ready",
626
- "restart-pending": "restarting",
627
- recovering: "recovering",
628
- stopping: "stopped",
629
- stopped: "stopped"
630
- };
631
- var devHostStateOf = (state, gaveUp) => state === "stopped" && gaveUp ? "failed" : devHostStateByBackendState[state];
632
- var RESTART_ROLE_ORDER = ["server", "shared", "barrel", "config"];
633
- var generationValue = (generation) => generation ?? -1;
634
- var isLegacyBackendFallbackFile = (file, workspaceRoot) => {
635
- const abs = path2.resolve(file);
636
- const ext = path2.extname(abs).toLowerCase();
637
- if (!SOURCE_EXTS.has(ext))
638
- return false;
639
- const rel = path2.relative(path2.resolve(workspaceRoot), abs);
640
- if (!rel || rel.startsWith("..") || path2.isAbsolute(rel))
641
- return false;
642
- const parts = rel.split(path2.sep).filter(Boolean);
643
- const [scope] = parts;
644
- if (scope !== "apps" && scope !== "libs" && scope !== "pkgs")
645
- return false;
646
- const base = path2.basename(abs);
647
- return parts.includes("srvkit") || parts.includes("common") || SERVER_SUFFIXES.some((suffix) => base.endsWith(suffix)) || SHARED_SUFFIXES.some((suffix) => base.endsWith(suffix)) || RUNTIME_METADATA_BASENAMES.has(base) || base === "main.ts" || base === "server.ts";
648
- };
649
- var shouldMarkBuildPhaseRecovered = (previousByPhase, status) => {
650
- const previous = previousByPhase.get(status.phase);
651
- return Boolean(previous && status.ok && !previous.ok && generationValue(status.generation) >= previous.generation);
652
- };
653
- var createBackendBuildStatus = ({
654
- generation,
655
- ok,
656
- files = [],
657
- message
658
- }) => ({
659
- generation,
660
- phase: "backend",
661
- ok,
662
- files,
663
- message
664
- });
665
- var backendRestartReasonFromMessage = (message) => {
666
- const roleSet = new Set;
667
- for (const role of message.devPlan?.roles ?? []) {
668
- if (role === "server" || role === "shared" || role === "barrel" || role === "config")
669
- roleSet.add(role);
670
- }
671
- return {
672
- generation: message.devPlan?.generation ?? message.generation,
673
- files: [...new Set(message.files)].sort(),
674
- roles: RESTART_ROLE_ORDER.filter((role) => roleSet.has(role))
675
- };
676
- };
677
- var mergeBackendRestartReasons = (current, next) => ({
678
- generation: generationValue(next.generation) >= generationValue(current?.generation) ? next.generation : current?.generation,
679
- files: [...new Set([...current?.files ?? [], ...next.files])].sort(),
680
- roles: RESTART_ROLE_ORDER.filter((role) => current?.roles.includes(role) || next.roles.includes(role))
681
- });
682
- var shouldReplaceLastGoodMessage = (current, next) => !current || generationValue(next.data.generation) >= generationValue(current.data.generation);
683
- var shouldQueueBuildStatusReplay = (backendReady, pendingReplayCount) => !backendReady || pendingReplayCount > 0;
684
- var hasBuildFailureForGeneration = (statusByPhase, generation) => {
685
- if (typeof generation !== "number")
686
- return false;
687
- for (const status of statusByPhase.values()) {
688
- if (!status.ok && status.generation === generation)
689
- return true;
690
- }
691
- return false;
692
- };
693
- var decideBuilderRssRecycle = ({
694
- rssBytes,
695
- ceilingBytes,
696
- buildFailed,
697
- msSinceLastRecycle,
698
- minIntervalMs = BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS
699
- }) => {
700
- if (!ceilingBytes)
701
- return "unbounded";
702
- if (rssBytes < ceilingBytes)
703
- return "below-ceiling";
704
- if (buildFailed)
705
- return "build-failed";
706
- if (msSinceLastRecycle !== null && msSinceLastRecycle < minIntervalMs)
707
- return "too-soon";
708
- return "recycle";
709
- };
710
- var decideBuilderRssSettle = ({
711
- rssBytes,
712
- ceilingBytes,
713
- hardMultiple = BUILDER_RSS_HARD_MULTIPLE
714
- }) => rssBytes >= ceilingBytes * hardMultiple ? "recycle-now" : "wait-and-recheck";
715
- var decideIdleSuspend = ({
716
- enabled,
717
- suspended,
718
- builderReady,
719
- backendReady,
720
- buildFailed,
721
- restartPending,
722
- msSinceWake,
723
- minUptimeMs = DEV_IDLE_MIN_UPTIME_MS
724
- }) => {
725
- if (!enabled)
726
- return "disabled";
727
- if (suspended)
728
- return "already-suspended";
729
- if (!builderReady)
730
- return "builder-not-ready";
731
- if (!backendReady)
732
- return "backend-not-ready";
733
- if (buildFailed)
734
- return "build-failed";
735
- if (restartPending)
736
- return "restart-pending";
737
- if (msSinceWake !== null && msSinceWake < minUptimeMs)
738
- return "too-soon";
739
- return "suspend";
740
- };
741
- var resolveIdleSuspendMs = (raw) => {
742
- if (raw === undefined || raw === "")
743
- return DEV_IDLE_SUSPEND_MS;
744
- const parsed = Number(raw);
745
- if (!Number.isFinite(parsed) || parsed <= 0)
746
- return null;
747
- return Math.round(parsed);
748
- };
749
- var hasAnyBuildFailure = (statusByPhase) => [...statusByPhase.values()].some((status) => !status.ok);
750
- var shouldRefreshConfigOnIdleWake = (batch) => !!batch && batch.kinds.has("config");
751
- var RETURNING_BUILDER_STATUSES = new Set(["starting", "recycling", "restarting"]);
752
- var shouldHoldForReturningBuilder = ({
753
- status,
754
- heldCount,
755
- limit = HELD_BUILDER_REQUEST_LIMIT
756
- }) => RETURNING_BUILDER_STATUSES.has(status) && heldCount < limit;
757
- var shouldWarnBuilderRssCeilingTight = (reportsSinceRecycle, limit = BUILDER_TIGHT_RSS_REPORT_LIMIT) => reportsSinceRecycle >= limit;
758
- var isRssCeilingUnreachable = (freshRssBytes, ceilingBytes) => freshRssBytes !== null && ceilingBytes !== null && freshRssBytes >= ceilingBytes;
759
- var shouldRelayRecycledFrontendState = (current, next) => {
760
- if (!current || current.type !== next.type)
761
- return true;
762
- if (current.type === "pages-updated" && next.type === "pages-updated")
763
- return current.data.bundlePath !== next.data.bundlePath;
764
- if (current.type === "css-updated" && next.type === "css-updated")
765
- return JSON.stringify(current.data.cssAssets) !== JSON.stringify(next.data.cssAssets);
766
- return true;
767
- };
768
- var mergeDevPlans = (current, next) => {
769
- if (!current)
770
- return next;
771
- if (!next)
772
- return current;
773
- const reasonByFile = { ...current.reasonByFile };
774
- for (const [file, reasons] of Object.entries(next.reasonByFile)) {
775
- reasonByFile[file] = [...new Set([...reasonByFile[file] ?? [], ...reasons])].sort();
776
- }
777
- return {
778
- generation: Math.max(current.generation, next.generation),
779
- files: [...new Set([...current.files, ...next.files])].sort(),
780
- generatedFiles: [...new Set([...current.generatedFiles, ...next.generatedFiles])].sort(),
781
- roles: [...new Set([...current.roles, ...next.roles])].sort(),
782
- actions: [...new Set([...current.actions, ...next.actions])].sort(),
783
- reasonByFile
784
- };
785
- };
786
- var mergeInvalidateMessages = (current, next) => {
787
- const generation = Math.max(generationValue(current.generation), generationValue(next.generation));
788
- return {
789
- type: "invalidate",
790
- kinds: [...new Set([...current.kinds, ...next.kinds])].sort(),
791
- files: [...new Set([...current.files, ...next.files])].sort(),
792
- generation: generation >= 0 ? generation : undefined,
793
- devPlan: mergeDevPlans(current.devPlan, next.devPlan)
794
- };
795
- };
796
- var buildStatusReplaySequence = (pendingReplay, latestByPhase) => [...pendingReplay, ...latestByPhase.values()];
797
- var filesChangedSince = (before, after) => [...before].filter(([file, stamp]) => after.get(file) !== stamp).map(([file]) => file);
798
-
799
- // pkgs/@akanjs/devkit/akanApp/BackendImportGraph.ts
800
482
  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;
801
483
  var GRAPH_IMPORT_KINDS = new Set([
802
484
  "import-statement",
@@ -827,7 +509,7 @@ class BackendImportGraph {
827
509
  return this.#lastRefreshSucceeded;
828
510
  }
829
511
  has(file) {
830
- return this.#files.has(path3.resolve(file));
512
+ return this.#files.has(path2.resolve(file));
831
513
  }
832
514
  async fingerprint() {
833
515
  const stamps = await Promise.all([...this.#files].map(async (file) => {
@@ -855,9 +537,9 @@ class BackendImportGraph {
855
537
  const roots = await this.#entrypoints();
856
538
  const files = new Set;
857
539
  const queue = [...roots];
858
- const workspaceRoot = path3.resolve(this.#app.workspace.workspaceRoot);
540
+ const workspaceRoot = path2.resolve(this.#app.workspace.workspaceRoot);
859
541
  while (queue.length > 0) {
860
- const current = path3.resolve(queue.pop());
542
+ const current = path2.resolve(queue.pop());
861
543
  if (files.has(current))
862
544
  continue;
863
545
  if (!this.#isWorkspaceSource(current, workspaceRoot))
@@ -866,7 +548,7 @@ class BackendImportGraph {
866
548
  if (!imports)
867
549
  continue;
868
550
  files.add(current);
869
- const importerDir = path3.dirname(current);
551
+ const importerDir = path2.dirname(current);
870
552
  for (const imp of imports) {
871
553
  if (!GRAPH_IMPORT_KINDS.has(imp.kind) || !imp.path || NON_SOURCE_EXT_RE.test(imp.path))
872
554
  continue;
@@ -897,7 +579,7 @@ class BackendImportGraph {
897
579
  const roots = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
898
580
  const existing = [];
899
581
  for (const root of roots) {
900
- const abs = path3.resolve(root);
582
+ const abs = path2.resolve(root);
901
583
  if (await Bun.file(abs).exists())
902
584
  existing.push(abs);
903
585
  }
@@ -906,25 +588,25 @@ class BackendImportGraph {
906
588
  #resolve(specifier, importerDir) {
907
589
  try {
908
590
  const resolved = Bun.resolveSync(specifier, importerDir);
909
- if (!path3.isAbsolute(resolved))
591
+ if (!path2.isAbsolute(resolved))
910
592
  return null;
911
- if (!SOURCE_EXTS.has(path3.extname(resolved).toLowerCase()))
593
+ if (!SOURCE_EXTS.has(path2.extname(resolved).toLowerCase()))
912
594
  return null;
913
- return path3.resolve(resolved);
595
+ return path2.resolve(resolved);
914
596
  } catch {
915
597
  return null;
916
598
  }
917
599
  }
918
600
  #isWorkspaceSource(file, workspaceRoot) {
919
- const rel = path3.relative(workspaceRoot, file);
920
- if (rel.startsWith("..") || path3.isAbsolute(rel))
601
+ const rel = path2.relative(workspaceRoot, file);
602
+ if (rel.startsWith("..") || path2.isAbsolute(rel))
921
603
  return false;
922
- if (rel.includes(`${path3.sep}node_modules${path3.sep}`) || rel.includes(`${path3.sep}.akan${path3.sep}`))
604
+ if (rel.includes(`${path2.sep}node_modules${path2.sep}`) || rel.includes(`${path2.sep}.akan${path2.sep}`))
923
605
  return false;
924
- return SOURCE_EXTS.has(path3.extname(file).toLowerCase());
606
+ return SOURCE_EXTS.has(path2.extname(file).toLowerCase());
925
607
  }
926
608
  #scanImports(file, source) {
927
- const ext = path3.extname(file).toLowerCase();
609
+ const ext = path2.extname(file).toLowerCase();
928
610
  if (ext === ".tsx")
929
611
  return this.#tsxTranspiler.scanImports(source);
930
612
  if (ext === ".jsx")
@@ -937,6 +619,7 @@ class BackendImportGraph {
937
619
 
938
620
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
939
621
  var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
622
+ var asMib = (bytes) => Math.round(bytes / 1024 / 1024);
940
623
  var BACKEND_RESTART_DEBOUNCE_MS = 120;
941
624
  var BACKEND_GRACEFUL_TIMEOUT_MS = 8000;
942
625
  var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
@@ -952,7 +635,7 @@ var BUILDER_RECOVERY_MAX_DELAY_MS = 60000;
952
635
 
953
636
  class AkanAppHost {
954
637
  app;
955
- logger = new Logger3("AkanAppHost");
638
+ logger = new Logger2("AkanAppHost");
956
639
  stdio;
957
640
  env;
958
641
  #onDevEvent;
@@ -1030,23 +713,18 @@ class AkanAppHost {
1030
713
  async stop() {
1031
714
  this.#cancelIdleSuspend();
1032
715
  this.#stopIdleWatcher();
1033
- if (this.#restartTimer) {
1034
- clearTimeout(this.#restartTimer);
1035
- this.#restartTimer = null;
1036
- }
1037
- if (this.#backendRecoveryTimer) {
1038
- clearTimeout(this.#backendRecoveryTimer);
1039
- this.#backendRecoveryTimer = null;
1040
- }
1041
- if (this.#builderRecoveryTimer) {
1042
- clearTimeout(this.#builderRecoveryTimer);
1043
- this.#builderRecoveryTimer = null;
1044
- }
716
+ this.#clearRestartTimers();
1045
717
  this.#failPendingBuilderMessages("dev server is shutting down");
1046
718
  await this.#stopBackend();
1047
719
  this.#stopBuilder();
1048
720
  return this;
1049
721
  }
722
+ #clearRestartTimers() {
723
+ for (const timer of [this.#restartTimer, this.#backendRecoveryTimer, this.#builderRecoveryTimer])
724
+ if (timer)
725
+ clearTimeout(timer);
726
+ this.#restartTimer = this.#backendRecoveryTimer = this.#builderRecoveryTimer = null;
727
+ }
1050
728
  kill() {
1051
729
  this.stop();
1052
730
  }
@@ -1342,7 +1020,6 @@ ${this.#backendStderrTail.join(`
1342
1020
  if (this.#rssCeilingAbandoned)
1343
1021
  return;
1344
1022
  const ceilingBytes = IncrementalBuilderHost.maxRssBytes();
1345
- const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
1346
1023
  const decision = decideBuilderRssRecycle({
1347
1024
  rssBytes: metrics.rssBytes,
1348
1025
  ceilingBytes,
@@ -1380,7 +1057,6 @@ ${this.#backendStderrTail.join(`
1380
1057
  if (!isRssCeilingUnreachable(freshRssBytes, ceilingBytes))
1381
1058
  return;
1382
1059
  this.#rssCeilingAbandoned = true;
1383
- const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
1384
1060
  this.logger.error(`[builder-recycle] a freshly recycled builder is already at ${asMib(freshRssBytes ?? 0)}MiB with nothing built on demand, so the ${asMib(ceilingBytes)}MiB ceiling cannot be met for this app; no longer enforcing it this session. Raise AKAN_BUILDER_MAX_RSS_MB, or set it to 0 to leave the builder unbounded.`);
1385
1061
  }
1386
1062
  #armRssRecycle(reason, over) {
@@ -1407,7 +1083,6 @@ ${this.#backendStderrTail.join(`
1407
1083
  }, BUILDER_RSS_RECYCLE_QUIET_MS);
1408
1084
  }
1409
1085
  async#recycleBuilderForRssWhenStillOver(reason, { rssBytes, ceilingBytes }) {
1410
- const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
1411
1086
  if (decideBuilderRssSettle({ rssBytes, ceilingBytes }) === "recycle-now") {
1412
1087
  this.#recycleBuilderForRss(reason);
1413
1088
  return;
@@ -1433,16 +1108,18 @@ ${this.#backendStderrTail.join(`
1433
1108
  this.#recycleBuilderForRss(settledBytes === null ? reason : `${reason}; still ${asMib(settledBytes)}MiB after settling`);
1434
1109
  }
1435
1110
  static async readProcessRssBytes(pid) {
1111
+ if (process.platform === "win32")
1112
+ return await AkanAppHost.#readRssKbVia(["tasklist", "/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], AkanAppHost.#tasklistRssKb);
1436
1113
  const status = await Bun.file(`/proc/${pid}/status`).text().catch(() => null);
1437
1114
  const vmRssKb = status === null ? null : /VmRSS:\s+(\d+) kB/.exec(status)?.[1];
1438
1115
  if (vmRssKb)
1439
1116
  return Number(vmRssKb) * 1024;
1440
- return await AkanAppHost.#readRssViaPs(pid);
1117
+ return await AkanAppHost.#readRssKbVia(["ps", "-o", "rss=", "-p", String(pid)], (output) => Number(output.trim()));
1441
1118
  }
1442
- static async#readRssViaPs(pid, timeoutMs = PS_RSS_TIMEOUT_MS) {
1119
+ static async#readRssKbVia(command, parseKb, timeoutMs = PS_RSS_TIMEOUT_MS) {
1443
1120
  let proc;
1444
1121
  try {
1445
- proc = Bun.spawn(["ps", "-o", "rss=", "-p", String(pid)], { stdio: ["ignore", "pipe", "ignore"] });
1122
+ proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"] });
1446
1123
  } catch {
1447
1124
  return null;
1448
1125
  }
@@ -1450,7 +1127,7 @@ ${this.#backendStderrTail.join(`
1450
1127
  try {
1451
1128
  const output = await new Response(proc.stdout).text();
1452
1129
  await proc.exited;
1453
- const rssKb = Number(output.trim());
1130
+ const rssKb = parseKb(output);
1454
1131
  return Number.isFinite(rssKb) && rssKb > 0 ? rssKb * 1024 : null;
1455
1132
  } catch {
1456
1133
  return null;
@@ -1458,6 +1135,10 @@ ${this.#backendStderrTail.join(`
1458
1135
  clearTimeout(killer);
1459
1136
  }
1460
1137
  }
1138
+ static #tasklistRssKb(output) {
1139
+ const memUsage = /"([^"]*)"\s*$/m.exec(output)?.[1];
1140
+ return memUsage ? Number(memUsage.replace(/\D/g, "")) : Number.NaN;
1141
+ }
1461
1142
  #cancelRssRecycle() {
1462
1143
  this.#rssRecycleReason = null;
1463
1144
  this.#rssRecycleOver = null;
@@ -1593,7 +1274,7 @@ ${this.#backendStderrTail.join(`
1593
1274
  await this.#backendGraph.refresh();
1594
1275
  await this.#startBuilder({ announceBootState: true });
1595
1276
  const missed = await this.#takeBuilderGapChanges();
1596
- const backendFiles = [...new Set([...files.filter((file) => this.#isBackendFile(file)), ...missed])];
1277
+ const backendFiles = [...new Set([...files.filter((file) => this.#backendGraph.has(file)), ...missed])];
1597
1278
  if (backendFiles.length === 0)
1598
1279
  return;
1599
1280
  this.logger.verbose(`[idle-suspend] ${backendFiles.length} backend file(s) changed while suspended`);
@@ -1648,10 +1329,6 @@ ${this.#backendStderrTail.join(`
1648
1329
  for (const message of pending)
1649
1330
  this.#sendToBuilder(message);
1650
1331
  }
1651
- #holdUntilBuilderReady(message) {
1652
- this.#pendingBuilderMessages.push(message);
1653
- this.logger.verbose(`[builder] holding ${message.type} until the builder is ready (${this.#pendingBuilderMessages.length} waiting)`);
1654
- }
1655
1332
  #discardPendingBuilderMessages(reason) {
1656
1333
  const held = this.#pendingBuilderMessages.splice(0);
1657
1334
  if (held.length === 0)
@@ -1663,12 +1340,17 @@ ${this.#backendStderrTail.join(`
1663
1340
  if (held.length === 0)
1664
1341
  return;
1665
1342
  this.logger.warn(`failing ${held.length} held builder request(s): ${reason}`);
1666
- for (const message of held) {
1667
- if (message.type === "build-route")
1668
- this.#sendToBackend({ type: "build-route-res", id: message.id, ok: false, error: reason });
1669
- else if (message.type === "build-csr")
1670
- this.#sendToBackend({ type: "build-csr-res", id: message.id, ok: false, error: reason });
1671
- }
1343
+ for (const message of held)
1344
+ this.#failBuilderRequest(message, reason);
1345
+ }
1346
+ #failBuilderRequest(message, error) {
1347
+ if (message.type === "build-route")
1348
+ this.#sendToBackend({ type: "build-route-res", id: message.id, ok: false, error });
1349
+ else if (message.type === "build-csr")
1350
+ this.#sendToBackend({ type: "build-csr-res", id: message.id, ok: false, error });
1351
+ else
1352
+ return false;
1353
+ return true;
1672
1354
  }
1673
1355
  #recycleBuilderForRss(reason) {
1674
1356
  if (this.#pendingRecycle || this.#restartTimer) {
@@ -1789,18 +1471,7 @@ ${this.#backendStderrTail.join(`
1789
1471
  }
1790
1472
  async#recycleDevChildren(message, { refreshConfig = false } = {}) {
1791
1473
  const generation = message.devPlan?.generation ?? message.generation;
1792
- if (this.#restartTimer) {
1793
- clearTimeout(this.#restartTimer);
1794
- this.#restartTimer = null;
1795
- }
1796
- if (this.#backendRecoveryTimer) {
1797
- clearTimeout(this.#backendRecoveryTimer);
1798
- this.#backendRecoveryTimer = null;
1799
- }
1800
- if (this.#builderRecoveryTimer) {
1801
- clearTimeout(this.#builderRecoveryTimer);
1802
- this.#builderRecoveryTimer = null;
1803
- }
1474
+ this.#clearRestartTimers();
1804
1475
  this.#builderRecoveryAttempts = 0;
1805
1476
  this.#pendingRestartReason = null;
1806
1477
  this.#lastGoodFrontend = {};
@@ -1890,7 +1561,7 @@ ${this.#backendStderrTail.join(`
1890
1561
  }
1891
1562
  if (message.kinds.includes("code"))
1892
1563
  await this.#backendGraph.refresh();
1893
- if (message.files.some((file) => this.#isBackendFile(file)))
1564
+ if (message.files.some((file) => this.#backendGraph.has(file)))
1894
1565
  return true;
1895
1566
  if (!this.#backendGraph.lastRefreshSucceeded) {
1896
1567
  const fallbackFiles = message.files.filter((file) => isLegacyBackendFallbackFile(file, this.app.workspace.workspaceRoot));
@@ -1901,9 +1572,6 @@ ${this.#backendStderrTail.join(`
1901
1572
  }
1902
1573
  return false;
1903
1574
  }
1904
- #isBackendFile(file) {
1905
- return this.#backendGraph.has(file);
1906
- }
1907
1575
  async#startBuilder({
1908
1576
  announceBootState = false
1909
1577
  } = {}) {
@@ -1989,27 +1657,12 @@ ${this.#backendStderrTail.join(`
1989
1657
  return;
1990
1658
  const status = this.#builder?.status ?? "stopped";
1991
1659
  if (shouldHoldForReturningBuilder({ status, heldCount: this.#pendingBuilderMessages.length })) {
1992
- this.#holdUntilBuilderReady(message);
1993
- return;
1994
- }
1995
- if (message.type === "build-route") {
1996
- this.#sendToBackend({
1997
- type: "build-route-res",
1998
- id: message.id,
1999
- ok: false,
2000
- error: `builder is ${status}; reload after the builder is ready`
2001
- });
1660
+ this.#pendingBuilderMessages.push(message);
1661
+ this.logger.verbose(`[builder] holding ${message.type} until the builder is ready (${this.#pendingBuilderMessages.length} waiting)`);
2002
1662
  return;
2003
1663
  }
2004
- if (message.type === "build-csr") {
2005
- this.#sendToBackend({
2006
- type: "build-csr-res",
2007
- id: message.id,
2008
- ok: false,
2009
- error: `builder is ${status}; reload after the builder is ready`
2010
- });
1664
+ if (this.#failBuilderRequest(message, `builder is ${status}; reload after the builder is ready`))
2011
1665
  return;
2012
- }
2013
1666
  this.logger.warn("akanAppHost builder is not running");
2014
1667
  }
2015
1668
  #stopBuilder() {
@@ -2021,25 +1674,20 @@ ${this.#backendStderrTail.join(`
2021
1674
  }
2022
1675
  }
2023
1676
  // pkgs/@akanjs/devkit/applicationTestPreload.ts
2024
- import path4 from "path";
1677
+ import path3 from "path";
2025
1678
  var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
2026
1679
  async function resolveSignalTestPreloadPath(target) {
2027
1680
  const candidates = [];
2028
- const addResolvedPackageCandidate = (basePath) => {
1681
+ for (const basePath of [target.cwdPath, process.cwd(), path3.dirname(Bun.main), import.meta.dir]) {
2029
1682
  try {
2030
- candidates.push(path4.join(path4.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
1683
+ candidates.push(path3.join(path3.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
2031
1684
  } catch {}
2032
- };
2033
- addResolvedPackageCandidate(target.cwdPath);
2034
- addResolvedPackageCandidate(process.cwd());
2035
- addResolvedPackageCandidate(path4.dirname(Bun.main));
2036
- addResolvedPackageCandidate(import.meta.dir);
2037
- 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));
1685
+ }
1686
+ 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));
2038
1687
  const uniqueCandidates = [...new Set(candidates)];
2039
- for (const candidate of uniqueCandidates) {
1688
+ for (const candidate of uniqueCandidates)
2040
1689
  if (await Bun.file(candidate).exists())
2041
1690
  return candidate;
2042
- }
2043
1691
  throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
2044
1692
  Probed paths:
2045
1693
  ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
@@ -2047,12 +1695,8 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
2047
1695
  }
2048
1696
 
2049
1697
  // pkgs/@akanjs/cli/application/application.runner.ts
2050
- import { Logger as Logger4 } from "akanjs/common";
2051
- import ora from "ora";
2052
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-kxwbssgn.js")).ApplicationBuildRunner;
2053
- var loadReleasePackager = async () => (await import("./applicationReleasePackager-4yasxwv9.js")).ApplicationReleasePackager;
2054
- var loadCapacitorApp = async () => (await import("./capacitorApp-36rbr858.js")).CapacitorApp;
2055
- var loadAbstractCompactor = async () => (await import("./abstractCompactor-mjh5ppsq.js")).AbstractCompactor;
1698
+ import { Logger as Logger3 } from "akanjs/common";
1699
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-38exmejd.js")).ApplicationBuildRunner;
2056
1700
  var loadPrompts = async () => await import("@inquirer/prompts");
2057
1701
 
2058
1702
  class ApplicationRunner extends runner("application") {
@@ -2071,9 +1715,6 @@ class ApplicationRunner extends runner("application") {
2071
1715
  async removeApplication(app) {
2072
1716
  await app.workspace.removeDir(`apps/${app.name}`);
2073
1717
  }
2074
- async getConfig(app) {
2075
- return await app.getConfig();
2076
- }
2077
1718
  async getScriptFilename(app) {
2078
1719
  if (!await app.exists("script")) {
2079
1720
  await app.mkdir("script");
@@ -2095,12 +1736,12 @@ class ApplicationRunner extends runner("application") {
2095
1736
  if (!await app.exists(scriptPath))
2096
1737
  throw new Error(`Script file not found: apps/${app.name}/${scriptPath}`);
2097
1738
  await app.spawn("bun", [scriptPath], {
2098
- env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script" }),
1739
+ env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script", ...await app.getDatabaseModeEnv() }),
2099
1740
  stdio: "inherit"
2100
1741
  });
2101
1742
  }
2102
1743
  #runtimeDirOf(app, override) {
2103
- return path5.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path5.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
1744
+ return path4.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path4.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
2104
1745
  }
2105
1746
  async runLogs(app, options) {
2106
1747
  const { LogControlUnavailableError, LogTailClient } = await import("akanjs/server/logging/logTailClient");
@@ -2110,13 +1751,10 @@ class ApplicationRunner extends runner("application") {
2110
1751
  const { level, grep, endpoint, trace, child, role, origin, since } = options;
2111
1752
  const query = Object.fromEntries(Object.entries({ level, grep, endpoint, trace, child, role, origin, since }).filter(([, value]) => value !== undefined && value !== null && value !== ""));
2112
1753
  const print = (record) => process.stdout.write(options.json ? `${JSON.stringify(record)}
2113
- ` : Logger4.render(record));
1754
+ ` : Logger3.render(record));
2114
1755
  const note = (text) => process.stderr.write(`[akan logs] ${text}
2115
1756
  `);
2116
- let finish = null;
2117
- const closed = new Promise((resolve) => {
2118
- finish = resolve;
2119
- });
1757
+ const { promise: closed, resolve: finish } = Promise.withResolvers();
2120
1758
  let client;
2121
1759
  try {
2122
1760
  client = await LogTailClient.connect(socketPath, {
@@ -2125,11 +1763,11 @@ class ApplicationRunner extends runner("application") {
2125
1763
  if (response.type === "dropped")
2126
1764
  note(`${response.count} records dropped (reader too slow)`);
2127
1765
  },
2128
- onClose: () => finish?.()
1766
+ onClose: () => finish()
2129
1767
  });
2130
1768
  } catch (error) {
2131
1769
  if (error instanceof LogControlUnavailableError)
2132
- 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.`);
1770
+ throw new Error(`${app.name} is not running (no ${path4.basename(socketPath)} in ${runtimeDir}). Start it with \`akan start ${app.name}\`, or pass --runtime-dir for a built app.`);
2133
1771
  throw error;
2134
1772
  }
2135
1773
  const sinceMs = since ? LogQueryMatcher.parseSince(since) : undefined;
@@ -2167,7 +1805,7 @@ const serverModule = await import(${JSON.stringify(serverPath)});
2167
1805
  const { assertAkanConsoleAllowed, startAkanConsole } = await import("akanjs/server");
2168
1806
  const server = serverModule.server;
2169
1807
  if (!server?.start) throw new Error("server.ts must export server with start()");
2170
- assertAkanConsoleAllowed(server.env);
1808
+ assertAkanConsoleAllowed();
2171
1809
  await server.start({ listen: false, web: false });
2172
1810
  try {
2173
1811
  await startAkanConsole(server, {
@@ -2186,17 +1824,35 @@ try {
2186
1824
  }
2187
1825
  `;
2188
1826
  await app.spawn("bun", ["-e", code], {
2189
- env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "console" }),
1827
+ env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "console", ...await app.getDatabaseModeEnv() }),
1828
+ stdio: "inherit"
1829
+ });
1830
+ }
1831
+ async transferDatabase(app, direction, dir) {
1832
+ const serverPath = `${app.cwdPath}/server.ts`;
1833
+ if (!await app.exists("server.ts"))
1834
+ throw new Error(`Server file not found: apps/${app.name}/server.ts`);
1835
+ const target = path4.resolve(app.workspace.workspaceRoot, dir);
1836
+ const code = `
1837
+ const { server } = await import(${JSON.stringify(serverPath)});
1838
+ const { DatabaseAdaptorRole, DocumentTransfer } = await import("akanjs/service");
1839
+ await server.start({ listen: false, web: false });
1840
+ try {
1841
+ const transfer = new DocumentTransfer(server.get(DatabaseAdaptorRole));
1842
+ const reports = await transfer.${direction === "export" ? "exportTo" : "importFrom"}(${JSON.stringify(target)});
1843
+ for (const { table, rows } of reports) console.info(\`${direction === "export" ? "exported" : "imported"} \${table}: \${rows} rows\`);
1844
+ } finally {
1845
+ await server.stop();
1846
+ }
1847
+ `;
1848
+ await app.spawn("bun", ["-e", code], {
1849
+ env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script", ...await app.getDatabaseModeEnv() }),
2190
1850
  stdio: "inherit"
2191
1851
  });
2192
1852
  }
2193
1853
  async typecheck(app, options = {}) {
2194
1854
  await new (await loadBuildRunner())(app).typecheck(options);
2195
1855
  }
2196
- async compact(sys, { module, minLines, interactive } = {}) {
2197
- const compactor = new (await loadAbstractCompactor())(sys, { minLines, interactive });
2198
- return await compactor.compactAll({ module });
2199
- }
2200
1856
  async test(exec) {
2201
1857
  const isSignalTarget = exec instanceof AppExecutor || exec instanceof LibExecutor;
2202
1858
  const preloadPath = isSignalTarget ? await resolveSignalTestPreloadPath(exec) : null;
@@ -2232,71 +1888,68 @@ try {
2232
1888
  setTimeout(() => openBrowser(`http://localhost:${env.AKAN_PUBLIC_CLIENT_PORT ?? env.PORT ?? "8282"}`), 3000);
2233
1889
  return appHost;
2234
1890
  }
2235
- async buildIos(app, { target, env = "debug", regenerate = false } = {}) {
1891
+ async buildMobile(app, platform, { target, env = "debug", profile = "release" } = {}) {
2236
1892
  const targets = await resolveMobileTargets(app, target);
2237
1893
  await this.#buildMobileCsr(app, env);
2238
1894
  await this.#runMobileTargets(targets, async (mobileTarget) => {
2239
- await new (await loadCapacitorApp())(app, mobileTarget.config).buildIos({ env, regenerate });
1895
+ this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).build(platform, { profile }));
2240
1896
  });
2241
1897
  }
2242
- async startIos(app, {
2243
- open = false,
2244
- operation = "local",
2245
- env = "local",
2246
- target,
2247
- device,
2248
- regenerate = false,
2249
- noAllowProvisioningUpdates = false
2250
- } = {}) {
1898
+ async startMobile(app, platform, { target, env = "local", operation = "local", device, teamId } = {}) {
2251
1899
  const targets = await resolveMobileTargets(app, target);
2252
- if (operation === "release")
1900
+ const [mobileTarget] = targets;
1901
+ if (!mobileTarget || targets.length > 1)
1902
+ throw new Error(`start-${platform === "ios" || platform === "android" ? platform : "desktop"} runs one mobile target at a time; pass --target <name>.`);
1903
+ const nativeApp = new NativeApp(app, mobileTarget);
1904
+ const selection = { ...device ? { device } : {}, ...teamId ? { teamId } : {} };
1905
+ if (operation === "release") {
2253
1906
  await this.#buildMobileCsr(app, env);
2254
- await this.#runMobileTargets(targets, async (mobileTarget) => {
2255
- const capacitorApp = new (await loadCapacitorApp())(app, mobileTarget.config);
2256
- await capacitorApp.runIos({ operation, env, regenerate, noAllowProvisioningUpdates, iosDeviceId: device });
2257
- if (open)
2258
- await capacitorApp.openIos();
1907
+ const running = await nativeApp.run(platform, { ...selection, profile: "release" });
1908
+ await running.exited;
1909
+ return;
1910
+ }
1911
+ const upstream = `http://localhost:${await app.getDevPort()}`;
1912
+ if (!await ApplicationRunner.#answers(upstream))
1913
+ throw new Error(`No dev server answers on ${upstream}; run \`akan start ${app.name}\` first.`);
1914
+ const { i18n } = await app.getConfig();
1915
+ const session = await nativeApp.dev(platform, { upstream, lang: i18n.defaultLocale, ...selection });
1916
+ app.log(`${app.name}/${mobileTarget.name} on ${platform} follows ${upstream} through ${session.gateway}.`);
1917
+ process.once("SIGINT", () => {
1918
+ session.stop().finally(() => process.exit(130));
2259
1919
  });
1920
+ await session.exited;
2260
1921
  }
2261
- async releaseIos(app, { target, env = "main", regenerate = false } = {}) {
2262
- const targets = await resolveMobileTargets(app, target);
2263
- await this.#buildMobileCsr(app, env);
2264
- for (const mobileTarget of targets) {
2265
- await new (await loadCapacitorApp())(app, mobileTarget.config).buildIos({ env, regenerate });
1922
+ async startDesktop(app, options = {}) {
1923
+ await this.startMobile(app, NativeApp.desktopPlatform(), options);
1924
+ }
1925
+ static async#answers(url) {
1926
+ try {
1927
+ await fetch(url, { signal: AbortSignal.timeout(3000) });
1928
+ return true;
1929
+ } catch {
1930
+ return false;
2266
1931
  }
2267
1932
  }
2268
- async buildAndroid(app, { target, env = "debug", regenerate = false } = {}) {
1933
+ async releaseIos(app, { target, env = "main", teamId, adHoc = false } = {}) {
2269
1934
  const targets = await resolveMobileTargets(app, target);
2270
1935
  await this.#buildMobileCsr(app, env);
2271
- await this.#runMobileTargets(targets, async (mobileTarget) => {
2272
- await new (await loadCapacitorApp())(app, mobileTarget.config).buildAndroid("apk", { env, regenerate });
2273
- });
1936
+ for (const mobileTarget of targets)
1937
+ this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).releaseIos({ teamId, adHoc }));
2274
1938
  }
2275
- async startAndroid(app, {
2276
- open = false,
2277
- operation = "local",
2278
- env = "local",
2279
- target,
2280
- regenerate = false
2281
- } = {}) {
2282
- const targets = await resolveMobileTargets(app, target);
2283
- if (operation === "release")
2284
- await this.#buildMobileCsr(app, env);
2285
- await this.#runMobileTargets(targets, async (mobileTarget) => {
2286
- const capacitorApp = new (await loadCapacitorApp())(app, mobileTarget.config);
2287
- await capacitorApp.runAndroid({ operation, env, regenerate });
2288
- if (open)
2289
- await capacitorApp.openAndroid();
2290
- });
2291
- }
2292
- async releaseAndroid(app, assembleType, { target, env = "main", regenerate = false } = {}) {
1939
+ async releaseAndroid(app, format, { target, env = "main" } = {}) {
2293
1940
  const targets = await resolveMobileTargets(app, target);
1941
+ NativeApp.androidSigning();
2294
1942
  await this.#buildMobileCsr(app, env);
2295
- for (const mobileTarget of targets) {
2296
- await new (await loadCapacitorApp())(app, mobileTarget.config).buildAndroid(assembleType, { env, regenerate });
2297
- app.log(`Release Android ${app.name}/${mobileTarget.name} ${assembleType} Completed.`);
2298
- app.log(`Path : ${app.cwdPath}/android/app/build/outputs/${assembleType === "apk" ? "apk" : "bundle"}/release`);
2299
- }
1943
+ for (const mobileTarget of targets)
1944
+ this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget).releaseAndroid({ formats: [format] }));
1945
+ }
1946
+ #reportBuild(app, mobileTarget, { artifacts, warnings, signing }) {
1947
+ for (const warning of warnings)
1948
+ app.logger.warn(warning);
1949
+ if (signing)
1950
+ app.log(`Signed with ${signing.identity} (${signing.profile}, team ${signing.teamId})`);
1951
+ for (const artifact of artifacts)
1952
+ app.log(`${app.name}/${mobileTarget.name} ${artifact.kind}: ${artifact.path}`);
2300
1953
  }
2301
1954
  async#buildMobileCsr(app, env) {
2302
1955
  const prevEnv = {
@@ -2335,24 +1988,23 @@ try {
2335
1988
  return;
2336
1989
  for (const failure of failures) {
2337
1990
  const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
2338
- Logger4.rawLog(`Mobile target ${failure.target} failed: ${message}`, undefined, "error");
1991
+ Logger3.rawLog(`Mobile target ${failure.target} failed: ${message}`, undefined, "error");
2339
1992
  }
2340
1993
  throw new Error(`${failures.length}/${results.length} mobile targets failed`);
2341
1994
  }
2342
- async codepush(app, os) {
2343
- const [target] = await resolveMobileTargets(app, undefined);
2344
- if (!target)
2345
- throw new Error(`No mobile target configured for ${app.name}`);
2346
- const capacitorApp = new (await loadCapacitorApp())(app, target.config);
2347
- await capacitorApp.init();
2348
- }
2349
1995
  #getLocalDatabaseServices(mode) {
2350
1996
  if (mode === "single")
2351
1997
  return [];
2352
1998
  if (mode === "multiple")
2353
- return ["redis", "libsql"];
1999
+ return ["redis"];
2354
2000
  return ["redis", "postgres"];
2355
2001
  }
2002
+ async#assertComposeHas(workspace, services) {
2003
+ const compose = Bun.YAML.parse(await Bun.file(`${workspace.workspaceRoot}/local/docker-compose.yaml`).text());
2004
+ const missing = services.filter((service) => !compose?.services?.[service]);
2005
+ if (missing.length)
2006
+ throw new Error(`local/docker-compose.yaml declares no ${missing.join(" or ")} service. Add it, or move the file aside so the next akan dbup writes the current template.`);
2007
+ }
2356
2008
  async#isLocalDatabaseUp(workspace, mode) {
2357
2009
  const requiredServices = this.#getLocalDatabaseServices(mode);
2358
2010
  if (!requiredServices.length)
@@ -2366,16 +2018,18 @@ try {
2366
2018
  async dbup(workspace, mode = "multiple") {
2367
2019
  if (mode === "single")
2368
2020
  return true;
2021
+ const services = this.#getLocalDatabaseServices(mode);
2022
+ await workspace.applyTemplate({
2023
+ basePath: "local",
2024
+ template: "localDev",
2025
+ dict: { repoName: workspace.repoName },
2026
+ overwrite: false
2027
+ });
2028
+ await this.#assertComposeHas(workspace, services);
2369
2029
  try {
2370
- await workspace.applyTemplate({
2371
- basePath: "local",
2372
- template: "localDev",
2373
- dict: { repoName: workspace.repoName },
2374
- overwrite: false
2375
- });
2376
2030
  const wasAlreadyUp = await this.#isLocalDatabaseUp(workspace, mode);
2377
2031
  if (!wasAlreadyUp)
2378
- await workspace.spawn(`docker`, ["compose", "up", "-d", ...this.#getLocalDatabaseServices(mode)], {
2032
+ await workspace.spawn(`docker`, ["compose", "up", "-d", ...services], {
2379
2033
  cwd: `${workspace.workspaceRoot}/local`
2380
2034
  });
2381
2035
  return wasAlreadyUp;
@@ -2393,74 +2047,182 @@ ${detail}`
2393
2047
  async dbdown(workspace) {
2394
2048
  await workspace.spawn(`docker`, ["compose", "down"], { cwd: `${workspace.workspaceRoot}/local` });
2395
2049
  }
2396
- async configureApp(app) {
2397
- const [target] = await resolveMobileTargets(app, undefined);
2398
- if (!target)
2399
- throw new Error(`No mobile target configured for ${app.name}`);
2400
- const capacitorApp = new (await loadCapacitorApp())(app, target.config);
2401
- await capacitorApp.init();
2402
- if (await (await loadPrompts()).confirm({ message: "want to add camera permission?" }))
2403
- await capacitorApp.addCamera();
2404
- if (await (await loadPrompts()).confirm({ message: "want to add contact permission?" }))
2405
- await capacitorApp.addContact();
2406
- if (await (await loadPrompts()).confirm({ message: "want to add location permission?" }))
2407
- await capacitorApp.addLocation();
2408
- await capacitorApp.save();
2409
- }
2410
- async releaseSource(app, { rebuild, buildNum = 0, environment = "debug", local = true } = {}) {
2411
- await new (await loadReleasePackager())(app, { build: () => this.build(app).then(() => {
2412
- return;
2413
- }) }).releaseSource({
2414
- rebuild,
2415
- buildNum,
2416
- environment,
2417
- local
2418
- });
2419
- return;
2050
+ }
2051
+
2052
+ // pkgs/@akanjs/cli/application/hostProcessProbe.ts
2053
+ import { readdirSync, readFileSync, readlinkSync } from "fs";
2054
+
2055
+ class HostProcessProbe {
2056
+ static timeoutMs = 3000;
2057
+ static win32TimeoutMs = 20000;
2058
+ static #procTcpListen = "0A";
2059
+ static #win32ProcessQuery = "[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine,CreationDate | ConvertTo-Json -Compress";
2060
+ platform;
2061
+ #win32Table = null;
2062
+ constructor(platform = process.platform) {
2063
+ this.platform = platform;
2064
+ }
2065
+ async listenersOn(port) {
2066
+ if (this.platform === "win32")
2067
+ return HostProcessProbe.parseNetstat(await HostProcessProbe.run(["netstat", "-ano"]), port);
2068
+ if (this.platform === "linux")
2069
+ return HostProcessProbe.#procListenersOn(port);
2070
+ const output = await HostProcessProbe.run(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
2071
+ return [
2072
+ ...new Set(output.split(`
2073
+ `).map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0))
2074
+ ];
2420
2075
  }
2421
- async createApplicationTemplate(workspace, appName) {
2422
- await workspace.applyTemplate({ basePath: `apps/${appName}`, template: "appRoot", dict: { appName } });
2076
+ async parentOf(pid) {
2077
+ if (this.platform === "win32") {
2078
+ const child = await this.#win32Process(pid);
2079
+ const parent = child ? await this.#win32Process(child.ppid) : null;
2080
+ return child && parent ? HostProcessProbe.parentPidOf(child, parent) : null;
2081
+ }
2082
+ if (this.platform === "linux") {
2083
+ const stat = HostProcessProbe.#readProc(pid, "stat");
2084
+ return stat === null ? null : HostProcessProbe.parseProcStatPpid(stat);
2085
+ }
2086
+ const parsed = Number((await HostProcessProbe.run(["ps", "-p", String(pid), "-o", "ppid="])).trim());
2087
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
2423
2088
  }
2424
- async compressProjectFiles(app, { rebuild, buildNum = 0, environment = "debug", local = true } = {}) {
2425
- await new (await loadReleasePackager())(app, {
2426
- build: () => this.build(app).then(() => {
2427
- return;
2428
- })
2429
- }).compressProjectFiles({
2430
- rebuild,
2431
- buildNum,
2432
- environment,
2433
- local
2434
- });
2435
- return;
2089
+ async commandOf(pid) {
2090
+ if (this.platform === "win32")
2091
+ return (await this.#win32Process(pid))?.command ?? null;
2092
+ if (this.platform === "linux") {
2093
+ const cmdline = HostProcessProbe.#readProc(pid, "cmdline");
2094
+ return cmdline?.split("\x00").filter(Boolean).join(" ") || null;
2095
+ }
2096
+ return (await HostProcessProbe.run(["ps", "-ww", "-p", String(pid), "-o", "command="])).trim() || null;
2436
2097
  }
2437
- async generateApplicationTemplate(app) {
2438
- const openAIApiKey = process.env.OPENAI_API_KEY;
2439
- if (!openAIApiKey)
2440
- throw new Error("OPENAI_API_KEY is not set");
2441
- const [{ StringOutputParser }, { PromptTemplate }, { RunnableSequence }, { ChatOpenAI }, prompts] = await Promise.all([
2442
- import("@langchain/core/output_parsers"),
2443
- import("@langchain/core/prompts"),
2444
- import("@langchain/core/runnables"),
2445
- import("@langchain/openai"),
2446
- loadPrompts()
2447
- ]);
2448
- const chatModel = new ChatOpenAI({ modelName: "gpt-4o", openAIApiKey });
2449
- const projectName = await prompts.input({ message: "please enter project name." });
2450
- const projectDesc = await prompts.input({
2451
- message: "please enter project description. (40 ~ 60 characters)"
2452
- });
2453
- const spinner = ora("Gerating project files...");
2454
- const mainPrompt = PromptTemplate.fromTemplate(`prompt.requestApplication()`);
2455
- const chain = RunnableSequence.from([mainPrompt, chatModel, new StringOutputParser]);
2456
- await chain.invoke({ projectName, projectDesc });
2457
- spinner.succeed("Loading complete!");
2098
+ static parentPidOf(child, parent) {
2099
+ if (child.ppid !== parent.pid)
2100
+ return null;
2101
+ if (parent.startedAt !== null && child.startedAt !== null && parent.startedAt > child.startedAt)
2102
+ return null;
2103
+ return parent.pid;
2104
+ }
2105
+ static parseNetstat(output, port) {
2106
+ const pids = new Set;
2107
+ for (const line of output.split(/\r?\n/)) {
2108
+ const fields = line.trim().split(/\s+/);
2109
+ if (fields[0] !== "TCP" || fields.length < 5)
2110
+ continue;
2111
+ const [, local, remote] = fields;
2112
+ if (!local?.endsWith(`:${port}`) || !remote?.endsWith(":0"))
2113
+ continue;
2114
+ const pid = Number(fields.at(-1));
2115
+ if (Number.isInteger(pid) && pid > 0)
2116
+ pids.add(pid);
2117
+ }
2118
+ return [...pids];
2119
+ }
2120
+ static parseProcNetTcp(text, port) {
2121
+ const inodes = [];
2122
+ for (const line of text.split(`
2123
+ `).slice(1)) {
2124
+ const fields = line.trim().split(/\s+/);
2125
+ const [, local, , state, , , , , , inode] = fields;
2126
+ if (!local || !inode || state !== HostProcessProbe.#procTcpListen || inode === "0")
2127
+ continue;
2128
+ if (Number.parseInt(local.slice(local.lastIndexOf(":") + 1), 16) === port)
2129
+ inodes.push(inode);
2130
+ }
2131
+ return inodes;
2132
+ }
2133
+ static parseProcStatPpid(stat) {
2134
+ const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
2135
+ return Number.isInteger(ppid) && ppid > 0 ? ppid : null;
2136
+ }
2137
+ static parseWin32Processes(json) {
2138
+ const table = new Map;
2139
+ let parsed;
2140
+ try {
2141
+ parsed = JSON.parse(json.trim() || "[]");
2142
+ } catch {
2143
+ return table;
2144
+ }
2145
+ const rows = Array.isArray(parsed) ? parsed : [parsed];
2146
+ for (const row of rows) {
2147
+ if (typeof row?.ProcessId !== "number")
2148
+ continue;
2149
+ table.set(row.ProcessId, {
2150
+ pid: row.ProcessId,
2151
+ ppid: row.ParentProcessId ?? 0,
2152
+ command: row.CommandLine?.trim() || null,
2153
+ startedAt: HostProcessProbe.#parseCimDate(row.CreationDate)
2154
+ });
2155
+ }
2156
+ return table;
2157
+ }
2158
+ static async run(command, timeoutMs = HostProcessProbe.timeoutMs) {
2159
+ let proc;
2160
+ try {
2161
+ proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
2162
+ } catch {
2163
+ return "";
2164
+ }
2165
+ const timer = setTimeout(() => proc.kill("SIGKILL"), timeoutMs);
2166
+ try {
2167
+ return await new Response(proc.stdout).text();
2168
+ } catch {
2169
+ return "";
2170
+ } finally {
2171
+ clearTimeout(timer);
2172
+ }
2173
+ }
2174
+ async#win32Process(pid) {
2175
+ this.#win32Table ??= await HostProcessProbe.#queryWin32Processes();
2176
+ if (!this.#win32Table.has(pid))
2177
+ this.#win32Table = await HostProcessProbe.#queryWin32Processes();
2178
+ return this.#win32Table.get(pid) ?? null;
2179
+ }
2180
+ static async#queryWin32Processes() {
2181
+ const output = await HostProcessProbe.run(["powershell", "-NoProfile", "-NonInteractive", "-Command", HostProcessProbe.#win32ProcessQuery], HostProcessProbe.win32TimeoutMs);
2182
+ return HostProcessProbe.parseWin32Processes(output);
2183
+ }
2184
+ static #parseCimDate(value) {
2185
+ if (!value)
2186
+ return null;
2187
+ const legacy = /\/Date\((\d+)/.exec(value)?.[1];
2188
+ const ms = legacy ? Number(legacy) : Date.parse(value);
2189
+ return Number.isFinite(ms) ? ms : null;
2190
+ }
2191
+ static #procListenersOn(port) {
2192
+ const inodes = new Set(["tcp", "tcp6"].flatMap((table) => HostProcessProbe.parseProcNetTcp(HostProcessProbe.#readProc("net", table) ?? "", port)));
2193
+ if (!inodes.size)
2194
+ return [];
2195
+ const pids = [];
2196
+ for (const entry of HostProcessProbe.#listDir("/proc")) {
2197
+ if (!/^\d+$/.test(entry))
2198
+ continue;
2199
+ const holds = HostProcessProbe.#listDir(`/proc/${entry}/fd`).some((fd) => {
2200
+ const link = HostProcessProbe.#orElse(() => readlinkSync(`/proc/${entry}/fd/${fd}`), "");
2201
+ const inode = /^socket:\[(\d+)\]$/.exec(link)?.[1];
2202
+ return !!inode && inodes.has(inode);
2203
+ });
2204
+ if (holds)
2205
+ pids.push(Number(entry));
2206
+ }
2207
+ return pids;
2208
+ }
2209
+ static #readProc(pid, file) {
2210
+ return HostProcessProbe.#orElse(() => readFileSync(`/proc/${pid}/${file}`, "utf8"), null);
2211
+ }
2212
+ static #listDir(dir) {
2213
+ return HostProcessProbe.#orElse(() => readdirSync(dir), []);
2214
+ }
2215
+ static #orElse(read, fallback) {
2216
+ try {
2217
+ return read();
2218
+ } catch {
2219
+ return fallback;
2220
+ }
2458
2221
  }
2459
2222
  }
2460
2223
 
2461
2224
  // pkgs/@akanjs/cli/application/devPortReclaimer.ts
2462
2225
  class DevPortReclaimer {
2463
- static lsofTimeoutMs = 3000;
2464
2226
  static releaseTimeoutMs = 8000;
2465
2227
  static maxAncestorDepth = 8;
2466
2228
  static #releasePollMs = 100;
@@ -2469,7 +2231,12 @@ class DevPortReclaimer {
2469
2231
  return [...new Set(ports.flatMap((port) => [port, port + DevPortReclaimer.wsPortOffset]))].sort((left, right) => left - right);
2470
2232
  }
2471
2233
  static isAkanCommand(command) {
2472
- 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");
2234
+ const normalized = command.replaceAll("\\", "/");
2235
+ return /(^|\/)akan(\.exe)?"?(\s|$)/.test(normalized) || normalized.includes("@akanjs/cli") || normalized.includes("akanjs/server") || /\bapps\/[^/\s]+\/main\.ts\b/.test(normalized) || normalized.includes("incrementalBuilder.proc") || normalized.includes("rscWorker");
2236
+ }
2237
+ #probe;
2238
+ constructor(probe = new HostProcessProbe) {
2239
+ this.#probe = probe;
2473
2240
  }
2474
2241
  async reclaim(ports) {
2475
2242
  const killed = [];
@@ -2494,10 +2261,10 @@ class DevPortReclaimer {
2494
2261
  }
2495
2262
  async holdersOf(port) {
2496
2263
  const holders = [];
2497
- for (const listenerPid of await this.#pidsOn(port)) {
2264
+ for (const listenerPid of await this.#probe.listenersOn(port)) {
2498
2265
  if (listenerPid === process.pid || listenerPid === process.ppid)
2499
2266
  continue;
2500
- const command = await this.#commandOf(listenerPid);
2267
+ const command = await this.#probe.commandOf(listenerPid);
2501
2268
  if (!command)
2502
2269
  continue;
2503
2270
  if (!DevPortReclaimer.isAkanCommand(command)) {
@@ -2512,44 +2279,37 @@ class DevPortReclaimer {
2512
2279
  async#akanRootOf(pid, command) {
2513
2280
  let root = { pid, command };
2514
2281
  for (let depth = 0;depth < DevPortReclaimer.maxAncestorDepth; depth += 1) {
2515
- const parentPid = await this.#ppidOf(root.pid);
2282
+ const parentPid = await this.#probe.parentOf(root.pid);
2516
2283
  if (parentPid === null || parentPid <= 1 || parentPid === process.pid || parentPid === process.ppid)
2517
2284
  break;
2518
- const parentCommand = await this.#commandOf(parentPid);
2285
+ const parentCommand = await this.#probe.commandOf(parentPid);
2519
2286
  if (!parentCommand || !DevPortReclaimer.isAkanCommand(parentCommand))
2520
2287
  break;
2521
2288
  root = { pid: parentPid, command: parentCommand };
2522
2289
  }
2523
2290
  return root;
2524
2291
  }
2525
- async#ppidOf(pid) {
2526
- const output = await this.#run(["ps", "-p", String(pid), "-o", "ppid="]);
2527
- const parsed = Number(output.trim());
2528
- return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
2529
- }
2530
- async#pidsOn(port) {
2531
- const output = await this.#run(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
2532
- return [
2533
- ...new Set(output.split(`
2534
- `).map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0))
2535
- ];
2536
- }
2537
- async#commandOf(pid) {
2538
- const output = await this.#run(["ps", "-p", String(pid), "-o", "command="]);
2539
- return output.trim() || null;
2540
- }
2541
2292
  async#terminate(pid) {
2293
+ if (this.#probe.platform === "win32") {
2294
+ await HostProcessProbe.run(["taskkill", "/PID", String(pid), "/T", "/F"]);
2295
+ return await this.#released(pid);
2296
+ }
2542
2297
  if (!this.#signal(pid, "SIGTERM"))
2543
2298
  return !this.#alive(pid);
2299
+ if (await this.#released(pid))
2300
+ return true;
2301
+ this.#signal(pid, "SIGKILL");
2302
+ await Bun.sleep(DevPortReclaimer.#releasePollMs);
2303
+ return !this.#alive(pid);
2304
+ }
2305
+ async#released(pid) {
2544
2306
  const deadline = Date.now() + DevPortReclaimer.releaseTimeoutMs;
2545
2307
  while (Date.now() < deadline) {
2546
2308
  if (!this.#alive(pid))
2547
2309
  return true;
2548
2310
  await Bun.sleep(DevPortReclaimer.#releasePollMs);
2549
2311
  }
2550
- this.#signal(pid, "SIGKILL");
2551
- await Bun.sleep(DevPortReclaimer.#releasePollMs);
2552
- return !this.#alive(pid);
2312
+ return false;
2553
2313
  }
2554
2314
  #signal(pid, signal) {
2555
2315
  try {
@@ -2567,26 +2327,15 @@ class DevPortReclaimer {
2567
2327
  return error.code === "EPERM";
2568
2328
  }
2569
2329
  }
2570
- async#run(command) {
2571
- const proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"] });
2572
- const timer = setTimeout(() => proc.kill("SIGKILL"), DevPortReclaimer.lsofTimeoutMs);
2573
- try {
2574
- return await new Response(proc.stdout).text();
2575
- } catch {
2576
- return "";
2577
- } finally {
2578
- clearTimeout(timer);
2579
- }
2580
- }
2581
2330
  }
2582
2331
 
2583
2332
  // pkgs/@akanjs/cli/application/devStreamView.ts
2584
- import { Logger as Logger5 } from "akanjs/common";
2333
+ import { Logger as Logger4 } from "akanjs/common";
2585
2334
  import chalk from "chalk";
2586
2335
  var appColors = ["cyan", "magenta", "yellow", "green", "blue", "red"];
2587
2336
 
2588
2337
  class DevStreamView {
2589
- #logger = new Logger5("akan start");
2338
+ #logger = new Logger4("akan start");
2590
2339
  #partial = new Map;
2591
2340
  #lastState = new Map;
2592
2341
  #color = new Map;
@@ -2690,8 +2439,9 @@ class DevBootConcurrency {
2690
2439
 
2691
2440
  // pkgs/@akanjs/cli/application/devSessionLog.ts
2692
2441
  import { mkdir, rename } from "fs/promises";
2693
- import path6 from "path";
2442
+ import path5 from "path";
2694
2443
  import dayjs from "dayjs";
2444
+
2695
2445
  class DevSessionLog {
2696
2446
  static fileName = "dev.log";
2697
2447
  static previousFileName = "dev.prev.log";
@@ -2713,19 +2463,19 @@ class DevSessionLog {
2713
2463
  return [...this.#appNames];
2714
2464
  }
2715
2465
  pathOf(app) {
2716
- return path6.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2466
+ return path5.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2717
2467
  }
2718
2468
  previousPathOf(app) {
2719
- return path6.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2469
+ return path5.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2720
2470
  }
2721
2471
  relativePathOf(app) {
2722
- return path6.relative(this.#workspaceRoot, this.pathOf(app));
2472
+ return path5.relative(this.#workspaceRoot, this.pathOf(app));
2723
2473
  }
2724
2474
  async open() {
2725
2475
  const stamp = dayjs(this.#now()).format("YYYY-MM-DD HH:mm:ss");
2726
2476
  for (const app of this.#appNames) {
2727
2477
  const file = this.pathOf(app);
2728
- await mkdir(path6.dirname(file), { recursive: true });
2478
+ await mkdir(path5.dirname(file), { recursive: true });
2729
2479
  await rename(file, this.previousPathOf(app)).catch(() => {
2730
2480
  return;
2731
2481
  });
@@ -2769,7 +2519,7 @@ class DevSessionLog {
2769
2519
  this.#writers.clear();
2770
2520
  }
2771
2521
  #runtimeDirOf(app) {
2772
- return path6.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2522
+ return path5.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2773
2523
  }
2774
2524
  #writeLine(app, text) {
2775
2525
  const writer = this.#writers.get(app);
@@ -2792,6 +2542,16 @@ class DevSessionLog {
2792
2542
 
2793
2543
  // pkgs/@akanjs/cli/application/devSupervisor.ts
2794
2544
  class DevSupervisor {
2545
+ static resolveDevUi(plain, {
2546
+ isTty = !!process.stdout.isTTY,
2547
+ columns = process.stdout.columns ?? 0
2548
+ } = {}) {
2549
+ if (plain)
2550
+ return { mode: "stream", downgraded: false };
2551
+ if (!isTty || columns <= 0)
2552
+ return { mode: "stream", downgraded: true };
2553
+ return { mode: "tui", downgraded: false };
2554
+ }
2795
2555
  static supervisedEnvKey = "AKAN_DEV_SUPERVISED";
2796
2556
  static childHooks() {
2797
2557
  if (process.env[DevSupervisor.supervisedEnvKey] !== "1" || !process.send)
@@ -2855,16 +2615,14 @@ class DevSupervisor {
2855
2615
  return ports;
2856
2616
  }
2857
2617
  #makeChild(app, port) {
2858
- let markReady;
2859
- const ready = new Promise((resolve) => {
2860
- markReady = resolve;
2861
- });
2618
+ const { promise: ready, resolve: markReady } = Promise.withResolvers();
2862
2619
  return {
2863
2620
  status: {
2864
2621
  app,
2865
2622
  name: app.name,
2866
2623
  port,
2867
2624
  url: `http://localhost:${port}`,
2625
+ shareUrl: this.#options.shares?.get(app.name) ?? null,
2868
2626
  state: "starting",
2869
2627
  detail: "queued",
2870
2628
  exitCode: null
@@ -2894,16 +2652,18 @@ class DevSupervisor {
2894
2652
  }
2895
2653
  }
2896
2654
  async#waitForReady(child) {
2897
- let timer = null;
2655
+ if (await DevSupervisor.timesOut(child.ready, DevSupervisor.readyTimeoutMs))
2656
+ this.#note(`${child.status.name} has not reported ready after ${Math.round(DevSupervisor.readyTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
2657
+ }
2658
+ static async timesOut(work, ms) {
2659
+ let timer;
2898
2660
  const expired = new Promise((resolve) => {
2899
- timer = setTimeout(() => resolve("expired"), DevSupervisor.readyTimeoutMs);
2661
+ timer = setTimeout(() => resolve(true), ms);
2900
2662
  });
2901
2663
  try {
2902
- if (await Promise.race([child.ready.then(() => "ready"), expired]) === "expired")
2903
- this.#note(`${child.status.name} has not reported ready after ${Math.round(DevSupervisor.readyTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
2664
+ return await Promise.race([work.then(() => false), expired]);
2904
2665
  } finally {
2905
- if (timer)
2906
- clearTimeout(timer);
2666
+ clearTimeout(timer);
2907
2667
  }
2908
2668
  }
2909
2669
  static childArgs(name, { write = true } = {}) {
@@ -3025,44 +2785,68 @@ class DevSupervisor {
3025
2785
  for (const child of running)
3026
2786
  child.proc.kill("SIGTERM");
3027
2787
  const exited = Promise.all(running.map(async (child) => await child.proc.exited));
3028
- let timer = null;
3029
- const expired = new Promise((resolve) => {
3030
- timer = setTimeout(() => resolve("expired"), DevSupervisor.shutdownGraceMs);
3031
- });
3032
- try {
3033
- if (await Promise.race([exited.then(() => "exited"), expired]) === "expired") {
3034
- for (const child of running) {
3035
- if (!child.proc.killed) {
3036
- this.#note(`${child.status.name} did not exit in time; killing it`, "warn");
3037
- child.proc.kill("SIGKILL");
3038
- }
3039
- }
3040
- }
3041
- } finally {
3042
- if (timer)
3043
- clearTimeout(timer);
2788
+ if (!await DevSupervisor.timesOut(exited, DevSupervisor.shutdownGraceMs))
2789
+ return;
2790
+ for (const child of running) {
2791
+ if (child.proc.killed)
2792
+ continue;
2793
+ this.#note(`${child.status.name} did not exit in time; killing it`, "warn");
2794
+ child.proc.kill("SIGKILL");
3044
2795
  }
3045
2796
  }
3046
2797
  }
3047
2798
 
3048
- // pkgs/@akanjs/cli/application/devUiMode.ts
3049
- var resolveDevUi = (plain, { isTty = !!process.stdout.isTTY, columns = process.stdout.columns ?? 0 } = {}) => {
3050
- if (plain)
3051
- return { mode: "stream", downgraded: false };
3052
- if (!isTty || columns <= 0)
3053
- return { mode: "stream", downgraded: true };
3054
- return { mode: "tui", downgraded: false };
3055
- };
2799
+ // pkgs/@akanjs/cli/application/interruptTeardown.ts
2800
+ import { Logger as Logger5 } from "akanjs/common";
2801
+
2802
+ class InterruptTeardown {
2803
+ #teardowns = [];
2804
+ #exit;
2805
+ #listen;
2806
+ #report;
2807
+ #interrupted = false;
2808
+ ownsExit = true;
2809
+ constructor({ exit, listen, report } = {}) {
2810
+ this.#exit = exit ?? ((code) => process.exit(code));
2811
+ this.#listen = listen ?? ((onSignal) => {
2812
+ process.on("SIGINT", onSignal);
2813
+ });
2814
+ this.#report = report ?? ((message) => Logger5.rawLog(message, undefined, "error"));
2815
+ }
2816
+ add(run, abandoned) {
2817
+ this.#teardowns.push({ run, abandoned, running: false });
2818
+ if (this.#teardowns.length === 1)
2819
+ this.#listen(() => this.#onSignal());
2820
+ }
2821
+ #onSignal() {
2822
+ if (this.#interrupted) {
2823
+ for (const one of this.#teardowns)
2824
+ if (one.running)
2825
+ this.#report(one.abandoned);
2826
+ this.#exit(130);
2827
+ return;
2828
+ }
2829
+ this.#interrupted = true;
2830
+ Promise.all(this.#teardowns.map(async (one) => {
2831
+ one.running = true;
2832
+ await one.run().catch(() => {
2833
+ return;
2834
+ });
2835
+ one.running = false;
2836
+ })).finally(() => {
2837
+ if (this.ownsExit)
2838
+ this.#exit(0);
2839
+ });
2840
+ }
2841
+ }
3056
2842
 
3057
2843
  // pkgs/@akanjs/cli/application/application.script.ts
3058
2844
  class ApplicationScript extends script("application", [ApplicationRunner, LibraryScript]) {
3059
2845
  static dbShutdownTimeoutMs = 20000;
2846
+ #interrupt = new InterruptTeardown;
3060
2847
  async confirmDatabaseModeDependencyInstall(databaseMode, installSpecs) {
3061
2848
  return await confirm({
3062
- message: [
3063
- `Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}.`,
3064
- "Install them now?"
3065
- ].join(" "),
2849
+ message: `Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}. Install them now?`,
3066
2850
  default: true
3067
2851
  });
3068
2852
  }
@@ -3073,61 +2857,16 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3073
2857
  const shouldInstall = await this.confirmDatabaseModeDependencyInstall(databaseMode, installSpecs);
3074
2858
  if (!shouldInstall)
3075
2859
  throw new Error(`Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}.`);
3076
- const spinner = app.workspace.spinning(`Installing database dependencies for ${databaseMode} mode...`);
3077
- try {
3078
- await app.workspace.spawn("bun", ["add", ...installSpecs], {
3079
- stdio: "inherit"
3080
- });
3081
- await app.workspace.getPackageJson({ refresh: true });
3082
- spinner.succeed(`Installed database dependencies for ${databaseMode} mode`);
3083
- } catch (error) {
3084
- spinner.fail(`Failed to install database dependencies for ${databaseMode} mode`);
3085
- throw error;
3086
- }
3087
- }
3088
- async confirmMobileDependencyInstall(installSpecs) {
3089
- return await confirm({
3090
- message: [`Mobile builds require missing dependencies: ${installSpecs.join(", ")}.`, "Install them now?"].join(" "),
3091
- default: true
3092
- });
2860
+ await this.#addDependencies(app, installSpecs, `database dependencies for ${databaseMode} mode`);
3093
2861
  }
3094
- async syncMobileDependencies(app, akanConfig) {
3095
- const installSpecs = akanConfig.getMissingMobileDependencySpecs();
3096
- if (installSpecs.length === 0)
3097
- return;
3098
- const shouldInstall = await this.confirmMobileDependencyInstall(installSpecs);
3099
- if (!shouldInstall)
3100
- throw new Error(`Mobile builds require missing dependencies: ${installSpecs.join(", ")}.`);
3101
- const spinner = app.workspace.spinning("Installing mobile dependencies...");
2862
+ async#addDependencies(app, installSpecs, what) {
2863
+ const spinner = app.workspace.spinning(`Installing ${what}...`);
3102
2864
  try {
3103
- await app.workspace.spawn("bun", ["add", ...installSpecs], {
3104
- stdio: "inherit"
3105
- });
2865
+ await app.workspace.spawn("bun", ["add", ...installSpecs], { stdio: "inherit" });
3106
2866
  await app.workspace.getPackageJson({ refresh: true });
3107
- spinner.succeed("Installed mobile dependencies");
3108
- } catch (error) {
3109
- spinner.fail("Failed to install mobile dependencies");
3110
- throw error;
3111
- }
3112
- }
3113
- async syncMobileAppCapacitorPlugins(app, akanConfig) {
3114
- const plugins = akanConfig.getMobileAppCapacitorPlugins();
3115
- if (plugins.length === 0)
3116
- return;
3117
- const packageJson = await app.getPackageJson({ refresh: true });
3118
- const dependencies = packageJson.dependencies ?? {};
3119
- const missing = plugins.filter((plugin) => !dependencies[plugin]);
3120
- if (missing.length === 0)
3121
- return;
3122
- const spinner = app.workspace.spinning(`Adding default Capacitor plugins to ${app.name}...`);
3123
- try {
3124
- packageJson.dependencies = { ...dependencies, ...Object.fromEntries(missing.map((plugin) => [plugin, "*"])) };
3125
- await app.setPackageJson(packageJson);
3126
- await app.workspace.spawn("bun", ["install"], { stdio: "inherit" });
3127
- await app.getPackageJson({ refresh: true });
3128
- spinner.succeed(`Added default Capacitor plugins to ${app.name}: ${missing.join(", ")}`);
2867
+ spinner.succeed(`Installed ${what}`);
3129
2868
  } catch (error) {
3130
- spinner.fail(`Failed to add default Capacitor plugins to ${app.name}`);
2869
+ spinner.fail(`Failed to install ${what}`);
3131
2870
  throw error;
3132
2871
  }
3133
2872
  }
@@ -3156,17 +2895,6 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3156
2895
  else
3157
2896
  await this.libraryScript.syncLibrary(sys);
3158
2897
  }
3159
- async compact(sys, { module = null, minLines, interactive = false } = {}) {
3160
- const { scanned, reports } = await this.applicationRunner.compact(sys, { module, minLines, interactive });
3161
- if (!reports.length) {
3162
- Logger6.rawLog(`No abstract file long enough to compact in ${sys.name} (${scanned} scanned)`);
3163
- return;
3164
- }
3165
- for (const report of reports)
3166
- Logger6.rawLog(`${report.status}: ${report.path} (${report.beforeLines} -> ${report.afterLines} lines)`);
3167
- const compactedNum = reports.filter((report) => report.status === "compacted").length;
3168
- Logger6.rawLog(`Compacted ${compactedNum}/${reports.length} abstract files in ${sys.name}`);
3169
- }
3170
2898
  async script(app, filename) {
3171
2899
  const scriptFilename = filename ?? await this.applicationRunner.getScriptFilename(app);
3172
2900
  await app.scanSync();
@@ -3184,10 +2912,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3184
2912
  if (!quiet)
3185
2913
  Logger6.rawLog(`Creating an optimized production build for ${app.name}...`);
3186
2914
  try {
3187
- const result = await this.applicationRunner.build(app, {
3188
- fast,
3189
- spinner: !quiet
3190
- });
2915
+ const result = await this.applicationRunner.build(app, { fast, spinner: !quiet });
3191
2916
  Logger6.rawLog(`${app.name} built in dist/apps/${app.name}`);
3192
2917
  if (!quiet)
3193
2918
  ApplicationBuildReporter.printSummary(result);
@@ -3212,8 +2937,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3212
2937
  async test(exec, { write = true } = {}) {
3213
2938
  if (exec instanceof LibExecutor) {
3214
2939
  await this.libraryScript.syncLibrary(exec);
3215
- const spinner = exec.spinning(`Preparing ${exec.name}...`);
3216
- spinner.succeed(`${exec.name} prepared`);
2940
+ exec.spinning(`Preparing ${exec.name}...`).succeed(`${exec.name} prepared`);
3217
2941
  await this.applicationRunner.test(exec);
3218
2942
  return;
3219
2943
  }
@@ -3230,23 +2954,33 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3230
2954
  }
3231
2955
  await this.applicationRunner.test(exec);
3232
2956
  }
3233
- async start(apps, { open = false, dbup = true, write = true, plain = false, kill = false, concurrency = null } = {}) {
2957
+ async start(apps, {
2958
+ open = false,
2959
+ dbup = true,
2960
+ write = true,
2961
+ plain = false,
2962
+ kill = false,
2963
+ share = false,
2964
+ concurrency = null
2965
+ } = {}) {
3234
2966
  const first = apps[0];
3235
2967
  if (!first)
3236
2968
  throw new Error("No app selected to start");
3237
- const { mode, downgraded } = resolveDevUi(plain);
2969
+ const { mode, downgraded } = DevSupervisor.resolveDevUi(plain);
3238
2970
  if (downgraded)
3239
2971
  first.workspace.log("no sized terminal to draw on; printing prefixed lines instead");
3240
2972
  if (kill)
3241
2973
  await this.reclaimDevPorts(apps);
2974
+ const shares = share ? await this.#shareOnInterrupt(apps) : null;
3242
2975
  if (mode === "stream" && apps.length === 1)
3243
2976
  return await this.startOne(first, { open, dbup, write, ...DevSupervisor.childHooks() });
3244
- await this.#startMany(apps, { open, dbup, write, mode, concurrency });
2977
+ this.#interrupt.ownsExit = false;
2978
+ await this.#startMany(apps, { open, dbup, write, mode, concurrency, shares });
3245
2979
  }
3246
2980
  async startOne(app, { open = false, dbup = true, stdio = "inherit", write = true, onDevEvent } = {}) {
3247
2981
  await app.scanSync({ write });
3248
2982
  const akanConfig = await app.getConfig();
3249
- const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
2983
+ const databaseMode = akanConfig.resolveDatabaseMode();
3250
2984
  await this.syncDatabaseModeDependencies(app, akanConfig, databaseMode);
3251
2985
  if (app.getEnv() === "local" && dbup && databaseMode !== "single") {
3252
2986
  const wasDbAlreadyUp = await this.dbup(app.workspace, databaseMode);
@@ -3254,15 +2988,12 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3254
2988
  this.#stopDatabaseOnInterrupt(app.workspace);
3255
2989
  }
3256
2990
  const spinner = app.spinning("Preparing backend...");
3257
- const akanAppHost = await this.applicationRunner.start(app, {
2991
+ return await this.applicationRunner.start(app, {
3258
2992
  open,
3259
- onStart: () => {
3260
- spinner.succeed(`${app.name} prepared, ready to start`);
3261
- },
2993
+ onStart: () => spinner.succeed(`${app.name} prepared, ready to start`),
3262
2994
  onDevEvent,
3263
2995
  stdio
3264
2996
  });
3265
- return akanAppHost;
3266
2997
  }
3267
2998
  async reclaimDevPorts(apps) {
3268
2999
  const workspace = apps[0]?.workspace;
@@ -3277,13 +3008,20 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3277
3008
  if (killed.length === 0 && foreign.length === 0)
3278
3009
  workspace.log("dev ports are free; nothing to kill");
3279
3010
  }
3280
- async#startMany(apps, { open, dbup, write, mode, concurrency }) {
3011
+ async#startMany(apps, {
3012
+ open,
3013
+ dbup,
3014
+ write,
3015
+ mode,
3016
+ concurrency,
3017
+ shares
3018
+ }) {
3281
3019
  const workspace = apps[0]?.workspace;
3282
3020
  if (!workspace)
3283
3021
  throw new Error("No app selected to start");
3284
3022
  const startedDatabase = dbup ? await this.#prepareSharedDatabase(apps) : false;
3285
- const supervisor = new DevSupervisor({ apps, mode, concurrency, open, write });
3286
- const view = mode === "tui" ? await (await import("./devTuiView-c08ccbat.js")).createDevTuiView(supervisor) : new DevStreamView(apps.map((app) => app.name));
3023
+ const supervisor = new DevSupervisor({ apps, mode, concurrency, open, write, shares });
3024
+ const view = mode === "tui" ? new (await import("./devTui-q7k7bakd.js")).DevTui(supervisor) : new DevStreamView(apps.map((app) => app.name));
3287
3025
  await supervisor.run(view);
3288
3026
  if (startedDatabase)
3289
3027
  await this.#stopDatabase(workspace);
@@ -3297,7 +3035,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3297
3035
  if (app.getEnv() !== "local")
3298
3036
  continue;
3299
3037
  const akanConfig = await app.getConfig();
3300
- const mode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
3038
+ const mode = akanConfig.resolveDatabaseMode();
3301
3039
  await this.syncDatabaseModeDependencies(app, akanConfig, mode);
3302
3040
  if (mode !== "single")
3303
3041
  modes.add(mode);
@@ -3308,82 +3046,60 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3308
3046
  started = true;
3309
3047
  return started;
3310
3048
  }
3311
- async buildIos(app, { write = true, target, env = "debug", regenerate = false } = {}) {
3049
+ async buildIos(app, { write = true, ...options } = {}) {
3312
3050
  await app.scanSync({ write });
3313
- await this.applicationRunner.buildIos(app, { target, env, regenerate });
3051
+ await this.applicationRunner.buildMobile(app, "ios", options);
3314
3052
  }
3315
- async startIos(app, {
3316
- open = false,
3317
- operation = "local",
3318
- env = "local",
3319
- write = true,
3320
- target,
3321
- device,
3322
- regenerate = false,
3323
- noAllowProvisioningUpdates = false
3324
- } = {}) {
3053
+ async buildAndroid(app, { write = true, ...options } = {}) {
3325
3054
  await app.scanSync({ write });
3326
- const akanConfig = await app.getConfig();
3327
- await this.syncMobileDependencies(app, akanConfig);
3328
- await this.syncMobileAppCapacitorPlugins(app, akanConfig);
3329
- await this.applicationRunner.startIos(app, {
3330
- open,
3331
- operation,
3332
- env,
3333
- target,
3334
- device,
3335
- regenerate,
3336
- noAllowProvisioningUpdates
3337
- });
3055
+ await this.applicationRunner.buildMobile(app, "android", options);
3338
3056
  }
3339
- async releaseIos(app, { write = true, target, env = "main", regenerate = false, allowLocalRelease = false } = {}) {
3057
+ async startIos(app, { write = true, ...options } = {}) {
3340
3058
  await app.scanSync({ write });
3341
- if (env === "local" && !allowLocalRelease)
3342
- throw new Error("releaseIos --env local is blocked. Pass allowLocalRelease only for explicit local release testing.");
3343
- await this.applicationRunner.releaseIos(app, { target, env, regenerate });
3059
+ await this.applicationRunner.startMobile(app, "ios", options);
3344
3060
  }
3345
- async buildAndroid(app, { write = true, target, env = "debug", regenerate = false } = {}) {
3061
+ async startAndroid(app, { write = true, ...options } = {}) {
3346
3062
  await app.scanSync({ write });
3347
- await this.applicationRunner.buildAndroid(app, { target, env, regenerate });
3063
+ await this.applicationRunner.startMobile(app, "android", options);
3348
3064
  }
3349
- async startAndroid(app, {
3350
- open = false,
3351
- operation = "local",
3352
- env = "local",
3065
+ async startDesktop(app, { write = true, ...options } = {}) {
3066
+ await app.scanSync({ write });
3067
+ await this.applicationRunner.startDesktop(app, options);
3068
+ }
3069
+ async releaseIos(app, {
3353
3070
  write = true,
3354
- target,
3355
- regenerate = false
3071
+ allowLocalRelease = false,
3072
+ ...options
3356
3073
  } = {}) {
3357
3074
  await app.scanSync({ write });
3358
- const akanConfig = await app.getConfig();
3359
- await this.syncMobileDependencies(app, akanConfig);
3360
- await this.syncMobileAppCapacitorPlugins(app, akanConfig);
3361
- await this.applicationRunner.startAndroid(app, {
3362
- open,
3363
- operation,
3364
- env,
3365
- target,
3366
- regenerate
3367
- });
3075
+ ApplicationScript.#assertReleaseEnv("releaseIos", options.env ?? "main", allowLocalRelease);
3076
+ await this.applicationRunner.releaseIos(app, options);
3368
3077
  }
3369
- async releaseAndroid(app, assembleType, { write = true, target, env = "main", regenerate = false, allowLocalRelease = false } = {}) {
3078
+ async releaseAndroid(app, format, {
3079
+ write = true,
3080
+ allowLocalRelease = false,
3081
+ ...options
3082
+ } = {}) {
3370
3083
  await app.scanSync({ write });
3371
- if (env === "local" && !allowLocalRelease)
3372
- throw new Error("releaseAndroid --env local is blocked. Pass allowLocalRelease only for explicit local release testing.");
3373
- await this.applicationRunner.releaseAndroid(app, assembleType, {
3374
- target,
3375
- env,
3376
- regenerate
3377
- });
3084
+ ApplicationScript.#assertReleaseEnv("releaseAndroid", options.env ?? "main", allowLocalRelease);
3085
+ await this.applicationRunner.releaseAndroid(app, format, options);
3378
3086
  }
3379
- async configureApp(app) {
3380
- await this.applicationRunner.configureApp(app);
3087
+ static #assertReleaseEnv(command, env, allowLocalRelease) {
3088
+ if (env === "local" && !allowLocalRelease)
3089
+ throw new Error(`${command} --env local is blocked. Pass allowLocalRelease only for explicit local release testing.`);
3381
3090
  }
3382
- async releaseSource(app, options) {
3383
- await this.applicationRunner.releaseSource(app, options);
3091
+ async transferDatabase(app, direction, dir) {
3092
+ await this.applicationRunner.transferDatabase(app, direction, dir);
3384
3093
  }
3385
- async codepush(app, os) {
3386
- await this.applicationRunner.codepush(app, os);
3094
+ async dbupDeclared(workspace, mode) {
3095
+ const declared = mode ? [mode] : await Promise.all((await workspace.getApps()).map(async (appName) => (await AppExecutor.from(workspace, appName).getConfig()).database.modes)).then((modes) => [...new Set(modes.flat())]);
3096
+ const needing = declared.filter((each) => each !== "single");
3097
+ if (!needing.length) {
3098
+ workspace.log("No app here declares a database mode that runs on local services; single needs none.");
3099
+ return;
3100
+ }
3101
+ for (const each of needing)
3102
+ await this.dbup(workspace, each);
3387
3103
  }
3388
3104
  async dbup(workspace, mode = "multiple") {
3389
3105
  const spinner = workspace.spinning(`Starting local database (${mode})...`);
@@ -3401,34 +3117,36 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3401
3117
  }
3402
3118
  spinner.succeed("Local database (/local/docker-compose.yaml) is down");
3403
3119
  }
3404
- #stopDatabaseOnInterrupt(workspace) {
3405
- let stopping = false;
3406
- process.on("SIGINT", () => {
3407
- if (stopping) {
3408
- Logger6.rawLog("Abandoning the local database teardown; containers are left running.", undefined, "error");
3409
- process.exit(130);
3410
- }
3411
- stopping = true;
3412
- this.#stopDatabase(workspace).finally(() => process.exit(0));
3120
+ async#shareOnInterrupt(apps) {
3121
+ const shares = [];
3122
+ const urls = new Map;
3123
+ this.#interrupt.add(async () => {
3124
+ await Promise.all(shares.map(async (share) => await share.close()));
3125
+ }, "Abandoning the tunnel release; the shares expire on their own.");
3126
+ await Promise.all(apps.map(async (app) => {
3127
+ const { TunnelShare } = await import("./TunnelShare-scf4e4pd.js");
3128
+ const share = await TunnelShare.open(app, { workspace: app.workspace });
3129
+ shares.push(share);
3130
+ urls.set(app.name, share.url);
3131
+ Logger6.rawLog(`${app.name} is shared at ${share.url}`);
3132
+ })).catch((error) => {
3133
+ Logger6.rawLog(`Could not open the tunnel: ${error instanceof Error ? error.message : String(error)}`, undefined, "error");
3413
3134
  });
3135
+ return urls;
3136
+ }
3137
+ #stopDatabaseOnInterrupt(workspace) {
3138
+ this.#interrupt.add(async () => {
3139
+ await this.#stopDatabase(workspace);
3140
+ }, "Abandoning the local database teardown; containers are left running.");
3414
3141
  }
3415
3142
  async#stopDatabase(workspace) {
3416
- let timer = null;
3417
- const timeout = new Promise((resolve) => {
3418
- timer = setTimeout(() => resolve("timeout"), ApplicationScript.dbShutdownTimeoutMs);
3143
+ const stopped = this.dbdown(workspace).catch(() => {
3144
+ return;
3419
3145
  });
3420
- try {
3421
- const result = await Promise.race([this.dbdown(workspace).catch(() => {
3422
- return;
3423
- }), timeout]);
3424
- if (result !== "timeout")
3425
- return;
3426
- Logger6.rawLog(`Local database did not stop within ${ApplicationScript.dbShutdownTimeoutMs}ms; run \`akan dbdown\` once Docker responds.`, undefined, "error");
3427
- } finally {
3428
- if (timer)
3429
- clearTimeout(timer);
3430
- }
3146
+ if (!await DevSupervisor.timesOut(stopped, ApplicationScript.dbShutdownTimeoutMs))
3147
+ return;
3148
+ Logger6.rawLog(`Local database did not stop within ${ApplicationScript.dbShutdownTimeoutMs}ms; run \`akan dbdown\` once Docker responds.`, undefined, "error");
3431
3149
  }
3432
3150
  }
3433
3151
 
3434
- export { openBrowser, plainTextOf, HOST_SOURCE, DevLogBuffer, ApplicationScript };
3152
+ export { ApplicationScript };