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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/.build-stamp +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/{abstractCompactor-e5yq60qz.js → abstractCompactor-cefxzggj.js} +3 -5
  5. package/{agent.command-f0gqkfgz.js → agent.command-r93wmq7m.js} +11 -13
  6. package/{application.command-x903m2qq.js → application.command-5nqfqgdn.js} +62 -38
  7. package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-b21m07fs.js} +23 -20
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-3wh58n2m.js} +8 -9
  9. package/buildBatch.proc.js +12 -14
  10. package/capacitorApp-gzcx6txb.js +12 -0
  11. package/{cloud.command-zs6kyf83.js → cloud.command-g0pk0k60.js} +15 -18
  12. package/commandManifest.json +1 -1
  13. package/{context.command-fmepgjzg.js → context.command-qd8kqrnh.js} +19 -23
  14. package/dependencyScanner-shs82ynp.js +8 -0
  15. package/devTui-2xf0meaq.js +552 -0
  16. package/devTuiView-7jy8f30g.js +9 -0
  17. package/getDirname-dr3jz1xg.js +7 -0
  18. package/{guideline.command-n6sw0a0c.js → guideline.command-f38x8jhw.js} +16 -18
  19. package/guidelines/agentRule/agentRule.generate.json +31 -0
  20. package/guidelines/agentRule/agentRule.instruction.md +533 -0
  21. package/guidelines/conventions/conventions.instruction.md +406 -197
  22. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  23. package/guidelines/framework/framework.generate.json +3 -2
  24. package/guidelines/framework/framework.instruction.md +1 -9
  25. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  26. package/guidelines/mcpRule/mcpRule.instruction.md +217 -0
  27. package/guidelines/modelStore/modelStore.instruction.md +12 -0
  28. package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
  29. package/guidelines/queryRule/queryRule.generate.json +31 -0
  30. package/guidelines/queryRule/queryRule.instruction.md +143 -0
  31. package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
  32. package/guidelines/runtimeRule/runtimeRule.instruction.md +213 -0
  33. package/guidelines/ssrRule/ssrRule.instruction.md +49 -5
  34. package/guidelines/transportRule/transportRule.generate.json +31 -0
  35. package/guidelines/transportRule/transportRule.instruction.md +60 -0
  36. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +51 -760
  37. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
  38. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
  39. package/incrementalBuilder.proc.js +26 -25
  40. package/{index-774gcx29.js → index-0emb9eat.js} +41 -34
  41. package/index-3d9y066c.js +76 -0
  42. package/{index-3vta1tqa.js → index-3vvbv974.js} +32 -32
  43. package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
  44. package/{index-y4vkyhvv.js → index-5xdqxbn9.js} +14 -14
  45. package/{index-1577bej2.js → index-6wwzs1bc.js} +2 -2
  46. package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
  47. package/{index-jhpa13zj.js → index-9bwhxrqf.js} +57 -57
  48. package/{index-bjpxzr6s.js → index-9wtbw98m.js} +6 -3
  49. package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
  50. package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
  51. package/{index-kdh76j26.js → index-cezy7yb3.js} +100 -32
  52. package/{index-62955kn5.js → index-d61rcfa2.js} +758 -127
  53. package/{index-82jrh4hj.js → index-deekbtkj.js} +13 -13
  54. package/{index-rxkpk7n4.js → index-e82dyeqy.js} +4 -4
  55. package/{index-qzdjd4ct.js → index-h759vjfa.js} +18 -18
  56. package/{index-j37qq1f2.js → index-h7yw2z9n.js} +7 -7
  57. package/{index-qb61h0k7.js → index-jydtyzva.js} +1098 -1360
  58. package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
  59. package/{index-4m0bphac.js → index-kfnr6c19.js} +31 -30
  60. package/{index-xmmzf150.js → index-mpmyqm1y.js} +1 -1
  61. package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
  62. package/{index-0cj2zxbm.js → index-n85fer99.js} +12 -11
  63. package/{index-779ske1y.js → index-pgsswppj.js} +27 -27
  64. package/index-pvb4vdwr.js +159 -0
  65. package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
  66. package/{index-z8ptfkzv.js → index-q8sn827n.js} +969 -240
  67. package/{index-5sn5w2mt.js → index-r50qen0z.js} +94 -61
  68. package/{index-eyyf2149.js → index-rntcdss1.js} +36 -37
  69. package/{index-gcv8ncfh.js → index-sjak5v5b.js} +16 -12
  70. package/{index-j9cxndc0.js → index-w8kmx1b6.js} +20 -11
  71. package/{index-jf2ya7r2.js → index-y74k5anj.js} +8 -8
  72. package/{index-d43t8af2.js → index-zkvpaggp.js} +58 -25
  73. package/index.js +22 -25
  74. package/{library.command-cv9gdbmg.js → library.command-em103wtn.js} +11 -10
  75. package/{localRegistry.command-d8fg36zc.js → localRegistry.command-gz8apn4y.js} +20 -23
  76. package/{module.command-qyr1s9cn.js → module.command-451pztkc.js} +13 -16
  77. package/{package.command-wsjx0k47.js → package.command-rfhyf38t.js} +8 -10
  78. package/package.json +5 -7
  79. package/{page.command-rdzg65rm.js → page.command-kt19da5g.js} +7 -9
  80. package/{primitive.command-89g38b53.js → primitive.command-pgy69ere.js} +13 -16
  81. package/{quality.command-0cs2z92d.js → quality.command-gpe2vqb5.js} +226 -105
  82. package/recipeScanner-fpagtgc1.js +11 -0
  83. package/{repair.command-6fphxfwj.js → repair.command-2sbrz187.js} +12 -14
  84. package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-ze6x894c.js} +0 -2
  85. package/{scalar.command-xfer69s6.js → scalar.command-jcf4e5sv.js} +11 -14
  86. package/subspace.command-kqzqtk0p.js +755 -0
  87. package/templates/app/env/env.client.debug.ts.template +1 -5
  88. package/templates/app/env/env.client.develop.ts.template +1 -5
  89. package/templates/app/env/env.client.local.ts.template +1 -5
  90. package/templates/app/env/env.client.main.ts.template +1 -5
  91. package/templates/app/env/env.client.testing.ts.template +1 -5
  92. package/templates/app/env/env.client.type.ts +2 -6
  93. package/templates/app/env/env.server.debug.ts.template +1 -5
  94. package/templates/app/env/env.server.develop.ts.template +1 -5
  95. package/templates/app/env/env.server.local.ts.template +1 -5
  96. package/templates/app/env/env.server.main.ts.template +1 -5
  97. package/templates/app/main.ts +3 -1
  98. package/templates/appSample/lib/task/task.signal.ts +11 -6
  99. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  100. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  101. package/templates/crudPages/[__model__Id]/edit/page.tsx +5 -5
  102. package/templates/crudPages/[__model__Id]/page.tsx +10 -13
  103. package/templates/crudPages/new/page.tsx +5 -5
  104. package/templates/crudPages/page.tsx +9 -9
  105. package/templates/crudSinglePage/page.tsx +11 -7
  106. package/templates/env/_env.server.type.ts +7 -5
  107. package/templates/lib/db.ts +1 -1
  108. package/templates/lib/dict.ts +1 -1
  109. package/templates/lib/sig.ts +1 -1
  110. package/templates/lib/srv.ts +6 -2
  111. package/templates/module/__model__.dictionary.ts +4 -1
  112. package/templates/module/__model__.signal.ts +2 -3
  113. package/templates/module/__model__.store.ts +1 -2
  114. package/templates/workspaceRoot/.gitignore.template +0 -1
  115. package/templates/workspaceRoot/.husky/pre-commit.template +16 -0
  116. package/templates/workspaceRoot/.vscode/settings.json.template +1 -1
  117. package/templates/workspaceRoot/AGENTS.md.template +4 -0
  118. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  119. package/templates/workspaceRoot/biome.json.template +2 -1
  120. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
  121. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  122. package/templates/workspaceRoot/package.json.template +2 -4
  123. package/typeChecker-3xgrzc2v.js +7 -0
  124. package/typecheck.proc.js +4 -5
  125. package/{workflow.command-2ygfqt4f.js → workflow.command-m4z25a2p.js} +22 -25
  126. package/{workspace.command-88m0zs64.js → workspace.command-dqgj9zbk.js} +96 -81
  127. package/capacitorApp-9xswcgx7.js +0 -61
  128. package/dependencyScanner-f6grrep3.js +0 -9
  129. package/getDirname-m9s0cmhq.js +0 -8
  130. package/index-mxvakhsm.js +0 -66
  131. package/index-r24hmh0q.js +0 -4
  132. package/recipeScanner-tghkzrrt.js +0 -12
  133. package/typeChecker-kravn7ns.js +0 -8
  134. /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
  135. /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
@@ -1,16 +1,189 @@
1
1
  // @bun
2
- import {
3
- Spinner
4
- } from "./index-mxvakhsm.js";
5
- import {
6
- getDirname
7
- } from "./index-1577bej2.js";
8
2
  import {
9
3
  FileSys
10
- } from "./index-67546d0j.js";
4
+ } from "./index-q0y2jq9j.js";
11
5
  import {
12
- __require
13
- } 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
+ }
14
187
 
15
188
  // pkgs/@akanjs/devkit/executors.ts
16
189
  import {
@@ -22,19 +195,20 @@ import { readFileSync as readFileSync2 } from "fs";
22
195
  import {
23
196
  copyFile,
24
197
  cp as cpEntry,
25
- mkdir,
198
+ mkdir as mkdir3,
26
199
  readdir as readDirEntries,
27
200
  realpath,
28
- rm as rm2,
29
- stat,
201
+ rm as rm3,
202
+ stat as stat2,
30
203
  symlink
31
204
  } from "fs/promises";
32
- import path5 from "path";
205
+ import path8 from "path";
33
206
  import { pathToFileURL } from "url";
34
207
  import {
35
208
  capitalize,
209
+ getPageSourceFileViolation,
36
210
  isRouteSourceFile,
37
- Logger,
211
+ Logger as Logger2,
38
212
  parseRouteModuleKey,
39
213
  validatePageSourceFile,
40
214
  validateSubRoutePageKey
@@ -44,7 +218,7 @@ import chalk2 from "chalk";
44
218
 
45
219
  // pkgs/@akanjs/devkit/agentsIndex.ts
46
220
  var recipeScannerLoad = null;
47
- var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-tghkzrrt.js");
221
+ var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-fpagtgc1.js");
48
222
  var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
49
223
  var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
50
224
  var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
@@ -54,9 +228,9 @@ ${block}`;
54
228
  var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
55
229
  var readDevkitVersion = async () => {
56
230
  const { readFile } = await import("fs/promises");
57
- const { getDirname: getDirname2 } = await import("./getDirname-m9s0cmhq.js");
231
+ const { getDirname } = await import("./getDirname-dr3jz1xg.js");
58
232
  try {
59
- const raw = await readFile(`${getDirname2(import.meta.url)}/package.json`, "utf-8");
233
+ const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
60
234
  return JSON.parse(raw).version ?? null;
61
235
  } catch {
62
236
  return null;
@@ -155,15 +329,36 @@ ${AGENT_BLOCK_START}
155
329
  ${block}
156
330
  ${AGENT_BLOCK_END}
157
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.`;
158
350
  var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
159
351
 
160
352
  @AGENTS.md
353
+
354
+ ${CLAUDE_COMMENT_RULE}
161
355
  `;
162
356
 
163
357
  // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
164
358
  import fs from "fs";
165
- import path from "path";
359
+ import path2 from "path";
166
360
  import { fileURLToPath } from "url";
361
+ import { normalizeRoutePrefix } from "akanjs/base";
167
362
  import { resolveAkanI18nConfig } from "akanjs/common";
168
363
 
169
364
  // pkgs/@akanjs/devkit/akanConfig/types.ts
@@ -199,9 +394,8 @@ var DEFAULT_OPTIMIZE_IMPORTS = [
199
394
  "react-icons/*"
200
395
  ];
201
396
  var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
397
+ var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
202
398
  var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
203
- var NATIVE_RUNTIME_PACKAGES = ["sharp"];
204
- var DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"];
205
399
  var MOBILE_RUNTIME_PACKAGES = [
206
400
  "@capacitor/cli",
207
401
  "@capacitor/core",
@@ -230,8 +424,6 @@ var DATABASE_MODE_RUNTIME_PACKAGES = {
230
424
  };
231
425
  var AKAN_RUNTIME_PACKAGES = new Set([
232
426
  ...SSR_RUNTIME_PACKAGES,
233
- ...NATIVE_RUNTIME_PACKAGES,
234
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
235
427
  ...MOBILE_RUNTIME_PACKAGES,
236
428
  ...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat()
237
429
  ]);
@@ -246,14 +438,15 @@ var DEFAULT_AKAN_IMAGE_CONFIG = {
246
438
  dangerouslyAllowSVG: false,
247
439
  maximumRedirects: 3,
248
440
  fetchTimeoutMs: 7000,
249
- maxRemoteBytes: 25 * 1024 * 1024
441
+ maxRemoteBytes: 25 * 1024 * 1024,
442
+ maxConcurrency: 0
250
443
  };
251
444
  var normalizeIndexPath = (indexPath) => {
252
445
  const normalized = indexPath?.trim();
253
446
  if (!normalized)
254
447
  return;
255
- const path2 = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
256
- return path2 === "/" ? "/" : path2;
448
+ const path = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
449
+ return path === "/" ? "/" : path;
257
450
  };
258
451
  var normalizeStringList = (values) => {
259
452
  const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
@@ -293,21 +486,39 @@ var normalizeDeepLinks = (deepLinks) => {
293
486
  ...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
294
487
  };
295
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
+ };
296
503
 
297
504
  class AkanAppConfig {
298
505
  app;
299
506
  rootPackageJson;
300
507
  docker;
508
+ dockerfile;
301
509
  defaultDatabaseMode;
510
+ web;
302
511
  externalLibs;
303
512
  barrelImports;
304
513
  optimizeImports;
305
514
  images;
306
515
  i18n;
516
+ api;
307
517
  publicEnv;
308
518
  mobile;
309
519
  hasMobileConfig;
310
520
  secrets;
521
+ assets;
311
522
  syncPageLibs;
312
523
  baseDevEnv;
313
524
  libs;
@@ -316,7 +527,7 @@ class AkanAppConfig {
316
527
  subRoutes = new Map;
317
528
  basePaths = new Set;
318
529
  branches = new Set(["debug", "develop", "main"]);
319
- constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = []) {
530
+ constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = [], libContributions = emptyLibContributions()) {
320
531
  this.app = app;
321
532
  this.rootPackageJson = rootPackageJson;
322
533
  this.libs = libs;
@@ -324,7 +535,7 @@ class AkanAppConfig {
324
535
  this.plugins = plugins;
325
536
  this.#applyRoutes(config?.routes);
326
537
  this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
327
- this.externalLibs = config?.externalLibs ?? [];
538
+ this.externalLibs = [...new Set([...config?.externalLibs ?? [], ...libContributions.externalLibs])];
328
539
  this.barrelImports = [
329
540
  ...DEFAULT_BARREL_IMPORTS,
330
541
  ...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
@@ -336,12 +547,33 @@ class AkanAppConfig {
336
547
  this.i18n = resolveAkanI18nConfig(config?.i18n);
337
548
  process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
338
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;
339
556
  this.publicEnv = config?.publicEnv ?? [];
340
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
+ };
341
565
  this.syncPageLibs = config?.syncPageLibs ?? false;
342
566
  this.hasMobileConfig = Boolean(config.mobile);
343
567
  this.mobile = this.#resolveMobileConfig(config.mobile);
344
- 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;
345
577
  }
346
578
  #resolveMobileConfig(mobile) {
347
579
  const {
@@ -451,23 +683,31 @@ class AkanAppConfig {
451
683
  return archs.map((arch) => `FROM ${image[arch] ?? defaultImage} AS ${arch}`).join(`
452
684
  `);
453
685
  }
454
- #makeDockerContent(docker) {
455
- if (docker.content)
456
- return {
457
- content: docker.content,
458
- image: {},
459
- preRuns: [],
460
- postRuns: [],
461
- command: []
462
- };
463
- const preRunScripts = this.#getDockerRunScripts(docker.preRuns ?? []);
464
- const postRunScripts = this.#getDockerRunScripts(docker.postRuns ?? []);
465
- const imageScript = docker.image ? this.#getDockerImageScript(docker.image, "oven/bun:1-slim") : "FROM oven/bun:1-slim";
466
- const command = docker.command ?? ["bun", "main.js"];
467
- 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/*
468
710
  RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
469
- RUN apt-get update && apt-get upgrade -y
470
- 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
471
711
  ARG TARGETARCH
472
712
  ${preRunScripts.join(`
473
713
  `)}
@@ -487,16 +727,12 @@ ENV AKAN_PUBLIC_ENV=${this.baseDevEnv.env}
487
727
  ${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join(",")}` : ""}
488
728
  ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
489
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}
490
732
  ENV AKAN_PUBLIC_OPERATION_MODE=cloud
491
-
733
+ ENV AKAN_LOG_TO_FILE=0
734
+ ${webEnvLines}
492
735
  CMD [${command.map((c) => `"${c}"`).join(",")}]`;
493
- return {
494
- content,
495
- image: imageScript,
496
- preRuns: docker.preRuns ?? [],
497
- postRuns: docker.postRuns ?? [],
498
- command
499
- };
500
736
  }
501
737
  static #importGeneration = 0;
502
738
  static async importConfigModule(cwdPath, { bustImportCache = false } = {}) {
@@ -507,13 +743,28 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
507
743
  static async from(app, { bustImportCache = false } = {}) {
508
744
  const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
509
745
  AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
510
- WorkspaceExecutor.getBaseDevEnv(path.join(app.workspace.workspaceRoot, ".env")),
746
+ WorkspaceExecutor.getBaseDevEnv(path2.join(app.workspace.workspaceRoot, ".env")),
511
747
  app.workspace.getLibs(),
512
748
  app.workspace.getPackageJson()
513
749
  ]);
514
750
  const resolved = typeof configImp === "function" ? configImp(app) : configImp;
515
751
  const { plugins, ...config } = resolved ?? {};
516
- 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
+ };
517
768
  }
518
769
  #resolveProductionDependencyVersion(lib) {
519
770
  const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
@@ -523,13 +774,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
523
774
  return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
524
775
  }
525
776
  #getProductionRuntimePackages() {
526
- return [
527
- ...this.externalLibs,
528
- ...SSR_RUNTIME_PACKAGES,
529
- ...NATIVE_RUNTIME_PACKAGES,
530
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
531
- ...this.getDatabaseModeRuntimePackages()
532
- ];
777
+ return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
533
778
  }
534
779
  getDatabaseModeRuntimePackages(databaseMode = this.defaultDatabaseMode) {
535
780
  return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
@@ -578,11 +823,11 @@ var akanPackageJson = null;
578
823
  function getAkanPackageJson() {
579
824
  if (akanPackageJson)
580
825
  return akanPackageJson;
581
- const sourceDir = path.dirname(fileURLToPath(import.meta.url));
826
+ const sourceDir = path2.dirname(fileURLToPath(import.meta.url));
582
827
  const packageJsonPaths = [
583
- path.join(sourceDir, "../../../akanjs/package.json"),
584
- path.join(process.cwd(), "pkgs/akanjs/package.json"),
585
- 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")
586
831
  ];
587
832
  try {
588
833
  packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
@@ -617,14 +862,18 @@ function mergeImageConfig(config = {}) {
617
862
  class AkanLibConfig {
618
863
  lib;
619
864
  externalLibs;
865
+ docker;
866
+ assets;
620
867
  plugins;
621
868
  constructor(lib, config, plugins = []) {
622
869
  this.lib = lib;
623
870
  this.externalLibs = config?.externalLibs ?? [];
871
+ this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
872
+ this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
624
873
  this.plugins = plugins;
625
874
  }
626
- static async from(lib) {
627
- 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 });
628
877
  const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
629
878
  const { plugins, ...config } = resolved ?? {};
630
879
  return new AkanLibConfig(lib, config, plugins ?? []);
@@ -633,49 +882,448 @@ class AkanLibConfig {
633
882
  //! need to refactor
634
883
  var increaseBuildNum = async (app) => {
635
884
  const appConfig = await AkanAppConfig.from(app);
636
- const akanConfigPath = path.join(app.cwdPath, "akan.config.ts");
885
+ const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
637
886
  const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
638
887
  const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum + 1}`);
639
888
  fs.writeFileSync(akanConfigPath, akanConfigContent);
640
889
  };
641
890
  var decreaseBuildNum = async (app) => {
642
891
  const appConfig = await AkanAppConfig.from(app);
643
- const akanConfigPath = path.join(app.cwdPath, "akan.config.ts");
892
+ const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
644
893
  const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
645
894
  const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
646
895
  fs.writeFileSync(akanConfigPath, akanConfigContent);
647
896
  };
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
+ }
1238
+
648
1239
  // pkgs/@akanjs/devkit/linter.ts
649
1240
  import { spawn } from "child_process";
650
1241
  import { existsSync, readFileSync } from "fs";
651
- import path2 from "path";
1242
+ import path5 from "path";
652
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"];
653
1292
 
654
1293
  class Linter {
655
1294
  lintRoot;
1295
+ configPath;
656
1296
  #biomeBin;
657
1297
  constructor(cwdPath) {
658
1298
  this.lintRoot = this.#findBiomeRootPath(cwdPath);
659
- const localBiomeBin = path2.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");
660
1301
  this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
661
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
+ }
662
1311
  #findBiomeRootPath(dir) {
663
- const configPath = path2.join(dir, "biome.json");
664
- if (existsSync(configPath))
1312
+ if (Linter.#configPathIn(dir))
665
1313
  return dir;
666
- const parentDir = path2.dirname(dir);
1314
+ const parentDir = path5.dirname(dir);
667
1315
  if (parentDir === dir)
668
1316
  throw new Error(`biome.json not found from ${dir}`);
669
1317
  return this.#findBiomeRootPath(parentDir);
670
1318
  }
671
1319
  #toBiomePath(filePath) {
672
- const relativePath = path2.relative(this.lintRoot, filePath);
673
- if (!relativePath.startsWith("..") && !path2.isAbsolute(relativePath))
1320
+ const relativePath = path5.relative(this.lintRoot, filePath);
1321
+ if (!relativePath.startsWith("..") && !path5.isAbsolute(relativePath))
674
1322
  return relativePath;
675
1323
  return filePath;
676
1324
  }
677
1325
  #resolveFilePath(filePath) {
678
- return path2.isAbsolute(filePath) ? filePath : path2.join(this.lintRoot, filePath);
1326
+ return path5.isAbsolute(filePath) ? filePath : path5.join(this.lintRoot, filePath);
679
1327
  }
680
1328
  async#runBiome(args, input) {
681
1329
  return await new Promise((resolve, reject) => {
@@ -696,18 +1344,11 @@ class Linter {
696
1344
  proc.stdin.end(input);
697
1345
  });
698
1346
  }
699
- #parseBiomeReport(output) {
700
- const jsonStart = output.indexOf("{");
701
- const jsonEnd = output.lastIndexOf("}");
702
- if (jsonStart === -1 || jsonEnd === -1 || jsonEnd < jsonStart)
703
- throw new Error(output.trim() || "No Biome JSON output");
704
- return JSON.parse(output.slice(jsonStart, jsonEnd + 1));
705
- }
706
1347
  #diagnosticFilePath(diagnostic, fallbackFilePath) {
707
1348
  const diagnosticPath = diagnostic.location?.path;
708
1349
  if (!diagnosticPath)
709
1350
  return fallbackFilePath;
710
- return path2.isAbsolute(diagnosticPath) ? diagnosticPath : path2.join(this.lintRoot, diagnosticPath);
1351
+ return path5.isAbsolute(diagnosticPath) ? diagnosticPath : path5.join(this.lintRoot, diagnosticPath);
711
1352
  }
712
1353
  #createLintMessage(diagnostic) {
713
1354
  const start = diagnostic.location?.start;
@@ -772,10 +1413,10 @@ class Linter {
772
1413
  "--max-diagnostics=none",
773
1414
  "--no-errors-on-unmatched",
774
1415
  "--config-path",
775
- path2.join(this.lintRoot, "biome.json"),
1416
+ this.configPath,
776
1417
  this.#toBiomePath(filePath)
777
1418
  ]);
778
- const report = this.#parseBiomeReport(stdout || stderr);
1419
+ const report = parseBiomeReport(stdout || stderr);
779
1420
  const results = this.#toLintResults(report, filePath);
780
1421
  const { errors, warnings } = this.#splitMessages(results);
781
1422
  const output = write && existsSync(filePath) ? readFileSync(filePath, "utf8") : undefined;
@@ -893,22 +1534,34 @@ ${errorText}, ${warningText} found`];
893
1534
  if (!dryRun)
894
1535
  return await this.#checkFile(resolvedFilePath, { write: true });
895
1536
  const source = readFileSync(resolvedFilePath, "utf8");
896
- 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([
897
1547
  "check",
898
1548
  "--write",
1549
+ "--reporter=json",
1550
+ "--max-diagnostics=none",
1551
+ "--no-errors-on-unmatched",
899
1552
  "--config-path",
900
- path2.join(this.lintRoot, "biome.json"),
901
- "--stdin-file-path",
902
- this.#toBiomePath(resolvedFilePath)
903
- ], source);
904
- const lintResult = await this.lintFile(resolvedFilePath);
905
- 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
+ };
906
1559
  }
907
1560
  async getConfigForFile(filePath) {
908
1561
  const resolvedFilePath = this.#resolveFilePath(filePath);
909
1562
  if (!existsSync(resolvedFilePath))
910
1563
  throw new Error(`File not found: ${filePath}`);
911
- return JSON.parse(readFileSync(path2.join(this.lintRoot, "biome.json"), "utf8"));
1564
+ return JSON.parse(readFileSync(this.configPath, "utf8"));
912
1565
  }
913
1566
  async getProblematicRules(filePath) {
914
1567
  const { results } = await this.lintFile(filePath);
@@ -925,7 +1578,7 @@ ${errorText}, ${warningText} found`];
925
1578
 
926
1579
  // pkgs/@akanjs/devkit/repoIdentity.ts
927
1580
  import { execFileSync } from "child_process";
928
- import path3 from "path";
1581
+ import path6 from "path";
929
1582
  var resolved = new Map;
930
1583
  var readRemoteName = (workspaceRoot) => {
931
1584
  try {
@@ -943,14 +1596,14 @@ var resolveRepoName = (workspaceRoot) => {
943
1596
  const cached = resolved.get(workspaceRoot);
944
1597
  if (cached)
945
1598
  return cached;
946
- const repoName = readRemoteName(workspaceRoot) ?? path3.basename(workspaceRoot);
1599
+ const repoName = readRemoteName(workspaceRoot) ?? path6.basename(workspaceRoot);
947
1600
  resolved.set(workspaceRoot, repoName);
948
1601
  return repoName;
949
1602
  };
950
1603
 
951
1604
  // pkgs/@akanjs/devkit/scanInfo.ts
952
- import { rm } from "fs/promises";
953
- import path4 from "path";
1605
+ import { rm as rm2 } from "fs/promises";
1606
+ import path7 from "path";
954
1607
 
955
1608
  // pkgs/@akanjs/devkit/workspaceLayout.ts
956
1609
  var appRootAllowedFiles = new Set([
@@ -984,6 +1637,34 @@ var appRootAllowedDirs = new Set([
984
1637
  "ui",
985
1638
  "webkit"
986
1639
  ]);
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 };
987
1668
  var libFacetRootAllowedFiles = new Set([
988
1669
  "cnst.ts",
989
1670
  "db.ts",
@@ -995,7 +1676,9 @@ var libFacetRootAllowedFiles = new Set([
995
1676
  "useClient.ts",
996
1677
  "useServer.ts"
997
1678
  ]);
998
- var isScannedAppRootEntry = (name) => !name.startsWith(".") || appRootAllowedDirs.has(name);
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);
999
1682
 
1000
1683
  // pkgs/@akanjs/devkit/scanInfo.ts
1001
1684
  var databaseFileTypes = [
@@ -1024,18 +1707,16 @@ var moduleUiFileTypes = {
1024
1707
  scalar: new Set(["Template", "Unit"])
1025
1708
  };
1026
1709
  var testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
1027
- var rootSignalTestFilePattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
1028
- 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);
1029
1711
  var isAllowedTestFile = (filename) => testFilePattern.test(filename);
1030
- var isAllowedLibRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || rootSignalTestFilePattern.test(filename);
1031
- 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("/"));
1032
1713
  async function clearGeneratedRootCapacitorConfigs(exec) {
1033
1714
  if (exec.type !== "app")
1034
1715
  return;
1035
- 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 })));
1036
1717
  }
1037
1718
  var getModuleNameFromPath = (kind, modulePath) => {
1038
- const dirname = path4.basename(modulePath);
1719
+ const dirname = path7.basename(modulePath);
1039
1720
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
1040
1721
  };
1041
1722
  async function assertScanConvention(exec, libRoot) {
@@ -1044,28 +1725,31 @@ async function assertScanConvention(exec, libRoot) {
1044
1725
  const addViolation = (relativePath, reason) => {
1045
1726
  violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
1046
1727
  };
1047
- if (exec.type === "app") {
1048
- const { files, dirs } = await exec.getFilesAndDirs(".");
1049
- files.filter((filename) => !appRootAllowedFiles.has(filename)).forEach((filename) => {
1050
- addViolation(filename, "unsupported app root file");
1051
- });
1052
- dirs.filter((dirname) => !appRootAllowedDirs.has(dirname)).forEach((dirname) => {
1053
- addViolation(dirname, "unsupported app root folder");
1054
- });
1055
- }
1056
- libRoot.files.filter((filename) => !isAllowedLibRootFile(filename)).forEach((filename) => {
1057
- 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");
1058
1742
  });
1059
1743
  libRoot.dirs.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname)).forEach((dirname) => {
1060
- addViolation(path4.join("lib", dirname), "unsupported internal lib folder");
1744
+ addViolation(path7.join("lib", dirname), "unsupported internal lib folder");
1061
1745
  });
1062
1746
  const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
1063
1747
  const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
1064
1748
  const scalarDirs = await exec.readdir("lib/__scalar");
1065
1749
  await Promise.all([
1066
- ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path4.join("lib", dirname))),
1067
- ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path4.join("lib", dirname))),
1068
- ...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)))
1069
1753
  ]);
1070
1754
  if (violations.length > 0) {
1071
1755
  throw new Error(`[scan-convention]
@@ -1077,11 +1761,11 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
1077
1761
  const { files, dirs } = await exec.getFilesAndDirs(modulePath);
1078
1762
  const moduleName = getModuleNameFromPath(kind, modulePath);
1079
1763
  dirs.forEach((dirname) => {
1080
- violations.push(`${getScanPath(exec, path4.join(modulePath, dirname))}: unsupported module folder`);
1764
+ violations.push(`${getScanPath(exec, path7.join(modulePath, dirname))}: unsupported module folder`);
1081
1765
  });
1082
1766
  const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
1083
1767
  files.forEach((filename) => {
1084
- const filePath = path4.join(modulePath, filename);
1768
+ const filePath = path7.join(modulePath, filename);
1085
1769
  if (filename === "index.ts" || filename === "index.tsx" || isAllowedTestFile(filename))
1086
1770
  return;
1087
1771
  if (filename === `${moduleName}.abstract.md`)
@@ -1125,7 +1809,7 @@ class ScanInfo {
1125
1809
  { all: new Set, databases: new Set, services: new Set, scalars: new Set }
1126
1810
  ]));
1127
1811
  static async getScanResult(exec) {
1128
- const [akanConfig2, scanner, pkgs, libs] = await Promise.all([
1812
+ const [akanConfig, scanner, pkgs, libs] = await Promise.all([
1129
1813
  exec.getConfig(),
1130
1814
  createDependencyScanner(exec),
1131
1815
  exec.workspace.getPkgs(),
@@ -1161,7 +1845,7 @@ class ScanInfo {
1161
1845
  });
1162
1846
  await Promise.all([
1163
1847
  ...databaseDirs.map(async (name) => {
1164
- const filenames = await exec.readdir(path4.join("lib", name));
1848
+ const filenames = await exec.readdir(path7.join("lib", name));
1165
1849
  filenames.forEach((filename) => {
1166
1850
  if (filename.endsWith(".constant.ts"))
1167
1851
  files.constant.databases.push(name);
@@ -1189,7 +1873,7 @@ class ScanInfo {
1189
1873
  }),
1190
1874
  ...serviceDirs.map(async (dirname) => {
1191
1875
  const name = dirname.slice(1);
1192
- const filenames = await exec.readdir(path4.join("lib", dirname));
1876
+ const filenames = await exec.readdir(path7.join("lib", dirname));
1193
1877
  filenames.forEach((filename) => {
1194
1878
  if (filename.endsWith(".dictionary.ts"))
1195
1879
  files.dictionary.services.push(name);
@@ -1212,7 +1896,7 @@ class ScanInfo {
1212
1896
  });
1213
1897
  }),
1214
1898
  ...scalarDirs.map(async (name) => {
1215
- const filenames = await exec.readdir(path4.join("lib/__scalar", name));
1899
+ const filenames = await exec.readdir(path7.join("lib/__scalar", name));
1216
1900
  filenames.forEach((filename) => {
1217
1901
  if (filename.endsWith(".constant.ts"))
1218
1902
  files.constant.scalars.push(name);
@@ -1234,12 +1918,11 @@ class ScanInfo {
1234
1918
  })
1235
1919
  ]);
1236
1920
  const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
1237
- const scanResult = {
1921
+ const common = {
1238
1922
  name: exec.name,
1239
1923
  type: exec.type,
1240
1924
  repoName: exec.workspace.repoName,
1241
- serveDomain: WorkspaceExecutor.getBaseDevEnv(path4.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
1242
- akanConfig: akanConfig2,
1925
+ serveDomain: WorkspaceExecutor.getBaseDevEnv(path7.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
1243
1926
  files,
1244
1927
  libDeps,
1245
1928
  pkgDeps,
@@ -1247,6 +1930,7 @@ class ScanInfo {
1247
1930
  devDependencies: npmDevDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
1248
1931
  routes
1249
1932
  };
1933
+ const scanResult = akanConfig instanceof AkanAppConfig ? { ...common, akanConfig } : { ...common, akanConfig };
1250
1934
  return scanResult;
1251
1935
  }
1252
1936
  constructor(scanResult) {
@@ -1440,16 +2124,16 @@ class LibInfo extends ScanInfo {
1440
2124
  class PkgInfo {
1441
2125
  exec;
1442
2126
  name;
1443
- scanResult;
2127
+ #scanResult;
1444
2128
  static async scanExecutor(exec) {
1445
2129
  const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
1446
2130
  const scanner = await createDependencyScanner(exec);
1447
2131
  const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
1448
- 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("/*", "")));
1449
2133
  const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
1450
- const pkgDeps = [...pkgPathDepSet].map((path5) => {
1451
- const pathSplitLength = path5.split("/").length;
1452
- 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("/");
1453
2137
  }).filter((pkg) => pkg !== exec.name);
1454
2138
  const pkgScanResult = {
1455
2139
  name: exec.name,
@@ -1471,10 +2155,10 @@ class PkgInfo {
1471
2155
  constructor(exec, scanResult) {
1472
2156
  this.exec = exec;
1473
2157
  this.name = exec.name;
1474
- this.scanResult = scanResult;
2158
+ this.#scanResult = scanResult;
1475
2159
  }
1476
2160
  getScanResult() {
1477
- return this.scanResult;
2161
+ return this.#scanResult;
1478
2162
  }
1479
2163
  }
1480
2164
 
@@ -1635,16 +2319,16 @@ class Executor {
1635
2319
  linter = null;
1636
2320
  constructor(name, cwdPath) {
1637
2321
  this.name = name;
1638
- this.logger = new Logger(name);
2322
+ this.logger = new Logger2(name);
1639
2323
  this.logs = [];
1640
2324
  this.cwdPath = cwdPath;
1641
2325
  }
1642
2326
  #stdout(data) {
1643
2327
  if (Executor.verbose)
1644
- Logger.raw(chalk2.dim(data.toString()));
2328
+ Logger2.raw(chalk2.dim(data.toString()));
1645
2329
  }
1646
2330
  #stderr(data) {
1647
- Logger.raw(chalk2.red(data.toString()));
2331
+ Logger2.raw(chalk2.red(data.toString()));
1648
2332
  }
1649
2333
  exec(command, options = {}) {
1650
2334
  const cwd = options.cwd?.toString() ?? this.cwdPath;
@@ -1672,7 +2356,7 @@ class Executor {
1672
2356
  }));
1673
2357
  });
1674
2358
  proc.on("exit", (code, signal) => {
1675
- if (!!code || signal)
2359
+ if (code || signal)
1676
2360
  reject(new CommandExecutionError({
1677
2361
  command,
1678
2362
  cwd,
@@ -1770,7 +2454,7 @@ class Executor {
1770
2454
  }));
1771
2455
  });
1772
2456
  proc.on("exit", (code, signal) => {
1773
- if (!!code || signal)
2457
+ if (code || signal)
1774
2458
  reject(new CommandExecutionError({
1775
2459
  command: modulePath,
1776
2460
  args,
@@ -1786,10 +2470,10 @@ class Executor {
1786
2470
  });
1787
2471
  }
1788
2472
  getPath(filePath) {
1789
- if (path5.isAbsolute(filePath))
2473
+ if (path8.isAbsolute(filePath))
1790
2474
  return filePath;
1791
2475
  if (filePath.startsWith("."))
1792
- return path5.join(this.cwdPath, filePath);
2476
+ return path8.join(this.cwdPath, filePath);
1793
2477
  const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
1794
2478
  const targetParts = filePath.split(/[\\/]/).filter(Boolean);
1795
2479
  let overlapLength = 0;
@@ -1803,12 +2487,12 @@ class Executor {
1803
2487
  if (isOverlap)
1804
2488
  overlapLength = i;
1805
2489
  }
1806
- return path5.join(this.cwdPath, ...targetParts.slice(overlapLength));
2490
+ return path8.join(this.cwdPath, ...targetParts.slice(overlapLength));
1807
2491
  }
1808
2492
  async mkdir(dirPath) {
1809
2493
  const writePath = this.getPath(dirPath);
1810
2494
  if (!await FileSys.dirExists(writePath))
1811
- await mkdir(writePath, { recursive: true });
2495
+ await mkdir3(writePath, { recursive: true });
1812
2496
  this.logger.verbose(`Make directory ${writePath}`);
1813
2497
  return this;
1814
2498
  }
@@ -1846,18 +2530,19 @@ class Executor {
1846
2530
  return this;
1847
2531
  }
1848
2532
  async removeDir(dirPath) {
1849
- const readPath = path5.join(this.getPath(dirPath), ".");
2533
+ const readPath = path8.join(this.getPath(dirPath), ".");
1850
2534
  if (await FileSys.entryExists(readPath))
1851
- await rm2(readPath, { recursive: true, force: true });
2535
+ await rm3(readPath, { recursive: true, force: true });
1852
2536
  this.logger.verbose(`Remove directory ${readPath}`);
1853
2537
  return this;
1854
2538
  }
1855
2539
  async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
1856
2540
  const writePath = this.getPath(filePath);
1857
- const dir = path5.dirname(writePath);
2541
+ const dir = path8.dirname(writePath);
1858
2542
  if (!await FileSys.dirExists(dir))
1859
- await mkdir(dir, { recursive: true });
1860
- 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
+ `;
1861
2546
  if (await FileSys.fileExists(writePath)) {
1862
2547
  const currentContent = await FileSys.readText(writePath);
1863
2548
  if (currentContent === contentStr || !overwrite) {
@@ -1865,7 +2550,7 @@ class Executor {
1865
2550
  contentStr = currentContent;
1866
2551
  } else {
1867
2552
  await FileSys.writeText(writePath, contentStr);
1868
- if (Logger.isVerbose())
2553
+ if (Logger2.isVerbose())
1869
2554
  this.logger.rawLog(chalk2.yellow(`File Update: ${filePath}`));
1870
2555
  }
1871
2556
  } else {
@@ -1879,7 +2564,7 @@ class Executor {
1879
2564
  await this.writeFile(filePath, content);
1880
2565
  }
1881
2566
  async getLocalFile(targetPath) {
1882
- const filePath = path5.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
2567
+ const filePath = path8.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
1883
2568
  const content = await this.readFile(filePath);
1884
2569
  return { filePath, content };
1885
2570
  }
@@ -1896,9 +2581,9 @@ class Executor {
1896
2581
  const dest = this.getPath(destPath);
1897
2582
  if (!await FileSys.exists(src))
1898
2583
  return;
1899
- const isDirectory = (await stat(src)).isDirectory();
2584
+ const isDirectory = (await stat2(src)).isDirectory();
1900
2585
  if (!await FileSys.exists(dest) && isDirectory)
1901
- await mkdir(dest, { recursive: true });
2586
+ await mkdir3(dest, { recursive: true });
1902
2587
  if (dereference)
1903
2588
  await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
1904
2589
  else
@@ -1976,26 +2661,26 @@ class Executor {
1976
2661
  const result = await getContent.default(scanInfo ?? null, dict, options);
1977
2662
  if (result === null)
1978
2663
  return null;
1979
- 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");
1980
2665
  const content = typeof result === "object" ? result.content : result;
1981
- const dirname = path5.dirname(targetPath);
1982
- 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}`);
1983
2668
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
1984
2669
  return this.writeFile(convertedTargetPath, content, { overwrite });
1985
2670
  } else if (targetPath.endsWith(".template")) {
1986
2671
  const content = await FileSys.readText(templatePath);
1987
- 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));
1988
2673
  const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
1989
2674
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
1990
2675
  return this.writeFile(convertedTargetPath, convertedContent, {
1991
2676
  overwrite
1992
2677
  });
1993
- } else if (staticTemplateFileExtensions.has(path5.extname(targetPath).toLowerCase())) {
1994
- 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);
1995
2680
  const writePath = this.getPath(convertedTargetPath);
1996
- const dirname = path5.dirname(writePath);
2681
+ const dirname = path8.dirname(writePath);
1997
2682
  if (!await FileSys.dirExists(dirname))
1998
- await mkdir(dirname, { recursive: true });
2683
+ await mkdir3(dirname, { recursive: true });
1999
2684
  await copyFile(templatePath, writePath);
2000
2685
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2001
2686
  return { filePath: writePath, content: "" };
@@ -2013,11 +2698,11 @@ class Executor {
2013
2698
  const templateRoot = await this.#resolveTemplateRoot();
2014
2699
  const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
2015
2700
  const prefixTemplatePath = templatePath;
2016
- if ((await stat(prefixTemplatePath)).isFile()) {
2017
- const filename = path5.basename(prefixTemplatePath);
2701
+ if ((await stat2(prefixTemplatePath)).isFile()) {
2702
+ const filename = path8.basename(prefixTemplatePath);
2018
2703
  const fileContent = await this.#applyTemplateFile({
2019
2704
  templatePath: prefixTemplatePath,
2020
- targetPath: path5.join(basePath, filename),
2705
+ targetPath: path8.join(basePath, filename),
2021
2706
  scanInfo,
2022
2707
  overwrite
2023
2708
  }, dict, options);
@@ -2025,19 +2710,19 @@ class Executor {
2025
2710
  } else {
2026
2711
  const subdirs = await readDirEntries(templatePath);
2027
2712
  const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
2028
- const subpath = path5.join(templatePath, subdir);
2029
- if ((await stat(subpath)).isFile()) {
2713
+ const subpath = path8.join(templatePath, subdir);
2714
+ if ((await stat2(subpath)).isFile()) {
2030
2715
  const fileContent = await this.#applyTemplateFile({
2031
2716
  templatePath: subpath,
2032
- targetPath: path5.join(basePath, subdir),
2717
+ targetPath: path8.join(basePath, subdir),
2033
2718
  scanInfo,
2034
2719
  overwrite
2035
2720
  }, dict, options);
2036
2721
  return fileContent ? [fileContent] : [];
2037
2722
  } else
2038
2723
  return await this._applyTemplate({
2039
- basePath: path5.join(basePath, subdir),
2040
- template: path5.join(template, subdir),
2724
+ basePath: path8.join(basePath, subdir),
2725
+ template: path8.join(template, subdir),
2041
2726
  scanInfo,
2042
2727
  dict,
2043
2728
  overwrite,
@@ -2049,8 +2734,8 @@ class Executor {
2049
2734
  }
2050
2735
  async#resolveTemplateRoot() {
2051
2736
  const candidates = [
2052
- path5.resolve(getDirname(import.meta.url), "templates"),
2053
- 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")
2054
2739
  ];
2055
2740
  for (const candidate of candidates) {
2056
2741
  if (await FileSys.dirExists(candidate))
@@ -2063,29 +2748,43 @@ class Executor {
2063
2748
  ...options.dict ?? {},
2064
2749
  ...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
2065
2750
  };
2066
- 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
+ }
2067
2766
  }
2068
2767
  async getTypeChecker() {
2069
- const { TypeChecker } = await import("./typeChecker-kravn7ns.js");
2768
+ const { TypeChecker } = await import("./typeChecker-3xgrzc2v.js");
2070
2769
  this.typeChecker ??= new TypeChecker(this);
2071
2770
  return this.typeChecker;
2072
2771
  }
2073
2772
  async typeCheck(filePath) {
2074
- const path6 = this.getPath(filePath);
2773
+ const path = this.getPath(filePath);
2075
2774
  const typeChecker = await this.getTypeChecker();
2076
- const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path6);
2775
+ const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path);
2077
2776
  const message = typeChecker.formatDiagnostics(fileDiagnostics);
2078
2777
  return { fileDiagnostics, fileErrors, fileWarnings, message };
2079
2778
  }
2080
2779
  async typeCheckAsync(filePath) {
2081
- const path6 = this.getPath(filePath);
2780
+ const path = this.getPath(filePath);
2082
2781
  const entry = await this.#resolveTypecheckWorkerEntry();
2083
2782
  const proc = Bun.spawn([process.execPath, entry], {
2084
2783
  cwd: this.cwdPath,
2085
2784
  env: {
2086
2785
  ...process.env,
2087
2786
  AKAN_TYPECHECK_CWD: this.cwdPath,
2088
- AKAN_TYPECHECK_FILE: path6
2787
+ AKAN_TYPECHECK_FILE: path
2089
2788
  },
2090
2789
  stdout: "pipe",
2091
2790
  stderr: "pipe"
@@ -2106,13 +2805,13 @@ class Executor {
2106
2805
  };
2107
2806
  }
2108
2807
  async#resolveTypecheckWorkerEntry() {
2109
- const dirname = getDirname(import.meta.url);
2808
+ const dirname = getDirname2(import.meta.url);
2110
2809
  const candidates = [
2111
- path5.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2112
- path5.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2113
- path5.join(dirname, "typecheck/typecheck.proc.ts"),
2114
- path5.join(dirname, "typecheck.proc.js"),
2115
- 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")
2116
2815
  ];
2117
2816
  for (const candidate of candidates)
2118
2817
  if (await Bun.file(candidate).exists())
@@ -2124,9 +2823,9 @@ class Executor {
2124
2823
  return this.linter;
2125
2824
  }
2126
2825
  async lint(filePath, { fix = false, dryRun = false } = {}) {
2127
- const path6 = this.getPath(filePath);
2826
+ const path = this.getPath(filePath);
2128
2827
  const linter = this.getLinter();
2129
- const { results, errors, warnings } = await linter.lint(path6, {
2828
+ const { results, errors, warnings } = await linter.lint(path, {
2130
2829
  fix,
2131
2830
  dryRun
2132
2831
  });
@@ -2239,8 +2938,8 @@ class WorkspaceExecutor extends Executor {
2239
2938
  await Promise.all(dirs.map(async (dir) => {
2240
2939
  if (dir.includes("_") || AVOID_DIRS.includes(dir))
2241
2940
  return;
2242
- const dirPath = path5.join(dirname, dir);
2243
- if ((await stat(dirPath)).isDirectory()) {
2941
+ const dirPath = path8.join(dirname, dir);
2942
+ if ((await stat2(dirPath)).isDirectory()) {
2244
2943
  results.push(`${prefix}${dir}`);
2245
2944
  if (maxDepth > 0)
2246
2945
  await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
@@ -2252,10 +2951,20 @@ class WorkspaceExecutor extends Executor {
2252
2951
  }
2253
2952
  async commit(message, { init = false, add = true } = {}) {
2254
2953
  if (init)
2255
- await this.exec(`git init --quiet`);
2954
+ await this.spawn("git", ["init", "--quiet"]);
2256
2955
  if (add)
2257
- await this.exec(`git add .`);
2258
- 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();
2259
2968
  }
2260
2969
  async#getDirHasFile(basePath, targetFilename) {
2261
2970
  const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
@@ -2264,9 +2973,9 @@ class WorkspaceExecutor extends Executor {
2264
2973
  await Promise.all(dirs.map(async (dir) => {
2265
2974
  if (AVOID_DIRS.includes(dir))
2266
2975
  return;
2267
- const dirPath = path5.join(dirname, dir);
2976
+ const dirPath = path8.join(dirname, dir);
2268
2977
  if (await FileSys.dirExists(dirPath)) {
2269
- const hasTargetFile = await FileSys.fileExists(path5.join(dirPath, targetFilename));
2978
+ const hasTargetFile = await FileSys.fileExists(path8.join(dirPath, targetFilename));
2270
2979
  if (hasTargetFile)
2271
2980
  results.push(`${prefix}${dir}`);
2272
2981
  if (maxDepth > 0)
@@ -2332,8 +3041,20 @@ class SysExecutor extends Executor {
2332
3041
  return this.#akanConfig;
2333
3042
  }
2334
3043
  async getModules() {
2335
- const path6 = this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
2336
- 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;
2337
3058
  }
2338
3059
  #scanInfo = null;
2339
3060
  hasScanInfo() {
@@ -2391,18 +3112,19 @@ class SysExecutor extends Executor {
2391
3112
  }) : await LibInfo.fromExecutor(this, {
2392
3113
  refresh
2393
3114
  });
2394
- if (write) {
2395
- await Promise.all(this.#getScanTemplateTasks(scanInfo));
2396
- await this.writeJson(`akan.${this.type}.json`, scanInfo.getScanResult());
2397
- if (this.type === "lib")
2398
- this.#updateDependencies(scanInfo);
2399
- if (writeLib) {
2400
- const libInfos = [...scanInfo.getLibInfos().values()];
2401
- await this.#updateDependencies(scanInfo);
2402
- await Promise.all(libInfos.flatMap((libInfo) => libInfo.exec.#getScanTemplateTasks(libInfo)));
2403
- }
2404
- await this.syncAgentsIndex(scanInfo);
2405
- }
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
+ });
2406
3128
  this.#scanInfo = scanInfo;
2407
3129
  return scanInfo;
2408
3130
  }
@@ -2437,7 +3159,7 @@ class SysExecutor extends Executor {
2437
3159
  await this.setPackageJson(libPkgJsonWithDeps);
2438
3160
  }
2439
3161
  async getLocalFile(targetPath) {
2440
- 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}`;
2441
3163
  const content = await this.workspace.readFile(filePath);
2442
3164
  return { filePath, content };
2443
3165
  }
@@ -2523,7 +3245,7 @@ class SysExecutor extends Executor {
2523
3245
  class AppExecutor extends SysExecutor {
2524
3246
  static #routeSourceValidator = null;
2525
3247
  static async#getRouteSourceValidator() {
2526
- AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-e4jmbfcx.js")).RouteSourceValidator;
3248
+ AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-ze6x894c.js")).RouteSourceValidator;
2527
3249
  return AppExecutor.#routeSourceValidator;
2528
3250
  }
2529
3251
  dist;
@@ -2534,9 +3256,9 @@ class AppExecutor extends SysExecutor {
2534
3256
  }
2535
3257
  static #execs = new Map;
2536
3258
  static from(executor, name) {
2537
- const exec2 = AppExecutor.#execs.get(name);
2538
- if (exec2)
2539
- return exec2;
3259
+ const exec = AppExecutor.#execs.get(name);
3260
+ if (exec)
3261
+ return exec;
2540
3262
  else if (executor instanceof WorkspaceExecutor)
2541
3263
  return new AppExecutor({ workspace: executor, name });
2542
3264
  else
@@ -2572,25 +3294,31 @@ class AppExecutor extends SysExecutor {
2572
3294
  };
2573
3295
  }
2574
3296
  async prepareCommand(type) {
2575
- const akanConfig2 = await this.getConfig();
2576
- 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";
2577
3299
  const routeEnv = {
2578
- 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,
2579
3303
  AKAN_DATABASE_MODE: databaseMode
2580
3304
  };
2581
3305
  Object.assign(process.env, routeEnv);
2582
3306
  if (type === "build") {
2583
3307
  this.#excludeDevOnlyPages = true;
2584
3308
  this.#pageKeys = null;
2585
- if (await this.exists(this.dist.cwdPath))
2586
- await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
3309
+ await this.dist.removeDir(this.dist.cwdPath);
2587
3310
  await Promise.all([this.dist.mkdir("private"), this.dist.mkdir("public")]);
2588
3311
  await Promise.all([
2589
3312
  this.cp("private", `${this.dist.cwdPath}/private`, { dereference: true }),
2590
- this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })
3313
+ ...akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []
2591
3314
  ]);
2592
- } else
3315
+ } else {
2593
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";
2594
3322
  const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
2595
3323
  const env = this.getCommandEnv({
2596
3324
  AKAN_COMMAND_TYPE: type,
@@ -2638,7 +3366,7 @@ class AppExecutor extends SysExecutor {
2638
3366
  async getPageKeys({ refresh } = {}) {
2639
3367
  if (this.#pageKeys && !refresh)
2640
3368
  return this.#pageKeys;
2641
- const akanConfig2 = await this.getConfig();
3369
+ const akanConfig = await this.getConfig();
2642
3370
  const glob = new Bun.Glob("**/*");
2643
3371
  const pageKeys = [];
2644
3372
  const owners = new Map;
@@ -2652,16 +3380,16 @@ class AppExecutor extends SysExecutor {
2652
3380
  absolute: false,
2653
3381
  onlyFiles: true
2654
3382
  })) {
2655
- const segments = rel.split(path5.sep);
3383
+ const segments = rel.split(path8.sep);
2656
3384
  if (segments.some((s) => s === "node_modules"))
2657
3385
  continue;
2658
3386
  const posix = `${root.keyPrefix}${segments.join("/")}`;
2659
- const absPath = path5.join(root.dir, ...segments);
3387
+ const absPath = path8.join(root.dir, ...segments);
2660
3388
  validatePageSourceFile(posix, { filePath: absPath });
2661
3389
  if (!isRouteSourceFile(posix))
2662
3390
  continue;
2663
3391
  const key = `./${posix}`;
2664
- validateSubRoutePageKey(key, akanConfig2.basePaths, {
3392
+ validateSubRoutePageKey(key, akanConfig.basePaths, {
2665
3393
  appName: this.name,
2666
3394
  filePath: absPath
2667
3395
  });
@@ -2679,7 +3407,8 @@ class AppExecutor extends SysExecutor {
2679
3407
  }
2680
3408
  if (!owner)
2681
3409
  owners.set(routeId, { absPath, fromLib });
2682
- const isRootLayout = parsed.kind === "layout" && parsed.moduleSegments.at(-1) === "_layout";
3410
+ const layoutSegments = getRootBoundarySegments(key);
3411
+ const isRootLayout = layoutSegments !== null && isRootBoundarySegments(layoutSegments, akanConfig.basePaths);
2683
3412
  const routeSource = await Bun.file(absPath).text();
2684
3413
  const validator = await AppExecutor.#getRouteSourceValidator();
2685
3414
  if (parsed.kind === "overrides")
@@ -2710,10 +3439,10 @@ class AppExecutor extends SysExecutor {
2710
3439
  return pageKeys.filter((key) => !isDevOnly(key));
2711
3440
  }
2712
3441
  async getPageRoots() {
2713
- const akanConfig2 = await this.getConfig();
3442
+ const akanConfig = await this.getConfig();
2714
3443
  const pageDir = `${this.cwdPath}/page`;
2715
3444
  const roots = [{ dir: pageDir, realDir: pageDir, keyPrefix: "" }];
2716
- for (const parent of AppExecutor.#pageLibParents(akanConfig2.basePaths)) {
3445
+ for (const parent of AppExecutor.#pageLibParents(akanConfig.basePaths)) {
2717
3446
  const libsDir = `${pageDir}/${parent}${AppExecutor.#pageLibsDir}`;
2718
3447
  const entries = await readDirEntries(libsDir).catch(() => []);
2719
3448
  for (const entry of entries.sort()) {
@@ -2734,9 +3463,9 @@ class AppExecutor extends SysExecutor {
2734
3463
  return parents.length ? parents : [""];
2735
3464
  }
2736
3465
  async syncPages(libDeps) {
2737
- const akanConfig2 = await this.getConfig();
2738
- const parents = AppExecutor.#pageLibParents(akanConfig2.basePaths);
2739
- const libs = await this.#resolvePageLibs(akanConfig2.syncPageLibs, libDeps);
3466
+ const akanConfig = await this.getConfig();
3467
+ const parents = AppExecutor.#pageLibParents(akanConfig.basePaths);
3468
+ const libs = await this.#resolvePageLibs(akanConfig.syncPageLibs, libDeps);
2740
3469
  const linked = (await Promise.all(parents.map(async (parent) => {
2741
3470
  const libsDir = `${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`;
2742
3471
  const entries = await readDirEntries(libsDir).catch(() => []);
@@ -2794,12 +3523,12 @@ class AppExecutor extends SysExecutor {
2794
3523
  static async#linkLibAsset(targetPath, linkPath) {
2795
3524
  const isWindows = process.platform === "win32";
2796
3525
  try {
2797
- const target = isWindows ? targetPath : path5.relative(path5.dirname(linkPath), targetPath);
3526
+ const target = isWindows ? targetPath : path8.relative(path8.dirname(linkPath), targetPath);
2798
3527
  await symlink(target, linkPath, isWindows ? "junction" : "dir");
2799
3528
  } catch (error) {
2800
3529
  if (!isWindows)
2801
3530
  throw error;
2802
- await mkdir(linkPath, { recursive: true });
3531
+ await mkdir3(linkPath, { recursive: true });
2803
3532
  await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
2804
3533
  }
2805
3534
  }
@@ -2836,7 +3565,7 @@ class AppExecutor extends SysExecutor {
2836
3565
  await this.writeFile(rel, content, opts);
2837
3566
  },
2838
3567
  readEnvClient: async () => {
2839
- const envClientPath = path5.join(this.cwdPath, "env", "env.client.ts");
3568
+ const envClientPath = path8.join(this.cwdPath, "env", "env.client.ts");
2840
3569
  if (!await FileSys.fileExists(envClientPath))
2841
3570
  return null;
2842
3571
  try {
@@ -2895,9 +3624,9 @@ class LibExecutor extends SysExecutor {
2895
3624
  }
2896
3625
  static #execs = new Map;
2897
3626
  static from(executor, name) {
2898
- const exec2 = LibExecutor.#execs.get(name);
2899
- if (exec2)
2900
- return exec2;
3627
+ const exec = LibExecutor.#execs.get(name);
3628
+ if (exec)
3629
+ return exec;
2901
3630
  else if (executor instanceof WorkspaceExecutor)
2902
3631
  return new LibExecutor({ workspace: executor, name });
2903
3632
  else
@@ -2907,7 +3636,7 @@ class LibExecutor extends SysExecutor {
2907
3636
  async getConfig({ refresh } = {}) {
2908
3637
  if (this.#akanConfig && !refresh)
2909
3638
  return this.#akanConfig;
2910
- this.#akanConfig = await AkanLibConfig.from(this);
3639
+ this.#akanConfig = await AkanLibConfig.from(this, { bustImportCache: refresh });
2911
3640
  return this.#akanConfig;
2912
3641
  }
2913
3642
  }
@@ -2946,7 +3675,7 @@ class PkgExecutor extends Executor {
2946
3675
  return rootVersion;
2947
3676
  try {
2948
3677
  const packageJsonPath = `pkgs/${dep}/package.json`;
2949
- if (!await Bun.file(path5.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
3678
+ if (!await Bun.file(path8.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
2950
3679
  return;
2951
3680
  const packageJson = await this.workspace.readJson(packageJsonPath);
2952
3681
  if (packageJson.name === dep)
@@ -3004,7 +3733,7 @@ class PkgExecutor extends Executor {
3004
3733
  default: "./index.ts"
3005
3734
  }
3006
3735
  },
3007
- engines: { bun: ">=1.3.13" },
3736
+ engines: { bun: ">=1.4.0" },
3008
3737
  ...dependencyMaps
3009
3738
  };
3010
3739
  await Promise.all([this.dist.writeJson("package.json", distPkgJson), this.writeJson("package.json", distPkgJson)]);
@@ -3047,4 +3776,4 @@ class ModuleExecutor extends Executor {
3047
3776
  }
3048
3777
  }
3049
3778
 
3050
- export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, appRootAllowedFiles, appRootAllowedDirs, libFacetRootAllowedFiles, isScannedAppRootEntry, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
3779
+ 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 };