@akanjs/cli 3.0.0-alpha.1 → 3.0.0-alpha.100

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 (162) hide show
  1. package/.build-stamp +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/{abstractCompactor-e5yq60qz.js → abstractCompactor-byytxs22.js} +3 -5
  5. package/{agent.command-yv00p0s0.js → agent.command-v1a4746v.js} +11 -12
  6. package/{application.command-0qsczf4k.js → application.command-fzdcnzhg.js} +65 -37
  7. package/{applicationBuildRunner-ct2pxyvw.js → applicationBuildRunner-s4gsk728.js} +28 -24
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-28v9vbzk.js} +8 -9
  9. package/buildBatch.proc.js +12 -13
  10. package/capacitorApp-8d6d4zkm.js +12 -0
  11. package/{cloud.command-dxfv18sa.js → cloud.command-wygy2z05.js} +15 -17
  12. package/commandManifest.json +1 -1
  13. package/{context.command-8d9e3k6b.js → context.command-4vdt1aqq.js} +19 -22
  14. package/dependencyScanner-shs82ynp.js +8 -0
  15. package/devTui-qnf1txdz.js +553 -0
  16. package/devTuiView-yzct2cq6.js +9 -0
  17. package/getDirname-dr3jz1xg.js +7 -0
  18. package/{guideline.command-3jbkxzh6.js → guideline.command-p92dq6ts.js} +16 -17
  19. package/guidelines/agentRule/agentRule.generate.json +31 -0
  20. package/guidelines/agentRule/agentRule.instruction.md +533 -0
  21. package/guidelines/conventions/conventions.generate.json +25 -0
  22. package/guidelines/conventions/conventions.instruction.md +908 -0
  23. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  24. package/guidelines/framework/framework.generate.json +3 -2
  25. package/guidelines/framework/framework.instruction.md +1 -9
  26. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  27. package/guidelines/mcpRule/mcpRule.instruction.md +217 -0
  28. package/guidelines/modelStore/modelStore.instruction.md +12 -0
  29. package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
  30. package/guidelines/queryRule/queryRule.generate.json +31 -0
  31. package/guidelines/queryRule/queryRule.instruction.md +175 -0
  32. package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
  33. package/guidelines/runtimeRule/runtimeRule.instruction.md +220 -0
  34. package/guidelines/ssrRule/ssrRule.instruction.md +49 -5
  35. package/guidelines/transportRule/transportRule.generate.json +31 -0
  36. package/guidelines/transportRule/transportRule.instruction.md +60 -0
  37. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
  38. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +263 -0
  39. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
  40. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
  41. package/incrementalBuilder.proc.js +26 -24
  42. package/{index-mr5s7mqd.js → index-0amdxycf.js} +8 -8
  43. package/{index-t3723f99.js → index-1yxtbmbj.js} +111 -42
  44. package/{index-ej426mcr.js → index-29sxqfaw.js} +65 -38
  45. package/index-3d9y066c.js +76 -0
  46. package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
  47. package/index-5svjba73.js +159 -0
  48. package/index-6wwzs1bc.js +7 -0
  49. package/{index-k95xkh4p.js → index-7d6vny25.js} +16 -12
  50. package/{index-ec09pws3.js → index-8p8p0cf8.js} +14 -14
  51. package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
  52. package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
  53. package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
  54. package/{index-0cj2zxbm.js → index-c69j78kk.js} +12 -11
  55. package/{index-rzwcpcc3.js → index-fc1bh0a7.js} +4 -4
  56. package/{index-7c6k4yvg.js → index-gq44bqjt.js} +58 -25
  57. package/{index-a00jg8x8.js → index-gtmz74tj.js} +79 -55
  58. package/{index-tdbas9r0.js → index-h7yw2z9n.js} +7 -7
  59. package/{index-bjpxzr6s.js → index-hwkygn9s.js} +51 -3
  60. package/{index-bbkek1bp.js → index-hyywke0f.js} +1 -1
  61. package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
  62. package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
  63. package/{index-pyt2b4nw.js → index-p5neav9b.js} +1129 -1356
  64. package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
  65. package/{index-bekddz9q.js → index-qkk27bk6.js} +32 -32
  66. package/{index-hgna5rjy.js → index-qsq3q8fy.js} +1034 -257
  67. package/{index-2q1f5j9k.js → index-srdssqj7.js} +18 -18
  68. package/{index-rh16j3c7.js → index-w8kmx1b6.js} +23 -13
  69. package/{index-nm6wghb6.js → index-yks023mb.js} +13 -13
  70. package/{index-1pvex4az.js → index-yza7zkpt.js} +94 -61
  71. package/{index-yzmhasdb.js → index-zmjppp25.js} +57 -57
  72. package/{index-8hsbnyes.js → index-znynzt5w.js} +100 -30
  73. package/{index-zasydjxd.js → index-zxgr3s8s.js} +31 -30
  74. package/{index-e16gnn26.js → index-zz5jyd94.js} +1002 -128
  75. package/index.js +22 -24
  76. package/{library.command-ebdj1vnm.js → library.command-s4fszngs.js} +11 -9
  77. package/{localRegistry.command-gb178ne8.js → localRegistry.command-gyw4da9z.js} +20 -22
  78. package/{module.command-9c1bnsx4.js → module.command-6n7dwvav.js} +13 -15
  79. package/{package.command-t93p4jkj.js → package.command-gr82ypnr.js} +8 -9
  80. package/package.json +5 -7
  81. package/{page.command-k3k0meh2.js → page.command-7gvgb3jb.js} +7 -8
  82. package/{primitive.command-de6nktdt.js → primitive.command-tk5ckw38.js} +13 -15
  83. package/{quality.command-z5kth2wa.js → quality.command-38v81baf.js} +229 -138
  84. package/recipeScanner-fpagtgc1.js +11 -0
  85. package/{repair.command-ehf2g112.js → repair.command-5xbwvsxp.js} +12 -13
  86. package/{routeSourceValidator-cncd92fv.js → routeSourceValidator-ze6x894c.js} +2 -40
  87. package/{scalar.command-7a2jtv33.js → scalar.command-0epwcr5k.js} +11 -13
  88. package/subspace.command-e2t36xb2.js +755 -0
  89. package/templates/app/env/env.client.debug.ts.template +1 -5
  90. package/templates/app/env/env.client.develop.ts.template +1 -5
  91. package/templates/app/env/env.client.local.ts.template +1 -5
  92. package/templates/app/env/env.client.main.ts.template +1 -5
  93. package/templates/app/env/env.client.testing.ts.template +1 -5
  94. package/templates/app/env/env.client.type.ts +2 -6
  95. package/templates/app/env/env.server.debug.ts.template +1 -5
  96. package/templates/app/env/env.server.develop.ts.template +1 -5
  97. package/templates/app/env/env.server.local.ts.template +1 -5
  98. package/templates/app/env/env.server.main.ts.template +1 -5
  99. package/templates/app/main.ts +3 -1
  100. package/templates/app/page/styles.css.template +3 -3
  101. package/templates/appSample/lib/task/task.signal.ts +11 -6
  102. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  103. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  104. package/templates/crudPages/[__model__Id]/edit/page.tsx +6 -6
  105. package/templates/crudPages/[__model__Id]/page.tsx +10 -13
  106. package/templates/crudPages/new/page.tsx +6 -6
  107. package/templates/crudPages/page.tsx +9 -9
  108. package/templates/crudSinglePage/page.tsx +11 -7
  109. package/templates/env/_env.server.type.ts +7 -5
  110. package/templates/lib/db.ts +4 -2
  111. package/templates/lib/dict.ts +1 -1
  112. package/templates/lib/sig.ts +5 -1
  113. package/templates/lib/srv.ts +6 -2
  114. package/templates/lib/useClient.ts +9 -3
  115. package/templates/module/__Model__.Util.tsx +1 -1
  116. package/templates/module/__Model__.Zone.tsx +3 -5
  117. package/templates/module/__model__.dictionary.ts +4 -1
  118. package/templates/module/__model__.signal.ts +2 -3
  119. package/templates/module/__model__.store.ts +1 -2
  120. package/templates/workspaceRoot/.gitignore.template +0 -1
  121. package/templates/workspaceRoot/.husky/pre-commit.template +16 -0
  122. package/templates/workspaceRoot/.vscode/settings.json.template +1 -1
  123. package/templates/workspaceRoot/AGENTS.md.template +4 -948
  124. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  125. package/templates/workspaceRoot/biome.json.template +4 -198
  126. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +15 -16
  127. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  128. package/templates/workspaceRoot/package.json.template +2 -4
  129. package/typeChecker-3xgrzc2v.js +7 -0
  130. package/typecheck.proc.js +4 -5
  131. package/{workflow.command-e0tt0vk2.js → workflow.command-kphbr7eh.js} +21 -23
  132. package/{workspace.command-fpda0evj.js → workspace.command-j1gatz3d.js} +108 -91
  133. package/capacitorApp-4r9sm3qg.js +0 -58
  134. package/dependencyScanner-f6grrep3.js +0 -9
  135. package/index-mxvakhsm.js +0 -66
  136. package/index-r24hmh0q.js +0 -4
  137. package/recipeScanner-tghkzrrt.js +0 -12
  138. package/templates/libRoot/base/baseLogic.ts +0 -14
  139. package/templates/libRoot/base/index.ts +0 -8
  140. package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
  141. package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -33
  142. package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
  143. package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
  144. package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
  145. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
  146. package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
  147. package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
  148. package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
  149. package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -18
  150. package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -61
  151. package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
  152. package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
  153. package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
  154. package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -58
  155. package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
  156. package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
  157. package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
  158. package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
  159. package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
  160. package/typeChecker-kravn7ns.js +0 -8
  161. /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
  162. /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
@@ -1,13 +1,189 @@
1
1
  // @bun
2
- import {
3
- Spinner
4
- } from "./index-mxvakhsm.js";
5
2
  import {
6
3
  FileSys
7
- } from "./index-67546d0j.js";
4
+ } from "./index-q0y2jq9j.js";
8
5
  import {
9
- __require
10
- } from "./index-r24hmh0q.js";
6
+ getDirname2
7
+ } from "./index-6wwzs1bc.js";
8
+
9
+ // pkgs/@akanjs/devkit/codegenLock.ts
10
+ import { mkdir, open, readFile, rm, stat } from "fs/promises";
11
+ import path from "path";
12
+ import { Logger } from "akanjs/common";
13
+
14
+ class CodegenLock {
15
+ static fileName = "codegen.lock";
16
+ static waitTimeoutMs = 1e4;
17
+ static unknownHolderStaleMs = 60000;
18
+ static #pollMs = 25;
19
+ static #logger = new Logger("CodegenLock");
20
+ static #queue = Promise.resolve();
21
+ static pathIn(workspaceRoot) {
22
+ return path.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
23
+ }
24
+ static async run(workspaceRoot, label, fn) {
25
+ const ahead = CodegenLock.#queue;
26
+ let done;
27
+ CodegenLock.#queue = new Promise((resolve) => {
28
+ done = resolve;
29
+ });
30
+ try {
31
+ await CodegenLock.#waitForQueue(ahead, label);
32
+ return await CodegenLock.#withFileLock(workspaceRoot, label, fn);
33
+ } finally {
34
+ done();
35
+ }
36
+ }
37
+ static async#waitForQueue(ahead, label) {
38
+ let timer = null;
39
+ const expired = new Promise((resolve) => {
40
+ timer = setTimeout(() => resolve("expired"), CodegenLock.waitTimeoutMs);
41
+ });
42
+ try {
43
+ if (await Promise.race([ahead.then(() => "done"), expired]) === "expired")
44
+ CodegenLock.#logger.warn(`codegen lock queued past ${CodegenLock.waitTimeoutMs}ms in this process; continuing without waiting (${label})`);
45
+ } finally {
46
+ if (timer)
47
+ clearTimeout(timer);
48
+ }
49
+ }
50
+ static async#withFileLock(workspaceRoot, label, fn) {
51
+ const lockPath = CodegenLock.pathIn(workspaceRoot);
52
+ await mkdir(path.dirname(lockPath), { recursive: true }).catch(() => {
53
+ return;
54
+ });
55
+ const held = await CodegenLock.#acquire(lockPath, label);
56
+ try {
57
+ return await fn();
58
+ } finally {
59
+ if (held)
60
+ await rm(lockPath, { force: true }).catch(() => {
61
+ return;
62
+ });
63
+ }
64
+ }
65
+ static async#acquire(lockPath, label) {
66
+ const deadline = Date.now() + CodegenLock.waitTimeoutMs;
67
+ for (;; ) {
68
+ const handle = await open(lockPath, "wx").catch(() => null);
69
+ if (handle) {
70
+ await handle.writeFile(JSON.stringify({ pid: process.pid, at: Date.now(), label })).catch(() => {
71
+ return;
72
+ });
73
+ await handle.close().catch(() => {
74
+ return;
75
+ });
76
+ return true;
77
+ }
78
+ if (await CodegenLock.#reclaimIfAbandoned(lockPath))
79
+ continue;
80
+ if (Date.now() >= deadline) {
81
+ CodegenLock.#logger.warn(`codegen lock at ${lockPath} held past ${CodegenLock.waitTimeoutMs}ms; continuing without it (${label})`);
82
+ return false;
83
+ }
84
+ await Bun.sleep(CodegenLock.#pollMs);
85
+ }
86
+ }
87
+ static async#reclaimIfAbandoned(lockPath) {
88
+ const info = await stat(lockPath).catch(() => null);
89
+ if (!info)
90
+ return true;
91
+ const holder = CodegenLock.#parseHolder(await readFile(lockPath, "utf8").catch(() => ""));
92
+ if (holder) {
93
+ if (CodegenLock.#isAlive(holder.pid))
94
+ return false;
95
+ } else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
96
+ return false;
97
+ await rm(lockPath, { force: true }).catch(() => {
98
+ return;
99
+ });
100
+ return true;
101
+ }
102
+ static #parseHolder(raw) {
103
+ try {
104
+ const parsed = JSON.parse(raw);
105
+ if (typeof parsed.pid !== "number" || typeof parsed.at !== "number")
106
+ return null;
107
+ return { pid: parsed.pid, at: parsed.at, label: typeof parsed.label === "string" ? parsed.label : "" };
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+ static #isAlive(pid) {
113
+ if (!Number.isInteger(pid) || pid <= 0)
114
+ return false;
115
+ try {
116
+ process.kill(pid, 0);
117
+ return true;
118
+ } catch (error) {
119
+ return error.code === "EPERM";
120
+ }
121
+ }
122
+ }
123
+
124
+ // pkgs/@akanjs/devkit/spinner.ts
125
+ import ora from "ora";
126
+
127
+ class Spinner {
128
+ static padding = 12;
129
+ static oraOptions = { discardStdin: false };
130
+ static canAnimate(stream = process.stderr) {
131
+ return !stream.isTTY || stream.columns > 0;
132
+ }
133
+ spinner;
134
+ stopWatch = null;
135
+ startAt = new Date;
136
+ prefix;
137
+ message;
138
+ enableSpin;
139
+ constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
140
+ Spinner.padding = Math.max(Spinner.padding, prefix.length);
141
+ this.prefix = prefix;
142
+ this.message = message;
143
+ this.spinner = ora({ ...Spinner.oraOptions, text: message });
144
+ this.spinner.prefixText = prefix.padStart(Spinner.padding, " ");
145
+ this.spinner.indent = indent;
146
+ this.enableSpin = enableSpin && Spinner.canAnimate();
147
+ }
148
+ start() {
149
+ this.startAt = new Date;
150
+ if (this.enableSpin) {
151
+ this.spinner.start();
152
+ this.stopWatch = setInterval(() => {
153
+ this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
154
+ this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
155
+ }, 1000);
156
+ } else
157
+ this.spinner.info();
158
+ return this;
159
+ }
160
+ succeed(message) {
161
+ this.spinner.succeed(`${message} (${this.#getElapsedTimeStr()})`);
162
+ this.#reset();
163
+ }
164
+ fail(message) {
165
+ this.spinner.fail(`${message} (${this.#getElapsedTimeStr()})`);
166
+ this.#reset();
167
+ }
168
+ isSpinning() {
169
+ return this.spinner.isSpinning;
170
+ }
171
+ #reset() {
172
+ if (this.stopWatch)
173
+ clearInterval(this.stopWatch);
174
+ this.stopWatch = null;
175
+ }
176
+ #getElapsedTimeStr({ floor = false } = {}) {
177
+ const ms = Date.now() - this.startAt.getTime();
178
+ if (ms < 1000)
179
+ return `${ms}ms`;
180
+ const s = Math.floor(ms / 1000);
181
+ if (s < 60)
182
+ return `${floor ? Math.floor(ms / 1000) : Math.floor(ms / 100) / 10}s`;
183
+ const m = Math.floor(s / 60);
184
+ return `${m}m ${s % 60}s`;
185
+ }
186
+ }
11
187
 
12
188
  // pkgs/@akanjs/devkit/executors.ts
13
189
  import {
@@ -19,19 +195,20 @@ import { readFileSync as readFileSync2 } from "fs";
19
195
  import {
20
196
  copyFile,
21
197
  cp as cpEntry,
22
- mkdir,
198
+ mkdir as mkdir3,
23
199
  readdir as readDirEntries,
24
200
  realpath,
25
- rm as rm2,
26
- stat,
201
+ rm as rm3,
202
+ stat as stat2,
27
203
  symlink
28
204
  } from "fs/promises";
29
- import path5 from "path";
205
+ import path8 from "path";
30
206
  import { pathToFileURL } from "url";
31
207
  import {
32
208
  capitalize,
209
+ getPageSourceFileViolation,
33
210
  isRouteSourceFile,
34
- Logger,
211
+ Logger as Logger2,
35
212
  parseRouteModuleKey,
36
213
  validatePageSourceFile,
37
214
  validateSubRoutePageKey
@@ -41,9 +218,24 @@ import chalk2 from "chalk";
41
218
 
42
219
  // pkgs/@akanjs/devkit/agentsIndex.ts
43
220
  var recipeScannerLoad = null;
44
- var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-tghkzrrt.js");
221
+ var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-fpagtgc1.js");
45
222
  var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
46
223
  var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
224
+ var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
225
+ var stampBlockVersion = (block, version) => `${AGENT_VERSION_PREFIX}${version} -->
226
+
227
+ ${block}`;
228
+ var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
229
+ var readDevkitVersion = async () => {
230
+ const { readFile } = await import("fs/promises");
231
+ const { getDirname } = await import("./getDirname-dr3jz1xg.js");
232
+ try {
233
+ const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
234
+ return JSON.parse(raw).version ?? null;
235
+ } catch {
236
+ return null;
237
+ }
238
+ };
47
239
  var upsertAgentBlock = (existing, block) => {
48
240
  const managed = `${AGENT_BLOCK_START}
49
241
  ${block}
@@ -137,15 +329,36 @@ ${AGENT_BLOCK_START}
137
329
  ${block}
138
330
  ${AGENT_BLOCK_END}
139
331
  `;
332
+ var CLAUDE_COMMENT_RULE = `## Comments \u2014 Overrides Your Default
333
+
334
+ Write **no comments** unless the comment passes the test below. This is the rule agents break most often here, so it
335
+ is repeated outside the guide: a diff that adds a comment the test rejects is a diff to redo.
336
+
337
+ Before typing \`//\`, \`/*\`, or a doc block, ask \u2014 **does this sentence carry a fact that is nowhere in the code?**
338
+
339
+ - Restates the identifier, the signature, or the line under it \u2192 delete it.
340
+ - Labels a section (\`// helpers\`, \`// state\`) or narrates a step (\`// fetch the user\`, \`// then save\`) \u2192 delete it.
341
+ - JSDoc on an ordinary function, or a why/how preamble on ordinary logic \u2192 delete it.
342
+ - Explains the edit you just made, for whoever reads the diff \u2192 say it in your reply, not in the file.
343
+ - Names a vendor or protocol quirk, an infrastructure constraint, a library gotcha, security reasoning, a math
344
+ derivation, a domain field's business meaning, a state transition, or why an obvious alternative was rejected \u2192
345
+ keep it, one line.
346
+
347
+ That keep-list is exact \u2014 \`Comments\` in the guide is the full version. "It aids readability" and "this logic is
348
+ subtle" are not on it: rename or split the code instead. When you edit an existing file, match its density; if the
349
+ surrounding code carries none, your diff carries none.`;
140
350
  var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
141
351
 
142
352
  @AGENTS.md
353
+
354
+ ${CLAUDE_COMMENT_RULE}
143
355
  `;
144
356
 
145
357
  // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
146
358
  import fs from "fs";
147
- import path from "path";
359
+ import path2 from "path";
148
360
  import { fileURLToPath } from "url";
361
+ import { normalizeRoutePrefix } from "akanjs/base";
149
362
  import { resolveAkanI18nConfig } from "akanjs/common";
150
363
 
151
364
  // pkgs/@akanjs/devkit/akanConfig/types.ts
@@ -181,9 +394,8 @@ var DEFAULT_OPTIMIZE_IMPORTS = [
181
394
  "react-icons/*"
182
395
  ];
183
396
  var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
397
+ var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
184
398
  var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
185
- var NATIVE_RUNTIME_PACKAGES = ["sharp"];
186
- var DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"];
187
399
  var MOBILE_RUNTIME_PACKAGES = [
188
400
  "@capacitor/cli",
189
401
  "@capacitor/core",
@@ -212,8 +424,6 @@ var DATABASE_MODE_RUNTIME_PACKAGES = {
212
424
  };
213
425
  var AKAN_RUNTIME_PACKAGES = new Set([
214
426
  ...SSR_RUNTIME_PACKAGES,
215
- ...NATIVE_RUNTIME_PACKAGES,
216
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
217
427
  ...MOBILE_RUNTIME_PACKAGES,
218
428
  ...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat()
219
429
  ]);
@@ -228,14 +438,15 @@ var DEFAULT_AKAN_IMAGE_CONFIG = {
228
438
  dangerouslyAllowSVG: false,
229
439
  maximumRedirects: 3,
230
440
  fetchTimeoutMs: 7000,
231
- maxRemoteBytes: 25 * 1024 * 1024
441
+ maxRemoteBytes: 25 * 1024 * 1024,
442
+ maxConcurrency: 0
232
443
  };
233
444
  var normalizeIndexPath = (indexPath) => {
234
445
  const normalized = indexPath?.trim();
235
446
  if (!normalized)
236
447
  return;
237
- const path2 = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
238
- return path2 === "/" ? "/" : path2;
448
+ const path = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
449
+ return path === "/" ? "/" : path;
239
450
  };
240
451
  var normalizeStringList = (values) => {
241
452
  const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
@@ -275,21 +486,39 @@ var normalizeDeepLinks = (deepLinks) => {
275
486
  ...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
276
487
  };
277
488
  };
489
+ var emptyLibContributions = () => ({
490
+ externalLibs: [],
491
+ docker: { preRuns: [], postRuns: [] },
492
+ keepFonts: []
493
+ });
494
+ var normalizeKeepFonts = (keepFonts) => [
495
+ ...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean))
496
+ ];
497
+ var dedupeDockerRuns = (runs) => {
498
+ const byKey = new Map;
499
+ for (const run of runs)
500
+ byKey.set(typeof run === "string" ? run : JSON.stringify(run), run);
501
+ return [...byKey.values()];
502
+ };
278
503
 
279
504
  class AkanAppConfig {
280
505
  app;
281
506
  rootPackageJson;
282
507
  docker;
508
+ dockerfile;
283
509
  defaultDatabaseMode;
510
+ web;
284
511
  externalLibs;
285
512
  barrelImports;
286
513
  optimizeImports;
287
514
  images;
288
515
  i18n;
516
+ api;
289
517
  publicEnv;
290
518
  mobile;
291
519
  hasMobileConfig;
292
520
  secrets;
521
+ assets;
293
522
  syncPageLibs;
294
523
  baseDevEnv;
295
524
  libs;
@@ -298,7 +527,7 @@ class AkanAppConfig {
298
527
  subRoutes = new Map;
299
528
  basePaths = new Set;
300
529
  branches = new Set(["debug", "develop", "main"]);
301
- constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = []) {
530
+ constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = [], libContributions = emptyLibContributions()) {
302
531
  this.app = app;
303
532
  this.rootPackageJson = rootPackageJson;
304
533
  this.libs = libs;
@@ -306,7 +535,7 @@ class AkanAppConfig {
306
535
  this.plugins = plugins;
307
536
  this.#applyRoutes(config?.routes);
308
537
  this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
309
- this.externalLibs = config?.externalLibs ?? [];
538
+ this.externalLibs = [...new Set([...config?.externalLibs ?? [], ...libContributions.externalLibs])];
310
539
  this.barrelImports = [
311
540
  ...DEFAULT_BARREL_IMPORTS,
312
541
  ...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
@@ -318,12 +547,33 @@ class AkanAppConfig {
318
547
  this.i18n = resolveAkanI18nConfig(config?.i18n);
319
548
  process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
320
549
  process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
550
+ this.api = {
551
+ prefix: normalizeRoutePrefix(config?.api?.prefix) ?? "/api",
552
+ websocketPrefix: normalizeRoutePrefix(config?.api?.websocketPrefix) ?? "/ws"
553
+ };
554
+ process.env.AKAN_PUBLIC_API_PREFIX = this.api.prefix;
555
+ process.env.AKAN_PUBLIC_WS_PREFIX = this.api.websocketPrefix;
321
556
  this.publicEnv = config?.publicEnv ?? [];
322
557
  this.secrets = config?.secrets ?? [];
558
+ this.assets = {
559
+ pruneFonts: config?.assets?.pruneFonts ?? true,
560
+ keepFonts: [
561
+ ...normalizeKeepFonts(config?.assets?.keepFonts),
562
+ ...libContributions.keepFonts ?? []
563
+ ]
564
+ };
323
565
  this.syncPageLibs = config?.syncPageLibs ?? false;
324
566
  this.hasMobileConfig = Boolean(config.mobile);
325
567
  this.mobile = this.#resolveMobileConfig(config.mobile);
326
- this.docker = this.#makeDockerContent(config?.docker ?? {});
568
+ this.web = this.#resolveWebConfig(config.web);
569
+ this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
570
+ this.dockerfile = this.#makeDockerfile();
571
+ }
572
+ #resolveWebConfig(web) {
573
+ const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
574
+ if (!resolved.csr && this.hasMobileConfig)
575
+ throw new Error(`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares mobile targets; the Capacitor build ships that bundle. Drop the mobile section or leave CSR on.`);
576
+ return resolved;
327
577
  }
328
578
  #resolveMobileConfig(mobile) {
329
579
  const {
@@ -433,23 +683,31 @@ class AkanAppConfig {
433
683
  return archs.map((arch) => `FROM ${image[arch] ?? defaultImage} AS ${arch}`).join(`
434
684
  `);
435
685
  }
436
- #makeDockerContent(docker) {
437
- if (docker.content)
438
- return {
439
- content: docker.content,
440
- image: {},
441
- preRuns: [],
442
- postRuns: [],
443
- command: []
444
- };
445
- const preRunScripts = this.#getDockerRunScripts(docker.preRuns ?? []);
446
- const postRunScripts = this.#getDockerRunScripts(docker.postRuns ?? []);
447
- const imageScript = docker.image ? this.#getDockerImageScript(docker.image, "oven/bun:1-slim") : "FROM oven/bun:1-slim";
448
- const command = docker.command ?? ["bun", "main.js"];
449
- const content = `${imageScript}
686
+ static #resolveDocker(docker, libDocker) {
687
+ if (typeof docker === "string")
688
+ return docker;
689
+ return {
690
+ image: docker?.image ?? DEFAULT_DOCKER_IMAGE,
691
+ preRuns: dedupeDockerRuns([...libDocker.preRuns, ...docker?.preRuns ?? []]),
692
+ postRuns: dedupeDockerRuns([...libDocker.postRuns, ...docker?.postRuns ?? []]),
693
+ command: docker?.command ?? ["bun", "main.js"]
694
+ };
695
+ }
696
+ #makeDockerfile() {
697
+ if (typeof this.docker === "string")
698
+ return this.docker;
699
+ const { image, preRuns, postRuns, command } = this.docker;
700
+ const preRunScripts = this.#getDockerRunScripts(preRuns);
701
+ const postRunScripts = this.#getDockerRunScripts(postRuns);
702
+ const imageScript = this.#getDockerImageScript(image, DEFAULT_DOCKER_IMAGE);
703
+ const webEnvLines = [
704
+ ...this.web.ssr ? [] : ["ENV AKAN_SSR=false"],
705
+ ...this.web.csr ? [] : ["ENV AKAN_CSR=false"]
706
+ ].join(`
707
+ `);
708
+ return `${imageScript}
709
+ RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
450
710
  RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
451
- RUN apt-get update && apt-get upgrade -y
452
- RUN apt-get install -y --no-install-recommends git redis build-essential python3 ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils udev ffmpeg
453
711
  ARG TARGETARCH
454
712
  ${preRunScripts.join(`
455
713
  `)}
@@ -469,16 +727,12 @@ ENV AKAN_PUBLIC_ENV=${this.baseDevEnv.env}
469
727
  ${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join(",")}` : ""}
470
728
  ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
471
729
  ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
730
+ ENV AKAN_PUBLIC_API_PREFIX=${this.api.prefix}
731
+ ENV AKAN_PUBLIC_WS_PREFIX=${this.api.websocketPrefix}
472
732
  ENV AKAN_PUBLIC_OPERATION_MODE=cloud
473
-
733
+ ENV AKAN_LOG_TO_FILE=0
734
+ ${webEnvLines}
474
735
  CMD [${command.map((c) => `"${c}"`).join(",")}]`;
475
- return {
476
- content,
477
- image: imageScript,
478
- preRuns: docker.preRuns ?? [],
479
- postRuns: docker.postRuns ?? [],
480
- command
481
- };
482
736
  }
483
737
  static #importGeneration = 0;
484
738
  static async importConfigModule(cwdPath, { bustImportCache = false } = {}) {
@@ -489,13 +743,28 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
489
743
  static async from(app, { bustImportCache = false } = {}) {
490
744
  const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
491
745
  AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
492
- WorkspaceExecutor.getBaseDevEnv(path.join(app.workspace.workspaceRoot, ".env")),
746
+ WorkspaceExecutor.getBaseDevEnv(path2.join(app.workspace.workspaceRoot, ".env")),
493
747
  app.workspace.getLibs(),
494
748
  app.workspace.getPackageJson()
495
749
  ]);
496
750
  const resolved = typeof configImp === "function" ? configImp(app) : configImp;
497
751
  const { plugins, ...config } = resolved ?? {};
498
- return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? []);
752
+ const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
753
+ return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
754
+ }
755
+ static async#collectLibContributions(app, libs, bustImportCache) {
756
+ const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
757
+ app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
758
+ return null;
759
+ })));
760
+ return {
761
+ externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
762
+ docker: {
763
+ preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
764
+ postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? [])
765
+ },
766
+ keepFonts: libConfigs.flatMap((libConfig) => (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`))
767
+ };
499
768
  }
500
769
  #resolveProductionDependencyVersion(lib) {
501
770
  const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
@@ -505,13 +774,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
505
774
  return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
506
775
  }
507
776
  #getProductionRuntimePackages() {
508
- return [
509
- ...this.externalLibs,
510
- ...SSR_RUNTIME_PACKAGES,
511
- ...NATIVE_RUNTIME_PACKAGES,
512
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
513
- ...this.getDatabaseModeRuntimePackages()
514
- ];
777
+ return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
515
778
  }
516
779
  getDatabaseModeRuntimePackages(databaseMode = this.defaultDatabaseMode) {
517
780
  return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
@@ -560,11 +823,11 @@ var akanPackageJson = null;
560
823
  function getAkanPackageJson() {
561
824
  if (akanPackageJson)
562
825
  return akanPackageJson;
563
- const sourceDir = path.dirname(fileURLToPath(import.meta.url));
826
+ const sourceDir = path2.dirname(fileURLToPath(import.meta.url));
564
827
  const packageJsonPaths = [
565
- path.join(sourceDir, "../../../akanjs/package.json"),
566
- path.join(process.cwd(), "pkgs/akanjs/package.json"),
567
- path.join(path.dirname(Bun.main), "node_modules/akanjs/package.json")
828
+ path2.join(sourceDir, "../../../akanjs/package.json"),
829
+ path2.join(process.cwd(), "pkgs/akanjs/package.json"),
830
+ path2.join(path2.dirname(Bun.main), "node_modules/akanjs/package.json")
568
831
  ];
569
832
  try {
570
833
  packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
@@ -599,14 +862,18 @@ function mergeImageConfig(config = {}) {
599
862
  class AkanLibConfig {
600
863
  lib;
601
864
  externalLibs;
865
+ docker;
866
+ assets;
602
867
  plugins;
603
868
  constructor(lib, config, plugins = []) {
604
869
  this.lib = lib;
605
870
  this.externalLibs = config?.externalLibs ?? [];
871
+ this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
872
+ this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
606
873
  this.plugins = plugins;
607
874
  }
608
- static async from(lib) {
609
- const [configImp] = await Promise.all([import(`${lib.cwdPath}/akan.config.ts`).then((mod) => mod.default)]);
875
+ static async from(lib, { bustImportCache = false } = {}) {
876
+ const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
610
877
  const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
611
878
  const { plugins, ...config } = resolved ?? {};
612
879
  return new AkanLibConfig(lib, config, plugins ?? []);
@@ -615,54 +882,448 @@ class AkanLibConfig {
615
882
  //! need to refactor
616
883
  var increaseBuildNum = async (app) => {
617
884
  const appConfig = await AkanAppConfig.from(app);
618
- const akanConfigPath = path.join(app.cwdPath, "akan.config.ts");
885
+ const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
619
886
  const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
620
887
  const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum + 1}`);
621
888
  fs.writeFileSync(akanConfigPath, akanConfigContent);
622
889
  };
623
890
  var decreaseBuildNum = async (app) => {
624
891
  const appConfig = await AkanAppConfig.from(app);
625
- const akanConfigPath = path.join(app.cwdPath, "akan.config.ts");
892
+ const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
626
893
  const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
627
894
  const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
628
895
  fs.writeFileSync(akanConfigPath, akanConfigContent);
629
896
  };
630
- // pkgs/@akanjs/devkit/getDirname.ts
631
- import path2 from "path";
632
- import { fileURLToPath as fileURLToPath2 } from "url";
633
- var getDirname = (url) => path2.dirname(fileURLToPath2(url));
897
+ // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
898
+ import { mkdir as mkdir2 } from "fs/promises";
899
+ import path4 from "path";
900
+
901
+ // pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
902
+ import fs2 from "fs";
903
+ import path3 from "path";
904
+
905
+ class AsyncDefaultExportDetector {
906
+ static #typescriptLoad;
907
+ static #loadTypescript() {
908
+ AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
909
+ return AsyncDefaultExportDetector.#typescriptLoad;
910
+ }
911
+ static async detect(moduleAbsPath) {
912
+ try {
913
+ const typescript = await AsyncDefaultExportDetector.#loadTypescript();
914
+ const source = fs2.readFileSync(path3.resolve(moduleAbsPath), "utf8");
915
+ const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, AsyncDefaultExportDetector.#scriptKind(typescript, moduleAbsPath));
916
+ return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
917
+ } catch {
918
+ return false;
919
+ }
920
+ }
921
+ #ts;
922
+ constructor(typescript) {
923
+ this.#ts = typescript;
924
+ }
925
+ detectInSourceFile(sourceFile) {
926
+ const ts = this.#ts;
927
+ const asyncBindings = new Map;
928
+ let defaultIdentifier = null;
929
+ for (const statement of sourceFile.statements) {
930
+ if (ts.isFunctionDeclaration(statement)) {
931
+ if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
932
+ return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
933
+ }
934
+ if (statement.name) {
935
+ asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
936
+ }
937
+ continue;
938
+ }
939
+ if (ts.isVariableStatement(statement)) {
940
+ for (const declaration of statement.declarationList.declarations) {
941
+ if (!ts.isIdentifier(declaration.name))
942
+ continue;
943
+ asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
944
+ }
945
+ continue;
946
+ }
947
+ if (ts.isExportAssignment(statement)) {
948
+ if (this.#isAsyncFunctionExpression(statement.expression))
949
+ return true;
950
+ if (ts.isIdentifier(statement.expression))
951
+ defaultIdentifier = statement.expression.text;
952
+ continue;
953
+ }
954
+ if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
955
+ const exportClause = statement.exportClause;
956
+ for (const specifier of exportClause.elements) {
957
+ if (specifier.name.text !== "default")
958
+ continue;
959
+ defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
960
+ }
961
+ }
962
+ }
963
+ return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
964
+ }
965
+ #hasModifier(node, kind) {
966
+ const ts = this.#ts;
967
+ return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
968
+ }
969
+ #isAsyncFunctionExpression(node) {
970
+ const ts = this.#ts;
971
+ return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
972
+ }
973
+ static #scriptKind(typescript, moduleAbsPath) {
974
+ return moduleAbsPath.endsWith(".tsx") || moduleAbsPath.endsWith(".jsx") ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS;
975
+ }
976
+ }
977
+
978
+ // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
979
+ var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
980
+ async function appHasStModule(appCwdPath) {
981
+ return Bun.file(path4.join(appCwdPath, "lib", "st.ts")).exists();
982
+ }
983
+ var IMPLICIT_LAYOUT_DIR = path4.join(".akan", "generated", "root-layouts");
984
+ var IMPLICIT_DICT_DIR = path4.join(".akan", "generated", "dict");
985
+ var IMPLICIT_OVERRIDES_DIR = path4.join(".akan", "generated", "overrides");
986
+ var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
987
+ async function writeGeneratedOverridesLayoutFile(opts) {
988
+ const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
989
+ const absPath = path4.join(path4.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
990
+ const userRel = path4.relative(path4.dirname(absPath), opts.userAbsPath).split(path4.sep).join("/");
991
+ const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
992
+ const source = `"use client";
993
+ import { UiOverrideProvider } from "akanjs/ui";
994
+ import { createElement, type ReactNode } from "react";
995
+ import value from ${JSON.stringify(userSpecifier)};
996
+
997
+ export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
998
+ return createElement(UiOverrideProvider, { value }, children);
999
+ }
1000
+ `;
1001
+ await Bun.write(absPath, source);
1002
+ return absPath;
1003
+ }
1004
+ function getRootBoundarySegments(key) {
1005
+ const match = LAYOUT_KEY_RE.exec(key);
1006
+ if (!match)
1007
+ return null;
1008
+ const prefix = match[1]?.replace(/\/$/, "");
1009
+ if (!prefix)
1010
+ return [];
1011
+ return prefix.split("/").filter(Boolean);
1012
+ }
1013
+ function implicitRootLayoutKey(segments) {
1014
+ return `./${[...segments, "__root_layout"].join("/")}.tsx`;
1015
+ }
1016
+ function implicitRootLayoutAbsPath(appCwdPath, segments) {
1017
+ const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
1018
+ return path4.join(path4.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
1019
+ }
1020
+ function implicitDictionaryMacroAbsPath(appCwdPath) {
1021
+ return path4.join(path4.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
1022
+ }
1023
+ function isRootBoundarySegments(segments, basePaths) {
1024
+ const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
1025
+ if (firstVisibleIndex === -1)
1026
+ return segments.length <= 1;
1027
+ if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
1028
+ return false;
1029
+ const visible = segments.slice(firstVisibleIndex);
1030
+ const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
1031
+ return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
1032
+ }
1033
+ function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
1034
+ const boundaries = new Map;
1035
+ for (const key of pageKeys) {
1036
+ const segments = getRootBoundarySegments(key);
1037
+ if (!segments)
1038
+ continue;
1039
+ if (!isRootBoundarySegments(segments, basePaths))
1040
+ continue;
1041
+ const id = segments.join("/");
1042
+ boundaries.set(id, {
1043
+ sourceKey: key,
1044
+ sourceAbsPath: path4.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
1045
+ segments
1046
+ });
1047
+ }
1048
+ const hasExplicitRootBoundary = [...boundaries.values()].some((boundary) => boundary.segments.length === 0);
1049
+ if (!hasExplicitRootBoundary && boundaries.size === 0) {
1050
+ boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
1051
+ }
1052
+ return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
1053
+ }
1054
+ function hasAncestorRootBoundary(boundary, boundaries) {
1055
+ return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
1056
+ }
1057
+ function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
1058
+ const rootLayoutKey = pageKeys.find((key) => {
1059
+ const segments = getRootBoundarySegments(key);
1060
+ return segments !== null && segments.length === 0;
1061
+ });
1062
+ return rootLayoutKey ? path4.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
1063
+ }
1064
+ function routePrefixForSegments(segments) {
1065
+ const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
1066
+ return visible[0] ?? null;
1067
+ }
1068
+ async function assertEnvClientConvention(appCwdPath, appName) {
1069
+ const envPath = path4.join(appCwdPath, "env", "env.client.ts");
1070
+ if (!await Bun.file(envPath).exists()) {
1071
+ throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
1072
+ }
1073
+ }
1074
+ async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
1075
+ const absPath = implicitDictionaryMacroAbsPath(appCwdPath);
1076
+ await mkdir2(path4.dirname(absPath), { recursive: true });
1077
+ await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
1078
+
1079
+ export const allDictionary = getAllDictionary();
1080
+ `);
1081
+ return absPath;
1082
+ }
1083
+ async function writeGeneratedRootLayoutFile(opts) {
1084
+ await assertEnvClientConvention(opts.appCwdPath, opts.appName);
1085
+ const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
1086
+ const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
1087
+ await mkdir2(path4.dirname(absPath), { recursive: true });
1088
+ const dictMacroRel = dictMacroAbsPath ? path4.relative(path4.dirname(absPath), dictMacroAbsPath).split(path4.sep).join("/") : null;
1089
+ const dictMacroSpecifier = dictMacroRel ? dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}` : null;
1090
+ const sourceRel = opts.boundary.sourceAbsPath ? path4.relative(path4.dirname(absPath), opts.boundary.sourceAbsPath).split(path4.sep).join("/") : null;
1091
+ const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
1092
+ const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
1093
+ const inheritedSourceRel = inheritedSourceAbsPath ? path4.relative(path4.dirname(absPath), inheritedSourceAbsPath).split(path4.sep).join("/") : null;
1094
+ const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
1095
+ const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
1096
+ void st;
1097
+ ` : `import "@apps/${opts.appName}/client";
1098
+ `;
1099
+ const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedLayout from ${JSON.stringify(inheritedSourceSpecifier)};
1100
+ ` : `const inheritedLayout = {};
1101
+ `;
1102
+ const prefix = routePrefixForSegments(opts.boundary.segments);
1103
+ const userImport = sourceSpecifier ? `import UserLayout, * as userLayout from ${JSON.stringify(sourceSpecifier)};
1104
+ ` : `const UserLayout = ({ children }) => children;
1105
+ const userLayout = {};
1106
+ `;
1107
+ const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
1108
+ const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
1109
+ const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
1110
+ const layoutBinding = isAsyncUserLayout ? ` const layout =
1111
+ process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
1112
+ ? await UserLayout({ params, searchParams, children })
1113
+ : ${userLayoutElement};
1114
+ ` : "";
1115
+ const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
1116
+ const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
1117
+ const source = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
1118
+ import { loadFonts } from "akanjs/client";
1119
+ import { System } from "akanjs/ui";
1120
+ import { env } from "@apps/${opts.appName}/env/env.client";
1121
+ import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
1122
+ ${clientImport}${inheritedImport}${userImport}
1123
+ const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
1124
+ const defaultFonts = userFonts.filter((font) => font.default);
1125
+ if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
1126
+ const defaultFont = defaultFonts[0];
1127
+ const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
1128
+
1129
+ export async function generateHead(props: PageProps) {
1130
+ if (userLayout.generateHead) return userLayout.generateHead(props);
1131
+ if (userLayout.head !== undefined) return userLayout.head;
1132
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
1133
+ return inheritedLayout.head;
1134
+ }
1135
+
1136
+ export async function generateMetadata(props: PageProps) {
1137
+ if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
1138
+ if (userLayout.metadata !== undefined) return userLayout.metadata;
1139
+ if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
1140
+ return inheritedLayout.metadata;
1141
+ }
1142
+
1143
+ export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
1144
+ export const Error = userLayout.Error ?? inheritedLayout.Error;
1145
+ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
1146
+
1147
+ ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
1148
+ ${layoutBinding} return (
1149
+ <System.Provider
1150
+ of={GeneratedLayout as never}
1151
+ appName=${JSON.stringify(opts.appName)}
1152
+ ${prefix ? `prefix=${JSON.stringify(prefix)}
1153
+ ` : ""}params={params}
1154
+ manifest={userLayout.manifest ?? inheritedLayout.manifest}
1155
+ env={env}
1156
+ theme={userLayout.theme ?? inheritedLayout.theme}
1157
+ fonts={loadFonts(userFonts)}
1158
+ className={defaultFontClassName}
1159
+ gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
1160
+ layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
1161
+ reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
1162
+ wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
1163
+ allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
1164
+ >
1165
+ ${layoutChild}
1166
+ </System.Provider>
1167
+ );
1168
+ }
1169
+ ` : `import type { LayoutProps, PageProps } from "akanjs/client";
1170
+ ${inheritedImport}${userImport}
1171
+ export async function generateHead(props: PageProps) {
1172
+ if (userLayout.generateHead) return userLayout.generateHead(props);
1173
+ if (userLayout.head !== undefined) return userLayout.head;
1174
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
1175
+ return inheritedLayout.head;
1176
+ }
1177
+
1178
+ export async function generateMetadata(props: PageProps) {
1179
+ if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
1180
+ if (userLayout.metadata !== undefined) return userLayout.metadata;
1181
+ if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
1182
+ return inheritedLayout.metadata;
1183
+ }
1184
+
1185
+ export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
1186
+ export const Error = userLayout.Error ?? inheritedLayout.Error;
1187
+ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
1188
+
1189
+ ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
1190
+ ${layoutBinding} return ${layoutReturn};
1191
+ }
1192
+ `;
1193
+ await Bun.write(absPath, source);
1194
+ return absPath;
1195
+ }
1196
+ async function resolveSsrPageEntries(opts) {
1197
+ const absPageDir = path4.resolve(opts.appCwdPath, "page");
1198
+ const hasSt = await appHasStModule(opts.appCwdPath);
1199
+ const basePaths = opts.basePaths ?? [];
1200
+ const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
1201
+ const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
1202
+ const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
1203
+ const segments = getRootBoundarySegments(key);
1204
+ return segments !== null && isRootBoundarySegments(segments, basePaths);
1205
+ }));
1206
+ const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
1207
+ const userAbsPath = path4.resolve(absPageDir, key);
1208
+ if (OVERRIDES_KEY_RE.test(key)) {
1209
+ const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
1210
+ appCwdPath: opts.appCwdPath,
1211
+ key,
1212
+ userAbsPath
1213
+ });
1214
+ return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
1215
+ }
1216
+ return { key, moduleAbsPath: userAbsPath };
1217
+ }));
1218
+ const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
1219
+ key: implicitRootLayoutKey(boundary.segments),
1220
+ moduleAbsPath: await writeGeneratedRootLayoutFile({
1221
+ appCwdPath: opts.appCwdPath,
1222
+ appName: opts.appName,
1223
+ boundary,
1224
+ rootSourceAbsPath,
1225
+ includeStInit: hasSt && boundary.segments.length === 0,
1226
+ includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
1227
+ }),
1228
+ seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
1229
+ })));
1230
+ const entries = [...base, ...generated];
1231
+ entries.sort((a, b) => a.key.localeCompare(b.key));
1232
+ return entries;
1233
+ }
1234
+ async function resolveSsrPageEntriesForApp(app, pageKeys) {
1235
+ const config = await app.getConfig();
1236
+ return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
1237
+ }
634
1238
 
635
1239
  // pkgs/@akanjs/devkit/linter.ts
636
1240
  import { spawn } from "child_process";
637
1241
  import { existsSync, readFileSync } from "fs";
638
- import path3 from "path";
1242
+ import path5 from "path";
639
1243
  import chalk from "chalk";
1244
+ var parseBiomeReport = (output) => {
1245
+ for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
1246
+ const end = objectEndAt(output, start);
1247
+ if (end === -1)
1248
+ break;
1249
+ const report = asBiomeReport(output.slice(start, end + 1));
1250
+ if (report)
1251
+ return report;
1252
+ }
1253
+ throw new Error(output.trim() || "No Biome JSON output");
1254
+ };
1255
+ var objectEndAt = (output, start) => {
1256
+ let depth = 0;
1257
+ let inString = false;
1258
+ let escaped = false;
1259
+ for (let idx = start;idx < output.length; idx += 1) {
1260
+ const char = output[idx];
1261
+ if (inString) {
1262
+ if (escaped)
1263
+ escaped = false;
1264
+ else if (char === "\\")
1265
+ escaped = true;
1266
+ else if (char === '"')
1267
+ inString = false;
1268
+ continue;
1269
+ }
1270
+ if (char === '"')
1271
+ inString = true;
1272
+ else if (char === "{")
1273
+ depth += 1;
1274
+ else if (char === "}" && --depth === 0)
1275
+ return idx;
1276
+ }
1277
+ return -1;
1278
+ };
1279
+ var asBiomeReport = (candidate) => {
1280
+ try {
1281
+ const parsed = JSON.parse(candidate);
1282
+ if (!parsed || typeof parsed !== "object")
1283
+ return null;
1284
+ if (!("diagnostics" in parsed) && !("summary" in parsed))
1285
+ return null;
1286
+ return parsed;
1287
+ } catch {
1288
+ return null;
1289
+ }
1290
+ };
1291
+ var BIOME_CONFIG_FILES = ["biome.json", "biome.jsonc"];
640
1292
 
641
1293
  class Linter {
642
1294
  lintRoot;
1295
+ configPath;
643
1296
  #biomeBin;
644
1297
  constructor(cwdPath) {
645
1298
  this.lintRoot = this.#findBiomeRootPath(cwdPath);
646
- const localBiomeBin = path3.join(this.lintRoot, "node_modules/.bin/biome");
1299
+ this.configPath = Linter.#configPathIn(this.lintRoot) ?? path5.join(this.lintRoot, "biome.json");
1300
+ const localBiomeBin = path5.join(this.lintRoot, "node_modules/.bin/biome");
647
1301
  this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
648
1302
  }
1303
+ static #configPathIn(dir) {
1304
+ for (const fileName of BIOME_CONFIG_FILES) {
1305
+ const configPath = path5.join(dir, fileName);
1306
+ if (existsSync(configPath))
1307
+ return configPath;
1308
+ }
1309
+ return null;
1310
+ }
649
1311
  #findBiomeRootPath(dir) {
650
- const configPath = path3.join(dir, "biome.json");
651
- if (existsSync(configPath))
1312
+ if (Linter.#configPathIn(dir))
652
1313
  return dir;
653
- const parentDir = path3.dirname(dir);
1314
+ const parentDir = path5.dirname(dir);
654
1315
  if (parentDir === dir)
655
1316
  throw new Error(`biome.json not found from ${dir}`);
656
1317
  return this.#findBiomeRootPath(parentDir);
657
1318
  }
658
1319
  #toBiomePath(filePath) {
659
- const relativePath = path3.relative(this.lintRoot, filePath);
660
- if (!relativePath.startsWith("..") && !path3.isAbsolute(relativePath))
1320
+ const relativePath = path5.relative(this.lintRoot, filePath);
1321
+ if (!relativePath.startsWith("..") && !path5.isAbsolute(relativePath))
661
1322
  return relativePath;
662
1323
  return filePath;
663
1324
  }
664
1325
  #resolveFilePath(filePath) {
665
- return path3.isAbsolute(filePath) ? filePath : path3.join(this.lintRoot, filePath);
1326
+ return path5.isAbsolute(filePath) ? filePath : path5.join(this.lintRoot, filePath);
666
1327
  }
667
1328
  async#runBiome(args, input) {
668
1329
  return await new Promise((resolve, reject) => {
@@ -683,18 +1344,11 @@ class Linter {
683
1344
  proc.stdin.end(input);
684
1345
  });
685
1346
  }
686
- #parseBiomeReport(output) {
687
- const jsonStart = output.indexOf("{");
688
- const jsonEnd = output.lastIndexOf("}");
689
- if (jsonStart === -1 || jsonEnd === -1 || jsonEnd < jsonStart)
690
- throw new Error(output.trim() || "No Biome JSON output");
691
- return JSON.parse(output.slice(jsonStart, jsonEnd + 1));
692
- }
693
1347
  #diagnosticFilePath(diagnostic, fallbackFilePath) {
694
1348
  const diagnosticPath = diagnostic.location?.path;
695
1349
  if (!diagnosticPath)
696
1350
  return fallbackFilePath;
697
- return path3.isAbsolute(diagnosticPath) ? diagnosticPath : path3.join(this.lintRoot, diagnosticPath);
1351
+ return path5.isAbsolute(diagnosticPath) ? diagnosticPath : path5.join(this.lintRoot, diagnosticPath);
698
1352
  }
699
1353
  #createLintMessage(diagnostic) {
700
1354
  const start = diagnostic.location?.start;
@@ -759,10 +1413,10 @@ class Linter {
759
1413
  "--max-diagnostics=none",
760
1414
  "--no-errors-on-unmatched",
761
1415
  "--config-path",
762
- path3.join(this.lintRoot, "biome.json"),
1416
+ this.configPath,
763
1417
  this.#toBiomePath(filePath)
764
1418
  ]);
765
- const report = this.#parseBiomeReport(stdout || stderr);
1419
+ const report = parseBiomeReport(stdout || stderr);
766
1420
  const results = this.#toLintResults(report, filePath);
767
1421
  const { errors, warnings } = this.#splitMessages(results);
768
1422
  const output = write && existsSync(filePath) ? readFileSync(filePath, "utf8") : undefined;
@@ -880,22 +1534,34 @@ ${errorText}, ${warningText} found`];
880
1534
  if (!dryRun)
881
1535
  return await this.#checkFile(resolvedFilePath, { write: true });
882
1536
  const source = readFileSync(resolvedFilePath, "utf8");
883
- const { stdout } = await this.#runBiome([
1537
+ const { stdout } = await this.#runBiome(["check", "--write", "--config-path", this.configPath, "--stdin-file-path", this.#toBiomePath(resolvedFilePath)], source);
1538
+ const lintResult = await this.lintFile(resolvedFilePath);
1539
+ return { ...lintResult, fixed: stdout !== source, output: stdout };
1540
+ }
1541
+ async fixFiles(filePaths) {
1542
+ const resolved = filePaths.map((filePath) => this.#resolveFilePath(filePath)).filter(existsSync);
1543
+ if (resolved.length === 0)
1544
+ return { fixed: [] };
1545
+ const before = new Map(resolved.map((filePath) => [filePath, readFileSync(filePath, "utf8")]));
1546
+ await this.#runBiome([
884
1547
  "check",
885
1548
  "--write",
1549
+ "--reporter=json",
1550
+ "--max-diagnostics=none",
1551
+ "--no-errors-on-unmatched",
886
1552
  "--config-path",
887
- path3.join(this.lintRoot, "biome.json"),
888
- "--stdin-file-path",
889
- this.#toBiomePath(resolvedFilePath)
890
- ], source);
891
- const lintResult = await this.lintFile(resolvedFilePath);
892
- return { ...lintResult, fixed: stdout !== source, output: stdout };
1553
+ this.configPath,
1554
+ ...resolved.map((filePath) => this.#toBiomePath(filePath))
1555
+ ]);
1556
+ return {
1557
+ fixed: resolved.filter((filePath) => readFileSync(filePath, "utf8") !== before.get(filePath))
1558
+ };
893
1559
  }
894
1560
  async getConfigForFile(filePath) {
895
1561
  const resolvedFilePath = this.#resolveFilePath(filePath);
896
1562
  if (!existsSync(resolvedFilePath))
897
1563
  throw new Error(`File not found: ${filePath}`);
898
- return JSON.parse(readFileSync(path3.join(this.lintRoot, "biome.json"), "utf8"));
1564
+ return JSON.parse(readFileSync(this.configPath, "utf8"));
899
1565
  }
900
1566
  async getProblematicRules(filePath) {
901
1567
  const { results } = await this.lintFile(filePath);
@@ -910,22 +1576,36 @@ ${errorText}, ${warningText} found`];
910
1576
  }
911
1577
  }
912
1578
 
1579
+ // pkgs/@akanjs/devkit/repoIdentity.ts
1580
+ import { execFileSync } from "child_process";
1581
+ import path6 from "path";
1582
+ var resolved = new Map;
1583
+ var readRemoteName = (workspaceRoot) => {
1584
+ try {
1585
+ const url = execFileSync("git", ["config", "--get", "remote.origin.url"], {
1586
+ cwd: workspaceRoot,
1587
+ encoding: "utf-8",
1588
+ stdio: ["ignore", "pipe", "ignore"]
1589
+ }).trim();
1590
+ return url.replace(/\.git$/, "").split(/[/:]/).pop() || null;
1591
+ } catch {
1592
+ return null;
1593
+ }
1594
+ };
1595
+ var resolveRepoName = (workspaceRoot) => {
1596
+ const cached = resolved.get(workspaceRoot);
1597
+ if (cached)
1598
+ return cached;
1599
+ const repoName = readRemoteName(workspaceRoot) ?? path6.basename(workspaceRoot);
1600
+ resolved.set(workspaceRoot, repoName);
1601
+ return repoName;
1602
+ };
1603
+
913
1604
  // pkgs/@akanjs/devkit/scanInfo.ts
914
- import { rm } from "fs/promises";
915
- import path4 from "path";
916
- var databaseFileTypes = [
917
- "constant",
918
- "dictionary",
919
- "document",
920
- "service",
921
- "signal",
922
- "store",
923
- "template",
924
- "unit",
925
- "util",
926
- "view",
927
- "zone"
928
- ];
1605
+ import { rm as rm2 } from "fs/promises";
1606
+ import path7 from "path";
1607
+
1608
+ // pkgs/@akanjs/devkit/workspaceLayout.ts
929
1609
  var appRootAllowedFiles = new Set([
930
1610
  "AGENTS.md",
931
1611
  "CLAUDE.md",
@@ -939,25 +1619,53 @@ var appRootAllowedFiles = new Set([
939
1619
  "tsconfig.json",
940
1620
  "tsconfig.tsbuildinfo"
941
1621
  ]);
942
- var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
943
1622
  var appRootAllowedDirs = new Set([
944
1623
  ".akan",
945
1624
  "android",
1625
+ "common",
946
1626
  "env",
947
1627
  "ios",
948
1628
  "lib",
949
1629
  "mobile",
950
1630
  "page",
1631
+ "plugin",
951
1632
  "private",
952
1633
  "public",
953
1634
  "script",
954
- "ui",
1635
+ "secrets",
955
1636
  "srvkit",
956
- "webkit",
957
- "common",
958
- "secrets"
1637
+ "ui",
1638
+ "webkit"
959
1639
  ]);
960
1640
  var libRootAllowedFiles = new Set([
1641
+ "AGENTS.md",
1642
+ "CLAUDE.md",
1643
+ "README.md",
1644
+ "akan.config.ts",
1645
+ "akan.lib.json",
1646
+ "client.ts",
1647
+ "index.ts",
1648
+ "package.json",
1649
+ "server.ts",
1650
+ "tsconfig.json",
1651
+ "tsconfig.spec.json",
1652
+ "tsconfig.tsbuildinfo"
1653
+ ]);
1654
+ var libRootAllowedDirs = new Set([
1655
+ "common",
1656
+ "env",
1657
+ "lib",
1658
+ "page",
1659
+ "plugin",
1660
+ "private",
1661
+ "public",
1662
+ "srvkit",
1663
+ "ui",
1664
+ "webkit"
1665
+ ]);
1666
+ var rootAllowedFiles = { app: appRootAllowedFiles, lib: libRootAllowedFiles };
1667
+ var rootAllowedDirs = { app: appRootAllowedDirs, lib: libRootAllowedDirs };
1668
+ var libFacetRootAllowedFiles = new Set([
961
1669
  "cnst.ts",
962
1670
  "db.ts",
963
1671
  "dict.ts",
@@ -968,6 +1676,25 @@ var libRootAllowedFiles = new Set([
968
1676
  "useClient.ts",
969
1677
  "useServer.ts"
970
1678
  ]);
1679
+ var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
1680
+ var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
1681
+ var isScannedRootEntry = (type, name) => !name.startsWith(".") || rootAllowedDirs[type].has(name);
1682
+
1683
+ // pkgs/@akanjs/devkit/scanInfo.ts
1684
+ var databaseFileTypes = [
1685
+ "constant",
1686
+ "dictionary",
1687
+ "document",
1688
+ "service",
1689
+ "signal",
1690
+ "store",
1691
+ "template",
1692
+ "unit",
1693
+ "util",
1694
+ "view",
1695
+ "zone"
1696
+ ];
1697
+ var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
971
1698
  var internalLibDirs = new Set(["__lib", "__scalar"]);
972
1699
  var moduleNonUiFileTypes = {
973
1700
  database: new Set(["constant", "dictionary", "document", "service", "signal", "store"]),
@@ -980,18 +1707,16 @@ var moduleUiFileTypes = {
980
1707
  scalar: new Set(["Template", "Unit"])
981
1708
  };
982
1709
  var testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
983
- var rootSignalTestFilePattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
984
- var createDependencyScanner = async (exec) => (await import("./dependencyScanner-f6grrep3.js")).TypeScriptDependencyScanner.from(exec);
1710
+ var createDependencyScanner = async (exec) => (await import("./dependencyScanner-shs82ynp.js")).TypeScriptDependencyScanner.from(exec);
985
1711
  var isAllowedTestFile = (filename) => testFilePattern.test(filename);
986
- var isAllowedLibRootFile = (filename) => libRootAllowedFiles.has(filename) || rootSignalTestFilePattern.test(filename);
987
- var getScanPath = (exec, relativePath) => path4.posix.join(`${exec.type}s`, exec.name, relativePath.split(path4.sep).join("/"));
1712
+ var getScanPath = (exec, relativePath) => path7.posix.join(`${exec.type}s`, exec.name, relativePath.split(path7.sep).join("/"));
988
1713
  async function clearGeneratedRootCapacitorConfigs(exec) {
989
1714
  if (exec.type !== "app")
990
1715
  return;
991
- await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm(exec.getPath(filename), { force: true })));
1716
+ await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm2(exec.getPath(filename), { force: true })));
992
1717
  }
993
1718
  var getModuleNameFromPath = (kind, modulePath) => {
994
- const dirname = path4.basename(modulePath);
1719
+ const dirname = path7.basename(modulePath);
995
1720
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
996
1721
  };
997
1722
  async function assertScanConvention(exec, libRoot) {
@@ -1000,28 +1725,31 @@ async function assertScanConvention(exec, libRoot) {
1000
1725
  const addViolation = (relativePath, reason) => {
1001
1726
  violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
1002
1727
  };
1003
- if (exec.type === "app") {
1004
- const { files, dirs } = await exec.getFilesAndDirs(".");
1005
- files.filter((filename) => !appRootAllowedFiles.has(filename)).forEach((filename) => {
1006
- addViolation(filename, "unsupported app root file");
1007
- });
1008
- dirs.filter((dirname) => !appRootAllowedDirs.has(dirname)).forEach((dirname) => {
1009
- addViolation(dirname, "unsupported app root folder");
1010
- });
1011
- }
1012
- libRoot.files.filter((filename) => !isAllowedLibRootFile(filename)).forEach((filename) => {
1013
- addViolation(path4.join("lib", filename), "unsupported lib root file");
1728
+ const allowedRootFiles = rootAllowedFiles[exec.type];
1729
+ const allowedRootDirs = rootAllowedDirs[exec.type];
1730
+ const { files, dirs } = await exec.getFilesAndDirs(".");
1731
+ files.filter((filename) => !allowedRootFiles.has(filename)).forEach((filename) => {
1732
+ addViolation(filename, `unsupported ${exec.type} root file`);
1733
+ });
1734
+ dirs.filter((dirname) => !allowedRootDirs.has(dirname)).forEach((dirname) => {
1735
+ addViolation(dirname, `unsupported ${exec.type} root folder`);
1736
+ });
1737
+ if (exec.type === "lib")
1738
+ for (const { relativePath, reason } of await exec.getPageConventionViolations())
1739
+ addViolation(relativePath, reason);
1740
+ libRoot.files.filter((filename) => !isAllowedLibFacetRootFile(filename)).forEach((filename) => {
1741
+ addViolation(path7.join("lib", filename), "unsupported lib facet root file");
1014
1742
  });
1015
1743
  libRoot.dirs.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname)).forEach((dirname) => {
1016
- addViolation(path4.join("lib", dirname), "unsupported internal lib folder");
1744
+ addViolation(path7.join("lib", dirname), "unsupported internal lib folder");
1017
1745
  });
1018
1746
  const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
1019
1747
  const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
1020
1748
  const scalarDirs = await exec.readdir("lib/__scalar");
1021
1749
  await Promise.all([
1022
- ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path4.join("lib", dirname))),
1023
- ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path4.join("lib", dirname))),
1024
- ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path4.join("lib/__scalar", dirname)))
1750
+ ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path7.join("lib", dirname))),
1751
+ ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path7.join("lib", dirname))),
1752
+ ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path7.join("lib/__scalar", dirname)))
1025
1753
  ]);
1026
1754
  if (violations.length > 0) {
1027
1755
  throw new Error(`[scan-convention]
@@ -1033,11 +1761,11 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
1033
1761
  const { files, dirs } = await exec.getFilesAndDirs(modulePath);
1034
1762
  const moduleName = getModuleNameFromPath(kind, modulePath);
1035
1763
  dirs.forEach((dirname) => {
1036
- violations.push(`${getScanPath(exec, path4.join(modulePath, dirname))}: unsupported module folder`);
1764
+ violations.push(`${getScanPath(exec, path7.join(modulePath, dirname))}: unsupported module folder`);
1037
1765
  });
1038
1766
  const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
1039
1767
  files.forEach((filename) => {
1040
- const filePath = path4.join(modulePath, filename);
1768
+ const filePath = path7.join(modulePath, filename);
1041
1769
  if (filename === "index.ts" || filename === "index.tsx" || isAllowedTestFile(filename))
1042
1770
  return;
1043
1771
  if (filename === `${moduleName}.abstract.md`)
@@ -1081,7 +1809,7 @@ class ScanInfo {
1081
1809
  { all: new Set, databases: new Set, services: new Set, scalars: new Set }
1082
1810
  ]));
1083
1811
  static async getScanResult(exec) {
1084
- const [akanConfig2, scanner, pkgs, libs] = await Promise.all([
1812
+ const [akanConfig, scanner, pkgs, libs] = await Promise.all([
1085
1813
  exec.getConfig(),
1086
1814
  createDependencyScanner(exec),
1087
1815
  exec.workspace.getPkgs(),
@@ -1117,7 +1845,7 @@ class ScanInfo {
1117
1845
  });
1118
1846
  await Promise.all([
1119
1847
  ...databaseDirs.map(async (name) => {
1120
- const filenames = await exec.readdir(path4.join("lib", name));
1848
+ const filenames = await exec.readdir(path7.join("lib", name));
1121
1849
  filenames.forEach((filename) => {
1122
1850
  if (filename.endsWith(".constant.ts"))
1123
1851
  files.constant.databases.push(name);
@@ -1145,7 +1873,7 @@ class ScanInfo {
1145
1873
  }),
1146
1874
  ...serviceDirs.map(async (dirname) => {
1147
1875
  const name = dirname.slice(1);
1148
- const filenames = await exec.readdir(path4.join("lib", dirname));
1876
+ const filenames = await exec.readdir(path7.join("lib", dirname));
1149
1877
  filenames.forEach((filename) => {
1150
1878
  if (filename.endsWith(".dictionary.ts"))
1151
1879
  files.dictionary.services.push(name);
@@ -1168,7 +1896,7 @@ class ScanInfo {
1168
1896
  });
1169
1897
  }),
1170
1898
  ...scalarDirs.map(async (name) => {
1171
- const filenames = await exec.readdir(path4.join("lib/__scalar", name));
1899
+ const filenames = await exec.readdir(path7.join("lib/__scalar", name));
1172
1900
  filenames.forEach((filename) => {
1173
1901
  if (filename.endsWith(".constant.ts"))
1174
1902
  files.constant.scalars.push(name);
@@ -1190,12 +1918,11 @@ class ScanInfo {
1190
1918
  })
1191
1919
  ]);
1192
1920
  const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
1193
- const scanResult = {
1921
+ const common = {
1194
1922
  name: exec.name,
1195
1923
  type: exec.type,
1196
1924
  repoName: exec.workspace.repoName,
1197
- serveDomain: WorkspaceExecutor.getBaseDevEnv(path4.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
1198
- akanConfig: akanConfig2,
1925
+ serveDomain: WorkspaceExecutor.getBaseDevEnv(path7.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
1199
1926
  files,
1200
1927
  libDeps,
1201
1928
  pkgDeps,
@@ -1203,6 +1930,7 @@ class ScanInfo {
1203
1930
  devDependencies: npmDevDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
1204
1931
  routes
1205
1932
  };
1933
+ const scanResult = akanConfig instanceof AkanAppConfig ? { ...common, akanConfig } : { ...common, akanConfig };
1206
1934
  return scanResult;
1207
1935
  }
1208
1936
  constructor(scanResult) {
@@ -1396,16 +2124,16 @@ class LibInfo extends ScanInfo {
1396
2124
  class PkgInfo {
1397
2125
  exec;
1398
2126
  name;
1399
- scanResult;
2127
+ #scanResult;
1400
2128
  static async scanExecutor(exec) {
1401
2129
  const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
1402
2130
  const scanner = await createDependencyScanner(exec);
1403
2131
  const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
1404
- const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path5) => tsconfig.compilerOptions.paths?.[path5]?.some((resolve) => resolve.startsWith("pkgs/"))).map((path5) => path5.replace("/*", "")));
2132
+ const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path) => tsconfig.compilerOptions.paths?.[path]?.some((resolve) => resolve.startsWith("pkgs/"))).map((path) => path.replace("/*", "")));
1405
2133
  const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
1406
- const pkgDeps = [...pkgPathDepSet].map((path5) => {
1407
- const pathSplitLength = path5.split("/").length;
1408
- return (tsconfig.compilerOptions.paths?.[path5]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
2134
+ const pkgDeps = [...pkgPathDepSet].map((path) => {
2135
+ const pathSplitLength = path.split("/").length;
2136
+ return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
1409
2137
  }).filter((pkg) => pkg !== exec.name);
1410
2138
  const pkgScanResult = {
1411
2139
  name: exec.name,
@@ -1427,10 +2155,10 @@ class PkgInfo {
1427
2155
  constructor(exec, scanResult) {
1428
2156
  this.exec = exec;
1429
2157
  this.name = exec.name;
1430
- this.scanResult = scanResult;
2158
+ this.#scanResult = scanResult;
1431
2159
  }
1432
2160
  getScanResult() {
1433
- return this.scanResult;
2161
+ return this.#scanResult;
1434
2162
  }
1435
2163
  }
1436
2164
 
@@ -1591,16 +2319,16 @@ class Executor {
1591
2319
  linter = null;
1592
2320
  constructor(name, cwdPath) {
1593
2321
  this.name = name;
1594
- this.logger = new Logger(name);
2322
+ this.logger = new Logger2(name);
1595
2323
  this.logs = [];
1596
2324
  this.cwdPath = cwdPath;
1597
2325
  }
1598
2326
  #stdout(data) {
1599
2327
  if (Executor.verbose)
1600
- Logger.raw(chalk2.dim(data.toString()));
2328
+ Logger2.raw(chalk2.dim(data.toString()));
1601
2329
  }
1602
2330
  #stderr(data) {
1603
- Logger.raw(chalk2.red(data.toString()));
2331
+ Logger2.raw(chalk2.red(data.toString()));
1604
2332
  }
1605
2333
  exec(command, options = {}) {
1606
2334
  const cwd = options.cwd?.toString() ?? this.cwdPath;
@@ -1628,7 +2356,7 @@ class Executor {
1628
2356
  }));
1629
2357
  });
1630
2358
  proc.on("exit", (code, signal) => {
1631
- if (!!code || signal)
2359
+ if (code || signal)
1632
2360
  reject(new CommandExecutionError({
1633
2361
  command,
1634
2362
  cwd,
@@ -1726,7 +2454,7 @@ class Executor {
1726
2454
  }));
1727
2455
  });
1728
2456
  proc.on("exit", (code, signal) => {
1729
- if (!!code || signal)
2457
+ if (code || signal)
1730
2458
  reject(new CommandExecutionError({
1731
2459
  command: modulePath,
1732
2460
  args,
@@ -1742,10 +2470,10 @@ class Executor {
1742
2470
  });
1743
2471
  }
1744
2472
  getPath(filePath) {
1745
- if (path5.isAbsolute(filePath))
2473
+ if (path8.isAbsolute(filePath))
1746
2474
  return filePath;
1747
2475
  if (filePath.startsWith("."))
1748
- return path5.join(this.cwdPath, filePath);
2476
+ return path8.join(this.cwdPath, filePath);
1749
2477
  const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
1750
2478
  const targetParts = filePath.split(/[\\/]/).filter(Boolean);
1751
2479
  let overlapLength = 0;
@@ -1759,12 +2487,12 @@ class Executor {
1759
2487
  if (isOverlap)
1760
2488
  overlapLength = i;
1761
2489
  }
1762
- return path5.join(this.cwdPath, ...targetParts.slice(overlapLength));
2490
+ return path8.join(this.cwdPath, ...targetParts.slice(overlapLength));
1763
2491
  }
1764
2492
  async mkdir(dirPath) {
1765
2493
  const writePath = this.getPath(dirPath);
1766
2494
  if (!await FileSys.dirExists(writePath))
1767
- await mkdir(writePath, { recursive: true });
2495
+ await mkdir3(writePath, { recursive: true });
1768
2496
  this.logger.verbose(`Make directory ${writePath}`);
1769
2497
  return this;
1770
2498
  }
@@ -1802,18 +2530,19 @@ class Executor {
1802
2530
  return this;
1803
2531
  }
1804
2532
  async removeDir(dirPath) {
1805
- const readPath = path5.join(this.getPath(dirPath), ".");
2533
+ const readPath = path8.join(this.getPath(dirPath), ".");
1806
2534
  if (await FileSys.entryExists(readPath))
1807
- await rm2(readPath, { recursive: true, force: true });
2535
+ await rm3(readPath, { recursive: true, force: true });
1808
2536
  this.logger.verbose(`Remove directory ${readPath}`);
1809
2537
  return this;
1810
2538
  }
1811
2539
  async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
1812
2540
  const writePath = this.getPath(filePath);
1813
- const dir = path5.dirname(writePath);
2541
+ const dir = path8.dirname(writePath);
1814
2542
  if (!await FileSys.dirExists(dir))
1815
- await mkdir(dir, { recursive: true });
1816
- let contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2);
2543
+ await mkdir3(dir, { recursive: true });
2544
+ let contentStr = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}
2545
+ `;
1817
2546
  if (await FileSys.fileExists(writePath)) {
1818
2547
  const currentContent = await FileSys.readText(writePath);
1819
2548
  if (currentContent === contentStr || !overwrite) {
@@ -1821,7 +2550,7 @@ class Executor {
1821
2550
  contentStr = currentContent;
1822
2551
  } else {
1823
2552
  await FileSys.writeText(writePath, contentStr);
1824
- if (Logger.isVerbose())
2553
+ if (Logger2.isVerbose())
1825
2554
  this.logger.rawLog(chalk2.yellow(`File Update: ${filePath}`));
1826
2555
  }
1827
2556
  } else {
@@ -1835,7 +2564,7 @@ class Executor {
1835
2564
  await this.writeFile(filePath, content);
1836
2565
  }
1837
2566
  async getLocalFile(targetPath) {
1838
- const filePath = path5.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
2567
+ const filePath = path8.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
1839
2568
  const content = await this.readFile(filePath);
1840
2569
  return { filePath, content };
1841
2570
  }
@@ -1852,9 +2581,9 @@ class Executor {
1852
2581
  const dest = this.getPath(destPath);
1853
2582
  if (!await FileSys.exists(src))
1854
2583
  return;
1855
- const isDirectory = (await stat(src)).isDirectory();
2584
+ const isDirectory = (await stat2(src)).isDirectory();
1856
2585
  if (!await FileSys.exists(dest) && isDirectory)
1857
- await mkdir(dest, { recursive: true });
2586
+ await mkdir3(dest, { recursive: true });
1858
2587
  if (dereference)
1859
2588
  await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
1860
2589
  else
@@ -1932,26 +2661,26 @@ class Executor {
1932
2661
  const result = await getContent.default(scanInfo ?? null, dict, options);
1933
2662
  if (result === null)
1934
2663
  return null;
1935
- const filename = typeof result === "object" ? result.filename : path5.basename(targetPath).replace(".js", ".ts");
2664
+ const filename = typeof result === "object" ? result.filename : path8.basename(targetPath).replace(".js", ".ts");
1936
2665
  const content = typeof result === "object" ? result.content : result;
1937
- const dirname = path5.dirname(targetPath);
1938
- const convertedTargetPath = Object.entries(dict).reduce((path6, [key, value]) => path6.replace(new RegExp(`__${key}__`, "g"), value), `${dirname}/${filename}`);
2666
+ const dirname = path8.dirname(targetPath);
2667
+ const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), `${dirname}/${filename}`);
1939
2668
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
1940
2669
  return this.writeFile(convertedTargetPath, content, { overwrite });
1941
2670
  } else if (targetPath.endsWith(".template")) {
1942
2671
  const content = await FileSys.readText(templatePath);
1943
- const convertedTargetPath = Object.entries(dict).reduce((path6, [key, value]) => path6.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
2672
+ const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
1944
2673
  const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
1945
2674
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
1946
2675
  return this.writeFile(convertedTargetPath, convertedContent, {
1947
2676
  overwrite
1948
2677
  });
1949
- } else if (staticTemplateFileExtensions.has(path5.extname(targetPath).toLowerCase())) {
1950
- const convertedTargetPath = Object.entries(dict).reduce((path6, [key, value]) => path6.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
2678
+ } else if (staticTemplateFileExtensions.has(path8.extname(targetPath).toLowerCase())) {
2679
+ const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
1951
2680
  const writePath = this.getPath(convertedTargetPath);
1952
- const dirname = path5.dirname(writePath);
2681
+ const dirname = path8.dirname(writePath);
1953
2682
  if (!await FileSys.dirExists(dirname))
1954
- await mkdir(dirname, { recursive: true });
2683
+ await mkdir3(dirname, { recursive: true });
1955
2684
  await copyFile(templatePath, writePath);
1956
2685
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
1957
2686
  return { filePath: writePath, content: "" };
@@ -1969,11 +2698,11 @@ class Executor {
1969
2698
  const templateRoot = await this.#resolveTemplateRoot();
1970
2699
  const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
1971
2700
  const prefixTemplatePath = templatePath;
1972
- if ((await stat(prefixTemplatePath)).isFile()) {
1973
- const filename = path5.basename(prefixTemplatePath);
2701
+ if ((await stat2(prefixTemplatePath)).isFile()) {
2702
+ const filename = path8.basename(prefixTemplatePath);
1974
2703
  const fileContent = await this.#applyTemplateFile({
1975
2704
  templatePath: prefixTemplatePath,
1976
- targetPath: path5.join(basePath, filename),
2705
+ targetPath: path8.join(basePath, filename),
1977
2706
  scanInfo,
1978
2707
  overwrite
1979
2708
  }, dict, options);
@@ -1981,19 +2710,19 @@ class Executor {
1981
2710
  } else {
1982
2711
  const subdirs = await readDirEntries(templatePath);
1983
2712
  const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
1984
- const subpath = path5.join(templatePath, subdir);
1985
- if ((await stat(subpath)).isFile()) {
2713
+ const subpath = path8.join(templatePath, subdir);
2714
+ if ((await stat2(subpath)).isFile()) {
1986
2715
  const fileContent = await this.#applyTemplateFile({
1987
2716
  templatePath: subpath,
1988
- targetPath: path5.join(basePath, subdir),
2717
+ targetPath: path8.join(basePath, subdir),
1989
2718
  scanInfo,
1990
2719
  overwrite
1991
2720
  }, dict, options);
1992
2721
  return fileContent ? [fileContent] : [];
1993
2722
  } else
1994
2723
  return await this._applyTemplate({
1995
- basePath: path5.join(basePath, subdir),
1996
- template: path5.join(template, subdir),
2724
+ basePath: path8.join(basePath, subdir),
2725
+ template: path8.join(template, subdir),
1997
2726
  scanInfo,
1998
2727
  dict,
1999
2728
  overwrite,
@@ -2005,8 +2734,8 @@ class Executor {
2005
2734
  }
2006
2735
  async#resolveTemplateRoot() {
2007
2736
  const candidates = [
2008
- path5.resolve(getDirname(import.meta.url), "templates"),
2009
- path5.resolve(getDirname(import.meta.url), "../cli/templates")
2737
+ path8.resolve(getDirname2(import.meta.url), "templates"),
2738
+ path8.resolve(getDirname2(import.meta.url), "../cli/templates")
2010
2739
  ];
2011
2740
  for (const candidate of candidates) {
2012
2741
  if (await FileSys.dirExists(candidate))
@@ -2019,29 +2748,43 @@ class Executor {
2019
2748
  ...options.dict ?? {},
2020
2749
  ...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
2021
2750
  };
2022
- return this._applyTemplate({ ...options, dict });
2751
+ const fileContents = await this._applyTemplate({ ...options, dict });
2752
+ await this.#formatAppliedTemplate(fileContents);
2753
+ return fileContents;
2754
+ }
2755
+ async#formatAppliedTemplate(fileContents) {
2756
+ const filePaths = fileContents.map((fileContent) => fileContent.filePath).filter((filePath) => filePath.endsWith(".ts") || filePath.endsWith(".tsx"));
2757
+ if (filePaths.length === 0)
2758
+ return;
2759
+ try {
2760
+ const { fixed } = await this.getLinter().fixFiles(filePaths);
2761
+ if (fixed.length > 0)
2762
+ this.logger.verbose(`Formatted ${fixed.length} scaffolded file(s)`);
2763
+ } catch (err) {
2764
+ this.logger.verbose(`Skipped formatting scaffolded files: ${err instanceof Error ? err.message : String(err)}`);
2765
+ }
2023
2766
  }
2024
2767
  async getTypeChecker() {
2025
- const { TypeChecker } = await import("./typeChecker-kravn7ns.js");
2768
+ const { TypeChecker } = await import("./typeChecker-3xgrzc2v.js");
2026
2769
  this.typeChecker ??= new TypeChecker(this);
2027
2770
  return this.typeChecker;
2028
2771
  }
2029
2772
  async typeCheck(filePath) {
2030
- const path6 = this.getPath(filePath);
2773
+ const path = this.getPath(filePath);
2031
2774
  const typeChecker = await this.getTypeChecker();
2032
- const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path6);
2775
+ const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path);
2033
2776
  const message = typeChecker.formatDiagnostics(fileDiagnostics);
2034
2777
  return { fileDiagnostics, fileErrors, fileWarnings, message };
2035
2778
  }
2036
2779
  async typeCheckAsync(filePath) {
2037
- const path6 = this.getPath(filePath);
2780
+ const path = this.getPath(filePath);
2038
2781
  const entry = await this.#resolveTypecheckWorkerEntry();
2039
2782
  const proc = Bun.spawn([process.execPath, entry], {
2040
2783
  cwd: this.cwdPath,
2041
2784
  env: {
2042
2785
  ...process.env,
2043
2786
  AKAN_TYPECHECK_CWD: this.cwdPath,
2044
- AKAN_TYPECHECK_FILE: path6
2787
+ AKAN_TYPECHECK_FILE: path
2045
2788
  },
2046
2789
  stdout: "pipe",
2047
2790
  stderr: "pipe"
@@ -2062,13 +2805,13 @@ class Executor {
2062
2805
  };
2063
2806
  }
2064
2807
  async#resolveTypecheckWorkerEntry() {
2065
- const dirname = getDirname(import.meta.url);
2808
+ const dirname = getDirname2(import.meta.url);
2066
2809
  const candidates = [
2067
- path5.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2068
- path5.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2069
- path5.join(dirname, "typecheck/typecheck.proc.ts"),
2070
- path5.join(dirname, "typecheck.proc.js"),
2071
- path5.join(dirname, "typecheck.proc.ts")
2810
+ path8.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2811
+ path8.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2812
+ path8.join(dirname, "typecheck/typecheck.proc.ts"),
2813
+ path8.join(dirname, "typecheck.proc.js"),
2814
+ path8.join(dirname, "typecheck.proc.ts")
2072
2815
  ];
2073
2816
  for (const candidate of candidates)
2074
2817
  if (await Bun.file(candidate).exists())
@@ -2080,9 +2823,9 @@ class Executor {
2080
2823
  return this.linter;
2081
2824
  }
2082
2825
  async lint(filePath, { fix = false, dryRun = false } = {}) {
2083
- const path6 = this.getPath(filePath);
2826
+ const path = this.getPath(filePath);
2084
2827
  const linter = this.getLinter();
2085
- const { results, errors, warnings } = await linter.lint(path6, {
2828
+ const { results, errors, warnings } = await linter.lint(path, {
2086
2829
  fix,
2087
2830
  dryRun
2088
2831
  });
@@ -2103,7 +2846,7 @@ class WorkspaceExecutor extends Executor {
2103
2846
  static #execs = new Map;
2104
2847
  static fromRoot({
2105
2848
  workspaceRoot = process.cwd(),
2106
- repoName = path5.basename(process.cwd())
2849
+ repoName = resolveRepoName(workspaceRoot)
2107
2850
  } = {}) {
2108
2851
  return WorkspaceExecutor.#execs.get(repoName) ?? new WorkspaceExecutor({ workspaceRoot, repoName });
2109
2852
  }
@@ -2195,8 +2938,8 @@ class WorkspaceExecutor extends Executor {
2195
2938
  await Promise.all(dirs.map(async (dir) => {
2196
2939
  if (dir.includes("_") || AVOID_DIRS.includes(dir))
2197
2940
  return;
2198
- const dirPath = path5.join(dirname, dir);
2199
- if ((await stat(dirPath)).isDirectory()) {
2941
+ const dirPath = path8.join(dirname, dir);
2942
+ if ((await stat2(dirPath)).isDirectory()) {
2200
2943
  results.push(`${prefix}${dir}`);
2201
2944
  if (maxDepth > 0)
2202
2945
  await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
@@ -2208,10 +2951,20 @@ class WorkspaceExecutor extends Executor {
2208
2951
  }
2209
2952
  async commit(message, { init = false, add = true } = {}) {
2210
2953
  if (init)
2211
- await this.exec(`git init --quiet`);
2954
+ await this.spawn("git", ["init", "--quiet"]);
2212
2955
  if (add)
2213
- await this.exec(`git add .`);
2214
- await this.exec(`git commit --quiet -m "${message}"`);
2956
+ await this.spawn("git", ["add", "."]);
2957
+ await this.spawn("git", ["commit", "--quiet", "-m", message]);
2958
+ }
2959
+ async hasChanges() {
2960
+ return !!(await this.spawn("git", ["status", "--porcelain"])).trim();
2961
+ }
2962
+ async listGitFiles(paths, { untracked = false } = {}) {
2963
+ if (!paths.length)
2964
+ return [];
2965
+ const mode = untracked ? ["--cached", "--others", "--exclude-standard"] : ["--cached"];
2966
+ const stdout = await this.spawn("git", ["ls-files", "-z", ...mode, "--", ...paths]);
2967
+ return stdout.split("\x00").filter((file) => !!file).sort();
2215
2968
  }
2216
2969
  async#getDirHasFile(basePath, targetFilename) {
2217
2970
  const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
@@ -2220,9 +2973,9 @@ class WorkspaceExecutor extends Executor {
2220
2973
  await Promise.all(dirs.map(async (dir) => {
2221
2974
  if (AVOID_DIRS.includes(dir))
2222
2975
  return;
2223
- const dirPath = path5.join(dirname, dir);
2976
+ const dirPath = path8.join(dirname, dir);
2224
2977
  if (await FileSys.dirExists(dirPath)) {
2225
- const hasTargetFile = await FileSys.fileExists(path5.join(dirPath, targetFilename));
2978
+ const hasTargetFile = await FileSys.fileExists(path8.join(dirPath, targetFilename));
2226
2979
  if (hasTargetFile)
2227
2980
  results.push(`${prefix}${dir}`);
2228
2981
  if (maxDepth > 0)
@@ -2288,8 +3041,20 @@ class SysExecutor extends Executor {
2288
3041
  return this.#akanConfig;
2289
3042
  }
2290
3043
  async getModules() {
2291
- const path6 = this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
2292
- return await this.workspace.getDirInModule(path6, this.name);
3044
+ const path = this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
3045
+ return await this.workspace.getDirInModule(path, this.name);
3046
+ }
3047
+ async getPageConventionViolations() {
3048
+ if (!await this.exists("page"))
3049
+ return [];
3050
+ const violations = [];
3051
+ for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
3052
+ const relativePath = entry.split(path8.sep).join("/");
3053
+ const reason = getPageSourceFileViolation(relativePath);
3054
+ if (reason)
3055
+ violations.push({ relativePath: `page/${relativePath}`, reason });
3056
+ }
3057
+ return violations;
2293
3058
  }
2294
3059
  #scanInfo = null;
2295
3060
  hasScanInfo() {
@@ -2347,18 +3112,19 @@ class SysExecutor extends Executor {
2347
3112
  }) : await LibInfo.fromExecutor(this, {
2348
3113
  refresh
2349
3114
  });
2350
- if (write) {
2351
- await Promise.all(this.#getScanTemplateTasks(scanInfo));
2352
- await this.writeJson(`akan.${this.type}.json`, scanInfo.getScanResult());
2353
- if (this.type === "lib")
2354
- this.#updateDependencies(scanInfo);
2355
- if (writeLib) {
2356
- const libInfos = [...scanInfo.getLibInfos().values()];
2357
- await this.#updateDependencies(scanInfo);
2358
- await Promise.all(libInfos.flatMap((libInfo) => libInfo.exec.#getScanTemplateTasks(libInfo)));
2359
- }
2360
- await this.syncAgentsIndex(scanInfo);
2361
- }
3115
+ if (write)
3116
+ await CodegenLock.run(this.workspace.workspaceRoot, `scan:${this.name}`, async () => {
3117
+ await Promise.all(this.#getScanTemplateTasks(scanInfo));
3118
+ await this.writeJson(`akan.${this.type}.json`, scanInfo.getScanResult());
3119
+ if (this.type === "lib")
3120
+ this.#updateDependencies(scanInfo);
3121
+ if (writeLib) {
3122
+ const libInfos = [...scanInfo.getLibInfos().values()];
3123
+ await this.#updateDependencies(scanInfo);
3124
+ await Promise.all(libInfos.flatMap((libInfo) => libInfo.exec.#getScanTemplateTasks(libInfo)));
3125
+ }
3126
+ await this.syncAgentsIndex(scanInfo);
3127
+ });
2362
3128
  this.#scanInfo = scanInfo;
2363
3129
  return scanInfo;
2364
3130
  }
@@ -2393,7 +3159,7 @@ class SysExecutor extends Executor {
2393
3159
  await this.setPackageJson(libPkgJsonWithDeps);
2394
3160
  }
2395
3161
  async getLocalFile(targetPath) {
2396
- const filePath = path5.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
3162
+ const filePath = path8.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
2397
3163
  const content = await this.workspace.readFile(filePath);
2398
3164
  return { filePath, content };
2399
3165
  }
@@ -2479,7 +3245,7 @@ class SysExecutor extends Executor {
2479
3245
  class AppExecutor extends SysExecutor {
2480
3246
  static #routeSourceValidator = null;
2481
3247
  static async#getRouteSourceValidator() {
2482
- AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-cncd92fv.js")).RouteSourceValidator;
3248
+ AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-ze6x894c.js")).RouteSourceValidator;
2483
3249
  return AppExecutor.#routeSourceValidator;
2484
3250
  }
2485
3251
  dist;
@@ -2490,9 +3256,9 @@ class AppExecutor extends SysExecutor {
2490
3256
  }
2491
3257
  static #execs = new Map;
2492
3258
  static from(executor, name) {
2493
- const exec2 = AppExecutor.#execs.get(name);
2494
- if (exec2)
2495
- return exec2;
3259
+ const exec = AppExecutor.#execs.get(name);
3260
+ if (exec)
3261
+ return exec;
2496
3262
  else if (executor instanceof WorkspaceExecutor)
2497
3263
  return new AppExecutor({ workspace: executor, name });
2498
3264
  else
@@ -2528,33 +3294,43 @@ class AppExecutor extends SysExecutor {
2528
3294
  };
2529
3295
  }
2530
3296
  async prepareCommand(type) {
2531
- const akanConfig2 = await this.getConfig();
2532
- const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig2.defaultDatabaseMode ?? "single";
3297
+ const akanConfig = await this.getConfig();
3298
+ const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
2533
3299
  const routeEnv = {
2534
- AKAN_PUBLIC_BASE_PATHS: [...akanConfig2.basePaths].join(","),
3300
+ AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
3301
+ AKAN_PUBLIC_API_PREFIX: akanConfig.api.prefix,
3302
+ AKAN_PUBLIC_WS_PREFIX: akanConfig.api.websocketPrefix,
2535
3303
  AKAN_DATABASE_MODE: databaseMode
2536
3304
  };
2537
3305
  Object.assign(process.env, routeEnv);
2538
3306
  if (type === "build") {
2539
3307
  this.#excludeDevOnlyPages = true;
2540
3308
  this.#pageKeys = null;
2541
- if (await this.exists(this.dist.cwdPath))
2542
- await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
3309
+ await this.dist.removeDir(this.dist.cwdPath);
2543
3310
  await Promise.all([this.dist.mkdir("private"), this.dist.mkdir("public")]);
2544
3311
  await Promise.all([
2545
3312
  this.cp("private", `${this.dist.cwdPath}/private`, { dereference: true }),
2546
- this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })
3313
+ ...akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []
2547
3314
  ]);
2548
- } else
3315
+ } else {
2549
3316
  await this.removeDir(".akan");
3317
+ if (!akanConfig.web.ssr || !akanConfig.web.csr)
3318
+ this.logger.verbose(`akan.config.ts disables web.${!akanConfig.web.ssr ? "ssr" : "csr"}; \`akan start\` still serves it, \`akan build\` will not`);
3319
+ }
3320
+ if (type === "start")
3321
+ process.env.NODE_ENV = "development";
2550
3322
  const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
2551
3323
  const env = this.getCommandEnv({
2552
3324
  AKAN_COMMAND_TYPE: type,
2553
3325
  ...routeEnv,
2554
3326
  ...devPort ? { PORT: devPort, AKAN_PUBLIC_CLIENT_PORT: devPort, AKAN_PUBLIC_SERVER_PORT: devPort } : {}
2555
3327
  });
2556
- if (type === "build")
2557
- Object.assign(process.env, env);
3328
+ if (type === "build") {
3329
+ const buildEnv = { ...env };
3330
+ delete buildEnv.AKAN_PUBLIC_CLIENT_PORT;
3331
+ delete buildEnv.AKAN_PUBLIC_SERVER_PORT;
3332
+ Object.assign(process.env, buildEnv);
3333
+ }
2558
3334
  return { env };
2559
3335
  }
2560
3336
  #publicEnv = null;
@@ -2594,7 +3370,7 @@ class AppExecutor extends SysExecutor {
2594
3370
  async getPageKeys({ refresh } = {}) {
2595
3371
  if (this.#pageKeys && !refresh)
2596
3372
  return this.#pageKeys;
2597
- const akanConfig2 = await this.getConfig();
3373
+ const akanConfig = await this.getConfig();
2598
3374
  const glob = new Bun.Glob("**/*");
2599
3375
  const pageKeys = [];
2600
3376
  const owners = new Map;
@@ -2608,16 +3384,16 @@ class AppExecutor extends SysExecutor {
2608
3384
  absolute: false,
2609
3385
  onlyFiles: true
2610
3386
  })) {
2611
- const segments = rel.split(path5.sep);
3387
+ const segments = rel.split(path8.sep);
2612
3388
  if (segments.some((s) => s === "node_modules"))
2613
3389
  continue;
2614
3390
  const posix = `${root.keyPrefix}${segments.join("/")}`;
2615
- const absPath = path5.join(root.dir, ...segments);
3391
+ const absPath = path8.join(root.dir, ...segments);
2616
3392
  validatePageSourceFile(posix, { filePath: absPath });
2617
3393
  if (!isRouteSourceFile(posix))
2618
3394
  continue;
2619
3395
  const key = `./${posix}`;
2620
- validateSubRoutePageKey(key, akanConfig2.basePaths, {
3396
+ validateSubRoutePageKey(key, akanConfig.basePaths, {
2621
3397
  appName: this.name,
2622
3398
  filePath: absPath
2623
3399
  });
@@ -2635,7 +3411,8 @@ class AppExecutor extends SysExecutor {
2635
3411
  }
2636
3412
  if (!owner)
2637
3413
  owners.set(routeId, { absPath, fromLib });
2638
- const isRootLayout = parsed.kind === "layout" && parsed.moduleSegments.at(-1) === "_layout";
3414
+ const layoutSegments = getRootBoundarySegments(key);
3415
+ const isRootLayout = layoutSegments !== null && isRootBoundarySegments(layoutSegments, akanConfig.basePaths);
2639
3416
  const routeSource = await Bun.file(absPath).text();
2640
3417
  const validator = await AppExecutor.#getRouteSourceValidator();
2641
3418
  if (parsed.kind === "overrides")
@@ -2666,10 +3443,10 @@ class AppExecutor extends SysExecutor {
2666
3443
  return pageKeys.filter((key) => !isDevOnly(key));
2667
3444
  }
2668
3445
  async getPageRoots() {
2669
- const akanConfig2 = await this.getConfig();
3446
+ const akanConfig = await this.getConfig();
2670
3447
  const pageDir = `${this.cwdPath}/page`;
2671
3448
  const roots = [{ dir: pageDir, realDir: pageDir, keyPrefix: "" }];
2672
- for (const parent of AppExecutor.#pageLibParents(akanConfig2.basePaths)) {
3449
+ for (const parent of AppExecutor.#pageLibParents(akanConfig.basePaths)) {
2673
3450
  const libsDir = `${pageDir}/${parent}${AppExecutor.#pageLibsDir}`;
2674
3451
  const entries = await readDirEntries(libsDir).catch(() => []);
2675
3452
  for (const entry of entries.sort()) {
@@ -2690,9 +3467,9 @@ class AppExecutor extends SysExecutor {
2690
3467
  return parents.length ? parents : [""];
2691
3468
  }
2692
3469
  async syncPages(libDeps) {
2693
- const akanConfig2 = await this.getConfig();
2694
- const parents = AppExecutor.#pageLibParents(akanConfig2.basePaths);
2695
- const libs = await this.#resolvePageLibs(akanConfig2.syncPageLibs, libDeps);
3470
+ const akanConfig = await this.getConfig();
3471
+ const parents = AppExecutor.#pageLibParents(akanConfig.basePaths);
3472
+ const libs = await this.#resolvePageLibs(akanConfig.syncPageLibs, libDeps);
2696
3473
  const linked = (await Promise.all(parents.map(async (parent) => {
2697
3474
  const libsDir = `${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`;
2698
3475
  const entries = await readDirEntries(libsDir).catch(() => []);
@@ -2750,12 +3527,12 @@ class AppExecutor extends SysExecutor {
2750
3527
  static async#linkLibAsset(targetPath, linkPath) {
2751
3528
  const isWindows = process.platform === "win32";
2752
3529
  try {
2753
- const target = isWindows ? targetPath : path5.relative(path5.dirname(linkPath), targetPath);
3530
+ const target = isWindows ? targetPath : path8.relative(path8.dirname(linkPath), targetPath);
2754
3531
  await symlink(target, linkPath, isWindows ? "junction" : "dir");
2755
3532
  } catch (error) {
2756
3533
  if (!isWindows)
2757
3534
  throw error;
2758
- await mkdir(linkPath, { recursive: true });
3535
+ await mkdir3(linkPath, { recursive: true });
2759
3536
  await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
2760
3537
  }
2761
3538
  }
@@ -2792,7 +3569,7 @@ class AppExecutor extends SysExecutor {
2792
3569
  await this.writeFile(rel, content, opts);
2793
3570
  },
2794
3571
  readEnvClient: async () => {
2795
- const envClientPath = path5.join(this.cwdPath, "env", "env.client.ts");
3572
+ const envClientPath = path8.join(this.cwdPath, "env", "env.client.ts");
2796
3573
  if (!await FileSys.fileExists(envClientPath))
2797
3574
  return null;
2798
3575
  try {
@@ -2851,9 +3628,9 @@ class LibExecutor extends SysExecutor {
2851
3628
  }
2852
3629
  static #execs = new Map;
2853
3630
  static from(executor, name) {
2854
- const exec2 = LibExecutor.#execs.get(name);
2855
- if (exec2)
2856
- return exec2;
3631
+ const exec = LibExecutor.#execs.get(name);
3632
+ if (exec)
3633
+ return exec;
2857
3634
  else if (executor instanceof WorkspaceExecutor)
2858
3635
  return new LibExecutor({ workspace: executor, name });
2859
3636
  else
@@ -2863,7 +3640,7 @@ class LibExecutor extends SysExecutor {
2863
3640
  async getConfig({ refresh } = {}) {
2864
3641
  if (this.#akanConfig && !refresh)
2865
3642
  return this.#akanConfig;
2866
- this.#akanConfig = await AkanLibConfig.from(this);
3643
+ this.#akanConfig = await AkanLibConfig.from(this, { bustImportCache: refresh });
2867
3644
  return this.#akanConfig;
2868
3645
  }
2869
3646
  }
@@ -2902,7 +3679,7 @@ class PkgExecutor extends Executor {
2902
3679
  return rootVersion;
2903
3680
  try {
2904
3681
  const packageJsonPath = `pkgs/${dep}/package.json`;
2905
- if (!await Bun.file(path5.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
3682
+ if (!await Bun.file(path8.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
2906
3683
  return;
2907
3684
  const packageJson = await this.workspace.readJson(packageJsonPath);
2908
3685
  if (packageJson.name === dep)
@@ -2960,7 +3737,7 @@ class PkgExecutor extends Executor {
2960
3737
  default: "./index.ts"
2961
3738
  }
2962
3739
  },
2963
- engines: { bun: ">=1.3.13" },
3740
+ engines: { bun: ">=1.4.0" },
2964
3741
  ...dependencyMaps
2965
3742
  };
2966
3743
  await Promise.all([this.dist.writeJson("package.json", distPkgJson), this.writeJson("package.json", distPkgJson)]);
@@ -3003,4 +3780,4 @@ class ModuleExecutor extends Executor {
3003
3780
  }
3004
3781
  }
3005
3782
 
3006
- export { AGENT_BLOCK_START, AGENT_BLOCK_END, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, getDirname, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
3783
+ export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, isScannedRootEntry, AppInfo, Spinner, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };