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

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 (187) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-awv258wb.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-1nqh35ez.js +1457 -0
  6. package/README.md +3 -0
  7. package/TunnelShare-scf4e4pd.js +9 -0
  8. package/{agent.command-d2gfxfck.js → agent.command-1s9ag7sb.js} +8 -9
  9. package/akanCode.js +102 -0
  10. package/{application.command-pj2m7584.js → application.command-49hwy6va.js} +51 -78
  11. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-ybyp97pv.js} +63 -64
  12. package/{applicationReleasePackager-4yasxwv9.js → applicationReleasePackager-esnrphw8.js} +40 -60
  13. package/buildBatch.proc.js +19 -24
  14. package/{capacitorApp-36rbr858.js → capacitorApp-1synz4en.js} +3 -4
  15. package/cloud.command-d7rqtzt9.js +94 -0
  16. package/cloud.script-bkbpcr96.js +19 -0
  17. package/code.command-nn48xzej.js +154 -0
  18. package/commandManifest.json +1 -1
  19. package/consoleToStderr-h5bryj94.js +7 -0
  20. package/context.command-s3mcmvpa.js +48 -0
  21. package/context.runner-k99nb5pk.js +21 -0
  22. package/{dependencyScanner-shs82ynp.js → dependencyScanner-ye2e6pdz.js} +2 -2
  23. package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
  24. package/fileSys-5ngz93ga.js +9 -0
  25. package/guideline.command-sct4qnt5.js +42 -0
  26. package/guidelines/agentRule/agentRule.instruction.md +206 -25
  27. package/guidelines/conventions/conventions.instruction.md +79 -24
  28. package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
  29. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  30. package/guidelines/queryRule/queryRule.instruction.md +17 -7
  31. package/guidelines/runtimeRule/runtimeRule.instruction.md +74 -0
  32. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  33. package/guidelines/transportRule/transportRule.instruction.md +51 -0
  34. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  35. package/incrementalBuilder.proc.js +31 -48
  36. package/{index-h7yw2z9n.js → index-0bq14vr1.js} +8 -14
  37. package/index-13ffe9wp.js +69 -0
  38. package/index-22d57v4c.js +266 -0
  39. package/{index-fz5q3gd7.js → index-31yr441d.js} +10 -17
  40. package/index-3hx1z96c.js +87 -0
  41. package/index-3sq321ak.js +53 -0
  42. package/{index-kcxmv48c.js → index-3t7g9e4r.js} +45 -79
  43. package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
  44. package/{index-47pj3aee.js → index-5e0n4hqx.js} +11 -12
  45. package/{index-259dsnz3.js → index-5pc1qrbv.js} +5 -8
  46. package/index-7md2g9vw.js +6 -0
  47. package/index-8mna0947.js +187 -0
  48. package/{index-zry5nz81.js → index-8sf7wmdr.js} +51 -30
  49. package/{index-2tv3fdx4.js → index-9d9bgcx4.js} +342 -765
  50. package/index-9dp031by.js +12 -0
  51. package/{index-ne2qy0nk.js → index-ahemdr5n.js} +12 -12
  52. package/{index-ws5e0j51.js → index-as7xmyz8.js} +40 -24
  53. package/index-axzjaqve.js +1118 -0
  54. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  55. package/index-c6eggbee.js +123 -0
  56. package/index-d2242ksy.js +99 -0
  57. package/{index-a64esmtb.js → index-ddxvdd1p.js} +169 -305
  58. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  59. package/{index-pnqbkf5p.js → index-dzf3r49a.js} +6 -2
  60. package/{index-w8kmx1b6.js → index-fgq75haa.js} +198 -290
  61. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  62. package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
  63. package/{index-wt7qrtd4.js → index-mxav6j3c.js} +93 -232
  64. package/{index-gyd220p2.js → index-myhc3c1j.js} +554 -577
  65. package/{index-600zhnve.js → index-nv5976x3.js} +56 -197
  66. package/index-p6pbz9n5.js +25 -0
  67. package/{index-4edzf1gr.js → index-przybvy5.js} +77 -150
  68. package/index-smme4r47.js +49 -0
  69. package/{index-mak3cd28.js → index-w2zdcw3k.js} +137 -255
  70. package/index-wa5jbxj1.js +760 -0
  71. package/{index-v0qh106m.js → index-ww4z397j.js} +527 -723
  72. package/{index-j9yhg404.js → index-xqjwd384.js} +4 -7
  73. package/{index-7737gxcp.js → index-xqpzbab5.js} +107 -241
  74. package/index-xsjh6nzh.js +2729 -0
  75. package/{index-ktb5njgy.js → index-ygn77a5w.js} +47 -99
  76. package/index-ztp3e73t.js +700 -0
  77. package/index.js +23 -22
  78. package/{library.command-nbsky672.js → library.command-1gx04jgn.js} +4 -5
  79. package/{localRegistry.command-yeq57wjr.js → localRegistry.command-zndxj1q6.js} +22 -42
  80. package/{module.command-29vqzrh8.js → module.command-3waddk95.js} +8 -11
  81. package/{package.command-axv0abmn.js → package.command-zrc7br1y.js} +6 -7
  82. package/package.json +14 -5
  83. package/{page.command-vs9s3zrh.js → page.command-vpjgardh.js} +4 -5
  84. package/{primitive.command-cbxmqbtc.js → primitive.command-sz84tk4t.js} +9 -22
  85. package/prompter-ptaa3nk0.js +8 -0
  86. package/{quality.command-gmbs7nws.js → quality.command-g869hafs.js} +203 -376
  87. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  88. package/{repair.command-bqsjptsj.js → repair.command-chsnjvvv.js} +11 -35
  89. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  90. package/{scalar.command-6k1vhe9s.js → scalar.command-2v21newx.js} +7 -10
  91. package/skills/akan-code/SKILL.md +130 -0
  92. package/skills/akan-debug/SKILL.md +42 -0
  93. package/skills/akan-deps/SKILL.md +50 -0
  94. package/skills/akan-err/SKILL.md +39 -0
  95. package/skills/akan-module/SKILL.md +45 -0
  96. package/skills/akan-scaffold/SKILL.md +76 -0
  97. package/skills/akan-scalar/SKILL.md +52 -0
  98. package/skills/akan-service/SKILL.md +34 -0
  99. package/skills/akan-store/SKILL.md +67 -0
  100. package/skills/akan-ui/SKILL.md +63 -0
  101. package/skills/akan-validate/SKILL.md +44 -0
  102. package/{subspace.command-fwjfcts7.js → subspace.command-ckcbwfp1.js} +138 -67
  103. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  104. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  105. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  106. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  107. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  108. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  109. package/templates/appSample/lib/task/task.signal.ts +1 -1
  110. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  111. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  112. package/templates/client.ts +7 -30
  113. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  114. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  115. package/templates/crudPages/new/page.tsx +2 -1
  116. package/templates/crudPages/page.tsx +2 -1
  117. package/templates/crudSinglePage/page.tsx +2 -1
  118. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  119. package/templates/lib/__lib/lib.document.ts +0 -2
  120. package/templates/lib/sig.ts +8 -30
  121. package/templates/localDev/docker-compose.yaml.template +3 -14
  122. package/templates/module/__model__.signal.ts +12 -4
  123. package/templates/pkgRoot/tsconfig.json.template +2 -7
  124. package/templates/server.ts +0 -2
  125. package/templates/service/__model__.abstract.md.template +8 -0
  126. package/tunnel.command-kd2yr4fx.js +129 -0
  127. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  128. package/typecheck.proc.js +1 -1
  129. package/workflow.command-44252cqv.js +79 -0
  130. package/{workspace.command-8s050m0h.js → workspace.command-fqcztt5p.js} +77 -109
  131. package/abstractCompactor-mjh5ppsq.js +0 -101
  132. package/cloud.command-jshwdq22.js +0 -93
  133. package/context.command-ez8a0zyz.js +0 -80
  134. package/devTuiView-c08ccbat.js +0 -9
  135. package/getDirname-dr3jz1xg.js +0 -7
  136. package/guideline.command-xmqwx8zm.js +0 -359
  137. package/guidelines/agentRule/agentRule.generate.json +0 -31
  138. package/guidelines/componentRule/componentRule.generate.json +0 -31
  139. package/guidelines/conventions/conventions.generate.json +0 -25
  140. package/guidelines/cssRule/cssRule.generate.json +0 -31
  141. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  142. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  143. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  144. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  145. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  146. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  147. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  148. package/guidelines/framework/framework.generate.json +0 -32
  149. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  150. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  151. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  152. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  153. package/guidelines/modelService/modelService.generate.json +0 -31
  154. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  155. package/guidelines/modelStore/modelStore.generate.json +0 -31
  156. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  157. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  158. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  159. package/guidelines/modelView/modelView.generate.json +0 -31
  160. package/guidelines/modelZone/modelZone.generate.json +0 -31
  161. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  162. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  163. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  164. package/guidelines/queryRule/queryRule.generate.json +0 -31
  165. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  166. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  167. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  168. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  169. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  170. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  171. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  172. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  173. package/guidelines/transportRule/transportRule.generate.json +0 -31
  174. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  175. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  176. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  177. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  178. package/index-2hq52jq3.js +0 -465
  179. package/index-6wwzs1bc.js +0 -7
  180. package/index-8pm30m73.js +0 -841
  181. package/index-kn1h7p1x.js +0 -560
  182. package/index-meh558jy.js +0 -65
  183. package/index-rwg2aa4f.js +0 -229
  184. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  185. package/templates/module/__model__.abstract.md +0 -28
  186. package/templates/service/__model__.abstract.md +0 -28
  187. package/workflow.command-nwpcdc8p.js +0 -107
@@ -1,178 +1,74 @@
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-w2zdcw3k.js";
6
40
  import {
7
41
  WorkspaceExecutor,
8
42
  AppExecutor,
9
43
  LibExecutor,
10
44
  PkgExecutor
11
- } from "./index-2tv3fdx4.js";
45
+ } from "./index-9d9bgcx4.js";
12
46
  import {
13
47
  LibraryScript
14
- } from "./index-259dsnz3.js";
48
+ } from "./index-5pc1qrbv.js";
15
49
  import {
16
- HmrWatcher,
17
- WatchRootResolver
18
- } from "./index-hwkygn9s.js";
50
+ stripAnsi
51
+ } from "./index-7md2g9vw.js";
19
52
  import {
20
53
  SlicePlanner,
21
54
  formatSlicePlan
22
- } from "./index-j9yhg404.js";
55
+ } from "./index-xqjwd384.js";
23
56
  import {
24
57
  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
- }
58
+ } from "./index-5e0n4hqx.js";
59
+ import {
60
+ openBrowser
61
+ } from "./index-9dp031by.js";
166
62
 
167
63
  // pkgs/@akanjs/cli/application/application.script.ts
168
64
  import { confirm } from "@inquirer/prompts";
169
65
  import { Logger as Logger6 } from "akanjs/common";
170
66
 
171
67
  // pkgs/@akanjs/cli/application/application.runner.ts
172
- import path5 from "path";
68
+ import path4 from "path";
173
69
 
174
70
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
175
- import { Logger as Logger3 } from "akanjs/common";
71
+ import { Logger as Logger2 } from "akanjs/common";
176
72
 
177
73
  // pkgs/@akanjs/devkit/sshTunnel.ts
178
74
  import { Client } from "ssh2";
@@ -261,7 +157,7 @@ var createSshTunnel = async (options) => {
261
157
  for (const socket of sockets)
262
158
  closeSocket(socket, error);
263
159
  closeQuietly(() => client.end());
264
- closeBunServer(server);
160
+ server.stop();
265
161
  };
266
162
  client.once("error", (error) => closeTunnel(error));
267
163
  client.once("close", () => closeTunnel());
@@ -281,22 +177,10 @@ var closeBunSocket = (socket, error) => {
281
177
  socket.data.stream?.end();
282
178
  closeQuietly(() => socket.end());
283
179
  };
284
- var closeBunServer = (server) => {
285
- server.stop();
286
- };
287
180
 
288
181
  // 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
182
  var createTunnel = async (service, { app, environment, port = service === "postgres" ? 5432 : 6379 }) => {
299
- const sshOptions = getSshTunnelOptions(app, environment);
183
+ const { serveDomain, repoName } = WorkspaceExecutor.getBaseDevEnv();
300
184
  await createSshTunnel({
301
185
  localHost: "0.0.0.0",
302
186
  localPort: port,
@@ -304,13 +188,18 @@ var createTunnel = async (service, { app, environment, port = service === "postg
304
188
  srcPort: port,
305
189
  dstHost: `${service}-0.${service}-svc.${app.name}-${environment}.svc.cluster.local`,
306
190
  dstPort: service === "postgres" ? 5432 : 6379,
307
- sshOptions
191
+ sshOptions: {
192
+ host: `${app.name}-${environment}.${serveDomain}`,
193
+ port: process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767,
194
+ username: process.env.SSH_TUNNEL_USERNAME ?? "root",
195
+ password: process.env.SSH_TUNNEL_PASSWORD ?? repoName
196
+ }
308
197
  });
309
198
  return `localhost:${port}`;
310
199
  };
311
200
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.host.ts
312
201
  import path from "path";
313
- import { Logger as Logger2 } from "akanjs/common";
202
+ import { Logger } from "akanjs/common";
314
203
  import { MemoryLimit } from "akanjs/server/memoryLimit";
315
204
  var builderMsgTypeSet = new Set([
316
205
  "build-route-res",
@@ -328,7 +217,7 @@ class IncrementalBuilderHost {
328
217
  static #restartMaxDelayMs = 30000;
329
218
  static #recycleDrainTimeoutMs = 30000;
330
219
  static #devMaxRssBytes = 1200 * 1024 * 1024;
331
- logger = new Logger2("IncrementalBuilderHost");
220
+ logger = new Logger("IncrementalBuilderHost");
332
221
  entry;
333
222
  env;
334
223
  app;
@@ -518,12 +407,8 @@ class IncrementalBuilderHost {
518
407
  const lost = [...this.#inFlight];
519
408
  this.#inFlight.clear();
520
409
  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
- }
410
+ for (const [id, type] of lost)
411
+ this.#onMessage({ type: `${type}-res`, id, ok: false, error: reason });
527
412
  }
528
413
  stop() {
529
414
  this.#manualStop = true;
@@ -592,211 +477,7 @@ class BuilderRequestRouter {
592
477
 
593
478
  // pkgs/@akanjs/devkit/akanApp/BackendImportGraph.ts
594
479
  import { stat } from "fs/promises";
595
- import path3 from "path";
596
-
597
- // pkgs/@akanjs/devkit/akanApp/devHostPolicy.ts
598
480
  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
481
  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
482
  var GRAPH_IMPORT_KINDS = new Set([
802
483
  "import-statement",
@@ -827,7 +508,7 @@ class BackendImportGraph {
827
508
  return this.#lastRefreshSucceeded;
828
509
  }
829
510
  has(file) {
830
- return this.#files.has(path3.resolve(file));
511
+ return this.#files.has(path2.resolve(file));
831
512
  }
832
513
  async fingerprint() {
833
514
  const stamps = await Promise.all([...this.#files].map(async (file) => {
@@ -855,9 +536,9 @@ class BackendImportGraph {
855
536
  const roots = await this.#entrypoints();
856
537
  const files = new Set;
857
538
  const queue = [...roots];
858
- const workspaceRoot = path3.resolve(this.#app.workspace.workspaceRoot);
539
+ const workspaceRoot = path2.resolve(this.#app.workspace.workspaceRoot);
859
540
  while (queue.length > 0) {
860
- const current = path3.resolve(queue.pop());
541
+ const current = path2.resolve(queue.pop());
861
542
  if (files.has(current))
862
543
  continue;
863
544
  if (!this.#isWorkspaceSource(current, workspaceRoot))
@@ -866,7 +547,7 @@ class BackendImportGraph {
866
547
  if (!imports)
867
548
  continue;
868
549
  files.add(current);
869
- const importerDir = path3.dirname(current);
550
+ const importerDir = path2.dirname(current);
870
551
  for (const imp of imports) {
871
552
  if (!GRAPH_IMPORT_KINDS.has(imp.kind) || !imp.path || NON_SOURCE_EXT_RE.test(imp.path))
872
553
  continue;
@@ -897,7 +578,7 @@ class BackendImportGraph {
897
578
  const roots = [`${this.#app.cwdPath}/main.ts`, `${this.#app.cwdPath}/server.ts`];
898
579
  const existing = [];
899
580
  for (const root of roots) {
900
- const abs = path3.resolve(root);
581
+ const abs = path2.resolve(root);
901
582
  if (await Bun.file(abs).exists())
902
583
  existing.push(abs);
903
584
  }
@@ -906,25 +587,25 @@ class BackendImportGraph {
906
587
  #resolve(specifier, importerDir) {
907
588
  try {
908
589
  const resolved = Bun.resolveSync(specifier, importerDir);
909
- if (!path3.isAbsolute(resolved))
590
+ if (!path2.isAbsolute(resolved))
910
591
  return null;
911
- if (!SOURCE_EXTS.has(path3.extname(resolved).toLowerCase()))
592
+ if (!SOURCE_EXTS.has(path2.extname(resolved).toLowerCase()))
912
593
  return null;
913
- return path3.resolve(resolved);
594
+ return path2.resolve(resolved);
914
595
  } catch {
915
596
  return null;
916
597
  }
917
598
  }
918
599
  #isWorkspaceSource(file, workspaceRoot) {
919
- const rel = path3.relative(workspaceRoot, file);
920
- if (rel.startsWith("..") || path3.isAbsolute(rel))
600
+ const rel = path2.relative(workspaceRoot, file);
601
+ if (rel.startsWith("..") || path2.isAbsolute(rel))
921
602
  return false;
922
- if (rel.includes(`${path3.sep}node_modules${path3.sep}`) || rel.includes(`${path3.sep}.akan${path3.sep}`))
603
+ if (rel.includes(`${path2.sep}node_modules${path2.sep}`) || rel.includes(`${path2.sep}.akan${path2.sep}`))
923
604
  return false;
924
- return SOURCE_EXTS.has(path3.extname(file).toLowerCase());
605
+ return SOURCE_EXTS.has(path2.extname(file).toLowerCase());
925
606
  }
926
607
  #scanImports(file, source) {
927
- const ext = path3.extname(file).toLowerCase();
608
+ const ext = path2.extname(file).toLowerCase();
928
609
  if (ext === ".tsx")
929
610
  return this.#tsxTranspiler.scanImports(source);
930
611
  if (ext === ".jsx")
@@ -937,6 +618,7 @@ class BackendImportGraph {
937
618
 
938
619
  // pkgs/@akanjs/devkit/akanApp/akanApp.host.ts
939
620
  var backendMsgTypeSet = new Set(["build-route", "build-csr"]);
621
+ var asMib = (bytes) => Math.round(bytes / 1024 / 1024);
940
622
  var BACKEND_RESTART_DEBOUNCE_MS = 120;
941
623
  var BACKEND_GRACEFUL_TIMEOUT_MS = 8000;
942
624
  var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
@@ -952,7 +634,7 @@ var BUILDER_RECOVERY_MAX_DELAY_MS = 60000;
952
634
 
953
635
  class AkanAppHost {
954
636
  app;
955
- logger = new Logger3("AkanAppHost");
637
+ logger = new Logger2("AkanAppHost");
956
638
  stdio;
957
639
  env;
958
640
  #onDevEvent;
@@ -1030,23 +712,18 @@ class AkanAppHost {
1030
712
  async stop() {
1031
713
  this.#cancelIdleSuspend();
1032
714
  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
- }
715
+ this.#clearRestartTimers();
1045
716
  this.#failPendingBuilderMessages("dev server is shutting down");
1046
717
  await this.#stopBackend();
1047
718
  this.#stopBuilder();
1048
719
  return this;
1049
720
  }
721
+ #clearRestartTimers() {
722
+ for (const timer of [this.#restartTimer, this.#backendRecoveryTimer, this.#builderRecoveryTimer])
723
+ if (timer)
724
+ clearTimeout(timer);
725
+ this.#restartTimer = this.#backendRecoveryTimer = this.#builderRecoveryTimer = null;
726
+ }
1050
727
  kill() {
1051
728
  this.stop();
1052
729
  }
@@ -1342,7 +1019,6 @@ ${this.#backendStderrTail.join(`
1342
1019
  if (this.#rssCeilingAbandoned)
1343
1020
  return;
1344
1021
  const ceilingBytes = IncrementalBuilderHost.maxRssBytes();
1345
- const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
1346
1022
  const decision = decideBuilderRssRecycle({
1347
1023
  rssBytes: metrics.rssBytes,
1348
1024
  ceilingBytes,
@@ -1380,7 +1056,6 @@ ${this.#backendStderrTail.join(`
1380
1056
  if (!isRssCeilingUnreachable(freshRssBytes, ceilingBytes))
1381
1057
  return;
1382
1058
  this.#rssCeilingAbandoned = true;
1383
- const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
1384
1059
  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
1060
  }
1386
1061
  #armRssRecycle(reason, over) {
@@ -1407,7 +1082,6 @@ ${this.#backendStderrTail.join(`
1407
1082
  }, BUILDER_RSS_RECYCLE_QUIET_MS);
1408
1083
  }
1409
1084
  async#recycleBuilderForRssWhenStillOver(reason, { rssBytes, ceilingBytes }) {
1410
- const asMib = (bytes) => Math.round(bytes / 1024 / 1024);
1411
1085
  if (decideBuilderRssSettle({ rssBytes, ceilingBytes }) === "recycle-now") {
1412
1086
  this.#recycleBuilderForRss(reason);
1413
1087
  return;
@@ -1433,16 +1107,18 @@ ${this.#backendStderrTail.join(`
1433
1107
  this.#recycleBuilderForRss(settledBytes === null ? reason : `${reason}; still ${asMib(settledBytes)}MiB after settling`);
1434
1108
  }
1435
1109
  static async readProcessRssBytes(pid) {
1110
+ if (process.platform === "win32")
1111
+ return await AkanAppHost.#readRssKbVia(["tasklist", "/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], AkanAppHost.#tasklistRssKb);
1436
1112
  const status = await Bun.file(`/proc/${pid}/status`).text().catch(() => null);
1437
1113
  const vmRssKb = status === null ? null : /VmRSS:\s+(\d+) kB/.exec(status)?.[1];
1438
1114
  if (vmRssKb)
1439
1115
  return Number(vmRssKb) * 1024;
1440
- return await AkanAppHost.#readRssViaPs(pid);
1116
+ return await AkanAppHost.#readRssKbVia(["ps", "-o", "rss=", "-p", String(pid)], (output) => Number(output.trim()));
1441
1117
  }
1442
- static async#readRssViaPs(pid, timeoutMs = PS_RSS_TIMEOUT_MS) {
1118
+ static async#readRssKbVia(command, parseKb, timeoutMs = PS_RSS_TIMEOUT_MS) {
1443
1119
  let proc;
1444
1120
  try {
1445
- proc = Bun.spawn(["ps", "-o", "rss=", "-p", String(pid)], { stdio: ["ignore", "pipe", "ignore"] });
1121
+ proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"] });
1446
1122
  } catch {
1447
1123
  return null;
1448
1124
  }
@@ -1450,7 +1126,7 @@ ${this.#backendStderrTail.join(`
1450
1126
  try {
1451
1127
  const output = await new Response(proc.stdout).text();
1452
1128
  await proc.exited;
1453
- const rssKb = Number(output.trim());
1129
+ const rssKb = parseKb(output);
1454
1130
  return Number.isFinite(rssKb) && rssKb > 0 ? rssKb * 1024 : null;
1455
1131
  } catch {
1456
1132
  return null;
@@ -1458,6 +1134,10 @@ ${this.#backendStderrTail.join(`
1458
1134
  clearTimeout(killer);
1459
1135
  }
1460
1136
  }
1137
+ static #tasklistRssKb(output) {
1138
+ const memUsage = /"([^"]*)"\s*$/m.exec(output)?.[1];
1139
+ return memUsage ? Number(memUsage.replace(/\D/g, "")) : Number.NaN;
1140
+ }
1461
1141
  #cancelRssRecycle() {
1462
1142
  this.#rssRecycleReason = null;
1463
1143
  this.#rssRecycleOver = null;
@@ -1593,7 +1273,7 @@ ${this.#backendStderrTail.join(`
1593
1273
  await this.#backendGraph.refresh();
1594
1274
  await this.#startBuilder({ announceBootState: true });
1595
1275
  const missed = await this.#takeBuilderGapChanges();
1596
- const backendFiles = [...new Set([...files.filter((file) => this.#isBackendFile(file)), ...missed])];
1276
+ const backendFiles = [...new Set([...files.filter((file) => this.#backendGraph.has(file)), ...missed])];
1597
1277
  if (backendFiles.length === 0)
1598
1278
  return;
1599
1279
  this.logger.verbose(`[idle-suspend] ${backendFiles.length} backend file(s) changed while suspended`);
@@ -1648,10 +1328,6 @@ ${this.#backendStderrTail.join(`
1648
1328
  for (const message of pending)
1649
1329
  this.#sendToBuilder(message);
1650
1330
  }
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
1331
  #discardPendingBuilderMessages(reason) {
1656
1332
  const held = this.#pendingBuilderMessages.splice(0);
1657
1333
  if (held.length === 0)
@@ -1663,12 +1339,17 @@ ${this.#backendStderrTail.join(`
1663
1339
  if (held.length === 0)
1664
1340
  return;
1665
1341
  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
- }
1342
+ for (const message of held)
1343
+ this.#failBuilderRequest(message, reason);
1344
+ }
1345
+ #failBuilderRequest(message, error) {
1346
+ if (message.type === "build-route")
1347
+ this.#sendToBackend({ type: "build-route-res", id: message.id, ok: false, error });
1348
+ else if (message.type === "build-csr")
1349
+ this.#sendToBackend({ type: "build-csr-res", id: message.id, ok: false, error });
1350
+ else
1351
+ return false;
1352
+ return true;
1672
1353
  }
1673
1354
  #recycleBuilderForRss(reason) {
1674
1355
  if (this.#pendingRecycle || this.#restartTimer) {
@@ -1789,18 +1470,7 @@ ${this.#backendStderrTail.join(`
1789
1470
  }
1790
1471
  async#recycleDevChildren(message, { refreshConfig = false } = {}) {
1791
1472
  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
- }
1473
+ this.#clearRestartTimers();
1804
1474
  this.#builderRecoveryAttempts = 0;
1805
1475
  this.#pendingRestartReason = null;
1806
1476
  this.#lastGoodFrontend = {};
@@ -1890,7 +1560,7 @@ ${this.#backendStderrTail.join(`
1890
1560
  }
1891
1561
  if (message.kinds.includes("code"))
1892
1562
  await this.#backendGraph.refresh();
1893
- if (message.files.some((file) => this.#isBackendFile(file)))
1563
+ if (message.files.some((file) => this.#backendGraph.has(file)))
1894
1564
  return true;
1895
1565
  if (!this.#backendGraph.lastRefreshSucceeded) {
1896
1566
  const fallbackFiles = message.files.filter((file) => isLegacyBackendFallbackFile(file, this.app.workspace.workspaceRoot));
@@ -1901,9 +1571,6 @@ ${this.#backendStderrTail.join(`
1901
1571
  }
1902
1572
  return false;
1903
1573
  }
1904
- #isBackendFile(file) {
1905
- return this.#backendGraph.has(file);
1906
- }
1907
1574
  async#startBuilder({
1908
1575
  announceBootState = false
1909
1576
  } = {}) {
@@ -1989,27 +1656,12 @@ ${this.#backendStderrTail.join(`
1989
1656
  return;
1990
1657
  const status = this.#builder?.status ?? "stopped";
1991
1658
  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
- });
1659
+ this.#pendingBuilderMessages.push(message);
1660
+ this.logger.verbose(`[builder] holding ${message.type} until the builder is ready (${this.#pendingBuilderMessages.length} waiting)`);
2002
1661
  return;
2003
1662
  }
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
- });
1663
+ if (this.#failBuilderRequest(message, `builder is ${status}; reload after the builder is ready`))
2011
1664
  return;
2012
- }
2013
1665
  this.logger.warn("akanAppHost builder is not running");
2014
1666
  }
2015
1667
  #stopBuilder() {
@@ -2021,25 +1673,20 @@ ${this.#backendStderrTail.join(`
2021
1673
  }
2022
1674
  }
2023
1675
  // pkgs/@akanjs/devkit/applicationTestPreload.ts
2024
- import path4 from "path";
1676
+ import path3 from "path";
2025
1677
  var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
2026
1678
  async function resolveSignalTestPreloadPath(target) {
2027
1679
  const candidates = [];
2028
- const addResolvedPackageCandidate = (basePath) => {
1680
+ for (const basePath of [target.cwdPath, process.cwd(), path3.dirname(Bun.main), import.meta.dir]) {
2029
1681
  try {
2030
- candidates.push(path4.join(path4.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
1682
+ candidates.push(path3.join(path3.dirname(Bun.resolveSync("akanjs/package.json", basePath)), SIGNAL_TEST_PRELOAD_PATH));
2031
1683
  } 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));
1684
+ }
1685
+ 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
1686
  const uniqueCandidates = [...new Set(candidates)];
2039
- for (const candidate of uniqueCandidates) {
1687
+ for (const candidate of uniqueCandidates)
2040
1688
  if (await Bun.file(candidate).exists())
2041
1689
  return candidate;
2042
- }
2043
1690
  throw new Error(`Failed to locate ${SIGNAL_TEST_PRELOAD_PATH} from ${target.cwdPath}.
2044
1691
  Probed paths:
2045
1692
  ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
@@ -2047,12 +1694,10 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
2047
1694
  }
2048
1695
 
2049
1696
  // 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;
1697
+ import { Logger as Logger3 } from "akanjs/common";
1698
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-ybyp97pv.js")).ApplicationBuildRunner;
1699
+ var loadReleasePackager = async () => (await import("./applicationReleasePackager-esnrphw8.js")).ApplicationReleasePackager;
1700
+ var loadCapacitorApp = async () => (await import("./capacitorApp-1synz4en.js")).CapacitorApp;
2056
1701
  var loadPrompts = async () => await import("@inquirer/prompts");
2057
1702
 
2058
1703
  class ApplicationRunner extends runner("application") {
@@ -2071,9 +1716,6 @@ class ApplicationRunner extends runner("application") {
2071
1716
  async removeApplication(app) {
2072
1717
  await app.workspace.removeDir(`apps/${app.name}`);
2073
1718
  }
2074
- async getConfig(app) {
2075
- return await app.getConfig();
2076
- }
2077
1719
  async getScriptFilename(app) {
2078
1720
  if (!await app.exists("script")) {
2079
1721
  await app.mkdir("script");
@@ -2095,12 +1737,12 @@ class ApplicationRunner extends runner("application") {
2095
1737
  if (!await app.exists(scriptPath))
2096
1738
  throw new Error(`Script file not found: apps/${app.name}/${scriptPath}`);
2097
1739
  await app.spawn("bun", [scriptPath], {
2098
- env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script" }),
1740
+ env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script", ...await app.getDatabaseModeEnv() }),
2099
1741
  stdio: "inherit"
2100
1742
  });
2101
1743
  }
2102
1744
  #runtimeDirOf(app, override) {
2103
- return path5.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path5.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
1745
+ return path4.resolve(override ?? process.env.AKAN_RUNTIME_DIR ?? path4.join(app.workspace.workspaceRoot, "local", "apps", app.name, "runtime"));
2104
1746
  }
2105
1747
  async runLogs(app, options) {
2106
1748
  const { LogControlUnavailableError, LogTailClient } = await import("akanjs/server/logging/logTailClient");
@@ -2110,13 +1752,10 @@ class ApplicationRunner extends runner("application") {
2110
1752
  const { level, grep, endpoint, trace, child, role, origin, since } = options;
2111
1753
  const query = Object.fromEntries(Object.entries({ level, grep, endpoint, trace, child, role, origin, since }).filter(([, value]) => value !== undefined && value !== null && value !== ""));
2112
1754
  const print = (record) => process.stdout.write(options.json ? `${JSON.stringify(record)}
2113
- ` : Logger4.render(record));
1755
+ ` : Logger3.render(record));
2114
1756
  const note = (text) => process.stderr.write(`[akan logs] ${text}
2115
1757
  `);
2116
- let finish = null;
2117
- const closed = new Promise((resolve) => {
2118
- finish = resolve;
2119
- });
1758
+ const { promise: closed, resolve: finish } = Promise.withResolvers();
2120
1759
  let client;
2121
1760
  try {
2122
1761
  client = await LogTailClient.connect(socketPath, {
@@ -2125,11 +1764,11 @@ class ApplicationRunner extends runner("application") {
2125
1764
  if (response.type === "dropped")
2126
1765
  note(`${response.count} records dropped (reader too slow)`);
2127
1766
  },
2128
- onClose: () => finish?.()
1767
+ onClose: () => finish()
2129
1768
  });
2130
1769
  } catch (error) {
2131
1770
  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.`);
1771
+ 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
1772
  throw error;
2134
1773
  }
2135
1774
  const sinceMs = since ? LogQueryMatcher.parseSince(since) : undefined;
@@ -2167,7 +1806,7 @@ const serverModule = await import(${JSON.stringify(serverPath)});
2167
1806
  const { assertAkanConsoleAllowed, startAkanConsole } = await import("akanjs/server");
2168
1807
  const server = serverModule.server;
2169
1808
  if (!server?.start) throw new Error("server.ts must export server with start()");
2170
- assertAkanConsoleAllowed(server.env);
1809
+ assertAkanConsoleAllowed();
2171
1810
  await server.start({ listen: false, web: false });
2172
1811
  try {
2173
1812
  await startAkanConsole(server, {
@@ -2186,17 +1825,35 @@ try {
2186
1825
  }
2187
1826
  `;
2188
1827
  await app.spawn("bun", ["-e", code], {
2189
- env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "console" }),
1828
+ env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "console", ...await app.getDatabaseModeEnv() }),
1829
+ stdio: "inherit"
1830
+ });
1831
+ }
1832
+ async transferDatabase(app, direction, dir) {
1833
+ const serverPath = `${app.cwdPath}/server.ts`;
1834
+ if (!await app.exists("server.ts"))
1835
+ throw new Error(`Server file not found: apps/${app.name}/server.ts`);
1836
+ const target = path4.resolve(app.workspace.workspaceRoot, dir);
1837
+ const code = `
1838
+ const { server } = await import(${JSON.stringify(serverPath)});
1839
+ const { DatabaseAdaptorRole, DocumentTransfer } = await import("akanjs/service");
1840
+ await server.start({ listen: false, web: false });
1841
+ try {
1842
+ const transfer = new DocumentTransfer(server.get(DatabaseAdaptorRole));
1843
+ const reports = await transfer.${direction === "export" ? "exportTo" : "importFrom"}(${JSON.stringify(target)});
1844
+ for (const { table, rows } of reports) console.info(\`${direction === "export" ? "exported" : "imported"} \${table}: \${rows} rows\`);
1845
+ } finally {
1846
+ await server.stop();
1847
+ }
1848
+ `;
1849
+ await app.spawn("bun", ["-e", code], {
1850
+ env: app.getCommandEnv({ AKAN_COMMAND_TYPE: "script", ...await app.getDatabaseModeEnv() }),
2190
1851
  stdio: "inherit"
2191
1852
  });
2192
1853
  }
2193
1854
  async typecheck(app, options = {}) {
2194
1855
  await new (await loadBuildRunner())(app).typecheck(options);
2195
1856
  }
2196
- async compact(sys, { module, minLines, interactive } = {}) {
2197
- const compactor = new (await loadAbstractCompactor())(sys, { minLines, interactive });
2198
- return await compactor.compactAll({ module });
2199
- }
2200
1857
  async test(exec) {
2201
1858
  const isSignalTarget = exec instanceof AppExecutor || exec instanceof LibExecutor;
2202
1859
  const preloadPath = isSignalTarget ? await resolveSignalTestPreloadPath(exec) : null;
@@ -2272,13 +1929,7 @@ try {
2272
1929
  await new (await loadCapacitorApp())(app, mobileTarget.config).buildAndroid("apk", { env, regenerate });
2273
1930
  });
2274
1931
  }
2275
- async startAndroid(app, {
2276
- open = false,
2277
- operation = "local",
2278
- env = "local",
2279
- target,
2280
- regenerate = false
2281
- } = {}) {
1932
+ async startAndroid(app, { open = false, operation = "local", env = "local", target, regenerate = false } = {}) {
2282
1933
  const targets = await resolveMobileTargets(app, target);
2283
1934
  if (operation === "release")
2284
1935
  await this.#buildMobileCsr(app, env);
@@ -2335,24 +1986,34 @@ try {
2335
1986
  return;
2336
1987
  for (const failure of failures) {
2337
1988
  const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
2338
- Logger4.rawLog(`Mobile target ${failure.target} failed: ${message}`, undefined, "error");
1989
+ Logger3.rawLog(`Mobile target ${failure.target} failed: ${message}`, undefined, "error");
2339
1990
  }
2340
1991
  throw new Error(`${failures.length}/${results.length} mobile targets failed`);
2341
1992
  }
2342
- async codepush(app, os) {
1993
+ async codepush(app) {
1994
+ throw new Error(`akan codepush is still in development and deploys nothing yet, so ${app.name} was not updated. To release the app's source with OTA update support, run \`akan release-source ${app.name}\`.`);
1995
+ }
1996
+ async#initCapacitorApp(app) {
2343
1997
  const [target] = await resolveMobileTargets(app, undefined);
2344
1998
  if (!target)
2345
1999
  throw new Error(`No mobile target configured for ${app.name}`);
2346
2000
  const capacitorApp = new (await loadCapacitorApp())(app, target.config);
2347
2001
  await capacitorApp.init();
2002
+ return capacitorApp;
2348
2003
  }
2349
2004
  #getLocalDatabaseServices(mode) {
2350
2005
  if (mode === "single")
2351
2006
  return [];
2352
2007
  if (mode === "multiple")
2353
- return ["redis", "libsql"];
2008
+ return ["redis"];
2354
2009
  return ["redis", "postgres"];
2355
2010
  }
2011
+ async#assertComposeHas(workspace, services) {
2012
+ const compose = Bun.YAML.parse(await Bun.file(`${workspace.workspaceRoot}/local/docker-compose.yaml`).text());
2013
+ const missing = services.filter((service) => !compose?.services?.[service]);
2014
+ if (missing.length)
2015
+ 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.`);
2016
+ }
2356
2017
  async#isLocalDatabaseUp(workspace, mode) {
2357
2018
  const requiredServices = this.#getLocalDatabaseServices(mode);
2358
2019
  if (!requiredServices.length)
@@ -2366,16 +2027,18 @@ try {
2366
2027
  async dbup(workspace, mode = "multiple") {
2367
2028
  if (mode === "single")
2368
2029
  return true;
2030
+ const services = this.#getLocalDatabaseServices(mode);
2031
+ await workspace.applyTemplate({
2032
+ basePath: "local",
2033
+ template: "localDev",
2034
+ dict: { repoName: workspace.repoName },
2035
+ overwrite: false
2036
+ });
2037
+ await this.#assertComposeHas(workspace, services);
2369
2038
  try {
2370
- await workspace.applyTemplate({
2371
- basePath: "local",
2372
- template: "localDev",
2373
- dict: { repoName: workspace.repoName },
2374
- overwrite: false
2375
- });
2376
2039
  const wasAlreadyUp = await this.#isLocalDatabaseUp(workspace, mode);
2377
2040
  if (!wasAlreadyUp)
2378
- await workspace.spawn(`docker`, ["compose", "up", "-d", ...this.#getLocalDatabaseServices(mode)], {
2041
+ await workspace.spawn(`docker`, ["compose", "up", "-d", ...services], {
2379
2042
  cwd: `${workspace.workspaceRoot}/local`
2380
2043
  });
2381
2044
  return wasAlreadyUp;
@@ -2394,11 +2057,7 @@ ${detail}`
2394
2057
  await workspace.spawn(`docker`, ["compose", "down"], { cwd: `${workspace.workspaceRoot}/local` });
2395
2058
  }
2396
2059
  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();
2060
+ const capacitorApp = await this.#initCapacitorApp(app);
2402
2061
  if (await (await loadPrompts()).confirm({ message: "want to add camera permission?" }))
2403
2062
  await capacitorApp.addCamera();
2404
2063
  if (await (await loadPrompts()).confirm({ message: "want to add contact permission?" }))
@@ -2408,59 +2067,187 @@ ${detail}`
2408
2067
  await capacitorApp.save();
2409
2068
  }
2410
2069
  async releaseSource(app, { rebuild, buildNum = 0, environment = "debug", local = true } = {}) {
2411
- await new (await loadReleasePackager())(app, { build: () => this.build(app).then(() => {
2070
+ const packager = new (await loadReleasePackager())(app, { build: () => this.build(app).then(() => {
2412
2071
  return;
2413
- }) }).releaseSource({
2414
- rebuild,
2415
- buildNum,
2416
- environment,
2417
- local
2418
- });
2419
- return;
2072
+ }) });
2073
+ await packager.releaseSource({ rebuild, buildNum, environment, local });
2420
2074
  }
2421
- async createApplicationTemplate(workspace, appName) {
2422
- await workspace.applyTemplate({ basePath: `apps/${appName}`, template: "appRoot", dict: { appName } });
2075
+ }
2076
+
2077
+ // pkgs/@akanjs/cli/application/hostProcessProbe.ts
2078
+ import { readdirSync, readFileSync, readlinkSync } from "fs";
2079
+
2080
+ class HostProcessProbe {
2081
+ static timeoutMs = 3000;
2082
+ static win32TimeoutMs = 20000;
2083
+ static #procTcpListen = "0A";
2084
+ static #win32ProcessQuery = "[Console]::OutputEncoding=[Text.Encoding]::UTF8; Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine,CreationDate | ConvertTo-Json -Compress";
2085
+ platform;
2086
+ #win32Table = null;
2087
+ constructor(platform = process.platform) {
2088
+ this.platform = platform;
2089
+ }
2090
+ async listenersOn(port) {
2091
+ if (this.platform === "win32")
2092
+ return HostProcessProbe.parseNetstat(await HostProcessProbe.run(["netstat", "-ano"]), port);
2093
+ if (this.platform === "linux")
2094
+ return HostProcessProbe.#procListenersOn(port);
2095
+ const output = await HostProcessProbe.run(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
2096
+ return [
2097
+ ...new Set(output.split(`
2098
+ `).map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0))
2099
+ ];
2423
2100
  }
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;
2101
+ async parentOf(pid) {
2102
+ if (this.platform === "win32") {
2103
+ const child = await this.#win32Process(pid);
2104
+ const parent = child ? await this.#win32Process(child.ppid) : null;
2105
+ return child && parent ? HostProcessProbe.parentPidOf(child, parent) : null;
2106
+ }
2107
+ if (this.platform === "linux") {
2108
+ const stat = HostProcessProbe.#readProc(pid, "stat");
2109
+ return stat === null ? null : HostProcessProbe.parseProcStatPpid(stat);
2110
+ }
2111
+ const parsed = Number((await HostProcessProbe.run(["ps", "-p", String(pid), "-o", "ppid="])).trim());
2112
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
2436
2113
  }
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!");
2114
+ async commandOf(pid) {
2115
+ if (this.platform === "win32")
2116
+ return (await this.#win32Process(pid))?.command ?? null;
2117
+ if (this.platform === "linux") {
2118
+ const cmdline = HostProcessProbe.#readProc(pid, "cmdline");
2119
+ return cmdline?.split("\x00").filter(Boolean).join(" ") || null;
2120
+ }
2121
+ return (await HostProcessProbe.run(["ps", "-ww", "-p", String(pid), "-o", "command="])).trim() || null;
2122
+ }
2123
+ static parentPidOf(child, parent) {
2124
+ if (child.ppid !== parent.pid)
2125
+ return null;
2126
+ if (parent.startedAt !== null && child.startedAt !== null && parent.startedAt > child.startedAt)
2127
+ return null;
2128
+ return parent.pid;
2129
+ }
2130
+ static parseNetstat(output, port) {
2131
+ const pids = new Set;
2132
+ for (const line of output.split(/\r?\n/)) {
2133
+ const fields = line.trim().split(/\s+/);
2134
+ if (fields[0] !== "TCP" || fields.length < 5)
2135
+ continue;
2136
+ const [, local, remote] = fields;
2137
+ if (!local?.endsWith(`:${port}`) || !remote?.endsWith(":0"))
2138
+ continue;
2139
+ const pid = Number(fields.at(-1));
2140
+ if (Number.isInteger(pid) && pid > 0)
2141
+ pids.add(pid);
2142
+ }
2143
+ return [...pids];
2144
+ }
2145
+ static parseProcNetTcp(text, port) {
2146
+ const inodes = [];
2147
+ for (const line of text.split(`
2148
+ `).slice(1)) {
2149
+ const fields = line.trim().split(/\s+/);
2150
+ const [, local, , state, , , , , , inode] = fields;
2151
+ if (!local || !inode || state !== HostProcessProbe.#procTcpListen || inode === "0")
2152
+ continue;
2153
+ if (Number.parseInt(local.slice(local.lastIndexOf(":") + 1), 16) === port)
2154
+ inodes.push(inode);
2155
+ }
2156
+ return inodes;
2157
+ }
2158
+ static parseProcStatPpid(stat) {
2159
+ const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
2160
+ return Number.isInteger(ppid) && ppid > 0 ? ppid : null;
2161
+ }
2162
+ static parseWin32Processes(json) {
2163
+ const table = new Map;
2164
+ let parsed;
2165
+ try {
2166
+ parsed = JSON.parse(json.trim() || "[]");
2167
+ } catch {
2168
+ return table;
2169
+ }
2170
+ const rows = Array.isArray(parsed) ? parsed : [parsed];
2171
+ for (const row of rows) {
2172
+ if (typeof row?.ProcessId !== "number")
2173
+ continue;
2174
+ table.set(row.ProcessId, {
2175
+ pid: row.ProcessId,
2176
+ ppid: row.ParentProcessId ?? 0,
2177
+ command: row.CommandLine?.trim() || null,
2178
+ startedAt: HostProcessProbe.#parseCimDate(row.CreationDate)
2179
+ });
2180
+ }
2181
+ return table;
2182
+ }
2183
+ static async run(command, timeoutMs = HostProcessProbe.timeoutMs) {
2184
+ let proc;
2185
+ try {
2186
+ proc = Bun.spawn(command, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
2187
+ } catch {
2188
+ return "";
2189
+ }
2190
+ const timer = setTimeout(() => proc.kill("SIGKILL"), timeoutMs);
2191
+ try {
2192
+ return await new Response(proc.stdout).text();
2193
+ } catch {
2194
+ return "";
2195
+ } finally {
2196
+ clearTimeout(timer);
2197
+ }
2198
+ }
2199
+ async#win32Process(pid) {
2200
+ this.#win32Table ??= await HostProcessProbe.#queryWin32Processes();
2201
+ if (!this.#win32Table.has(pid))
2202
+ this.#win32Table = await HostProcessProbe.#queryWin32Processes();
2203
+ return this.#win32Table.get(pid) ?? null;
2204
+ }
2205
+ static async#queryWin32Processes() {
2206
+ const output = await HostProcessProbe.run(["powershell", "-NoProfile", "-NonInteractive", "-Command", HostProcessProbe.#win32ProcessQuery], HostProcessProbe.win32TimeoutMs);
2207
+ return HostProcessProbe.parseWin32Processes(output);
2208
+ }
2209
+ static #parseCimDate(value) {
2210
+ if (!value)
2211
+ return null;
2212
+ const legacy = /\/Date\((\d+)/.exec(value)?.[1];
2213
+ const ms = legacy ? Number(legacy) : Date.parse(value);
2214
+ return Number.isFinite(ms) ? ms : null;
2215
+ }
2216
+ static #procListenersOn(port) {
2217
+ const inodes = new Set(["tcp", "tcp6"].flatMap((table) => HostProcessProbe.parseProcNetTcp(HostProcessProbe.#readProc("net", table) ?? "", port)));
2218
+ if (!inodes.size)
2219
+ return [];
2220
+ const pids = [];
2221
+ for (const entry of HostProcessProbe.#listDir("/proc")) {
2222
+ if (!/^\d+$/.test(entry))
2223
+ continue;
2224
+ const holds = HostProcessProbe.#listDir(`/proc/${entry}/fd`).some((fd) => {
2225
+ const link = HostProcessProbe.#orElse(() => readlinkSync(`/proc/${entry}/fd/${fd}`), "");
2226
+ const inode = /^socket:\[(\d+)\]$/.exec(link)?.[1];
2227
+ return !!inode && inodes.has(inode);
2228
+ });
2229
+ if (holds)
2230
+ pids.push(Number(entry));
2231
+ }
2232
+ return pids;
2233
+ }
2234
+ static #readProc(pid, file) {
2235
+ return HostProcessProbe.#orElse(() => readFileSync(`/proc/${pid}/${file}`, "utf8"), null);
2236
+ }
2237
+ static #listDir(dir) {
2238
+ return HostProcessProbe.#orElse(() => readdirSync(dir), []);
2239
+ }
2240
+ static #orElse(read, fallback) {
2241
+ try {
2242
+ return read();
2243
+ } catch {
2244
+ return fallback;
2245
+ }
2458
2246
  }
2459
2247
  }
2460
2248
 
2461
2249
  // pkgs/@akanjs/cli/application/devPortReclaimer.ts
2462
2250
  class DevPortReclaimer {
2463
- static lsofTimeoutMs = 3000;
2464
2251
  static releaseTimeoutMs = 8000;
2465
2252
  static maxAncestorDepth = 8;
2466
2253
  static #releasePollMs = 100;
@@ -2469,7 +2256,12 @@ class DevPortReclaimer {
2469
2256
  return [...new Set(ports.flatMap((port) => [port, port + DevPortReclaimer.wsPortOffset]))].sort((left, right) => left - right);
2470
2257
  }
2471
2258
  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");
2259
+ const normalized = command.replaceAll("\\", "/");
2260
+ 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");
2261
+ }
2262
+ #probe;
2263
+ constructor(probe = new HostProcessProbe) {
2264
+ this.#probe = probe;
2473
2265
  }
2474
2266
  async reclaim(ports) {
2475
2267
  const killed = [];
@@ -2494,10 +2286,10 @@ class DevPortReclaimer {
2494
2286
  }
2495
2287
  async holdersOf(port) {
2496
2288
  const holders = [];
2497
- for (const listenerPid of await this.#pidsOn(port)) {
2289
+ for (const listenerPid of await this.#probe.listenersOn(port)) {
2498
2290
  if (listenerPid === process.pid || listenerPid === process.ppid)
2499
2291
  continue;
2500
- const command = await this.#commandOf(listenerPid);
2292
+ const command = await this.#probe.commandOf(listenerPid);
2501
2293
  if (!command)
2502
2294
  continue;
2503
2295
  if (!DevPortReclaimer.isAkanCommand(command)) {
@@ -2512,44 +2304,37 @@ class DevPortReclaimer {
2512
2304
  async#akanRootOf(pid, command) {
2513
2305
  let root = { pid, command };
2514
2306
  for (let depth = 0;depth < DevPortReclaimer.maxAncestorDepth; depth += 1) {
2515
- const parentPid = await this.#ppidOf(root.pid);
2307
+ const parentPid = await this.#probe.parentOf(root.pid);
2516
2308
  if (parentPid === null || parentPid <= 1 || parentPid === process.pid || parentPid === process.ppid)
2517
2309
  break;
2518
- const parentCommand = await this.#commandOf(parentPid);
2310
+ const parentCommand = await this.#probe.commandOf(parentPid);
2519
2311
  if (!parentCommand || !DevPortReclaimer.isAkanCommand(parentCommand))
2520
2312
  break;
2521
2313
  root = { pid: parentPid, command: parentCommand };
2522
2314
  }
2523
2315
  return root;
2524
2316
  }
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
2317
  async#terminate(pid) {
2318
+ if (this.#probe.platform === "win32") {
2319
+ await HostProcessProbe.run(["taskkill", "/PID", String(pid), "/T", "/F"]);
2320
+ return await this.#released(pid);
2321
+ }
2542
2322
  if (!this.#signal(pid, "SIGTERM"))
2543
2323
  return !this.#alive(pid);
2324
+ if (await this.#released(pid))
2325
+ return true;
2326
+ this.#signal(pid, "SIGKILL");
2327
+ await Bun.sleep(DevPortReclaimer.#releasePollMs);
2328
+ return !this.#alive(pid);
2329
+ }
2330
+ async#released(pid) {
2544
2331
  const deadline = Date.now() + DevPortReclaimer.releaseTimeoutMs;
2545
2332
  while (Date.now() < deadline) {
2546
2333
  if (!this.#alive(pid))
2547
2334
  return true;
2548
2335
  await Bun.sleep(DevPortReclaimer.#releasePollMs);
2549
2336
  }
2550
- this.#signal(pid, "SIGKILL");
2551
- await Bun.sleep(DevPortReclaimer.#releasePollMs);
2552
- return !this.#alive(pid);
2337
+ return false;
2553
2338
  }
2554
2339
  #signal(pid, signal) {
2555
2340
  try {
@@ -2567,26 +2352,15 @@ class DevPortReclaimer {
2567
2352
  return error.code === "EPERM";
2568
2353
  }
2569
2354
  }
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
2355
  }
2582
2356
 
2583
2357
  // pkgs/@akanjs/cli/application/devStreamView.ts
2584
- import { Logger as Logger5 } from "akanjs/common";
2358
+ import { Logger as Logger4 } from "akanjs/common";
2585
2359
  import chalk from "chalk";
2586
2360
  var appColors = ["cyan", "magenta", "yellow", "green", "blue", "red"];
2587
2361
 
2588
2362
  class DevStreamView {
2589
- #logger = new Logger5("akan start");
2363
+ #logger = new Logger4("akan start");
2590
2364
  #partial = new Map;
2591
2365
  #lastState = new Map;
2592
2366
  #color = new Map;
@@ -2690,8 +2464,9 @@ class DevBootConcurrency {
2690
2464
 
2691
2465
  // pkgs/@akanjs/cli/application/devSessionLog.ts
2692
2466
  import { mkdir, rename } from "fs/promises";
2693
- import path6 from "path";
2467
+ import path5 from "path";
2694
2468
  import dayjs from "dayjs";
2469
+
2695
2470
  class DevSessionLog {
2696
2471
  static fileName = "dev.log";
2697
2472
  static previousFileName = "dev.prev.log";
@@ -2713,19 +2488,19 @@ class DevSessionLog {
2713
2488
  return [...this.#appNames];
2714
2489
  }
2715
2490
  pathOf(app) {
2716
- return path6.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2491
+ return path5.join(this.#runtimeDirOf(app), DevSessionLog.fileName);
2717
2492
  }
2718
2493
  previousPathOf(app) {
2719
- return path6.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2494
+ return path5.join(this.#runtimeDirOf(app), DevSessionLog.previousFileName);
2720
2495
  }
2721
2496
  relativePathOf(app) {
2722
- return path6.relative(this.#workspaceRoot, this.pathOf(app));
2497
+ return path5.relative(this.#workspaceRoot, this.pathOf(app));
2723
2498
  }
2724
2499
  async open() {
2725
2500
  const stamp = dayjs(this.#now()).format("YYYY-MM-DD HH:mm:ss");
2726
2501
  for (const app of this.#appNames) {
2727
2502
  const file = this.pathOf(app);
2728
- await mkdir(path6.dirname(file), { recursive: true });
2503
+ await mkdir(path5.dirname(file), { recursive: true });
2729
2504
  await rename(file, this.previousPathOf(app)).catch(() => {
2730
2505
  return;
2731
2506
  });
@@ -2769,7 +2544,7 @@ class DevSessionLog {
2769
2544
  this.#writers.clear();
2770
2545
  }
2771
2546
  #runtimeDirOf(app) {
2772
- return path6.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2547
+ return path5.join(this.#workspaceRoot, "local", "apps", app, "runtime");
2773
2548
  }
2774
2549
  #writeLine(app, text) {
2775
2550
  const writer = this.#writers.get(app);
@@ -2792,6 +2567,16 @@ class DevSessionLog {
2792
2567
 
2793
2568
  // pkgs/@akanjs/cli/application/devSupervisor.ts
2794
2569
  class DevSupervisor {
2570
+ static resolveDevUi(plain, {
2571
+ isTty = !!process.stdout.isTTY,
2572
+ columns = process.stdout.columns ?? 0
2573
+ } = {}) {
2574
+ if (plain)
2575
+ return { mode: "stream", downgraded: false };
2576
+ if (!isTty || columns <= 0)
2577
+ return { mode: "stream", downgraded: true };
2578
+ return { mode: "tui", downgraded: false };
2579
+ }
2795
2580
  static supervisedEnvKey = "AKAN_DEV_SUPERVISED";
2796
2581
  static childHooks() {
2797
2582
  if (process.env[DevSupervisor.supervisedEnvKey] !== "1" || !process.send)
@@ -2855,16 +2640,14 @@ class DevSupervisor {
2855
2640
  return ports;
2856
2641
  }
2857
2642
  #makeChild(app, port) {
2858
- let markReady;
2859
- const ready = new Promise((resolve) => {
2860
- markReady = resolve;
2861
- });
2643
+ const { promise: ready, resolve: markReady } = Promise.withResolvers();
2862
2644
  return {
2863
2645
  status: {
2864
2646
  app,
2865
2647
  name: app.name,
2866
2648
  port,
2867
2649
  url: `http://localhost:${port}`,
2650
+ shareUrl: this.#options.shares?.get(app.name) ?? null,
2868
2651
  state: "starting",
2869
2652
  detail: "queued",
2870
2653
  exitCode: null
@@ -2894,16 +2677,18 @@ class DevSupervisor {
2894
2677
  }
2895
2678
  }
2896
2679
  async#waitForReady(child) {
2897
- let timer = null;
2680
+ if (await DevSupervisor.timesOut(child.ready, DevSupervisor.readyTimeoutMs))
2681
+ this.#note(`${child.status.name} has not reported ready after ${Math.round(DevSupervisor.readyTimeoutMs / 1000)}s; starting the next app anyway`, "warn");
2682
+ }
2683
+ static async timesOut(work, ms) {
2684
+ let timer;
2898
2685
  const expired = new Promise((resolve) => {
2899
- timer = setTimeout(() => resolve("expired"), DevSupervisor.readyTimeoutMs);
2686
+ timer = setTimeout(() => resolve(true), ms);
2900
2687
  });
2901
2688
  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");
2689
+ return await Promise.race([work.then(() => false), expired]);
2904
2690
  } finally {
2905
- if (timer)
2906
- clearTimeout(timer);
2691
+ clearTimeout(timer);
2907
2692
  }
2908
2693
  }
2909
2694
  static childArgs(name, { write = true } = {}) {
@@ -3025,44 +2810,68 @@ class DevSupervisor {
3025
2810
  for (const child of running)
3026
2811
  child.proc.kill("SIGTERM");
3027
2812
  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);
2813
+ if (!await DevSupervisor.timesOut(exited, DevSupervisor.shutdownGraceMs))
2814
+ return;
2815
+ for (const child of running) {
2816
+ if (child.proc.killed)
2817
+ continue;
2818
+ this.#note(`${child.status.name} did not exit in time; killing it`, "warn");
2819
+ child.proc.kill("SIGKILL");
3044
2820
  }
3045
2821
  }
3046
2822
  }
3047
2823
 
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
- };
2824
+ // pkgs/@akanjs/cli/application/interruptTeardown.ts
2825
+ import { Logger as Logger5 } from "akanjs/common";
2826
+
2827
+ class InterruptTeardown {
2828
+ #teardowns = [];
2829
+ #exit;
2830
+ #listen;
2831
+ #report;
2832
+ #interrupted = false;
2833
+ ownsExit = true;
2834
+ constructor({ exit, listen, report } = {}) {
2835
+ this.#exit = exit ?? ((code) => process.exit(code));
2836
+ this.#listen = listen ?? ((onSignal) => {
2837
+ process.on("SIGINT", onSignal);
2838
+ });
2839
+ this.#report = report ?? ((message) => Logger5.rawLog(message, undefined, "error"));
2840
+ }
2841
+ add(run, abandoned) {
2842
+ this.#teardowns.push({ run, abandoned, running: false });
2843
+ if (this.#teardowns.length === 1)
2844
+ this.#listen(() => this.#onSignal());
2845
+ }
2846
+ #onSignal() {
2847
+ if (this.#interrupted) {
2848
+ for (const one of this.#teardowns)
2849
+ if (one.running)
2850
+ this.#report(one.abandoned);
2851
+ this.#exit(130);
2852
+ return;
2853
+ }
2854
+ this.#interrupted = true;
2855
+ Promise.all(this.#teardowns.map(async (one) => {
2856
+ one.running = true;
2857
+ await one.run().catch(() => {
2858
+ return;
2859
+ });
2860
+ one.running = false;
2861
+ })).finally(() => {
2862
+ if (this.ownsExit)
2863
+ this.#exit(0);
2864
+ });
2865
+ }
2866
+ }
3056
2867
 
3057
2868
  // pkgs/@akanjs/cli/application/application.script.ts
3058
2869
  class ApplicationScript extends script("application", [ApplicationRunner, LibraryScript]) {
3059
2870
  static dbShutdownTimeoutMs = 20000;
2871
+ #interrupt = new InterruptTeardown;
3060
2872
  async confirmDatabaseModeDependencyInstall(databaseMode, installSpecs) {
3061
2873
  return await confirm({
3062
- message: [
3063
- `Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}.`,
3064
- "Install them now?"
3065
- ].join(" "),
2874
+ message: `Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}. Install them now?`,
3066
2875
  default: true
3067
2876
  });
3068
2877
  }
@@ -3073,21 +2882,11 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3073
2882
  const shouldInstall = await this.confirmDatabaseModeDependencyInstall(databaseMode, installSpecs);
3074
2883
  if (!shouldInstall)
3075
2884
  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
- }
2885
+ await this.#addDependencies(app, installSpecs, `database dependencies for ${databaseMode} mode`);
3087
2886
  }
3088
2887
  async confirmMobileDependencyInstall(installSpecs) {
3089
2888
  return await confirm({
3090
- message: [`Mobile builds require missing dependencies: ${installSpecs.join(", ")}.`, "Install them now?"].join(" "),
2889
+ message: `Mobile builds require missing dependencies: ${installSpecs.join(", ")}. Install them now?`,
3091
2890
  default: true
3092
2891
  });
3093
2892
  }
@@ -3098,15 +2897,16 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3098
2897
  const shouldInstall = await this.confirmMobileDependencyInstall(installSpecs);
3099
2898
  if (!shouldInstall)
3100
2899
  throw new Error(`Mobile builds require missing dependencies: ${installSpecs.join(", ")}.`);
3101
- const spinner = app.workspace.spinning("Installing mobile dependencies...");
2900
+ await this.#addDependencies(app, installSpecs, "mobile dependencies");
2901
+ }
2902
+ async#addDependencies(app, installSpecs, what) {
2903
+ const spinner = app.workspace.spinning(`Installing ${what}...`);
3102
2904
  try {
3103
- await app.workspace.spawn("bun", ["add", ...installSpecs], {
3104
- stdio: "inherit"
3105
- });
2905
+ await app.workspace.spawn("bun", ["add", ...installSpecs], { stdio: "inherit" });
3106
2906
  await app.workspace.getPackageJson({ refresh: true });
3107
- spinner.succeed("Installed mobile dependencies");
2907
+ spinner.succeed(`Installed ${what}`);
3108
2908
  } catch (error) {
3109
- spinner.fail("Failed to install mobile dependencies");
2909
+ spinner.fail(`Failed to install ${what}`);
3110
2910
  throw error;
3111
2911
  }
3112
2912
  }
@@ -3156,17 +2956,6 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3156
2956
  else
3157
2957
  await this.libraryScript.syncLibrary(sys);
3158
2958
  }
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
2959
  async script(app, filename) {
3171
2960
  const scriptFilename = filename ?? await this.applicationRunner.getScriptFilename(app);
3172
2961
  await app.scanSync();
@@ -3184,10 +2973,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3184
2973
  if (!quiet)
3185
2974
  Logger6.rawLog(`Creating an optimized production build for ${app.name}...`);
3186
2975
  try {
3187
- const result = await this.applicationRunner.build(app, {
3188
- fast,
3189
- spinner: !quiet
3190
- });
2976
+ const result = await this.applicationRunner.build(app, { fast, spinner: !quiet });
3191
2977
  Logger6.rawLog(`${app.name} built in dist/apps/${app.name}`);
3192
2978
  if (!quiet)
3193
2979
  ApplicationBuildReporter.printSummary(result);
@@ -3212,8 +2998,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3212
2998
  async test(exec, { write = true } = {}) {
3213
2999
  if (exec instanceof LibExecutor) {
3214
3000
  await this.libraryScript.syncLibrary(exec);
3215
- const spinner = exec.spinning(`Preparing ${exec.name}...`);
3216
- spinner.succeed(`${exec.name} prepared`);
3001
+ exec.spinning(`Preparing ${exec.name}...`).succeed(`${exec.name} prepared`);
3217
3002
  await this.applicationRunner.test(exec);
3218
3003
  return;
3219
3004
  }
@@ -3230,23 +3015,33 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3230
3015
  }
3231
3016
  await this.applicationRunner.test(exec);
3232
3017
  }
3233
- async start(apps, { open = false, dbup = true, write = true, plain = false, kill = false, concurrency = null } = {}) {
3018
+ async start(apps, {
3019
+ open = false,
3020
+ dbup = true,
3021
+ write = true,
3022
+ plain = false,
3023
+ kill = false,
3024
+ share = false,
3025
+ concurrency = null
3026
+ } = {}) {
3234
3027
  const first = apps[0];
3235
3028
  if (!first)
3236
3029
  throw new Error("No app selected to start");
3237
- const { mode, downgraded } = resolveDevUi(plain);
3030
+ const { mode, downgraded } = DevSupervisor.resolveDevUi(plain);
3238
3031
  if (downgraded)
3239
3032
  first.workspace.log("no sized terminal to draw on; printing prefixed lines instead");
3240
3033
  if (kill)
3241
3034
  await this.reclaimDevPorts(apps);
3035
+ const shares = share ? await this.#shareOnInterrupt(apps) : null;
3242
3036
  if (mode === "stream" && apps.length === 1)
3243
3037
  return await this.startOne(first, { open, dbup, write, ...DevSupervisor.childHooks() });
3244
- await this.#startMany(apps, { open, dbup, write, mode, concurrency });
3038
+ this.#interrupt.ownsExit = false;
3039
+ await this.#startMany(apps, { open, dbup, write, mode, concurrency, shares });
3245
3040
  }
3246
3041
  async startOne(app, { open = false, dbup = true, stdio = "inherit", write = true, onDevEvent } = {}) {
3247
3042
  await app.scanSync({ write });
3248
3043
  const akanConfig = await app.getConfig();
3249
- const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
3044
+ const databaseMode = akanConfig.resolveDatabaseMode();
3250
3045
  await this.syncDatabaseModeDependencies(app, akanConfig, databaseMode);
3251
3046
  if (app.getEnv() === "local" && dbup && databaseMode !== "single") {
3252
3047
  const wasDbAlreadyUp = await this.dbup(app.workspace, databaseMode);
@@ -3254,15 +3049,12 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3254
3049
  this.#stopDatabaseOnInterrupt(app.workspace);
3255
3050
  }
3256
3051
  const spinner = app.spinning("Preparing backend...");
3257
- const akanAppHost = await this.applicationRunner.start(app, {
3052
+ return await this.applicationRunner.start(app, {
3258
3053
  open,
3259
- onStart: () => {
3260
- spinner.succeed(`${app.name} prepared, ready to start`);
3261
- },
3054
+ onStart: () => spinner.succeed(`${app.name} prepared, ready to start`),
3262
3055
  onDevEvent,
3263
3056
  stdio
3264
3057
  });
3265
- return akanAppHost;
3266
3058
  }
3267
3059
  async reclaimDevPorts(apps) {
3268
3060
  const workspace = apps[0]?.workspace;
@@ -3277,13 +3069,20 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3277
3069
  if (killed.length === 0 && foreign.length === 0)
3278
3070
  workspace.log("dev ports are free; nothing to kill");
3279
3071
  }
3280
- async#startMany(apps, { open, dbup, write, mode, concurrency }) {
3072
+ async#startMany(apps, {
3073
+ open,
3074
+ dbup,
3075
+ write,
3076
+ mode,
3077
+ concurrency,
3078
+ shares
3079
+ }) {
3281
3080
  const workspace = apps[0]?.workspace;
3282
3081
  if (!workspace)
3283
3082
  throw new Error("No app selected to start");
3284
3083
  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));
3084
+ const supervisor = new DevSupervisor({ apps, mode, concurrency, open, write, shares });
3085
+ const view = mode === "tui" ? new (await import("./devTui-q7k7bakd.js")).DevTui(supervisor) : new DevStreamView(apps.map((app) => app.name));
3287
3086
  await supervisor.run(view);
3288
3087
  if (startedDatabase)
3289
3088
  await this.#stopDatabase(workspace);
@@ -3297,7 +3096,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3297
3096
  if (app.getEnv() !== "local")
3298
3097
  continue;
3299
3098
  const akanConfig = await app.getConfig();
3300
- const mode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
3099
+ const mode = akanConfig.resolveDatabaseMode();
3301
3100
  await this.syncDatabaseModeDependencies(app, akanConfig, mode);
3302
3101
  if (mode !== "single")
3303
3102
  modes.add(mode);
@@ -3358,23 +3157,13 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3358
3157
  const akanConfig = await app.getConfig();
3359
3158
  await this.syncMobileDependencies(app, akanConfig);
3360
3159
  await this.syncMobileAppCapacitorPlugins(app, akanConfig);
3361
- await this.applicationRunner.startAndroid(app, {
3362
- open,
3363
- operation,
3364
- env,
3365
- target,
3366
- regenerate
3367
- });
3160
+ await this.applicationRunner.startAndroid(app, { open, operation, env, target, regenerate });
3368
3161
  }
3369
3162
  async releaseAndroid(app, assembleType, { write = true, target, env = "main", regenerate = false, allowLocalRelease = false } = {}) {
3370
3163
  await app.scanSync({ write });
3371
3164
  if (env === "local" && !allowLocalRelease)
3372
3165
  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
- });
3166
+ await this.applicationRunner.releaseAndroid(app, assembleType, { target, env, regenerate });
3378
3167
  }
3379
3168
  async configureApp(app) {
3380
3169
  await this.applicationRunner.configureApp(app);
@@ -3382,8 +3171,21 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3382
3171
  async releaseSource(app, options) {
3383
3172
  await this.applicationRunner.releaseSource(app, options);
3384
3173
  }
3385
- async codepush(app, os) {
3386
- await this.applicationRunner.codepush(app, os);
3174
+ async codepush(app) {
3175
+ await this.applicationRunner.codepush(app);
3176
+ }
3177
+ async transferDatabase(app, direction, dir) {
3178
+ await this.applicationRunner.transferDatabase(app, direction, dir);
3179
+ }
3180
+ async dbupDeclared(workspace, mode) {
3181
+ 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())]);
3182
+ const needing = declared.filter((each) => each !== "single");
3183
+ if (!needing.length) {
3184
+ workspace.log("No app here declares a database mode that runs on local services; single needs none.");
3185
+ return;
3186
+ }
3187
+ for (const each of needing)
3188
+ await this.dbup(workspace, each);
3387
3189
  }
3388
3190
  async dbup(workspace, mode = "multiple") {
3389
3191
  const spinner = workspace.spinning(`Starting local database (${mode})...`);
@@ -3401,34 +3203,36 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3401
3203
  }
3402
3204
  spinner.succeed("Local database (/local/docker-compose.yaml) is down");
3403
3205
  }
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));
3206
+ async#shareOnInterrupt(apps) {
3207
+ const shares = [];
3208
+ const urls = new Map;
3209
+ this.#interrupt.add(async () => {
3210
+ await Promise.all(shares.map(async (share) => await share.close()));
3211
+ }, "Abandoning the tunnel release; the shares expire on their own.");
3212
+ await Promise.all(apps.map(async (app) => {
3213
+ const { TunnelShare } = await import("./TunnelShare-scf4e4pd.js");
3214
+ const share = await TunnelShare.open(app, { workspace: app.workspace });
3215
+ shares.push(share);
3216
+ urls.set(app.name, share.url);
3217
+ Logger6.rawLog(`${app.name} is shared at ${share.url}`);
3218
+ })).catch((error) => {
3219
+ Logger6.rawLog(`Could not open the tunnel: ${error instanceof Error ? error.message : String(error)}`, undefined, "error");
3413
3220
  });
3221
+ return urls;
3222
+ }
3223
+ #stopDatabaseOnInterrupt(workspace) {
3224
+ this.#interrupt.add(async () => {
3225
+ await this.#stopDatabase(workspace);
3226
+ }, "Abandoning the local database teardown; containers are left running.");
3414
3227
  }
3415
3228
  async#stopDatabase(workspace) {
3416
- let timer = null;
3417
- const timeout = new Promise((resolve) => {
3418
- timer = setTimeout(() => resolve("timeout"), ApplicationScript.dbShutdownTimeoutMs);
3229
+ const stopped = this.dbdown(workspace).catch(() => {
3230
+ return;
3419
3231
  });
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
- }
3232
+ if (!await DevSupervisor.timesOut(stopped, ApplicationScript.dbShutdownTimeoutMs))
3233
+ return;
3234
+ Logger6.rawLog(`Local database did not stop within ${ApplicationScript.dbShutdownTimeoutMs}ms; run \`akan dbdown\` once Docker responds.`, undefined, "error");
3431
3235
  }
3432
3236
  }
3433
3237
 
3434
- export { openBrowser, plainTextOf, HOST_SOURCE, DevLogBuffer, ApplicationScript };
3238
+ export { ApplicationScript };