@akanjs/cli 3.0.0-alpha.8 → 3.0.0-alpha.81

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 (152) 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-pwc6d6yk.js} +3 -5
  5. package/{agent.command-pggr92jj.js → agent.command-180hh0ye.js} +11 -12
  6. package/{application.command-692yap1d.js → application.command-4fq5pmdd.js} +59 -35
  7. package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-tavpx3fv.js} +22 -20
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-ktjt0erc.js} +8 -9
  9. package/buildBatch.proc.js +12 -13
  10. package/capacitorApp-kwa67kzs.js +12 -0
  11. package/{cloud.command-wqfsyzv2.js → cloud.command-wm3mtfve.js} +15 -17
  12. package/commandManifest.json +1 -1
  13. package/{context.command-9bxtdn0w.js → context.command-dk2m2sjx.js} +19 -22
  14. package/dependencyScanner-c64m6fxp.js +8 -0
  15. package/getDirname-dr3jz1xg.js +7 -0
  16. package/{guideline.command-nfqahxex.js → guideline.command-w6ddv13r.js} +16 -17
  17. package/guidelines/agentRule/agentRule.generate.json +31 -0
  18. package/guidelines/agentRule/agentRule.instruction.md +525 -0
  19. package/guidelines/conventions/conventions.generate.json +25 -0
  20. package/guidelines/conventions/conventions.instruction.md +847 -0
  21. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  22. package/guidelines/framework/framework.generate.json +3 -2
  23. package/guidelines/framework/framework.instruction.md +1 -9
  24. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  25. package/guidelines/mcpRule/mcpRule.instruction.md +217 -0
  26. package/guidelines/queryRule/queryRule.generate.json +31 -0
  27. package/guidelines/queryRule/queryRule.instruction.md +143 -0
  28. package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +176 -0
  30. package/guidelines/ssrRule/ssrRule.instruction.md +49 -5
  31. package/guidelines/transportRule/transportRule.generate.json +31 -0
  32. package/guidelines/transportRule/transportRule.instruction.md +60 -0
  33. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
  34. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +263 -0
  35. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
  36. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
  37. package/incrementalBuilder.proc.js +21 -22
  38. package/{index-4hek879p.js → index-1kzsn94d.js} +1 -1
  39. package/{index-p3f4q532.js → index-1tgp56ke.js} +31 -30
  40. package/index-3d9y066c.js +76 -0
  41. package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
  42. package/{index-gex7c8kd.js → index-5n0hf8r9.js} +65 -38
  43. package/{index-mssqn2sq.js → index-5y7qba1c.js} +79 -33
  44. package/index-6wwzs1bc.js +7 -0
  45. package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
  46. package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
  47. package/{index-azv0xjb5.js → index-a8h184wr.js} +15 -11
  48. package/{index-67546d0j.js → index-ayy5ecax.js} +1 -1
  49. package/{index-xhgtkekh.js → index-b2nt0w0k.js} +27 -27
  50. package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
  51. package/{index-bz2sfzzt.js → index-cd5x18qh.js} +32 -32
  52. package/{index-bjpxzr6s.js → index-ctphds8v.js} +1 -1
  53. package/{index-w6wbb612.js → index-dq6wq1tv.js} +94 -61
  54. package/{index-6rhabeev.js → index-ft1sfjz0.js} +36 -25
  55. package/{index-7myvn0f0.js → index-h7yw2z9n.js} +7 -7
  56. package/{index-5pmx7bkg.js → index-j9vnq8tr.js} +18 -18
  57. package/{index-vg0sj9nc.js → index-k1n0h7dk.js} +840 -201
  58. package/{index-vtj3zps1.js → index-k86zc21w.js} +57 -57
  59. package/{index-5dw57f0d.js → index-m7e0f1sy.js} +155 -76
  60. package/index-m9c7k0gm.js +159 -0
  61. package/{index-46tjzh6s.js → index-mfm35dhh.js} +29 -29
  62. package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
  63. package/{index-ztpw544k.js → index-qcracvc9.js} +8 -8
  64. package/{index-2c3e2xh8.js → index-saheq2bs.js} +4 -4
  65. package/{index-gmy45vz2.js → index-v0s24t8j.js} +14 -14
  66. package/{index-0cj2zxbm.js → index-wet2ytnr.js} +12 -11
  67. package/{index-j9cxndc0.js → index-x23688ar.js} +12 -7
  68. package/{index-a1e0j63x.js → index-xxe90kqy.js} +58 -25
  69. package/{index-js368gk9.js → index-ypvdw8nr.js} +13 -13
  70. package/{index-qb61h0k7.js → index-z3c9mmw4.js} +1098 -1360
  71. package/index.js +22 -24
  72. package/{library.command-md9715jz.js → library.command-wpedct6c.js} +11 -9
  73. package/{localRegistry.command-exkbamyj.js → localRegistry.command-gk773cte.js} +20 -22
  74. package/{module.command-w5aaddxk.js → module.command-8n4kct3e.js} +13 -15
  75. package/{package.command-yfkrcy0z.js → package.command-fcbd0b1d.js} +8 -9
  76. package/package.json +5 -7
  77. package/{page.command-echbavp4.js → page.command-x4379grq.js} +7 -8
  78. package/{primitive.command-9fbhspw2.js → primitive.command-8etmnqhq.js} +13 -15
  79. package/{quality.command-3pnt9yep.js → quality.command-hfx7z8yr.js} +226 -104
  80. package/recipeScanner-fpagtgc1.js +11 -0
  81. package/{repair.command-carj4vax.js → repair.command-j0fs5qkz.js} +12 -13
  82. package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-ze6x894c.js} +0 -2
  83. package/{scalar.command-51ryzypc.js → scalar.command-2emk89eh.js} +11 -13
  84. package/subspace.command-3w3s1zas.js +714 -0
  85. package/templates/app/env/env.client.debug.ts.template +1 -5
  86. package/templates/app/env/env.client.develop.ts.template +1 -5
  87. package/templates/app/env/env.client.local.ts.template +1 -5
  88. package/templates/app/env/env.client.main.ts.template +1 -5
  89. package/templates/app/env/env.client.testing.ts.template +1 -5
  90. package/templates/app/env/env.client.type.ts +2 -6
  91. package/templates/app/env/env.server.debug.ts.template +1 -5
  92. package/templates/app/env/env.server.develop.ts.template +1 -5
  93. package/templates/app/env/env.server.local.ts.template +1 -5
  94. package/templates/app/env/env.server.main.ts.template +1 -5
  95. package/templates/app/main.ts +3 -1
  96. package/templates/appSample/lib/task/task.signal.ts +11 -6
  97. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  98. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  99. package/templates/crudPages/[__model__Id]/edit/page.tsx +6 -6
  100. package/templates/crudPages/[__model__Id]/page.tsx +10 -13
  101. package/templates/crudPages/new/page.tsx +6 -6
  102. package/templates/crudPages/page.tsx +9 -9
  103. package/templates/crudSinglePage/page.tsx +11 -7
  104. package/templates/env/_env.server.type.ts +7 -5
  105. package/templates/lib/db.ts +1 -1
  106. package/templates/lib/dict.ts +1 -1
  107. package/templates/lib/sig.ts +1 -1
  108. package/templates/lib/srv.ts +6 -2
  109. package/templates/module/__Model__.Util.tsx +1 -1
  110. package/templates/module/__Model__.Zone.tsx +3 -5
  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/AGENTS.md.template +4 -972
  116. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  117. package/templates/workspaceRoot/biome.json.template +4 -244
  118. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
  119. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  120. package/templates/workspaceRoot/package.json.template +2 -4
  121. package/typeChecker-3xgrzc2v.js +7 -0
  122. package/typecheck.proc.js +4 -5
  123. package/{workflow.command-91wmvm8k.js → workflow.command-cvpmzwjg.js} +21 -23
  124. package/{workspace.command-wa10mfd9.js → workspace.command-4ztb4fdh.js} +108 -91
  125. package/capacitorApp-p0z5yab2.js +0 -60
  126. package/dependencyScanner-f6grrep3.js +0 -9
  127. package/index-mxvakhsm.js +0 -66
  128. package/index-r24hmh0q.js +0 -4
  129. package/recipeScanner-tghkzrrt.js +0 -12
  130. package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
  131. package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -34
  132. package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
  133. package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
  134. package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
  135. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
  136. package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
  137. package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
  138. package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
  139. package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -19
  140. package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -62
  141. package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
  142. package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
  143. package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
  144. package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -99
  145. package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
  146. package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
  147. package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
  148. package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
  149. package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
  150. package/typeChecker-kravn7ns.js +0 -8
  151. /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
  152. /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
@@ -1,13 +1,74 @@
1
1
  // @bun
2
- import {
3
- Spinner
4
- } from "./index-mxvakhsm.js";
5
2
  import {
6
3
  FileSys
7
- } from "./index-67546d0j.js";
4
+ } from "./index-ayy5ecax.js";
8
5
  import {
9
- __require
10
- } from "./index-r24hmh0q.js";
6
+ getDirname2
7
+ } from "./index-6wwzs1bc.js";
8
+
9
+ // pkgs/@akanjs/devkit/spinner.ts
10
+ import ora from "ora";
11
+
12
+ class Spinner {
13
+ static padding = 12;
14
+ static oraOptions = { discardStdin: false };
15
+ static canAnimate(stream = process.stderr) {
16
+ return !stream.isTTY || stream.columns > 0;
17
+ }
18
+ spinner;
19
+ stopWatch = null;
20
+ startAt = new Date;
21
+ prefix;
22
+ message;
23
+ enableSpin;
24
+ constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
25
+ Spinner.padding = Math.max(Spinner.padding, prefix.length);
26
+ this.prefix = prefix;
27
+ this.message = message;
28
+ this.spinner = ora({ ...Spinner.oraOptions, text: message });
29
+ this.spinner.prefixText = prefix.padStart(Spinner.padding, " ");
30
+ this.spinner.indent = indent;
31
+ this.enableSpin = enableSpin && Spinner.canAnimate();
32
+ }
33
+ start() {
34
+ this.startAt = new Date;
35
+ if (this.enableSpin) {
36
+ this.spinner.start();
37
+ this.stopWatch = setInterval(() => {
38
+ this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
39
+ this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
40
+ }, 1000);
41
+ } else
42
+ this.spinner.info();
43
+ return this;
44
+ }
45
+ succeed(message) {
46
+ this.spinner.succeed(`${message} (${this.#getElapsedTimeStr()})`);
47
+ this.#reset();
48
+ }
49
+ fail(message) {
50
+ this.spinner.fail(`${message} (${this.#getElapsedTimeStr()})`);
51
+ this.#reset();
52
+ }
53
+ isSpinning() {
54
+ return this.spinner.isSpinning;
55
+ }
56
+ #reset() {
57
+ if (this.stopWatch)
58
+ clearInterval(this.stopWatch);
59
+ this.stopWatch = null;
60
+ }
61
+ #getElapsedTimeStr({ floor = false } = {}) {
62
+ const ms = Date.now() - this.startAt.getTime();
63
+ if (ms < 1000)
64
+ return `${ms}ms`;
65
+ const s = Math.floor(ms / 1000);
66
+ if (s < 60)
67
+ return `${floor ? Math.floor(ms / 1000) : Math.floor(ms / 100) / 10}s`;
68
+ const m = Math.floor(s / 60);
69
+ return `${m}m ${s % 60}s`;
70
+ }
71
+ }
11
72
 
12
73
  // pkgs/@akanjs/devkit/executors.ts
13
74
  import {
@@ -19,17 +80,18 @@ import { readFileSync as readFileSync2 } from "fs";
19
80
  import {
20
81
  copyFile,
21
82
  cp as cpEntry,
22
- mkdir,
83
+ mkdir as mkdir2,
23
84
  readdir as readDirEntries,
24
85
  realpath,
25
86
  rm as rm2,
26
87
  stat,
27
88
  symlink
28
89
  } from "fs/promises";
29
- import path5 from "path";
90
+ import path7 from "path";
30
91
  import { pathToFileURL } from "url";
31
92
  import {
32
93
  capitalize,
94
+ getPageSourceFileViolation,
33
95
  isRouteSourceFile,
34
96
  Logger,
35
97
  parseRouteModuleKey,
@@ -41,9 +103,24 @@ import chalk2 from "chalk";
41
103
 
42
104
  // pkgs/@akanjs/devkit/agentsIndex.ts
43
105
  var recipeScannerLoad = null;
44
- var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-tghkzrrt.js");
106
+ var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-fpagtgc1.js");
45
107
  var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
46
108
  var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
109
+ var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
110
+ var stampBlockVersion = (block, version) => `${AGENT_VERSION_PREFIX}${version} -->
111
+
112
+ ${block}`;
113
+ var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
114
+ var readDevkitVersion = async () => {
115
+ const { readFile } = await import("fs/promises");
116
+ const { getDirname } = await import("./getDirname-dr3jz1xg.js");
117
+ try {
118
+ const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
119
+ return JSON.parse(raw).version ?? null;
120
+ } catch {
121
+ return null;
122
+ }
123
+ };
47
124
  var upsertAgentBlock = (existing, block) => {
48
125
  const managed = `${AGENT_BLOCK_START}
49
126
  ${block}
@@ -137,9 +214,29 @@ ${AGENT_BLOCK_START}
137
214
  ${block}
138
215
  ${AGENT_BLOCK_END}
139
216
  `;
217
+ var CLAUDE_COMMENT_RULE = `## Comments \u2014 Overrides Your Default
218
+
219
+ Write **no comments** unless the comment passes the test below. This is the rule agents break most often here, so it
220
+ is repeated outside the guide: a diff that adds a comment the test rejects is a diff to redo.
221
+
222
+ Before typing \`//\`, \`/*\`, or a doc block, ask \u2014 **does this sentence carry a fact that is nowhere in the code?**
223
+
224
+ - Restates the identifier, the signature, or the line under it \u2192 delete it.
225
+ - Labels a section (\`// helpers\`, \`// state\`) or narrates a step (\`// fetch the user\`, \`// then save\`) \u2192 delete it.
226
+ - JSDoc on an ordinary function, or a why/how preamble on ordinary logic \u2192 delete it.
227
+ - Explains the edit you just made, for whoever reads the diff \u2192 say it in your reply, not in the file.
228
+ - Names a vendor or protocol quirk, an infrastructure constraint, a library gotcha, security reasoning, a math
229
+ derivation, a domain field's business meaning, a state transition, or why an obvious alternative was rejected \u2192
230
+ keep it, one line.
231
+
232
+ That keep-list is exact \u2014 \`Comments\` in the guide is the full version. "It aids readability" and "this logic is
233
+ subtle" are not on it: rename or split the code instead. When you edit an existing file, match its density; if the
234
+ surrounding code carries none, your diff carries none.`;
140
235
  var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
141
236
 
142
237
  @AGENTS.md
238
+
239
+ ${CLAUDE_COMMENT_RULE}
143
240
  `;
144
241
 
145
242
  // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
@@ -181,9 +278,8 @@ var DEFAULT_OPTIMIZE_IMPORTS = [
181
278
  "react-icons/*"
182
279
  ];
183
280
  var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
281
+ var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
184
282
  var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
185
- var NATIVE_RUNTIME_PACKAGES = ["sharp"];
186
- var DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"];
187
283
  var MOBILE_RUNTIME_PACKAGES = [
188
284
  "@capacitor/cli",
189
285
  "@capacitor/core",
@@ -212,8 +308,6 @@ var DATABASE_MODE_RUNTIME_PACKAGES = {
212
308
  };
213
309
  var AKAN_RUNTIME_PACKAGES = new Set([
214
310
  ...SSR_RUNTIME_PACKAGES,
215
- ...NATIVE_RUNTIME_PACKAGES,
216
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
217
311
  ...MOBILE_RUNTIME_PACKAGES,
218
312
  ...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat()
219
313
  ]);
@@ -228,7 +322,8 @@ var DEFAULT_AKAN_IMAGE_CONFIG = {
228
322
  dangerouslyAllowSVG: false,
229
323
  maximumRedirects: 3,
230
324
  fetchTimeoutMs: 7000,
231
- maxRemoteBytes: 25 * 1024 * 1024
325
+ maxRemoteBytes: 25 * 1024 * 1024,
326
+ maxConcurrency: 0
232
327
  };
233
328
  var normalizeIndexPath = (indexPath) => {
234
329
  const normalized = indexPath?.trim();
@@ -275,12 +370,28 @@ var normalizeDeepLinks = (deepLinks) => {
275
370
  ...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
276
371
  };
277
372
  };
373
+ var emptyLibContributions = () => ({
374
+ externalLibs: [],
375
+ docker: { preRuns: [], postRuns: [] },
376
+ keepFonts: []
377
+ });
378
+ var normalizeKeepFonts = (keepFonts) => [
379
+ ...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean))
380
+ ];
381
+ var dedupeDockerRuns = (runs) => {
382
+ const byKey = new Map;
383
+ for (const run of runs)
384
+ byKey.set(typeof run === "string" ? run : JSON.stringify(run), run);
385
+ return [...byKey.values()];
386
+ };
278
387
 
279
388
  class AkanAppConfig {
280
389
  app;
281
390
  rootPackageJson;
282
391
  docker;
392
+ dockerfile;
283
393
  defaultDatabaseMode;
394
+ web;
284
395
  externalLibs;
285
396
  barrelImports;
286
397
  optimizeImports;
@@ -290,6 +401,7 @@ class AkanAppConfig {
290
401
  mobile;
291
402
  hasMobileConfig;
292
403
  secrets;
404
+ assets;
293
405
  syncPageLibs;
294
406
  baseDevEnv;
295
407
  libs;
@@ -298,7 +410,7 @@ class AkanAppConfig {
298
410
  subRoutes = new Map;
299
411
  basePaths = new Set;
300
412
  branches = new Set(["debug", "develop", "main"]);
301
- constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = []) {
413
+ constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = [], libContributions = emptyLibContributions()) {
302
414
  this.app = app;
303
415
  this.rootPackageJson = rootPackageJson;
304
416
  this.libs = libs;
@@ -306,7 +418,7 @@ class AkanAppConfig {
306
418
  this.plugins = plugins;
307
419
  this.#applyRoutes(config?.routes);
308
420
  this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
309
- this.externalLibs = config?.externalLibs ?? [];
421
+ this.externalLibs = [...new Set([...config?.externalLibs ?? [], ...libContributions.externalLibs])];
310
422
  this.barrelImports = [
311
423
  ...DEFAULT_BARREL_IMPORTS,
312
424
  ...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
@@ -320,10 +432,25 @@ class AkanAppConfig {
320
432
  process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
321
433
  this.publicEnv = config?.publicEnv ?? [];
322
434
  this.secrets = config?.secrets ?? [];
435
+ this.assets = {
436
+ pruneFonts: config?.assets?.pruneFonts ?? true,
437
+ keepFonts: [
438
+ ...normalizeKeepFonts(config?.assets?.keepFonts),
439
+ ...libContributions.keepFonts ?? []
440
+ ]
441
+ };
323
442
  this.syncPageLibs = config?.syncPageLibs ?? false;
324
443
  this.hasMobileConfig = Boolean(config.mobile);
325
444
  this.mobile = this.#resolveMobileConfig(config.mobile);
326
- this.docker = this.#makeDockerContent(config?.docker ?? {});
445
+ this.web = this.#resolveWebConfig(config.web);
446
+ this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
447
+ this.dockerfile = this.#makeDockerfile();
448
+ }
449
+ #resolveWebConfig(web) {
450
+ const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
451
+ if (!resolved.csr && this.hasMobileConfig)
452
+ 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.`);
453
+ return resolved;
327
454
  }
328
455
  #resolveMobileConfig(mobile) {
329
456
  const {
@@ -433,23 +560,31 @@ class AkanAppConfig {
433
560
  return archs.map((arch) => `FROM ${image[arch] ?? defaultImage} AS ${arch}`).join(`
434
561
  `);
435
562
  }
436
- #makeDockerContent(docker) {
437
- if (docker.content)
438
- return {
439
- content: docker.content,
440
- image: {},
441
- preRuns: [],
442
- postRuns: [],
443
- command: []
444
- };
445
- const preRunScripts = this.#getDockerRunScripts(docker.preRuns ?? []);
446
- const postRunScripts = this.#getDockerRunScripts(docker.postRuns ?? []);
447
- const imageScript = docker.image ? this.#getDockerImageScript(docker.image, "oven/bun:1-slim") : "FROM oven/bun:1-slim";
448
- const command = docker.command ?? ["bun", "main.js"];
449
- const content = `${imageScript}
563
+ static #resolveDocker(docker, libDocker) {
564
+ if (typeof docker === "string")
565
+ return docker;
566
+ return {
567
+ image: docker?.image ?? DEFAULT_DOCKER_IMAGE,
568
+ preRuns: dedupeDockerRuns([...libDocker.preRuns, ...docker?.preRuns ?? []]),
569
+ postRuns: dedupeDockerRuns([...libDocker.postRuns, ...docker?.postRuns ?? []]),
570
+ command: docker?.command ?? ["bun", "main.js"]
571
+ };
572
+ }
573
+ #makeDockerfile() {
574
+ if (typeof this.docker === "string")
575
+ return this.docker;
576
+ const { image, preRuns, postRuns, command } = this.docker;
577
+ const preRunScripts = this.#getDockerRunScripts(preRuns);
578
+ const postRunScripts = this.#getDockerRunScripts(postRuns);
579
+ const imageScript = this.#getDockerImageScript(image, DEFAULT_DOCKER_IMAGE);
580
+ const webEnvLines = [
581
+ ...this.web.ssr ? [] : ["ENV AKAN_SSR=false"],
582
+ ...this.web.csr ? [] : ["ENV AKAN_CSR=false"]
583
+ ].join(`
584
+ `);
585
+ return `${imageScript}
586
+ RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
450
587
  RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
451
- RUN apt-get update && apt-get upgrade -y
452
- RUN apt-get install -y --no-install-recommends git redis build-essential python3 ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils udev ffmpeg
453
588
  ARG TARGETARCH
454
589
  ${preRunScripts.join(`
455
590
  `)}
@@ -470,15 +605,9 @@ ${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join("
470
605
  ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
471
606
  ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
472
607
  ENV AKAN_PUBLIC_OPERATION_MODE=cloud
473
-
608
+ ENV AKAN_LOG_TO_FILE=0
609
+ ${webEnvLines}
474
610
  CMD [${command.map((c) => `"${c}"`).join(",")}]`;
475
- return {
476
- content,
477
- image: imageScript,
478
- preRuns: docker.preRuns ?? [],
479
- postRuns: docker.postRuns ?? [],
480
- command
481
- };
482
611
  }
483
612
  static #importGeneration = 0;
484
613
  static async importConfigModule(cwdPath, { bustImportCache = false } = {}) {
@@ -495,7 +624,22 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
495
624
  ]);
496
625
  const resolved = typeof configImp === "function" ? configImp(app) : configImp;
497
626
  const { plugins, ...config } = resolved ?? {};
498
- return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? []);
627
+ const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
628
+ return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
629
+ }
630
+ static async#collectLibContributions(app, libs, bustImportCache) {
631
+ const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
632
+ app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
633
+ return null;
634
+ })));
635
+ return {
636
+ externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
637
+ docker: {
638
+ preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
639
+ postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? [])
640
+ },
641
+ keepFonts: libConfigs.flatMap((libConfig) => (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`))
642
+ };
499
643
  }
500
644
  #resolveProductionDependencyVersion(lib) {
501
645
  const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
@@ -505,13 +649,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
505
649
  return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
506
650
  }
507
651
  #getProductionRuntimePackages() {
508
- return [
509
- ...this.externalLibs,
510
- ...SSR_RUNTIME_PACKAGES,
511
- ...NATIVE_RUNTIME_PACKAGES,
512
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
513
- ...this.getDatabaseModeRuntimePackages()
514
- ];
652
+ return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
515
653
  }
516
654
  getDatabaseModeRuntimePackages(databaseMode = this.defaultDatabaseMode) {
517
655
  return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
@@ -599,14 +737,18 @@ function mergeImageConfig(config = {}) {
599
737
  class AkanLibConfig {
600
738
  lib;
601
739
  externalLibs;
740
+ docker;
741
+ assets;
602
742
  plugins;
603
743
  constructor(lib, config, plugins = []) {
604
744
  this.lib = lib;
605
745
  this.externalLibs = config?.externalLibs ?? [];
746
+ this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
747
+ this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
606
748
  this.plugins = plugins;
607
749
  }
608
- static async from(lib) {
609
- const [configImp] = await Promise.all([import(`${lib.cwdPath}/akan.config.ts`).then((mod) => mod.default)]);
750
+ static async from(lib, { bustImportCache = false } = {}) {
751
+ const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
610
752
  const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
611
753
  const { plugins, ...config } = resolved ?? {};
612
754
  return new AkanLibConfig(lib, config, plugins ?? []);
@@ -627,42 +769,436 @@ var decreaseBuildNum = async (app) => {
627
769
  const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
628
770
  fs.writeFileSync(akanConfigPath, akanConfigContent);
629
771
  };
630
- // pkgs/@akanjs/devkit/getDirname.ts
772
+ // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
773
+ import { mkdir } from "fs/promises";
774
+ import path3 from "path";
775
+
776
+ // pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
777
+ import fs2 from "fs";
631
778
  import path2 from "path";
632
- import { fileURLToPath as fileURLToPath2 } from "url";
633
- var getDirname = (url) => path2.dirname(fileURLToPath2(url));
779
+
780
+ class AsyncDefaultExportDetector {
781
+ static #typescriptLoad;
782
+ static #loadTypescript() {
783
+ AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
784
+ return AsyncDefaultExportDetector.#typescriptLoad;
785
+ }
786
+ static async detect(moduleAbsPath) {
787
+ try {
788
+ const typescript = await AsyncDefaultExportDetector.#loadTypescript();
789
+ const source = fs2.readFileSync(path2.resolve(moduleAbsPath), "utf8");
790
+ const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, AsyncDefaultExportDetector.#scriptKind(typescript, moduleAbsPath));
791
+ return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
792
+ } catch {
793
+ return false;
794
+ }
795
+ }
796
+ #ts;
797
+ constructor(typescript) {
798
+ this.#ts = typescript;
799
+ }
800
+ detectInSourceFile(sourceFile) {
801
+ const ts = this.#ts;
802
+ const asyncBindings = new Map;
803
+ let defaultIdentifier = null;
804
+ for (const statement of sourceFile.statements) {
805
+ if (ts.isFunctionDeclaration(statement)) {
806
+ if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
807
+ return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
808
+ }
809
+ if (statement.name) {
810
+ asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
811
+ }
812
+ continue;
813
+ }
814
+ if (ts.isVariableStatement(statement)) {
815
+ for (const declaration of statement.declarationList.declarations) {
816
+ if (!ts.isIdentifier(declaration.name))
817
+ continue;
818
+ asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
819
+ }
820
+ continue;
821
+ }
822
+ if (ts.isExportAssignment(statement)) {
823
+ if (this.#isAsyncFunctionExpression(statement.expression))
824
+ return true;
825
+ if (ts.isIdentifier(statement.expression))
826
+ defaultIdentifier = statement.expression.text;
827
+ continue;
828
+ }
829
+ if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
830
+ const exportClause = statement.exportClause;
831
+ for (const specifier of exportClause.elements) {
832
+ if (specifier.name.text !== "default")
833
+ continue;
834
+ defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
835
+ }
836
+ }
837
+ }
838
+ return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
839
+ }
840
+ #hasModifier(node, kind) {
841
+ const ts = this.#ts;
842
+ return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
843
+ }
844
+ #isAsyncFunctionExpression(node) {
845
+ const ts = this.#ts;
846
+ return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
847
+ }
848
+ static #scriptKind(typescript, moduleAbsPath) {
849
+ return moduleAbsPath.endsWith(".tsx") || moduleAbsPath.endsWith(".jsx") ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS;
850
+ }
851
+ }
852
+
853
+ // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
854
+ var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
855
+ async function appHasStModule(appCwdPath) {
856
+ return Bun.file(path3.join(appCwdPath, "lib", "st.ts")).exists();
857
+ }
858
+ var IMPLICIT_LAYOUT_DIR = path3.join(".akan", "generated", "root-layouts");
859
+ var IMPLICIT_DICT_DIR = path3.join(".akan", "generated", "dict");
860
+ var IMPLICIT_OVERRIDES_DIR = path3.join(".akan", "generated", "overrides");
861
+ var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
862
+ async function writeGeneratedOverridesLayoutFile(opts) {
863
+ const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
864
+ const absPath = path3.join(path3.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
865
+ const userRel = path3.relative(path3.dirname(absPath), opts.userAbsPath).split(path3.sep).join("/");
866
+ const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
867
+ const source = `"use client";
868
+ import { UiOverrideProvider } from "akanjs/ui";
869
+ import { createElement, type ReactNode } from "react";
870
+ import value from ${JSON.stringify(userSpecifier)};
871
+
872
+ export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
873
+ return createElement(UiOverrideProvider, { value }, children);
874
+ }
875
+ `;
876
+ await Bun.write(absPath, source);
877
+ return absPath;
878
+ }
879
+ function getRootBoundarySegments(key) {
880
+ const match = LAYOUT_KEY_RE.exec(key);
881
+ if (!match)
882
+ return null;
883
+ const prefix = match[1]?.replace(/\/$/, "");
884
+ if (!prefix)
885
+ return [];
886
+ return prefix.split("/").filter(Boolean);
887
+ }
888
+ function implicitRootLayoutKey(segments) {
889
+ return `./${[...segments, "__root_layout"].join("/")}.tsx`;
890
+ }
891
+ function implicitRootLayoutAbsPath(appCwdPath, segments) {
892
+ const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
893
+ return path3.join(path3.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
894
+ }
895
+ function implicitDictionaryMacroAbsPath(appCwdPath) {
896
+ return path3.join(path3.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
897
+ }
898
+ function isRootBoundarySegments(segments, basePaths) {
899
+ const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
900
+ if (firstVisibleIndex === -1)
901
+ return segments.length <= 1;
902
+ if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
903
+ return false;
904
+ const visible = segments.slice(firstVisibleIndex);
905
+ const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
906
+ return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
907
+ }
908
+ function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
909
+ const boundaries = new Map;
910
+ for (const key of pageKeys) {
911
+ const segments = getRootBoundarySegments(key);
912
+ if (!segments)
913
+ continue;
914
+ if (!isRootBoundarySegments(segments, basePaths))
915
+ continue;
916
+ const id = segments.join("/");
917
+ boundaries.set(id, {
918
+ sourceKey: key,
919
+ sourceAbsPath: path3.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
920
+ segments
921
+ });
922
+ }
923
+ const hasExplicitRootBoundary = [...boundaries.values()].some((boundary) => boundary.segments.length === 0);
924
+ if (!hasExplicitRootBoundary && boundaries.size === 0) {
925
+ boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
926
+ }
927
+ return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
928
+ }
929
+ function hasAncestorRootBoundary(boundary, boundaries) {
930
+ return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
931
+ }
932
+ function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
933
+ const rootLayoutKey = pageKeys.find((key) => {
934
+ const segments = getRootBoundarySegments(key);
935
+ return segments !== null && segments.length === 0;
936
+ });
937
+ return rootLayoutKey ? path3.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
938
+ }
939
+ function routePrefixForSegments(segments) {
940
+ const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
941
+ return visible[0] ?? null;
942
+ }
943
+ async function assertEnvClientConvention(appCwdPath, appName) {
944
+ const envPath = path3.join(appCwdPath, "env", "env.client.ts");
945
+ if (!await Bun.file(envPath).exists()) {
946
+ throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
947
+ }
948
+ }
949
+ async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
950
+ const absPath = implicitDictionaryMacroAbsPath(appCwdPath);
951
+ await mkdir(path3.dirname(absPath), { recursive: true });
952
+ await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
953
+
954
+ export const allDictionary = getAllDictionary();
955
+ `);
956
+ return absPath;
957
+ }
958
+ async function writeGeneratedRootLayoutFile(opts) {
959
+ await assertEnvClientConvention(opts.appCwdPath, opts.appName);
960
+ const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
961
+ const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
962
+ await mkdir(path3.dirname(absPath), { recursive: true });
963
+ const dictMacroRel = dictMacroAbsPath ? path3.relative(path3.dirname(absPath), dictMacroAbsPath).split(path3.sep).join("/") : null;
964
+ const dictMacroSpecifier = dictMacroRel ? dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}` : null;
965
+ const sourceRel = opts.boundary.sourceAbsPath ? path3.relative(path3.dirname(absPath), opts.boundary.sourceAbsPath).split(path3.sep).join("/") : null;
966
+ const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
967
+ const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
968
+ const inheritedSourceRel = inheritedSourceAbsPath ? path3.relative(path3.dirname(absPath), inheritedSourceAbsPath).split(path3.sep).join("/") : null;
969
+ const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
970
+ const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
971
+ void st;
972
+ ` : `import "@apps/${opts.appName}/client";
973
+ `;
974
+ const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedLayout from ${JSON.stringify(inheritedSourceSpecifier)};
975
+ ` : `const inheritedLayout = {};
976
+ `;
977
+ const prefix = routePrefixForSegments(opts.boundary.segments);
978
+ const userImport = sourceSpecifier ? `import UserLayout, * as userLayout from ${JSON.stringify(sourceSpecifier)};
979
+ ` : `const UserLayout = ({ children }) => children;
980
+ const userLayout = {};
981
+ `;
982
+ const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
983
+ const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
984
+ const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
985
+ const layoutBinding = isAsyncUserLayout ? ` const layout =
986
+ process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
987
+ ? await UserLayout({ params, searchParams, children })
988
+ : ${userLayoutElement};
989
+ ` : "";
990
+ const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
991
+ const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
992
+ const source = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
993
+ import { loadFonts } from "akanjs/client";
994
+ import { System } from "akanjs/ui";
995
+ import { env } from "@apps/${opts.appName}/env/env.client";
996
+ import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
997
+ ${clientImport}${inheritedImport}${userImport}
998
+ const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
999
+ const defaultFonts = userFonts.filter((font) => font.default);
1000
+ if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
1001
+ const defaultFont = defaultFonts[0];
1002
+ const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
1003
+
1004
+ export async function generateHead(props: PageProps) {
1005
+ if (userLayout.generateHead) return userLayout.generateHead(props);
1006
+ if (userLayout.head !== undefined) return userLayout.head;
1007
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
1008
+ return inheritedLayout.head;
1009
+ }
1010
+
1011
+ export async function generateMetadata(props: PageProps) {
1012
+ if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
1013
+ if (userLayout.metadata !== undefined) return userLayout.metadata;
1014
+ if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
1015
+ return inheritedLayout.metadata;
1016
+ }
1017
+
1018
+ export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
1019
+ export const Error = userLayout.Error ?? inheritedLayout.Error;
1020
+ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
1021
+
1022
+ ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
1023
+ ${layoutBinding} return (
1024
+ <System.Provider
1025
+ of={GeneratedLayout as never}
1026
+ appName=${JSON.stringify(opts.appName)}
1027
+ ${prefix ? `prefix=${JSON.stringify(prefix)}
1028
+ ` : ""}params={params}
1029
+ manifest={userLayout.manifest ?? inheritedLayout.manifest}
1030
+ env={env}
1031
+ theme={userLayout.theme ?? inheritedLayout.theme}
1032
+ fonts={loadFonts(userFonts)}
1033
+ className={defaultFontClassName}
1034
+ gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
1035
+ layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
1036
+ reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
1037
+ wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
1038
+ allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
1039
+ >
1040
+ ${layoutChild}
1041
+ </System.Provider>
1042
+ );
1043
+ }
1044
+ ` : `import type { LayoutProps, PageProps } from "akanjs/client";
1045
+ ${inheritedImport}${userImport}
1046
+ export async function generateHead(props: PageProps) {
1047
+ if (userLayout.generateHead) return userLayout.generateHead(props);
1048
+ if (userLayout.head !== undefined) return userLayout.head;
1049
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
1050
+ return inheritedLayout.head;
1051
+ }
1052
+
1053
+ export async function generateMetadata(props: PageProps) {
1054
+ if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
1055
+ if (userLayout.metadata !== undefined) return userLayout.metadata;
1056
+ if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
1057
+ return inheritedLayout.metadata;
1058
+ }
1059
+
1060
+ export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
1061
+ export const Error = userLayout.Error ?? inheritedLayout.Error;
1062
+ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
1063
+
1064
+ ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
1065
+ ${layoutBinding} return ${layoutReturn};
1066
+ }
1067
+ `;
1068
+ await Bun.write(absPath, source);
1069
+ return absPath;
1070
+ }
1071
+ async function resolveSsrPageEntries(opts) {
1072
+ const absPageDir = path3.resolve(opts.appCwdPath, "page");
1073
+ const hasSt = await appHasStModule(opts.appCwdPath);
1074
+ const basePaths = opts.basePaths ?? [];
1075
+ const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
1076
+ const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
1077
+ const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
1078
+ const segments = getRootBoundarySegments(key);
1079
+ return segments !== null && isRootBoundarySegments(segments, basePaths);
1080
+ }));
1081
+ const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
1082
+ const userAbsPath = path3.resolve(absPageDir, key);
1083
+ if (OVERRIDES_KEY_RE.test(key)) {
1084
+ const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
1085
+ appCwdPath: opts.appCwdPath,
1086
+ key,
1087
+ userAbsPath
1088
+ });
1089
+ return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
1090
+ }
1091
+ return { key, moduleAbsPath: userAbsPath };
1092
+ }));
1093
+ const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
1094
+ key: implicitRootLayoutKey(boundary.segments),
1095
+ moduleAbsPath: await writeGeneratedRootLayoutFile({
1096
+ appCwdPath: opts.appCwdPath,
1097
+ appName: opts.appName,
1098
+ boundary,
1099
+ rootSourceAbsPath,
1100
+ includeStInit: hasSt && boundary.segments.length === 0,
1101
+ includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
1102
+ }),
1103
+ seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
1104
+ })));
1105
+ const entries = [...base, ...generated];
1106
+ entries.sort((a, b) => a.key.localeCompare(b.key));
1107
+ return entries;
1108
+ }
1109
+ async function resolveSsrPageEntriesForApp(app, pageKeys) {
1110
+ const config = await app.getConfig();
1111
+ return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
1112
+ }
634
1113
 
635
1114
  // pkgs/@akanjs/devkit/linter.ts
636
1115
  import { spawn } from "child_process";
637
1116
  import { existsSync, readFileSync } from "fs";
638
- import path3 from "path";
1117
+ import path4 from "path";
639
1118
  import chalk from "chalk";
1119
+ var parseBiomeReport = (output) => {
1120
+ for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
1121
+ const end = objectEndAt(output, start);
1122
+ if (end === -1)
1123
+ break;
1124
+ const report = asBiomeReport(output.slice(start, end + 1));
1125
+ if (report)
1126
+ return report;
1127
+ }
1128
+ throw new Error(output.trim() || "No Biome JSON output");
1129
+ };
1130
+ var objectEndAt = (output, start) => {
1131
+ let depth = 0;
1132
+ let inString = false;
1133
+ let escaped = false;
1134
+ for (let idx = start;idx < output.length; idx += 1) {
1135
+ const char = output[idx];
1136
+ if (inString) {
1137
+ if (escaped)
1138
+ escaped = false;
1139
+ else if (char === "\\")
1140
+ escaped = true;
1141
+ else if (char === '"')
1142
+ inString = false;
1143
+ continue;
1144
+ }
1145
+ if (char === '"')
1146
+ inString = true;
1147
+ else if (char === "{")
1148
+ depth += 1;
1149
+ else if (char === "}" && --depth === 0)
1150
+ return idx;
1151
+ }
1152
+ return -1;
1153
+ };
1154
+ var asBiomeReport = (candidate) => {
1155
+ try {
1156
+ const parsed = JSON.parse(candidate);
1157
+ if (!parsed || typeof parsed !== "object")
1158
+ return null;
1159
+ if (!("diagnostics" in parsed) && !("summary" in parsed))
1160
+ return null;
1161
+ return parsed;
1162
+ } catch {
1163
+ return null;
1164
+ }
1165
+ };
1166
+ var BIOME_CONFIG_FILES = ["biome.json", "biome.jsonc"];
640
1167
 
641
1168
  class Linter {
642
1169
  lintRoot;
1170
+ configPath;
643
1171
  #biomeBin;
644
1172
  constructor(cwdPath) {
645
1173
  this.lintRoot = this.#findBiomeRootPath(cwdPath);
646
- const localBiomeBin = path3.join(this.lintRoot, "node_modules/.bin/biome");
1174
+ this.configPath = Linter.#configPathIn(this.lintRoot) ?? path4.join(this.lintRoot, "biome.json");
1175
+ const localBiomeBin = path4.join(this.lintRoot, "node_modules/.bin/biome");
647
1176
  this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
648
1177
  }
1178
+ static #configPathIn(dir) {
1179
+ for (const fileName of BIOME_CONFIG_FILES) {
1180
+ const configPath = path4.join(dir, fileName);
1181
+ if (existsSync(configPath))
1182
+ return configPath;
1183
+ }
1184
+ return null;
1185
+ }
649
1186
  #findBiomeRootPath(dir) {
650
- const configPath = path3.join(dir, "biome.json");
651
- if (existsSync(configPath))
1187
+ if (Linter.#configPathIn(dir))
652
1188
  return dir;
653
- const parentDir = path3.dirname(dir);
1189
+ const parentDir = path4.dirname(dir);
654
1190
  if (parentDir === dir)
655
1191
  throw new Error(`biome.json not found from ${dir}`);
656
1192
  return this.#findBiomeRootPath(parentDir);
657
1193
  }
658
1194
  #toBiomePath(filePath) {
659
- const relativePath = path3.relative(this.lintRoot, filePath);
660
- if (!relativePath.startsWith("..") && !path3.isAbsolute(relativePath))
1195
+ const relativePath = path4.relative(this.lintRoot, filePath);
1196
+ if (!relativePath.startsWith("..") && !path4.isAbsolute(relativePath))
661
1197
  return relativePath;
662
1198
  return filePath;
663
1199
  }
664
1200
  #resolveFilePath(filePath) {
665
- return path3.isAbsolute(filePath) ? filePath : path3.join(this.lintRoot, filePath);
1201
+ return path4.isAbsolute(filePath) ? filePath : path4.join(this.lintRoot, filePath);
666
1202
  }
667
1203
  async#runBiome(args, input) {
668
1204
  return await new Promise((resolve, reject) => {
@@ -683,18 +1219,11 @@ class Linter {
683
1219
  proc.stdin.end(input);
684
1220
  });
685
1221
  }
686
- #parseBiomeReport(output) {
687
- const jsonStart = output.indexOf("{");
688
- const jsonEnd = output.lastIndexOf("}");
689
- if (jsonStart === -1 || jsonEnd === -1 || jsonEnd < jsonStart)
690
- throw new Error(output.trim() || "No Biome JSON output");
691
- return JSON.parse(output.slice(jsonStart, jsonEnd + 1));
692
- }
693
1222
  #diagnosticFilePath(diagnostic, fallbackFilePath) {
694
1223
  const diagnosticPath = diagnostic.location?.path;
695
1224
  if (!diagnosticPath)
696
1225
  return fallbackFilePath;
697
- return path3.isAbsolute(diagnosticPath) ? diagnosticPath : path3.join(this.lintRoot, diagnosticPath);
1226
+ return path4.isAbsolute(diagnosticPath) ? diagnosticPath : path4.join(this.lintRoot, diagnosticPath);
698
1227
  }
699
1228
  #createLintMessage(diagnostic) {
700
1229
  const start = diagnostic.location?.start;
@@ -759,10 +1288,10 @@ class Linter {
759
1288
  "--max-diagnostics=none",
760
1289
  "--no-errors-on-unmatched",
761
1290
  "--config-path",
762
- path3.join(this.lintRoot, "biome.json"),
1291
+ this.configPath,
763
1292
  this.#toBiomePath(filePath)
764
1293
  ]);
765
- const report = this.#parseBiomeReport(stdout || stderr);
1294
+ const report = parseBiomeReport(stdout || stderr);
766
1295
  const results = this.#toLintResults(report, filePath);
767
1296
  const { errors, warnings } = this.#splitMessages(results);
768
1297
  const output = write && existsSync(filePath) ? readFileSync(filePath, "utf8") : undefined;
@@ -880,22 +1409,34 @@ ${errorText}, ${warningText} found`];
880
1409
  if (!dryRun)
881
1410
  return await this.#checkFile(resolvedFilePath, { write: true });
882
1411
  const source = readFileSync(resolvedFilePath, "utf8");
883
- const { stdout } = await this.#runBiome([
1412
+ const { stdout } = await this.#runBiome(["check", "--write", "--config-path", this.configPath, "--stdin-file-path", this.#toBiomePath(resolvedFilePath)], source);
1413
+ const lintResult = await this.lintFile(resolvedFilePath);
1414
+ return { ...lintResult, fixed: stdout !== source, output: stdout };
1415
+ }
1416
+ async fixFiles(filePaths) {
1417
+ const resolved = filePaths.map((filePath) => this.#resolveFilePath(filePath)).filter(existsSync);
1418
+ if (resolved.length === 0)
1419
+ return { fixed: [] };
1420
+ const before = new Map(resolved.map((filePath) => [filePath, readFileSync(filePath, "utf8")]));
1421
+ await this.#runBiome([
884
1422
  "check",
885
1423
  "--write",
1424
+ "--reporter=json",
1425
+ "--max-diagnostics=none",
1426
+ "--no-errors-on-unmatched",
886
1427
  "--config-path",
887
- path3.join(this.lintRoot, "biome.json"),
888
- "--stdin-file-path",
889
- this.#toBiomePath(resolvedFilePath)
890
- ], source);
891
- const lintResult = await this.lintFile(resolvedFilePath);
892
- return { ...lintResult, fixed: stdout !== source, output: stdout };
1428
+ this.configPath,
1429
+ ...resolved.map((filePath) => this.#toBiomePath(filePath))
1430
+ ]);
1431
+ return {
1432
+ fixed: resolved.filter((filePath) => readFileSync(filePath, "utf8") !== before.get(filePath))
1433
+ };
893
1434
  }
894
1435
  async getConfigForFile(filePath) {
895
1436
  const resolvedFilePath = this.#resolveFilePath(filePath);
896
1437
  if (!existsSync(resolvedFilePath))
897
1438
  throw new Error(`File not found: ${filePath}`);
898
- return JSON.parse(readFileSync(path3.join(this.lintRoot, "biome.json"), "utf8"));
1439
+ return JSON.parse(readFileSync(this.configPath, "utf8"));
899
1440
  }
900
1441
  async getProblematicRules(filePath) {
901
1442
  const { results } = await this.lintFile(filePath);
@@ -910,9 +1451,34 @@ ${errorText}, ${warningText} found`];
910
1451
  }
911
1452
  }
912
1453
 
1454
+ // pkgs/@akanjs/devkit/repoIdentity.ts
1455
+ import { execFileSync } from "child_process";
1456
+ import path5 from "path";
1457
+ var resolved = new Map;
1458
+ var readRemoteName = (workspaceRoot) => {
1459
+ try {
1460
+ const url = execFileSync("git", ["config", "--get", "remote.origin.url"], {
1461
+ cwd: workspaceRoot,
1462
+ encoding: "utf-8",
1463
+ stdio: ["ignore", "pipe", "ignore"]
1464
+ }).trim();
1465
+ return url.replace(/\.git$/, "").split(/[/:]/).pop() || null;
1466
+ } catch {
1467
+ return null;
1468
+ }
1469
+ };
1470
+ var resolveRepoName = (workspaceRoot) => {
1471
+ const cached = resolved.get(workspaceRoot);
1472
+ if (cached)
1473
+ return cached;
1474
+ const repoName = readRemoteName(workspaceRoot) ?? path5.basename(workspaceRoot);
1475
+ resolved.set(workspaceRoot, repoName);
1476
+ return repoName;
1477
+ };
1478
+
913
1479
  // pkgs/@akanjs/devkit/scanInfo.ts
914
1480
  import { rm } from "fs/promises";
915
- import path4 from "path";
1481
+ import path6 from "path";
916
1482
 
917
1483
  // pkgs/@akanjs/devkit/workspaceLayout.ts
918
1484
  var appRootAllowedFiles = new Set([
@@ -946,6 +1512,34 @@ var appRootAllowedDirs = new Set([
946
1512
  "ui",
947
1513
  "webkit"
948
1514
  ]);
1515
+ var libRootAllowedFiles = new Set([
1516
+ "AGENTS.md",
1517
+ "CLAUDE.md",
1518
+ "README.md",
1519
+ "akan.config.ts",
1520
+ "akan.lib.json",
1521
+ "client.ts",
1522
+ "index.ts",
1523
+ "package.json",
1524
+ "server.ts",
1525
+ "tsconfig.json",
1526
+ "tsconfig.spec.json",
1527
+ "tsconfig.tsbuildinfo"
1528
+ ]);
1529
+ var libRootAllowedDirs = new Set([
1530
+ "common",
1531
+ "env",
1532
+ "lib",
1533
+ "page",
1534
+ "plugin",
1535
+ "private",
1536
+ "public",
1537
+ "srvkit",
1538
+ "ui",
1539
+ "webkit"
1540
+ ]);
1541
+ var rootAllowedFiles = { app: appRootAllowedFiles, lib: libRootAllowedFiles };
1542
+ var rootAllowedDirs = { app: appRootAllowedDirs, lib: libRootAllowedDirs };
949
1543
  var libFacetRootAllowedFiles = new Set([
950
1544
  "cnst.ts",
951
1545
  "db.ts",
@@ -957,7 +1551,9 @@ var libFacetRootAllowedFiles = new Set([
957
1551
  "useClient.ts",
958
1552
  "useServer.ts"
959
1553
  ]);
960
- var isScannedAppRootEntry = (name) => !name.startsWith(".") || appRootAllowedDirs.has(name);
1554
+ var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
1555
+ var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
1556
+ var isScannedRootEntry = (type, name) => !name.startsWith(".") || rootAllowedDirs[type].has(name);
961
1557
 
962
1558
  // pkgs/@akanjs/devkit/scanInfo.ts
963
1559
  var databaseFileTypes = [
@@ -986,18 +1582,16 @@ var moduleUiFileTypes = {
986
1582
  scalar: new Set(["Template", "Unit"])
987
1583
  };
988
1584
  var testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
989
- var rootSignalTestFilePattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
990
- var createDependencyScanner = async (exec) => (await import("./dependencyScanner-f6grrep3.js")).TypeScriptDependencyScanner.from(exec);
1585
+ var createDependencyScanner = async (exec) => (await import("./dependencyScanner-c64m6fxp.js")).TypeScriptDependencyScanner.from(exec);
991
1586
  var isAllowedTestFile = (filename) => testFilePattern.test(filename);
992
- var isAllowedLibRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || rootSignalTestFilePattern.test(filename);
993
- var getScanPath = (exec, relativePath) => path4.posix.join(`${exec.type}s`, exec.name, relativePath.split(path4.sep).join("/"));
1587
+ var getScanPath = (exec, relativePath) => path6.posix.join(`${exec.type}s`, exec.name, relativePath.split(path6.sep).join("/"));
994
1588
  async function clearGeneratedRootCapacitorConfigs(exec) {
995
1589
  if (exec.type !== "app")
996
1590
  return;
997
1591
  await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm(exec.getPath(filename), { force: true })));
998
1592
  }
999
1593
  var getModuleNameFromPath = (kind, modulePath) => {
1000
- const dirname = path4.basename(modulePath);
1594
+ const dirname = path6.basename(modulePath);
1001
1595
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
1002
1596
  };
1003
1597
  async function assertScanConvention(exec, libRoot) {
@@ -1006,28 +1600,31 @@ async function assertScanConvention(exec, libRoot) {
1006
1600
  const addViolation = (relativePath, reason) => {
1007
1601
  violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
1008
1602
  };
1009
- if (exec.type === "app") {
1010
- const { files, dirs } = await exec.getFilesAndDirs(".");
1011
- files.filter((filename) => !appRootAllowedFiles.has(filename)).forEach((filename) => {
1012
- addViolation(filename, "unsupported app root file");
1013
- });
1014
- dirs.filter((dirname) => !appRootAllowedDirs.has(dirname)).forEach((dirname) => {
1015
- addViolation(dirname, "unsupported app root folder");
1016
- });
1017
- }
1018
- libRoot.files.filter((filename) => !isAllowedLibRootFile(filename)).forEach((filename) => {
1019
- addViolation(path4.join("lib", filename), "unsupported lib root file");
1603
+ const allowedRootFiles = rootAllowedFiles[exec.type];
1604
+ const allowedRootDirs = rootAllowedDirs[exec.type];
1605
+ const { files, dirs } = await exec.getFilesAndDirs(".");
1606
+ files.filter((filename) => !allowedRootFiles.has(filename)).forEach((filename) => {
1607
+ addViolation(filename, `unsupported ${exec.type} root file`);
1608
+ });
1609
+ dirs.filter((dirname) => !allowedRootDirs.has(dirname)).forEach((dirname) => {
1610
+ addViolation(dirname, `unsupported ${exec.type} root folder`);
1611
+ });
1612
+ if (exec.type === "lib")
1613
+ for (const { relativePath, reason } of await exec.getPageConventionViolations())
1614
+ addViolation(relativePath, reason);
1615
+ libRoot.files.filter((filename) => !isAllowedLibFacetRootFile(filename)).forEach((filename) => {
1616
+ addViolation(path6.join("lib", filename), "unsupported lib facet root file");
1020
1617
  });
1021
1618
  libRoot.dirs.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname)).forEach((dirname) => {
1022
- addViolation(path4.join("lib", dirname), "unsupported internal lib folder");
1619
+ addViolation(path6.join("lib", dirname), "unsupported internal lib folder");
1023
1620
  });
1024
1621
  const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
1025
1622
  const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
1026
1623
  const scalarDirs = await exec.readdir("lib/__scalar");
1027
1624
  await Promise.all([
1028
- ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path4.join("lib", dirname))),
1029
- ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path4.join("lib", dirname))),
1030
- ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path4.join("lib/__scalar", dirname)))
1625
+ ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path6.join("lib", dirname))),
1626
+ ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path6.join("lib", dirname))),
1627
+ ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path6.join("lib/__scalar", dirname)))
1031
1628
  ]);
1032
1629
  if (violations.length > 0) {
1033
1630
  throw new Error(`[scan-convention]
@@ -1039,11 +1636,11 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
1039
1636
  const { files, dirs } = await exec.getFilesAndDirs(modulePath);
1040
1637
  const moduleName = getModuleNameFromPath(kind, modulePath);
1041
1638
  dirs.forEach((dirname) => {
1042
- violations.push(`${getScanPath(exec, path4.join(modulePath, dirname))}: unsupported module folder`);
1639
+ violations.push(`${getScanPath(exec, path6.join(modulePath, dirname))}: unsupported module folder`);
1043
1640
  });
1044
1641
  const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
1045
1642
  files.forEach((filename) => {
1046
- const filePath = path4.join(modulePath, filename);
1643
+ const filePath = path6.join(modulePath, filename);
1047
1644
  if (filename === "index.ts" || filename === "index.tsx" || isAllowedTestFile(filename))
1048
1645
  return;
1049
1646
  if (filename === `${moduleName}.abstract.md`)
@@ -1087,7 +1684,7 @@ class ScanInfo {
1087
1684
  { all: new Set, databases: new Set, services: new Set, scalars: new Set }
1088
1685
  ]));
1089
1686
  static async getScanResult(exec) {
1090
- const [akanConfig2, scanner, pkgs, libs] = await Promise.all([
1687
+ const [akanConfig, scanner, pkgs, libs] = await Promise.all([
1091
1688
  exec.getConfig(),
1092
1689
  createDependencyScanner(exec),
1093
1690
  exec.workspace.getPkgs(),
@@ -1123,7 +1720,7 @@ class ScanInfo {
1123
1720
  });
1124
1721
  await Promise.all([
1125
1722
  ...databaseDirs.map(async (name) => {
1126
- const filenames = await exec.readdir(path4.join("lib", name));
1723
+ const filenames = await exec.readdir(path6.join("lib", name));
1127
1724
  filenames.forEach((filename) => {
1128
1725
  if (filename.endsWith(".constant.ts"))
1129
1726
  files.constant.databases.push(name);
@@ -1151,7 +1748,7 @@ class ScanInfo {
1151
1748
  }),
1152
1749
  ...serviceDirs.map(async (dirname) => {
1153
1750
  const name = dirname.slice(1);
1154
- const filenames = await exec.readdir(path4.join("lib", dirname));
1751
+ const filenames = await exec.readdir(path6.join("lib", dirname));
1155
1752
  filenames.forEach((filename) => {
1156
1753
  if (filename.endsWith(".dictionary.ts"))
1157
1754
  files.dictionary.services.push(name);
@@ -1174,7 +1771,7 @@ class ScanInfo {
1174
1771
  });
1175
1772
  }),
1176
1773
  ...scalarDirs.map(async (name) => {
1177
- const filenames = await exec.readdir(path4.join("lib/__scalar", name));
1774
+ const filenames = await exec.readdir(path6.join("lib/__scalar", name));
1178
1775
  filenames.forEach((filename) => {
1179
1776
  if (filename.endsWith(".constant.ts"))
1180
1777
  files.constant.scalars.push(name);
@@ -1196,12 +1793,11 @@ class ScanInfo {
1196
1793
  })
1197
1794
  ]);
1198
1795
  const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
1199
- const scanResult = {
1796
+ const common = {
1200
1797
  name: exec.name,
1201
1798
  type: exec.type,
1202
1799
  repoName: exec.workspace.repoName,
1203
- serveDomain: WorkspaceExecutor.getBaseDevEnv(path4.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
1204
- akanConfig: akanConfig2,
1800
+ serveDomain: WorkspaceExecutor.getBaseDevEnv(path6.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
1205
1801
  files,
1206
1802
  libDeps,
1207
1803
  pkgDeps,
@@ -1209,6 +1805,7 @@ class ScanInfo {
1209
1805
  devDependencies: npmDevDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
1210
1806
  routes
1211
1807
  };
1808
+ const scanResult = akanConfig instanceof AkanAppConfig ? { ...common, akanConfig } : { ...common, akanConfig };
1212
1809
  return scanResult;
1213
1810
  }
1214
1811
  constructor(scanResult) {
@@ -1402,16 +1999,16 @@ class LibInfo extends ScanInfo {
1402
1999
  class PkgInfo {
1403
2000
  exec;
1404
2001
  name;
1405
- scanResult;
2002
+ #scanResult;
1406
2003
  static async scanExecutor(exec) {
1407
2004
  const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
1408
2005
  const scanner = await createDependencyScanner(exec);
1409
2006
  const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
1410
- const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path5) => tsconfig.compilerOptions.paths?.[path5]?.some((resolve) => resolve.startsWith("pkgs/"))).map((path5) => path5.replace("/*", "")));
2007
+ const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path) => tsconfig.compilerOptions.paths?.[path]?.some((resolve) => resolve.startsWith("pkgs/"))).map((path) => path.replace("/*", "")));
1411
2008
  const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
1412
- const pkgDeps = [...pkgPathDepSet].map((path5) => {
1413
- const pathSplitLength = path5.split("/").length;
1414
- return (tsconfig.compilerOptions.paths?.[path5]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
2009
+ const pkgDeps = [...pkgPathDepSet].map((path) => {
2010
+ const pathSplitLength = path.split("/").length;
2011
+ return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
1415
2012
  }).filter((pkg) => pkg !== exec.name);
1416
2013
  const pkgScanResult = {
1417
2014
  name: exec.name,
@@ -1433,10 +2030,10 @@ class PkgInfo {
1433
2030
  constructor(exec, scanResult) {
1434
2031
  this.exec = exec;
1435
2032
  this.name = exec.name;
1436
- this.scanResult = scanResult;
2033
+ this.#scanResult = scanResult;
1437
2034
  }
1438
2035
  getScanResult() {
1439
- return this.scanResult;
2036
+ return this.#scanResult;
1440
2037
  }
1441
2038
  }
1442
2039
 
@@ -1634,7 +2231,7 @@ class Executor {
1634
2231
  }));
1635
2232
  });
1636
2233
  proc.on("exit", (code, signal) => {
1637
- if (!!code || signal)
2234
+ if (code || signal)
1638
2235
  reject(new CommandExecutionError({
1639
2236
  command,
1640
2237
  cwd,
@@ -1732,7 +2329,7 @@ class Executor {
1732
2329
  }));
1733
2330
  });
1734
2331
  proc.on("exit", (code, signal) => {
1735
- if (!!code || signal)
2332
+ if (code || signal)
1736
2333
  reject(new CommandExecutionError({
1737
2334
  command: modulePath,
1738
2335
  args,
@@ -1748,10 +2345,10 @@ class Executor {
1748
2345
  });
1749
2346
  }
1750
2347
  getPath(filePath) {
1751
- if (path5.isAbsolute(filePath))
2348
+ if (path7.isAbsolute(filePath))
1752
2349
  return filePath;
1753
2350
  if (filePath.startsWith("."))
1754
- return path5.join(this.cwdPath, filePath);
2351
+ return path7.join(this.cwdPath, filePath);
1755
2352
  const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
1756
2353
  const targetParts = filePath.split(/[\\/]/).filter(Boolean);
1757
2354
  let overlapLength = 0;
@@ -1765,12 +2362,12 @@ class Executor {
1765
2362
  if (isOverlap)
1766
2363
  overlapLength = i;
1767
2364
  }
1768
- return path5.join(this.cwdPath, ...targetParts.slice(overlapLength));
2365
+ return path7.join(this.cwdPath, ...targetParts.slice(overlapLength));
1769
2366
  }
1770
2367
  async mkdir(dirPath) {
1771
2368
  const writePath = this.getPath(dirPath);
1772
2369
  if (!await FileSys.dirExists(writePath))
1773
- await mkdir(writePath, { recursive: true });
2370
+ await mkdir2(writePath, { recursive: true });
1774
2371
  this.logger.verbose(`Make directory ${writePath}`);
1775
2372
  return this;
1776
2373
  }
@@ -1808,7 +2405,7 @@ class Executor {
1808
2405
  return this;
1809
2406
  }
1810
2407
  async removeDir(dirPath) {
1811
- const readPath = path5.join(this.getPath(dirPath), ".");
2408
+ const readPath = path7.join(this.getPath(dirPath), ".");
1812
2409
  if (await FileSys.entryExists(readPath))
1813
2410
  await rm2(readPath, { recursive: true, force: true });
1814
2411
  this.logger.verbose(`Remove directory ${readPath}`);
@@ -1816,10 +2413,11 @@ class Executor {
1816
2413
  }
1817
2414
  async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
1818
2415
  const writePath = this.getPath(filePath);
1819
- const dir = path5.dirname(writePath);
2416
+ const dir = path7.dirname(writePath);
1820
2417
  if (!await FileSys.dirExists(dir))
1821
- await mkdir(dir, { recursive: true });
1822
- let contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2);
2418
+ await mkdir2(dir, { recursive: true });
2419
+ let contentStr = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}
2420
+ `;
1823
2421
  if (await FileSys.fileExists(writePath)) {
1824
2422
  const currentContent = await FileSys.readText(writePath);
1825
2423
  if (currentContent === contentStr || !overwrite) {
@@ -1841,7 +2439,7 @@ class Executor {
1841
2439
  await this.writeFile(filePath, content);
1842
2440
  }
1843
2441
  async getLocalFile(targetPath) {
1844
- const filePath = path5.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
2442
+ const filePath = path7.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
1845
2443
  const content = await this.readFile(filePath);
1846
2444
  return { filePath, content };
1847
2445
  }
@@ -1860,7 +2458,7 @@ class Executor {
1860
2458
  return;
1861
2459
  const isDirectory = (await stat(src)).isDirectory();
1862
2460
  if (!await FileSys.exists(dest) && isDirectory)
1863
- await mkdir(dest, { recursive: true });
2461
+ await mkdir2(dest, { recursive: true });
1864
2462
  if (dereference)
1865
2463
  await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
1866
2464
  else
@@ -1938,26 +2536,26 @@ class Executor {
1938
2536
  const result = await getContent.default(scanInfo ?? null, dict, options);
1939
2537
  if (result === null)
1940
2538
  return null;
1941
- const filename = typeof result === "object" ? result.filename : path5.basename(targetPath).replace(".js", ".ts");
2539
+ const filename = typeof result === "object" ? result.filename : path7.basename(targetPath).replace(".js", ".ts");
1942
2540
  const content = typeof result === "object" ? result.content : result;
1943
- const dirname = path5.dirname(targetPath);
1944
- const convertedTargetPath = Object.entries(dict).reduce((path6, [key, value]) => path6.replace(new RegExp(`__${key}__`, "g"), value), `${dirname}/${filename}`);
2541
+ const dirname = path7.dirname(targetPath);
2542
+ const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), `${dirname}/${filename}`);
1945
2543
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
1946
2544
  return this.writeFile(convertedTargetPath, content, { overwrite });
1947
2545
  } else if (targetPath.endsWith(".template")) {
1948
2546
  const content = await FileSys.readText(templatePath);
1949
- const convertedTargetPath = Object.entries(dict).reduce((path6, [key, value]) => path6.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
2547
+ const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
1950
2548
  const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
1951
2549
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
1952
2550
  return this.writeFile(convertedTargetPath, convertedContent, {
1953
2551
  overwrite
1954
2552
  });
1955
- } else if (staticTemplateFileExtensions.has(path5.extname(targetPath).toLowerCase())) {
1956
- const convertedTargetPath = Object.entries(dict).reduce((path6, [key, value]) => path6.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
2553
+ } else if (staticTemplateFileExtensions.has(path7.extname(targetPath).toLowerCase())) {
2554
+ const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
1957
2555
  const writePath = this.getPath(convertedTargetPath);
1958
- const dirname = path5.dirname(writePath);
2556
+ const dirname = path7.dirname(writePath);
1959
2557
  if (!await FileSys.dirExists(dirname))
1960
- await mkdir(dirname, { recursive: true });
2558
+ await mkdir2(dirname, { recursive: true });
1961
2559
  await copyFile(templatePath, writePath);
1962
2560
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
1963
2561
  return { filePath: writePath, content: "" };
@@ -1976,10 +2574,10 @@ class Executor {
1976
2574
  const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
1977
2575
  const prefixTemplatePath = templatePath;
1978
2576
  if ((await stat(prefixTemplatePath)).isFile()) {
1979
- const filename = path5.basename(prefixTemplatePath);
2577
+ const filename = path7.basename(prefixTemplatePath);
1980
2578
  const fileContent = await this.#applyTemplateFile({
1981
2579
  templatePath: prefixTemplatePath,
1982
- targetPath: path5.join(basePath, filename),
2580
+ targetPath: path7.join(basePath, filename),
1983
2581
  scanInfo,
1984
2582
  overwrite
1985
2583
  }, dict, options);
@@ -1987,19 +2585,19 @@ class Executor {
1987
2585
  } else {
1988
2586
  const subdirs = await readDirEntries(templatePath);
1989
2587
  const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
1990
- const subpath = path5.join(templatePath, subdir);
2588
+ const subpath = path7.join(templatePath, subdir);
1991
2589
  if ((await stat(subpath)).isFile()) {
1992
2590
  const fileContent = await this.#applyTemplateFile({
1993
2591
  templatePath: subpath,
1994
- targetPath: path5.join(basePath, subdir),
2592
+ targetPath: path7.join(basePath, subdir),
1995
2593
  scanInfo,
1996
2594
  overwrite
1997
2595
  }, dict, options);
1998
2596
  return fileContent ? [fileContent] : [];
1999
2597
  } else
2000
2598
  return await this._applyTemplate({
2001
- basePath: path5.join(basePath, subdir),
2002
- template: path5.join(template, subdir),
2599
+ basePath: path7.join(basePath, subdir),
2600
+ template: path7.join(template, subdir),
2003
2601
  scanInfo,
2004
2602
  dict,
2005
2603
  overwrite,
@@ -2011,8 +2609,8 @@ class Executor {
2011
2609
  }
2012
2610
  async#resolveTemplateRoot() {
2013
2611
  const candidates = [
2014
- path5.resolve(getDirname(import.meta.url), "templates"),
2015
- path5.resolve(getDirname(import.meta.url), "../cli/templates")
2612
+ path7.resolve(getDirname2(import.meta.url), "templates"),
2613
+ path7.resolve(getDirname2(import.meta.url), "../cli/templates")
2016
2614
  ];
2017
2615
  for (const candidate of candidates) {
2018
2616
  if (await FileSys.dirExists(candidate))
@@ -2025,29 +2623,43 @@ class Executor {
2025
2623
  ...options.dict ?? {},
2026
2624
  ...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
2027
2625
  };
2028
- return this._applyTemplate({ ...options, dict });
2626
+ const fileContents = await this._applyTemplate({ ...options, dict });
2627
+ await this.#formatAppliedTemplate(fileContents);
2628
+ return fileContents;
2629
+ }
2630
+ async#formatAppliedTemplate(fileContents) {
2631
+ const filePaths = fileContents.map((fileContent) => fileContent.filePath).filter((filePath) => filePath.endsWith(".ts") || filePath.endsWith(".tsx"));
2632
+ if (filePaths.length === 0)
2633
+ return;
2634
+ try {
2635
+ const { fixed } = await this.getLinter().fixFiles(filePaths);
2636
+ if (fixed.length > 0)
2637
+ this.logger.verbose(`Formatted ${fixed.length} scaffolded file(s)`);
2638
+ } catch (err) {
2639
+ this.logger.verbose(`Skipped formatting scaffolded files: ${err instanceof Error ? err.message : String(err)}`);
2640
+ }
2029
2641
  }
2030
2642
  async getTypeChecker() {
2031
- const { TypeChecker } = await import("./typeChecker-kravn7ns.js");
2643
+ const { TypeChecker } = await import("./typeChecker-3xgrzc2v.js");
2032
2644
  this.typeChecker ??= new TypeChecker(this);
2033
2645
  return this.typeChecker;
2034
2646
  }
2035
2647
  async typeCheck(filePath) {
2036
- const path6 = this.getPath(filePath);
2648
+ const path = this.getPath(filePath);
2037
2649
  const typeChecker = await this.getTypeChecker();
2038
- const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path6);
2650
+ const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path);
2039
2651
  const message = typeChecker.formatDiagnostics(fileDiagnostics);
2040
2652
  return { fileDiagnostics, fileErrors, fileWarnings, message };
2041
2653
  }
2042
2654
  async typeCheckAsync(filePath) {
2043
- const path6 = this.getPath(filePath);
2655
+ const path = this.getPath(filePath);
2044
2656
  const entry = await this.#resolveTypecheckWorkerEntry();
2045
2657
  const proc = Bun.spawn([process.execPath, entry], {
2046
2658
  cwd: this.cwdPath,
2047
2659
  env: {
2048
2660
  ...process.env,
2049
2661
  AKAN_TYPECHECK_CWD: this.cwdPath,
2050
- AKAN_TYPECHECK_FILE: path6
2662
+ AKAN_TYPECHECK_FILE: path
2051
2663
  },
2052
2664
  stdout: "pipe",
2053
2665
  stderr: "pipe"
@@ -2068,13 +2680,13 @@ class Executor {
2068
2680
  };
2069
2681
  }
2070
2682
  async#resolveTypecheckWorkerEntry() {
2071
- const dirname = getDirname(import.meta.url);
2683
+ const dirname = getDirname2(import.meta.url);
2072
2684
  const candidates = [
2073
- path5.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2074
- path5.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2075
- path5.join(dirname, "typecheck/typecheck.proc.ts"),
2076
- path5.join(dirname, "typecheck.proc.js"),
2077
- path5.join(dirname, "typecheck.proc.ts")
2685
+ path7.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2686
+ path7.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2687
+ path7.join(dirname, "typecheck/typecheck.proc.ts"),
2688
+ path7.join(dirname, "typecheck.proc.js"),
2689
+ path7.join(dirname, "typecheck.proc.ts")
2078
2690
  ];
2079
2691
  for (const candidate of candidates)
2080
2692
  if (await Bun.file(candidate).exists())
@@ -2086,9 +2698,9 @@ class Executor {
2086
2698
  return this.linter;
2087
2699
  }
2088
2700
  async lint(filePath, { fix = false, dryRun = false } = {}) {
2089
- const path6 = this.getPath(filePath);
2701
+ const path = this.getPath(filePath);
2090
2702
  const linter = this.getLinter();
2091
- const { results, errors, warnings } = await linter.lint(path6, {
2703
+ const { results, errors, warnings } = await linter.lint(path, {
2092
2704
  fix,
2093
2705
  dryRun
2094
2706
  });
@@ -2109,7 +2721,7 @@ class WorkspaceExecutor extends Executor {
2109
2721
  static #execs = new Map;
2110
2722
  static fromRoot({
2111
2723
  workspaceRoot = process.cwd(),
2112
- repoName = path5.basename(process.cwd())
2724
+ repoName = resolveRepoName(workspaceRoot)
2113
2725
  } = {}) {
2114
2726
  return WorkspaceExecutor.#execs.get(repoName) ?? new WorkspaceExecutor({ workspaceRoot, repoName });
2115
2727
  }
@@ -2201,7 +2813,7 @@ class WorkspaceExecutor extends Executor {
2201
2813
  await Promise.all(dirs.map(async (dir) => {
2202
2814
  if (dir.includes("_") || AVOID_DIRS.includes(dir))
2203
2815
  return;
2204
- const dirPath = path5.join(dirname, dir);
2816
+ const dirPath = path7.join(dirname, dir);
2205
2817
  if ((await stat(dirPath)).isDirectory()) {
2206
2818
  results.push(`${prefix}${dir}`);
2207
2819
  if (maxDepth > 0)
@@ -2214,10 +2826,20 @@ class WorkspaceExecutor extends Executor {
2214
2826
  }
2215
2827
  async commit(message, { init = false, add = true } = {}) {
2216
2828
  if (init)
2217
- await this.exec(`git init --quiet`);
2829
+ await this.spawn("git", ["init", "--quiet"]);
2218
2830
  if (add)
2219
- await this.exec(`git add .`);
2220
- await this.exec(`git commit --quiet -m "${message}"`);
2831
+ await this.spawn("git", ["add", "."]);
2832
+ await this.spawn("git", ["commit", "--quiet", "-m", message]);
2833
+ }
2834
+ async hasChanges() {
2835
+ return !!(await this.spawn("git", ["status", "--porcelain"])).trim();
2836
+ }
2837
+ async listGitFiles(paths, { untracked = false } = {}) {
2838
+ if (!paths.length)
2839
+ return [];
2840
+ const mode = untracked ? ["--cached", "--others", "--exclude-standard"] : ["--cached"];
2841
+ const stdout = await this.spawn("git", ["ls-files", "-z", ...mode, "--", ...paths]);
2842
+ return stdout.split("\x00").filter((file) => !!file).sort();
2221
2843
  }
2222
2844
  async#getDirHasFile(basePath, targetFilename) {
2223
2845
  const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
@@ -2226,9 +2848,9 @@ class WorkspaceExecutor extends Executor {
2226
2848
  await Promise.all(dirs.map(async (dir) => {
2227
2849
  if (AVOID_DIRS.includes(dir))
2228
2850
  return;
2229
- const dirPath = path5.join(dirname, dir);
2851
+ const dirPath = path7.join(dirname, dir);
2230
2852
  if (await FileSys.dirExists(dirPath)) {
2231
- const hasTargetFile = await FileSys.fileExists(path5.join(dirPath, targetFilename));
2853
+ const hasTargetFile = await FileSys.fileExists(path7.join(dirPath, targetFilename));
2232
2854
  if (hasTargetFile)
2233
2855
  results.push(`${prefix}${dir}`);
2234
2856
  if (maxDepth > 0)
@@ -2294,8 +2916,20 @@ class SysExecutor extends Executor {
2294
2916
  return this.#akanConfig;
2295
2917
  }
2296
2918
  async getModules() {
2297
- const path6 = this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
2298
- return await this.workspace.getDirInModule(path6, this.name);
2919
+ const path = this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
2920
+ return await this.workspace.getDirInModule(path, this.name);
2921
+ }
2922
+ async getPageConventionViolations() {
2923
+ if (!await this.exists("page"))
2924
+ return [];
2925
+ const violations = [];
2926
+ for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
2927
+ const relativePath = entry.split(path7.sep).join("/");
2928
+ const reason = getPageSourceFileViolation(relativePath);
2929
+ if (reason)
2930
+ violations.push({ relativePath: `page/${relativePath}`, reason });
2931
+ }
2932
+ return violations;
2299
2933
  }
2300
2934
  #scanInfo = null;
2301
2935
  hasScanInfo() {
@@ -2399,7 +3033,7 @@ class SysExecutor extends Executor {
2399
3033
  await this.setPackageJson(libPkgJsonWithDeps);
2400
3034
  }
2401
3035
  async getLocalFile(targetPath) {
2402
- const filePath = path5.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
3036
+ const filePath = path7.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
2403
3037
  const content = await this.workspace.readFile(filePath);
2404
3038
  return { filePath, content };
2405
3039
  }
@@ -2485,7 +3119,7 @@ class SysExecutor extends Executor {
2485
3119
  class AppExecutor extends SysExecutor {
2486
3120
  static #routeSourceValidator = null;
2487
3121
  static async#getRouteSourceValidator() {
2488
- AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-e4jmbfcx.js")).RouteSourceValidator;
3122
+ AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-ze6x894c.js")).RouteSourceValidator;
2489
3123
  return AppExecutor.#routeSourceValidator;
2490
3124
  }
2491
3125
  dist;
@@ -2496,9 +3130,9 @@ class AppExecutor extends SysExecutor {
2496
3130
  }
2497
3131
  static #execs = new Map;
2498
3132
  static from(executor, name) {
2499
- const exec2 = AppExecutor.#execs.get(name);
2500
- if (exec2)
2501
- return exec2;
3133
+ const exec = AppExecutor.#execs.get(name);
3134
+ if (exec)
3135
+ return exec;
2502
3136
  else if (executor instanceof WorkspaceExecutor)
2503
3137
  return new AppExecutor({ workspace: executor, name });
2504
3138
  else
@@ -2534,25 +3168,29 @@ class AppExecutor extends SysExecutor {
2534
3168
  };
2535
3169
  }
2536
3170
  async prepareCommand(type) {
2537
- const akanConfig2 = await this.getConfig();
2538
- const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig2.defaultDatabaseMode ?? "single";
3171
+ const akanConfig = await this.getConfig();
3172
+ const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
2539
3173
  const routeEnv = {
2540
- AKAN_PUBLIC_BASE_PATHS: [...akanConfig2.basePaths].join(","),
3174
+ AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
2541
3175
  AKAN_DATABASE_MODE: databaseMode
2542
3176
  };
2543
3177
  Object.assign(process.env, routeEnv);
2544
3178
  if (type === "build") {
2545
3179
  this.#excludeDevOnlyPages = true;
2546
3180
  this.#pageKeys = null;
2547
- if (await this.exists(this.dist.cwdPath))
2548
- await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
3181
+ await this.dist.removeDir(this.dist.cwdPath);
2549
3182
  await Promise.all([this.dist.mkdir("private"), this.dist.mkdir("public")]);
2550
3183
  await Promise.all([
2551
3184
  this.cp("private", `${this.dist.cwdPath}/private`, { dereference: true }),
2552
- this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })
3185
+ ...akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []
2553
3186
  ]);
2554
- } else
3187
+ } else {
2555
3188
  await this.removeDir(".akan");
3189
+ if (!akanConfig.web.ssr || !akanConfig.web.csr)
3190
+ this.logger.verbose(`akan.config.ts disables web.${!akanConfig.web.ssr ? "ssr" : "csr"}; \`akan start\` still serves it, \`akan build\` will not`);
3191
+ }
3192
+ if (type === "start")
3193
+ process.env.NODE_ENV = "development";
2556
3194
  const devPort = type === "start" ? (await this.getDevPort()).toString() : undefined;
2557
3195
  const env = this.getCommandEnv({
2558
3196
  AKAN_COMMAND_TYPE: type,
@@ -2600,7 +3238,7 @@ class AppExecutor extends SysExecutor {
2600
3238
  async getPageKeys({ refresh } = {}) {
2601
3239
  if (this.#pageKeys && !refresh)
2602
3240
  return this.#pageKeys;
2603
- const akanConfig2 = await this.getConfig();
3241
+ const akanConfig = await this.getConfig();
2604
3242
  const glob = new Bun.Glob("**/*");
2605
3243
  const pageKeys = [];
2606
3244
  const owners = new Map;
@@ -2614,16 +3252,16 @@ class AppExecutor extends SysExecutor {
2614
3252
  absolute: false,
2615
3253
  onlyFiles: true
2616
3254
  })) {
2617
- const segments = rel.split(path5.sep);
3255
+ const segments = rel.split(path7.sep);
2618
3256
  if (segments.some((s) => s === "node_modules"))
2619
3257
  continue;
2620
3258
  const posix = `${root.keyPrefix}${segments.join("/")}`;
2621
- const absPath = path5.join(root.dir, ...segments);
3259
+ const absPath = path7.join(root.dir, ...segments);
2622
3260
  validatePageSourceFile(posix, { filePath: absPath });
2623
3261
  if (!isRouteSourceFile(posix))
2624
3262
  continue;
2625
3263
  const key = `./${posix}`;
2626
- validateSubRoutePageKey(key, akanConfig2.basePaths, {
3264
+ validateSubRoutePageKey(key, akanConfig.basePaths, {
2627
3265
  appName: this.name,
2628
3266
  filePath: absPath
2629
3267
  });
@@ -2641,7 +3279,8 @@ class AppExecutor extends SysExecutor {
2641
3279
  }
2642
3280
  if (!owner)
2643
3281
  owners.set(routeId, { absPath, fromLib });
2644
- const isRootLayout = parsed.kind === "layout" && parsed.moduleSegments.at(-1) === "_layout";
3282
+ const layoutSegments = getRootBoundarySegments(key);
3283
+ const isRootLayout = layoutSegments !== null && isRootBoundarySegments(layoutSegments, akanConfig.basePaths);
2645
3284
  const routeSource = await Bun.file(absPath).text();
2646
3285
  const validator = await AppExecutor.#getRouteSourceValidator();
2647
3286
  if (parsed.kind === "overrides")
@@ -2672,10 +3311,10 @@ class AppExecutor extends SysExecutor {
2672
3311
  return pageKeys.filter((key) => !isDevOnly(key));
2673
3312
  }
2674
3313
  async getPageRoots() {
2675
- const akanConfig2 = await this.getConfig();
3314
+ const akanConfig = await this.getConfig();
2676
3315
  const pageDir = `${this.cwdPath}/page`;
2677
3316
  const roots = [{ dir: pageDir, realDir: pageDir, keyPrefix: "" }];
2678
- for (const parent of AppExecutor.#pageLibParents(akanConfig2.basePaths)) {
3317
+ for (const parent of AppExecutor.#pageLibParents(akanConfig.basePaths)) {
2679
3318
  const libsDir = `${pageDir}/${parent}${AppExecutor.#pageLibsDir}`;
2680
3319
  const entries = await readDirEntries(libsDir).catch(() => []);
2681
3320
  for (const entry of entries.sort()) {
@@ -2696,9 +3335,9 @@ class AppExecutor extends SysExecutor {
2696
3335
  return parents.length ? parents : [""];
2697
3336
  }
2698
3337
  async syncPages(libDeps) {
2699
- const akanConfig2 = await this.getConfig();
2700
- const parents = AppExecutor.#pageLibParents(akanConfig2.basePaths);
2701
- const libs = await this.#resolvePageLibs(akanConfig2.syncPageLibs, libDeps);
3338
+ const akanConfig = await this.getConfig();
3339
+ const parents = AppExecutor.#pageLibParents(akanConfig.basePaths);
3340
+ const libs = await this.#resolvePageLibs(akanConfig.syncPageLibs, libDeps);
2702
3341
  const linked = (await Promise.all(parents.map(async (parent) => {
2703
3342
  const libsDir = `${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`;
2704
3343
  const entries = await readDirEntries(libsDir).catch(() => []);
@@ -2756,12 +3395,12 @@ class AppExecutor extends SysExecutor {
2756
3395
  static async#linkLibAsset(targetPath, linkPath) {
2757
3396
  const isWindows = process.platform === "win32";
2758
3397
  try {
2759
- const target = isWindows ? targetPath : path5.relative(path5.dirname(linkPath), targetPath);
3398
+ const target = isWindows ? targetPath : path7.relative(path7.dirname(linkPath), targetPath);
2760
3399
  await symlink(target, linkPath, isWindows ? "junction" : "dir");
2761
3400
  } catch (error) {
2762
3401
  if (!isWindows)
2763
3402
  throw error;
2764
- await mkdir(linkPath, { recursive: true });
3403
+ await mkdir2(linkPath, { recursive: true });
2765
3404
  await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
2766
3405
  }
2767
3406
  }
@@ -2798,7 +3437,7 @@ class AppExecutor extends SysExecutor {
2798
3437
  await this.writeFile(rel, content, opts);
2799
3438
  },
2800
3439
  readEnvClient: async () => {
2801
- const envClientPath = path5.join(this.cwdPath, "env", "env.client.ts");
3440
+ const envClientPath = path7.join(this.cwdPath, "env", "env.client.ts");
2802
3441
  if (!await FileSys.fileExists(envClientPath))
2803
3442
  return null;
2804
3443
  try {
@@ -2857,9 +3496,9 @@ class LibExecutor extends SysExecutor {
2857
3496
  }
2858
3497
  static #execs = new Map;
2859
3498
  static from(executor, name) {
2860
- const exec2 = LibExecutor.#execs.get(name);
2861
- if (exec2)
2862
- return exec2;
3499
+ const exec = LibExecutor.#execs.get(name);
3500
+ if (exec)
3501
+ return exec;
2863
3502
  else if (executor instanceof WorkspaceExecutor)
2864
3503
  return new LibExecutor({ workspace: executor, name });
2865
3504
  else
@@ -2869,7 +3508,7 @@ class LibExecutor extends SysExecutor {
2869
3508
  async getConfig({ refresh } = {}) {
2870
3509
  if (this.#akanConfig && !refresh)
2871
3510
  return this.#akanConfig;
2872
- this.#akanConfig = await AkanLibConfig.from(this);
3511
+ this.#akanConfig = await AkanLibConfig.from(this, { bustImportCache: refresh });
2873
3512
  return this.#akanConfig;
2874
3513
  }
2875
3514
  }
@@ -2908,7 +3547,7 @@ class PkgExecutor extends Executor {
2908
3547
  return rootVersion;
2909
3548
  try {
2910
3549
  const packageJsonPath = `pkgs/${dep}/package.json`;
2911
- if (!await Bun.file(path5.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
3550
+ if (!await Bun.file(path7.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
2912
3551
  return;
2913
3552
  const packageJson = await this.workspace.readJson(packageJsonPath);
2914
3553
  if (packageJson.name === dep)
@@ -2966,7 +3605,7 @@ class PkgExecutor extends Executor {
2966
3605
  default: "./index.ts"
2967
3606
  }
2968
3607
  },
2969
- engines: { bun: ">=1.3.13" },
3608
+ engines: { bun: ">=1.4.0" },
2970
3609
  ...dependencyMaps
2971
3610
  };
2972
3611
  await Promise.all([this.dist.writeJson("package.json", distPkgJson), this.writeJson("package.json", distPkgJson)]);
@@ -3009,4 +3648,4 @@ class ModuleExecutor extends Executor {
3009
3648
  }
3010
3649
  }
3011
3650
 
3012
- export { AGENT_BLOCK_START, AGENT_BLOCK_END, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, getDirname, appRootAllowedFiles, appRootAllowedDirs, libFacetRootAllowedFiles, isScannedAppRootEntry, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
3651
+ export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, isScannedRootEntry, AppInfo, Spinner, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };