@akanjs/cli 3.0.0-beta.3 → 3.0.0-beta.31

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 (194) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-tbdpdjat.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-my2x5fah.js +1457 -0
  6. package/README.md +3 -0
  7. package/TunnelShare-cwh9ezsm.js +9 -0
  8. package/{agent.command-d2gfxfck.js → agent.command-vz60x05c.js} +8 -9
  9. package/akanCode.js +102 -0
  10. package/application.command-pxqfjrqm.js +241 -0
  11. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-shnfyfdy.js} +121 -77
  12. package/buildBatch.proc.js +120 -32
  13. package/cloud.command-9v9tpgps.js +95 -0
  14. package/cloud.script-baqdthnz.js +20 -0
  15. package/code.command-13h14j25.js +154 -0
  16. package/commandManifest.json +1 -1
  17. package/consoleToStderr-h5bryj94.js +7 -0
  18. package/context.command-x1hbasm5.js +48 -0
  19. package/context.runner-kqbjjea1.js +21 -0
  20. package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
  21. package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
  22. package/fileSys-wzzrntkw.js +9 -0
  23. package/guideline.command-vfk11hpj.js +42 -0
  24. package/guidelines/agentRule/agentRule.instruction.md +209 -33
  25. package/guidelines/conventions/conventions.instruction.md +144 -41
  26. package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
  27. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  28. package/guidelines/queryRule/queryRule.instruction.md +21 -7
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +271 -6
  30. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  31. package/guidelines/transportRule/transportRule.instruction.md +51 -0
  32. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
  33. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  34. package/incrementalBuilder.proc.js +564 -109
  35. package/{index-mak3cd28.js → index-1skctzbq.js} +137 -255
  36. package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
  37. package/index-2wc41k5w.js +49 -0
  38. package/index-3hx1z96c.js +87 -0
  39. package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
  40. package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
  41. package/index-7md2g9vw.js +6 -0
  42. package/index-7yf7rnjg.js +53 -0
  43. package/{index-gyd220p2.js → index-84r80zdx.js} +554 -577
  44. package/{index-zry5nz81.js → index-8psse8dw.js} +51 -30
  45. package/index-947t6zm4.js +1119 -0
  46. package/index-94s080qm.js +266 -0
  47. package/index-9dp031by.js +12 -0
  48. package/index-9s5zga3q.js +4300 -0
  49. package/{index-ws5e0j51.js → index-arnkqb6f.js} +43 -26
  50. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  51. package/index-c6eggbee.js +123 -0
  52. package/{index-4edzf1gr.js → index-cxafpw23.js} +77 -150
  53. package/index-d2242ksy.js +99 -0
  54. package/index-d5yn19ef.js +700 -0
  55. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  56. package/{index-2tv3fdx4.js → index-e52z7psd.js} +1212 -1458
  57. package/{index-a64esmtb.js → index-ew8dna9s.js} +523 -1022
  58. package/{index-7737gxcp.js → index-f50dy7yq.js} +107 -241
  59. package/index-f8q701fb.js +1003 -0
  60. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  61. package/{index-j9yhg404.js → index-ksq4t16z.js} +4 -7
  62. package/index-nhhrjvk1.js +187 -0
  63. package/index-p6pbz9n5.js +25 -0
  64. package/index-ptshj7y1.js +2729 -0
  65. package/{index-fz5q3gd7.js → index-q9q7x4zj.js} +10 -17
  66. package/{index-pnqbkf5p.js → index-qk9vv9hm.js} +6 -2
  67. package/{index-600zhnve.js → index-rqxa9r98.js} +75 -203
  68. package/{index-ne2qy0nk.js → index-smbecayj.js} +12 -12
  69. package/{index-ktb5njgy.js → index-vcdxpmsx.js} +49 -99
  70. package/index-wa5jbxj1.js +760 -0
  71. package/index-ymnqrsxm.js +69 -0
  72. package/{index-v0qh106m.js → index-zc90rh75.js} +961 -892
  73. package/{index-259dsnz3.js → index-zvr7wfyg.js} +5 -8
  74. package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
  75. package/index.js +23 -22
  76. package/{library.command-nbsky672.js → library.command-m52jqc0s.js} +4 -5
  77. package/{localRegistry.command-yeq57wjr.js → localRegistry.command-x5vs1z43.js} +23 -42
  78. package/{module.command-29vqzrh8.js → module.command-ty3k6rwd.js} +8 -11
  79. package/{package.command-axv0abmn.js → package.command-hphdh4g4.js} +6 -7
  80. package/package.json +15 -7
  81. package/{page.command-vs9s3zrh.js → page.command-5aq8mzvq.js} +4 -5
  82. package/{primitive.command-cbxmqbtc.js → primitive.command-adwj84kx.js} +9 -22
  83. package/prompter-76k56b37.js +8 -0
  84. package/{quality.command-gmbs7nws.js → quality.command-y10kea2f.js} +205 -377
  85. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  86. package/{repair.command-bqsjptsj.js → repair.command-v7mwqvjh.js} +11 -35
  87. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  88. package/{scalar.command-6k1vhe9s.js → scalar.command-pg3gneer.js} +7 -10
  89. package/skills/akan-code/SKILL.md +130 -0
  90. package/skills/akan-debug/SKILL.md +42 -0
  91. package/skills/akan-deps/SKILL.md +50 -0
  92. package/skills/akan-err/SKILL.md +39 -0
  93. package/skills/akan-module/SKILL.md +45 -0
  94. package/skills/akan-scaffold/SKILL.md +76 -0
  95. package/skills/akan-scalar/SKILL.md +52 -0
  96. package/skills/akan-service/SKILL.md +34 -0
  97. package/skills/akan-store/SKILL.md +67 -0
  98. package/skills/akan-ui/SKILL.md +63 -0
  99. package/skills/akan-validate/SKILL.md +44 -0
  100. package/{subspace.command-fwjfcts7.js → subspace.command-ejdmvbq7.js} +138 -67
  101. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  102. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  103. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  104. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  105. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  106. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  107. package/templates/appSample/lib/task/task.signal.ts +1 -1
  108. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  109. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  110. package/templates/client.ts +7 -30
  111. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  112. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  113. package/templates/crudPages/new/page.tsx +2 -1
  114. package/templates/crudPages/page.tsx +2 -1
  115. package/templates/crudSinglePage/page.tsx +2 -1
  116. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  117. package/templates/lib/__lib/lib.document.ts +0 -2
  118. package/templates/lib/sig.ts +8 -30
  119. package/templates/localDev/docker-compose.yaml.template +3 -14
  120. package/templates/module/__model__.signal.ts +12 -4
  121. package/templates/pkgRoot/tsconfig.json.template +2 -7
  122. package/templates/server.ts +1 -3
  123. package/templates/service/__model__.abstract.md.template +8 -0
  124. package/templates/workspaceRoot/.gitignore.template +0 -1
  125. package/templates/workspaceRoot/package.json.template +1 -3
  126. package/tunnel.command-rz9qf230.js +129 -0
  127. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  128. package/typecheck.proc.js +1 -1
  129. package/workflow.command-6r809r2z.js +79 -0
  130. package/{workspace.command-8s050m0h.js → workspace.command-8g3cw7jd.js} +77 -109
  131. package/abstractCompactor-mjh5ppsq.js +0 -101
  132. package/application.command-pj2m7584.js +0 -204
  133. package/applicationReleasePackager-4yasxwv9.js +0 -244
  134. package/capacitorApp-36rbr858.js +0 -12
  135. package/cloud.command-jshwdq22.js +0 -93
  136. package/context.command-ez8a0zyz.js +0 -80
  137. package/devTuiView-c08ccbat.js +0 -9
  138. package/getDirname-dr3jz1xg.js +0 -7
  139. package/guideline.command-xmqwx8zm.js +0 -359
  140. package/guidelines/agentRule/agentRule.generate.json +0 -31
  141. package/guidelines/componentRule/componentRule.generate.json +0 -31
  142. package/guidelines/conventions/conventions.generate.json +0 -25
  143. package/guidelines/cssRule/cssRule.generate.json +0 -31
  144. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  145. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  146. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  147. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  148. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  149. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  150. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  151. package/guidelines/framework/framework.generate.json +0 -32
  152. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  153. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  154. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  155. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  156. package/guidelines/modelService/modelService.generate.json +0 -31
  157. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  158. package/guidelines/modelStore/modelStore.generate.json +0 -31
  159. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  160. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  161. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  162. package/guidelines/modelView/modelView.generate.json +0 -31
  163. package/guidelines/modelZone/modelZone.generate.json +0 -31
  164. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  165. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  166. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  167. package/guidelines/queryRule/queryRule.generate.json +0 -31
  168. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  169. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  170. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  171. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  172. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  173. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  174. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  175. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  176. package/guidelines/transportRule/transportRule.generate.json +0 -31
  177. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  178. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  179. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  180. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  181. package/index-2hq52jq3.js +0 -465
  182. package/index-47pj3aee.js +0 -76
  183. package/index-6wwzs1bc.js +0 -7
  184. package/index-8pm30m73.js +0 -841
  185. package/index-kn1h7p1x.js +0 -560
  186. package/index-meh558jy.js +0 -65
  187. package/index-rwg2aa4f.js +0 -229
  188. package/index-w8kmx1b6.js +0 -2176
  189. package/index-wt7qrtd4.js +0 -1516
  190. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  191. package/templates/module/__model__.abstract.md +0 -28
  192. package/templates/service/__model__.abstract.md +0 -28
  193. package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
  194. package/workflow.command-nwpcdc8p.js +0 -107
@@ -1,161 +1,474 @@
1
1
  // @bun
2
2
  import {
3
- FileSys
4
- } from "./index-q0y2jq9j.js";
5
- import {
6
- getDirname2
7
- } from "./index-6wwzs1bc.js";
3
+ getDirname2,
4
+ FileSys2
5
+ } from "./index-52w0fvh8.js";
8
6
 
9
- // pkgs/@akanjs/devkit/codegenLock.ts
10
- import { mkdir, open, readFile, rm, stat } from "fs/promises";
7
+ // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
8
+ import { readdir, rm } from "fs/promises";
9
+ import path2 from "path";
10
+
11
+ // pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
12
+ import fs from "fs";
11
13
  import path from "path";
12
- import { Logger } from "akanjs/common";
13
14
 
14
- class CodegenLock {
15
- static fileName = "codegen.lock";
16
- static waitTimeoutMs = 1e4;
17
- static unknownHolderStaleMs = 60000;
18
- static #pollMs = 25;
19
- static #logger = new Logger("CodegenLock");
20
- static #queue = Promise.resolve();
21
- static pathIn(workspaceRoot) {
22
- return path.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
23
- }
24
- static async run(workspaceRoot, label, fn) {
25
- const ahead = CodegenLock.#queue;
26
- let done;
27
- CodegenLock.#queue = new Promise((resolve) => {
28
- done = resolve;
29
- });
30
- try {
31
- await CodegenLock.#waitForQueue(ahead, label);
32
- return await CodegenLock.#withFileLock(workspaceRoot, label, fn);
33
- } finally {
34
- done();
35
- }
15
+ class AsyncDefaultExportDetector {
16
+ static #typescriptLoad;
17
+ static #loadTypescript() {
18
+ AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
19
+ return AsyncDefaultExportDetector.#typescriptLoad;
36
20
  }
37
- static async#waitForQueue(ahead, label) {
38
- let timer = null;
39
- const expired = new Promise((resolve) => {
40
- timer = setTimeout(() => resolve("expired"), CodegenLock.waitTimeoutMs);
41
- });
21
+ static async detect(moduleAbsPath) {
42
22
  try {
43
- if (await Promise.race([ahead.then(() => "done"), expired]) === "expired")
44
- CodegenLock.#logger.warn(`codegen lock queued past ${CodegenLock.waitTimeoutMs}ms in this process; continuing without waiting (${label})`);
45
- } finally {
46
- if (timer)
47
- clearTimeout(timer);
23
+ const typescript = await AsyncDefaultExportDetector.#loadTypescript();
24
+ const source = fs.readFileSync(path.resolve(moduleAbsPath), "utf8");
25
+ const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, /\.[tj]sx$/.test(moduleAbsPath) ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS);
26
+ return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
27
+ } catch {
28
+ return false;
48
29
  }
49
30
  }
50
- static async#withFileLock(workspaceRoot, label, fn) {
51
- const lockPath = CodegenLock.pathIn(workspaceRoot);
52
- await mkdir(path.dirname(lockPath), { recursive: true }).catch(() => {
53
- return;
54
- });
55
- const held = await CodegenLock.#acquire(lockPath, label);
56
- try {
57
- return await fn();
58
- } finally {
59
- if (held)
60
- await rm(lockPath, { force: true }).catch(() => {
61
- return;
62
- });
63
- }
31
+ #ts;
32
+ constructor(typescript) {
33
+ this.#ts = typescript;
64
34
  }
65
- static async#acquire(lockPath, label) {
66
- const deadline = Date.now() + CodegenLock.waitTimeoutMs;
67
- for (;; ) {
68
- const handle = await open(lockPath, "wx").catch(() => null);
69
- if (handle) {
70
- await handle.writeFile(JSON.stringify({ pid: process.pid, at: Date.now(), label })).catch(() => {
71
- return;
72
- });
73
- await handle.close().catch(() => {
74
- return;
75
- });
76
- return true;
35
+ detectInSourceFile(sourceFile) {
36
+ const ts = this.#ts;
37
+ const asyncBindings = new Map;
38
+ let defaultIdentifier = null;
39
+ for (const statement of sourceFile.statements) {
40
+ if (ts.isFunctionDeclaration(statement)) {
41
+ if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
42
+ return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
43
+ }
44
+ if (statement.name) {
45
+ asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
46
+ }
47
+ continue;
77
48
  }
78
- if (await CodegenLock.#reclaimIfAbandoned(lockPath))
49
+ if (ts.isVariableStatement(statement)) {
50
+ for (const declaration of statement.declarationList.declarations) {
51
+ if (!ts.isIdentifier(declaration.name))
52
+ continue;
53
+ asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
54
+ }
79
55
  continue;
80
- if (Date.now() >= deadline) {
81
- CodegenLock.#logger.warn(`codegen lock at ${lockPath} held past ${CodegenLock.waitTimeoutMs}ms; continuing without it (${label})`);
82
- return false;
83
56
  }
84
- await Bun.sleep(CodegenLock.#pollMs);
57
+ if (ts.isExportAssignment(statement)) {
58
+ if (this.#isAsyncFunctionExpression(statement.expression))
59
+ return true;
60
+ if (ts.isCallExpression(statement.expression))
61
+ return true;
62
+ if (ts.isIdentifier(statement.expression))
63
+ defaultIdentifier = statement.expression.text;
64
+ continue;
65
+ }
66
+ if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
67
+ for (const specifier of statement.exportClause.elements) {
68
+ if (specifier.name.text !== "default")
69
+ continue;
70
+ defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
71
+ }
72
+ }
85
73
  }
74
+ return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
86
75
  }
87
- static async#reclaimIfAbandoned(lockPath) {
88
- const info = await stat(lockPath).catch(() => null);
89
- if (!info)
90
- return true;
91
- const holder = CodegenLock.#parseHolder(await readFile(lockPath, "utf8").catch(() => ""));
92
- if (holder) {
93
- if (CodegenLock.#isAlive(holder.pid))
94
- return false;
95
- } else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
96
- return false;
97
- await rm(lockPath, { force: true }).catch(() => {
98
- return;
99
- });
100
- return true;
101
- }
102
- static #parseHolder(raw) {
103
- try {
104
- const parsed = JSON.parse(raw);
105
- if (typeof parsed.pid !== "number" || typeof parsed.at !== "number")
106
- return null;
107
- return { pid: parsed.pid, at: parsed.at, label: typeof parsed.label === "string" ? parsed.label : "" };
108
- } catch {
109
- return null;
110
- }
76
+ #hasModifier(node, kind) {
77
+ const ts = this.#ts;
78
+ return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
111
79
  }
112
- static #isAlive(pid) {
113
- if (!Number.isInteger(pid) || pid <= 0)
114
- return false;
115
- try {
116
- process.kill(pid, 0);
117
- return true;
118
- } catch (error) {
119
- return error.code === "EPERM";
120
- }
80
+ #isAsyncFunctionExpression(node) {
81
+ const ts = this.#ts;
82
+ return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
121
83
  }
122
84
  }
123
85
 
124
- // pkgs/@akanjs/devkit/spinner.ts
125
- import ora from "ora";
86
+ // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
87
+ var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
88
+ var IMPLICIT_LAYOUT_DIR = path2.join(".akan", "generated", "root-layouts");
89
+ var IMPLICIT_DICT_DIR = path2.join(".akan", "generated", "dict");
90
+ var IMPLICIT_OVERRIDES_DIR = path2.join(".akan", "generated", "overrides");
91
+ var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
92
+ var importSpecifier = (fromAbsPath, toAbsPath) => {
93
+ const rel = path2.relative(path2.dirname(fromAbsPath), toAbsPath).split(path2.sep).join("/");
94
+ return rel.startsWith(".") ? rel : `./${rel}`;
95
+ };
96
+ async function writeGeneratedOverridesLayoutFile(opts) {
97
+ const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
98
+ const absPath = path2.join(path2.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
99
+ const source = `"use client";
100
+ import { UiOverrideProvider } from "akanjs/ui";
101
+ import { createElement, type ReactNode } from "react";
102
+ import value from ${JSON.stringify(importSpecifier(absPath, opts.userAbsPath))};
126
103
 
127
- class Spinner {
128
- static padding = 12;
129
- static oraOptions = { discardStdin: false };
130
- static canAnimate(stream = process.stderr) {
131
- return !stream.isTTY || stream.columns > 0;
104
+ export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
105
+ return createElement(UiOverrideProvider, { value }, children);
106
+ }
107
+ `;
108
+ await Bun.write(absPath, source);
109
+ return absPath;
110
+ }
111
+ async function pruneGeneratedFiles(dirAbsPath, keepAbsPaths) {
112
+ const keep = new Set(keepAbsPaths);
113
+ const names = await readdir(dirAbsPath).catch(() => []);
114
+ await Promise.all(names.map((name) => path2.join(dirAbsPath, name)).filter((absPath) => !keep.has(absPath)).map((absPath) => rm(absPath, { force: true })));
115
+ }
116
+ function getRootBoundarySegments(key) {
117
+ const match = LAYOUT_KEY_RE.exec(key);
118
+ if (!match)
119
+ return null;
120
+ const prefix = match[1]?.replace(/\/$/, "");
121
+ if (!prefix)
122
+ return [];
123
+ return prefix.split("/").filter(Boolean);
124
+ }
125
+ function implicitRootLayoutKey(segments) {
126
+ return `./${[...segments, "__root_layout"].join("/")}.tsx`;
127
+ }
128
+ function implicitRootLayoutAbsPath(appCwdPath, segments) {
129
+ const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
130
+ return path2.join(path2.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
131
+ }
132
+ function isRootBoundarySegments(segments, basePaths) {
133
+ const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
134
+ if (firstVisibleIndex === -1)
135
+ return segments.length <= 1;
136
+ if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
137
+ return false;
138
+ const visible = segments.slice(firstVisibleIndex);
139
+ const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
140
+ return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
141
+ }
142
+ function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
143
+ const boundaries = new Map;
144
+ for (const key of pageKeys) {
145
+ const segments = getRootBoundarySegments(key);
146
+ if (!segments)
147
+ continue;
148
+ if (!isRootBoundarySegments(segments, basePaths))
149
+ continue;
150
+ const id = segments.join("/");
151
+ boundaries.set(id, {
152
+ sourceKey: key,
153
+ sourceAbsPath: path2.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
154
+ segments
155
+ });
132
156
  }
133
- spinner;
134
- stopWatch = null;
135
- startAt = new Date;
136
- prefix;
137
- message;
138
- enableSpin;
139
- constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
140
- Spinner.padding = Math.max(Spinner.padding, prefix.length);
141
- this.prefix = prefix;
142
- this.message = message;
143
- this.spinner = ora({ ...Spinner.oraOptions, text: message });
144
- this.spinner.prefixText = prefix.padStart(Spinner.padding, " ");
145
- this.spinner.indent = indent;
146
- this.enableSpin = enableSpin && Spinner.canAnimate();
157
+ if (boundaries.size === 0)
158
+ boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
159
+ return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
160
+ }
161
+ function hasAncestorRootBoundary(boundary, boundaries) {
162
+ return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
163
+ }
164
+ function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
165
+ const rootLayoutKey = pageKeys.find((key) => {
166
+ const segments = getRootBoundarySegments(key);
167
+ return segments !== null && segments.length === 0;
168
+ });
169
+ return rootLayoutKey ? path2.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
170
+ }
171
+ async function assertEnvClientConvention(appCwdPath, appName) {
172
+ const envPath = path2.join(appCwdPath, "env", "env.client.ts");
173
+ if (!await Bun.file(envPath).exists()) {
174
+ throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
147
175
  }
148
- start() {
149
- this.startAt = new Date;
150
- if (this.enableSpin) {
151
- this.spinner.start();
152
- this.stopWatch = setInterval(() => {
153
- this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
154
- this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
155
- }, 1000);
156
- } else
157
- this.spinner.info();
158
- return this;
176
+ }
177
+ async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
178
+ const absPath = path2.join(path2.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
179
+ await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
180
+
181
+ export const allDictionary = getAllDictionary();
182
+ `);
183
+ return absPath;
184
+ }
185
+ async function writeGeneratedRootLayoutFile(opts) {
186
+ await assertEnvClientConvention(opts.appCwdPath, opts.appName);
187
+ const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
188
+ const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
189
+ const dictMacroSpecifier = dictMacroAbsPath ? importSpecifier(absPath, dictMacroAbsPath) : null;
190
+ const sourceSpecifier = opts.boundary.sourceAbsPath ? importSpecifier(absPath, opts.boundary.sourceAbsPath) : null;
191
+ const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
192
+ const inheritedSourceSpecifier = inheritedSourceAbsPath ? importSpecifier(absPath, inheritedSourceAbsPath) : null;
193
+ const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
194
+ void st;
195
+ ` : `import "@apps/${opts.appName}/client";
196
+ `;
197
+ const inheritedLabel = inheritedSourceAbsPath ? path2.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
198
+ const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
199
+ const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
200
+ ` : `const inheritedLayout: LayoutModule = {};
201
+ `;
202
+ const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
203
+ const userLabel = opts.boundary.sourceAbsPath ? path2.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
204
+ const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
205
+ const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
206
+ const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
207
+ ` : `const UserLayout = ({ children }: LayoutProps) => children;
208
+ const userLayout: LayoutModule = {};
209
+ `;
210
+ const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
211
+ const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
212
+ const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
213
+ const layoutBinding = isAsyncUserLayout ? ` const layout =
214
+ process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
215
+ ? await UserLayout({ params, searchParams, children })
216
+ : ${userLayoutElement};
217
+ ` : "";
218
+ const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
219
+ const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
220
+ const layoutExports = `export async function generateHead(props: PageProps) {
221
+ if (userLayout.generateHead) return userLayout.generateHead(props);
222
+ if (userLayout.head !== undefined) return userLayout.head;
223
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
224
+ return inheritedLayout.head;
225
+ }
226
+
227
+ export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
228
+ export const Error = userLayout.Error ?? inheritedLayout.Error;
229
+ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
230
+
231
+ ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
232
+ ${layoutBinding} return `;
233
+ const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
234
+ import { loadFonts, resolveRouteModule } from "akanjs/client";
235
+ import type { ReactNode } from "react";
236
+ import { System } from "akanjs/ui";
237
+ import { env } from "@apps/${opts.appName}/env/env.client";
238
+ import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
239
+ ${clientImport}${inheritedImport}${userImport}
240
+ const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
241
+ const defaultFonts = userFonts.filter((font) => font.default);
242
+ if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
243
+ const defaultFont = defaultFonts[0];
244
+ const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
245
+
246
+ ${layoutExports}(
247
+ <System.Provider
248
+ of={GeneratedLayout as never}
249
+ appName=${JSON.stringify(opts.appName)}
250
+ ${prefix ? `prefix=${JSON.stringify(prefix)}
251
+ ` : ""}params={params}
252
+ manifest={userLayout.manifest ?? inheritedLayout.manifest}
253
+ env={env}
254
+ theme={userLayout.theme ?? inheritedLayout.theme}
255
+ fonts={loadFonts(userFonts)}
256
+ className={defaultFontClassName}
257
+ layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
258
+ reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
259
+ wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
260
+ allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
261
+ >
262
+ ${layoutChild}
263
+ </System.Provider>
264
+ );
265
+ }
266
+ ` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
267
+ import { resolveRouteModule } from "akanjs/client";
268
+ import type { ReactNode } from "react";
269
+ ${inheritedImport}${userImport}
270
+ ${layoutExports}${layoutReturn};
271
+ }
272
+ `;
273
+ await Bun.write(absPath, source);
274
+ return absPath;
275
+ }
276
+ async function resolveSsrPageEntries(opts) {
277
+ const absPageDir = path2.resolve(opts.appCwdPath, "page");
278
+ const hasSt = await Bun.file(path2.join(opts.appCwdPath, "lib", "st.ts")).exists();
279
+ const basePaths = opts.basePaths ?? [];
280
+ const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
281
+ const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
282
+ const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
283
+ const segments = getRootBoundarySegments(key);
284
+ return segments !== null && isRootBoundarySegments(segments, basePaths);
285
+ }));
286
+ const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
287
+ const userAbsPath = path2.resolve(absPageDir, key);
288
+ if (OVERRIDES_KEY_RE.test(key)) {
289
+ const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
290
+ appCwdPath: opts.appCwdPath,
291
+ key,
292
+ userAbsPath
293
+ });
294
+ return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
295
+ }
296
+ return { key, moduleAbsPath: userAbsPath };
297
+ }));
298
+ const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
299
+ key: implicitRootLayoutKey(boundary.segments),
300
+ moduleAbsPath: await writeGeneratedRootLayoutFile({
301
+ appCwdPath: opts.appCwdPath,
302
+ appName: opts.appName,
303
+ boundary,
304
+ rootSourceAbsPath,
305
+ includeStInit: hasSt && boundary.segments.length === 0,
306
+ includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
307
+ }),
308
+ seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
309
+ })));
310
+ const appCwdAbsPath = path2.resolve(opts.appCwdPath);
311
+ await Promise.all([
312
+ pruneGeneratedFiles(path2.join(appCwdAbsPath, IMPLICIT_OVERRIDES_DIR), base.filter(({ key }) => OVERRIDES_KEY_RE.test(key)).map(({ moduleAbsPath }) => moduleAbsPath)),
313
+ pruneGeneratedFiles(path2.join(appCwdAbsPath, IMPLICIT_LAYOUT_DIR), generated.map(({ moduleAbsPath }) => moduleAbsPath))
314
+ ]);
315
+ return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
316
+ }
317
+ async function resolveSsrPageEntriesForApp(app, pageKeys) {
318
+ const config = await app.getConfig();
319
+ return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
320
+ }
321
+
322
+ // pkgs/@akanjs/devkit/codegenLock.ts
323
+ import { mkdir, open, readFile, rm as rm2, stat } from "fs/promises";
324
+ import path3 from "path";
325
+ import { Logger } from "akanjs/common";
326
+
327
+ class CodegenLock {
328
+ static fileName = "codegen.lock";
329
+ static waitTimeoutMs = 1e4;
330
+ static unknownHolderStaleMs = 60000;
331
+ static #pollMs = 25;
332
+ static #logger = new Logger("CodegenLock");
333
+ static #queue = Promise.resolve();
334
+ static pathIn(workspaceRoot) {
335
+ return path3.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
336
+ }
337
+ static async run(workspaceRoot, label, fn) {
338
+ const ahead = CodegenLock.#queue;
339
+ let done;
340
+ CodegenLock.#queue = new Promise((resolve) => {
341
+ done = resolve;
342
+ });
343
+ try {
344
+ await CodegenLock.#waitForQueue(ahead, label);
345
+ return await CodegenLock.#withFileLock(workspaceRoot, label, fn);
346
+ } finally {
347
+ done();
348
+ }
349
+ }
350
+ static async#waitForQueue(ahead, label) {
351
+ let timer = null;
352
+ const expired = new Promise((resolve) => {
353
+ timer = setTimeout(() => resolve("expired"), CodegenLock.waitTimeoutMs);
354
+ });
355
+ try {
356
+ if (await Promise.race([ahead.then(() => "done"), expired]) === "expired")
357
+ CodegenLock.#logger.warn(`codegen lock queued past ${CodegenLock.waitTimeoutMs}ms in this process; continuing without waiting (${label})`);
358
+ } finally {
359
+ if (timer)
360
+ clearTimeout(timer);
361
+ }
362
+ }
363
+ static async#withFileLock(workspaceRoot, label, fn) {
364
+ const lockPath = CodegenLock.pathIn(workspaceRoot);
365
+ await mkdir(path3.dirname(lockPath), { recursive: true }).catch(() => {
366
+ return;
367
+ });
368
+ const held = await CodegenLock.#acquire(lockPath, label);
369
+ try {
370
+ return await fn();
371
+ } finally {
372
+ if (held)
373
+ await rm2(lockPath, { force: true }).catch(() => {
374
+ return;
375
+ });
376
+ }
377
+ }
378
+ static async#acquire(lockPath, label) {
379
+ const deadline = Date.now() + CodegenLock.waitTimeoutMs;
380
+ for (;; ) {
381
+ const handle = await open(lockPath, "wx").catch(() => null);
382
+ if (handle) {
383
+ await handle.writeFile(JSON.stringify({ pid: process.pid, at: Date.now(), label })).catch(() => {
384
+ return;
385
+ });
386
+ await handle.close().catch(() => {
387
+ return;
388
+ });
389
+ return true;
390
+ }
391
+ if (await CodegenLock.#reclaimIfAbandoned(lockPath))
392
+ continue;
393
+ if (Date.now() >= deadline) {
394
+ CodegenLock.#logger.warn(`codegen lock at ${lockPath} held past ${CodegenLock.waitTimeoutMs}ms; continuing without it (${label})`);
395
+ return false;
396
+ }
397
+ await Bun.sleep(CodegenLock.#pollMs);
398
+ }
399
+ }
400
+ static async#reclaimIfAbandoned(lockPath) {
401
+ const info = await stat(lockPath).catch(() => null);
402
+ if (!info)
403
+ return true;
404
+ const holder = CodegenLock.#parseHolder(await readFile(lockPath, "utf8").catch(() => ""));
405
+ if (holder) {
406
+ if (CodegenLock.#isAlive(holder.pid))
407
+ return false;
408
+ } else if (Date.now() - info.mtimeMs < CodegenLock.unknownHolderStaleMs)
409
+ return false;
410
+ await rm2(lockPath, { force: true }).catch(() => {
411
+ return;
412
+ });
413
+ return true;
414
+ }
415
+ static #parseHolder(raw) {
416
+ try {
417
+ const parsed = JSON.parse(raw);
418
+ if (typeof parsed.pid !== "number" || typeof parsed.at !== "number")
419
+ return null;
420
+ return { pid: parsed.pid, at: parsed.at, label: typeof parsed.label === "string" ? parsed.label : "" };
421
+ } catch {
422
+ return null;
423
+ }
424
+ }
425
+ static #isAlive(pid) {
426
+ if (!Number.isInteger(pid) || pid <= 0)
427
+ return false;
428
+ try {
429
+ process.kill(pid, 0);
430
+ return true;
431
+ } catch (error) {
432
+ return error.code === "EPERM";
433
+ }
434
+ }
435
+ }
436
+
437
+ // pkgs/@akanjs/devkit/spinner.ts
438
+ import ora from "ora";
439
+
440
+ class Spinner {
441
+ static padding = 12;
442
+ static oraOptions = { discardStdin: false };
443
+ static canAnimate(stream = process.stderr) {
444
+ return !stream.isTTY || stream.columns > 0;
445
+ }
446
+ spinner;
447
+ stopWatch = null;
448
+ startAt = new Date;
449
+ prefix;
450
+ message;
451
+ enableSpin;
452
+ constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
453
+ Spinner.padding = Math.max(Spinner.padding, prefix.length);
454
+ this.prefix = prefix;
455
+ this.message = message;
456
+ this.spinner = ora({ ...Spinner.oraOptions, text: message });
457
+ this.spinner.prefixText = prefix.padStart(Spinner.padding, " ");
458
+ this.spinner.indent = indent;
459
+ this.enableSpin = enableSpin && Spinner.canAnimate();
460
+ }
461
+ start() {
462
+ this.startAt = new Date;
463
+ if (this.enableSpin) {
464
+ this.spinner.start();
465
+ this.stopWatch = setInterval(() => {
466
+ this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
467
+ this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
468
+ }, 1000);
469
+ } else
470
+ this.spinner.info();
471
+ return this;
159
472
  }
160
473
  succeed(message) {
161
474
  this.spinner.succeed(`${message} (${this.#getElapsedTimeStr()})`);
@@ -191,18 +504,19 @@ import {
191
504
  fork,
192
505
  spawn as spawn2
193
506
  } from "child_process";
194
- import { readFileSync as readFileSync2 } from "fs";
507
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
195
508
  import {
196
509
  copyFile,
197
510
  cp as cpEntry,
198
- mkdir as mkdir3,
511
+ lstat,
512
+ mkdir as mkdir2,
199
513
  readdir as readDirEntries,
200
514
  realpath,
201
515
  rm as rm3,
202
516
  stat as stat2,
203
517
  symlink
204
518
  } from "fs/promises";
205
- import path8 from "path";
519
+ import path9 from "path";
206
520
  import { pathToFileURL } from "url";
207
521
  import {
208
522
  capitalize,
@@ -218,7 +532,7 @@ import chalk2 from "chalk";
218
532
 
219
533
  // pkgs/@akanjs/devkit/agentsIndex.ts
220
534
  var recipeScannerLoad = null;
221
- var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-fpagtgc1.js");
535
+ var loadRecipeScanner = () => recipeScannerLoad ??= import("./recipeScanner-4b69ybe0.js");
222
536
  var AGENT_BLOCK_START = "<!-- akan:agent:start -->";
223
537
  var AGENT_BLOCK_END = "<!-- akan:agent:end -->";
224
538
  var AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
@@ -228,7 +542,7 @@ ${block}`;
228
542
  var extractBlockVersion = (content) => content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
229
543
  var readDevkitVersion = async () => {
230
544
  const { readFile } = await import("fs/promises");
231
- const { getDirname } = await import("./getDirname-dr3jz1xg.js");
545
+ const { getDirname } = await import("./fileSys-wzzrntkw.js");
232
546
  try {
233
547
  const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
234
548
  return JSON.parse(raw).version ?? null;
@@ -354,79 +668,93 @@ var renderScopeClaudeMd = (scope) => `# ${scope.name} \u2014 Claude Code Guide
354
668
  ${CLAUDE_COMMENT_RULE}
355
669
  `;
356
670
 
357
- // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
358
- import fs from "fs";
359
- import path2 from "path";
360
- import { fileURLToPath } from "url";
361
- import { normalizeRoutePrefix } from "akanjs/base";
362
- import { resolveAkanI18nConfig } from "akanjs/common";
671
+ // pkgs/@akanjs/devkit/akanConfig/akanBin.ts
672
+ import path4 from "path";
363
673
 
364
674
  // pkgs/@akanjs/devkit/akanConfig/types.ts
365
- import { archs } from "akanjs";
675
+ import { archs, binPlatforms } from "akanjs";
366
676
 
677
+ // pkgs/@akanjs/devkit/akanConfig/akanBin.ts
678
+ class AkanBin {
679
+ static archive = /\.(zip|tar\.gz|tgz|tar\.xz|txz|tar\.bz2|tbz2|tar)$/i;
680
+ static fileName = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
681
+ static parse(declared, owner, baseDir) {
682
+ if (declared === undefined)
683
+ return {};
684
+ if (!declared || typeof declared !== "object" || Array.isArray(declared))
685
+ throw new Error(`${owner}: bin maps each executable's name to its platforms`);
686
+ return Object.fromEntries(Object.entries(declared).map(([name, platforms]) => [name, AkanBin.#platforms(name, platforms, owner, baseDir)]));
687
+ }
688
+ static isArchive(source) {
689
+ return AkanBin.archive.test("url" in source ? new URL(source.url).pathname : source.path);
690
+ }
691
+ static #platforms(name, platforms, owner, baseDir) {
692
+ if (!AkanBin.fileName.test(name))
693
+ throw new Error(`${owner}: bin.${name} is not a file name`);
694
+ if (!platforms || typeof platforms !== "object" || Array.isArray(platforms))
695
+ throw new Error(`${owner}: bin.${name} maps platforms (${binPlatforms.join(", ")}) to where the file comes from`);
696
+ const resolved = {};
697
+ for (const [platform, source] of Object.entries(platforms)) {
698
+ if (!binPlatforms.includes(platform))
699
+ throw new Error(`${owner}: bin.${name}.${platform} is not one of ${binPlatforms.join(", ")}`);
700
+ resolved[platform] = AkanBin.#source(`${owner}: bin.${name}.${platform}`, source, baseDir);
701
+ }
702
+ return resolved;
703
+ }
704
+ static #source(at, source, baseDir) {
705
+ if (!source || typeof source !== "object" || Array.isArray(source))
706
+ throw new Error(`${at} must be { url, sha256 } or { path }`);
707
+ const { url, sha256, path: localPath, file, ...rest } = source;
708
+ const extra = Object.keys(rest);
709
+ if (extra.length)
710
+ throw new Error(`${at}: unknown key ${extra.join(", ")}`);
711
+ if (url === undefined === (localPath === undefined))
712
+ throw new Error(`${at} takes either url and sha256, or path`);
713
+ const resolved = url !== undefined ? AkanBin.#urlSource(at, url, sha256) : AkanBin.#pathSource(at, localPath, sha256, baseDir);
714
+ const archive = AkanBin.isArchive(resolved);
715
+ if (file === undefined) {
716
+ if (archive)
717
+ throw new Error(`${at} names an archive: file picks the executable inside it`);
718
+ return resolved;
719
+ }
720
+ if (!archive)
721
+ throw new Error(`${at}: file picks the executable out of an archive, and this source is none`);
722
+ const parts = typeof file === "string" ? file.split(/[\\/]/).filter((part) => part && part !== ".") : [];
723
+ if (!parts.length || path4.isAbsolute(file) || parts.includes(".."))
724
+ throw new Error(`${at}: file must be a path inside the archive (got ${JSON.stringify(file)})`);
725
+ return { ...resolved, file: parts.join("/") };
726
+ }
727
+ static #urlSource(at, url, sha256) {
728
+ if (typeof url !== "string" || !URL.canParse(url) || !/^https?:$/.test(new URL(url).protocol))
729
+ throw new Error(`${at}: url must be an http(s) address`);
730
+ if (typeof sha256 !== "string" || !/^[0-9a-f]{64}$/i.test(sha256))
731
+ throw new Error(`${at}: sha256 must be the downloaded file's 64-digit hex digest`);
732
+ return { url, sha256: sha256.toLowerCase() };
733
+ }
734
+ static #pathSource(at, localPath, sha256, baseDir) {
735
+ if (typeof localPath !== "string" || !localPath)
736
+ throw new Error(`${at}: path must name a file`);
737
+ if (sha256 !== undefined)
738
+ throw new Error(`${at}: sha256 checks a download, and a path source downloads nothing`);
739
+ return { path: path4.resolve(baseDir, localPath) };
740
+ }
741
+ }
367
742
  // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
743
+ import fs2 from "fs";
744
+ import path5 from "path";
745
+ import { fileURLToPath } from "url";
746
+ import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
747
+ import { resolveAkanI18nConfig } from "akanjs/common";
368
748
  var DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui", "akanjs/server"];
369
749
  var DEFAULT_OPTIMIZE_IMPORTS = [
370
- "lucide-react",
371
- "date-fns",
372
- "lodash-es",
373
- "ramda",
374
- "antd",
375
- "react-bootstrap",
376
- "ahooks",
377
- "@ant-design/icons",
378
- "@headlessui/react",
379
- "@headlessui-float/react",
380
- "@heroicons/react/20/solid",
381
- "@heroicons/react/24/solid",
382
- "@heroicons/react/24/outline",
383
- "@visx/visx",
384
- "@tremor/react",
385
- "rxjs",
386
- "@mui/material",
387
- "@mui/icons-material",
388
- "recharts",
389
- "react-use",
390
- "@material-ui/core",
391
- "@material-ui/icons",
392
- "@tabler/icons-react",
393
- "mui-core",
394
- "react-icons/*"
395
- ];
750
+ "lucide-react date-fns lodash-es ramda antd react-bootstrap ahooks @ant-design/icons @headlessui/react",
751
+ "@headlessui-float/react @heroicons/react/20/solid @heroicons/react/24/solid @heroicons/react/24/outline",
752
+ "@visx/visx @tremor/react rxjs @mui/material @mui/icons-material recharts react-use @material-ui/core",
753
+ "@material-ui/icons @tabler/icons-react mui-core react-icons/*"
754
+ ].flatMap((line) => line.split(" "));
396
755
  var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
397
756
  var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
398
757
  var SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"];
399
- var MOBILE_RUNTIME_PACKAGES = [
400
- "@capacitor/cli",
401
- "@capacitor/core",
402
- "@capacitor/ios",
403
- "@capacitor/android",
404
- "@capacitor/assets"
405
- ];
406
- var MOBILE_APP_CAPACITOR_PLUGINS = [
407
- "@capacitor/app",
408
- "@capacitor/browser",
409
- "@capacitor/camera",
410
- "@capacitor/core",
411
- "@capacitor/device",
412
- "@capacitor/geolocation",
413
- "@capacitor/haptics",
414
- "@capacitor/inappbrowser",
415
- "@capacitor/keyboard",
416
- "@capacitor/preferences",
417
- "@capacitor/push-notifications",
418
- "capacitor-plugin-safe-area"
419
- ];
420
- var DATABASE_MODE_RUNTIME_PACKAGES = {
421
- single: [],
422
- multiple: ["@libsql/client", "bullmq", "ioredis", "protobufjs"],
423
- cluster: ["bullmq", "ioredis", "postgres", "protobufjs"]
424
- };
425
- var AKAN_RUNTIME_PACKAGES = new Set([
426
- ...SSR_RUNTIME_PACKAGES,
427
- ...MOBILE_RUNTIME_PACKAGES,
428
- ...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat()
429
- ]);
430
758
  var DEFAULT_AKAN_IMAGE_CONFIG = {
431
759
  deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
432
760
  imageSizes: [32, 48, 64, 96, 128, 256, 384],
@@ -471,26 +799,26 @@ var normalizeDeepLinkDomain = (domain) => {
471
799
  }
472
800
  };
473
801
  var normalizeDeepLinks = (deepLinks) => {
474
- if (!deepLinks)
802
+ const schemes = normalizeStringList(deepLinks?.schemes);
803
+ const domains = normalizeStringList(deepLinks?.domains?.map(normalizeDeepLinkDomain));
804
+ if (!schemes && !domains)
475
805
  return;
476
- const schemes = normalizeStringList(deepLinks.schemes);
477
- const domains = normalizeStringList(deepLinks.domains?.map(normalizeDeepLinkDomain));
478
- const teamId = deepLinks.ios?.teamId?.trim();
479
- const sha256CertFingerprints = normalizeStringList(deepLinks.android?.sha256CertFingerprints);
480
- if (!schemes && !domains && !teamId && !sha256CertFingerprints)
481
- return;
482
- return {
483
- ...schemes ? { schemes } : {},
484
- ...domains ? { domains } : {},
485
- ...teamId ? { ios: { teamId } } : {},
486
- ...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
487
- };
806
+ return { ...schemes ? { schemes } : {}, ...domains ? { domains } : {} };
488
807
  };
489
808
  var emptyLibContributions = () => ({
490
809
  externalLibs: [],
810
+ trustedDependencies: [],
811
+ bin: [],
491
812
  docker: { preRuns: [], postRuns: [] },
492
813
  keepFonts: []
493
814
  });
815
+ var normalizePackageNames = (names, owner) => {
816
+ if (names === undefined)
817
+ return [];
818
+ if (!Array.isArray(names) || names.some((name) => typeof name !== "string" || !name.trim()))
819
+ throw new Error(`${owner}: trustedDependencies lists package names`);
820
+ return [...new Set(names.map((name) => name.trim()))];
821
+ };
494
822
  var normalizeKeepFonts = (keepFonts) => [
495
823
  ...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean))
496
824
  ];
@@ -506,17 +834,20 @@ class AkanAppConfig {
506
834
  rootPackageJson;
507
835
  docker;
508
836
  dockerfile;
509
- defaultDatabaseMode;
837
+ database;
510
838
  web;
511
839
  externalLibs;
840
+ trustedDependencies;
841
+ bin;
842
+ libBins;
512
843
  barrelImports;
513
844
  optimizeImports;
514
845
  images;
515
846
  i18n;
516
847
  api;
517
848
  publicEnv;
518
- mobile;
519
- hasMobileConfig;
849
+ native;
850
+ hasNativeConfig;
520
851
  secrets;
521
852
  assets;
522
853
  syncPageLibs;
@@ -534,8 +865,17 @@ class AkanAppConfig {
534
865
  this.baseDevEnv = baseDevEnv;
535
866
  this.plugins = plugins;
536
867
  this.#applyRoutes(config?.routes);
537
- this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
868
+ this.database = AkanAppConfig.#database(app, config);
538
869
  this.externalLibs = [...new Set([...config?.externalLibs ?? [], ...libContributions.externalLibs])];
870
+ const owner = `apps/${app.name}/akan.config.ts`;
871
+ this.trustedDependencies = [
872
+ ...new Set([
873
+ ...normalizePackageNames(config?.trustedDependencies, owner),
874
+ ...libContributions.trustedDependencies ?? []
875
+ ])
876
+ ];
877
+ this.bin = AkanBin.parse(config?.bin, owner, app.cwdPath);
878
+ this.libBins = libContributions.bin ?? [];
539
879
  this.barrelImports = [
540
880
  ...DEFAULT_BARREL_IMPORTS,
541
881
  ...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
@@ -563,100 +903,186 @@ class AkanAppConfig {
563
903
  ]
564
904
  };
565
905
  this.syncPageLibs = config?.syncPageLibs ?? false;
566
- this.hasMobileConfig = Boolean(config.mobile);
567
- this.mobile = this.#resolveMobileConfig(config.mobile);
906
+ if (config.mobile !== undefined)
907
+ throw new Error(`apps/${this.app.name}/akan.config.ts declares \`mobile\`, which is now \`native\`: the platform settings sit in native.ios, native.android and native.desktop, and a target overrides them under native.targets.<name>.`);
908
+ this.hasNativeConfig = Boolean(config.native);
909
+ this.native = this.#resolveNativeConfig(config.native);
568
910
  this.web = this.#resolveWebConfig(config.web);
569
911
  this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
570
912
  this.dockerfile = this.#makeDockerfile();
571
913
  }
572
914
  #resolveWebConfig(web) {
573
915
  const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
574
- if (!resolved.csr && this.hasMobileConfig)
575
- throw new Error(`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares mobile targets; the Capacitor build ships that bundle. Drop the mobile section or leave CSR on.`);
916
+ if (!resolved.csr && this.hasNativeConfig)
917
+ throw new Error(`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares a native section; the native apps ship that bundle. Drop the native section or leave CSR on.`);
576
918
  return resolved;
577
919
  }
578
- #resolveMobileConfig(mobile) {
579
- const {
580
- targets: rawTargets,
581
- indexPath: _indexPath,
582
- ...rawMobile
583
- } = mobile ?? {};
584
- const appName = rawMobile.appName ?? this.app.name;
585
- const appId = rawMobile.appId ?? deriveDefaultAppId(this.baseDevEnv.repoName, this.app.name);
586
- const version = rawMobile.version ?? "0.0.1";
587
- const buildNum = rawMobile.buildNum ?? 1;
588
- const defaultTargetName = this.#defaultMobileTargetName(rawTargets);
589
- const targetEntries = Object.entries(rawTargets ?? {
590
- [defaultTargetName]: {}
591
- });
592
- const targets = Object.fromEntries(targetEntries.map(([name, rawTarget]) => {
593
- const target = rawTarget;
594
- const fallbackBasePath = !rawTargets && this.basePaths.has(name) ? name : undefined;
595
- const basePath = (target.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
596
- const indexPath = normalizeIndexPath(target.indexPath);
597
- const deepLinks = normalizeDeepLinks(target.deepLinks);
598
- if (basePath && !this.basePaths.has(basePath)) {
599
- throw new Error(`Mobile target '${name}' uses unknown basePath '${basePath}' in apps/${this.app.name}/akan.config.ts`);
600
- }
601
- const resolved = {
602
- ...rawMobile,
603
- ...target,
920
+ #resolveNativeConfig(native) {
921
+ const configPath = `apps/${this.app.name}/akan.config.ts`;
922
+ const { targets: rawTargets, ...root } = native ?? {};
923
+ AkanAppConfig.#assertNativeKeys(root, "native", configPath);
924
+ AkanAppConfig.#assertFiles(root, "native", configPath);
925
+ const appName = root.appName ?? this.app.name;
926
+ const appId = root.appId ?? deriveDefaultAppId(this.baseDevEnv.repoName, this.app.name);
927
+ const version = root.version ?? "0.0.1";
928
+ const buildNum = root.buildNum ?? 1;
929
+ const named = rawTargets !== undefined && Object.keys(rawTargets).length > 0;
930
+ const entries = named ? Object.entries(rawTargets) : [[this.basePaths.has(this.app.name) ? this.app.name : "default", {}]];
931
+ const targets = Object.fromEntries(entries.map(([name, own]) => {
932
+ const where = named ? `native.targets.${name}` : "native";
933
+ AkanAppConfig.#assertNativeKeys(own ?? {}, where, configPath);
934
+ const merged = AkanAppConfig.#overlay(root, own ?? {});
935
+ const fallbackBasePath = !named && this.basePaths.has(name) ? name : undefined;
936
+ const basePath = (merged.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
937
+ if (basePath && !this.basePaths.has(basePath))
938
+ throw new Error(`Native target '${name}' uses unknown basePath '${basePath}' in ${configPath}`);
939
+ const { updates } = merged;
940
+ if (updates && (!updates.url || !updates.publicKey))
941
+ throw new Error(`${where}.updates in ${configPath} has no url or publicKey${named ? "; give them in native.updates or the target's own" : ""}.`);
942
+ AkanAppConfig.#assertFiles(own ?? {}, where, configPath);
943
+ const {
944
+ basePath: _basePath,
945
+ indexPath: rawIndexPath,
946
+ deepLinks: rawDeepLinks,
947
+ updates: _updates,
948
+ ...rest
949
+ } = merged;
950
+ const indexPath = normalizeIndexPath(rawIndexPath);
951
+ const deepLinks = normalizeDeepLinks(rawDeepLinks);
952
+ const { teamId: rawTeamId, ...iosRest } = AkanAppConfig.#platformIndexPath(merged.ios ?? {});
953
+ const teamId = rawTeamId?.trim();
954
+ const { sha256CertFingerprints: rawFingerprints, ...androidRest } = AkanAppConfig.#platformIndexPath(merged.android ?? {});
955
+ const fingerprints = normalizeStringList(rawFingerprints);
956
+ const target = {
957
+ ...rest,
604
958
  name,
605
- basePath,
606
- indexPath,
607
- deepLinks,
608
- appName: target.appName ?? appName,
609
- appId: target.appId ?? appId,
610
- version: target.version ?? version,
611
- buildNum: target.buildNum ?? buildNum,
612
- plugins: {
613
- ...rawMobile.plugins,
614
- ...target.plugins
615
- },
616
- android: {
617
- ...rawMobile.android,
618
- ...target.android
619
- },
620
- ios: {
621
- ...rawMobile.ios,
622
- ...target.ios
623
- }
959
+ ...basePath ? { basePath } : {},
960
+ ...indexPath ? { indexPath } : {},
961
+ ...deepLinks ? { deepLinks } : {},
962
+ ...merged.ios ? { ios: { ...iosRest, ...teamId ? { teamId } : {} } } : {},
963
+ ...merged.android ? { android: { ...androidRest, ...fingerprints ? { sha256CertFingerprints: fingerprints } : {} } } : {},
964
+ ...merged.desktop ? { desktop: AkanAppConfig.#platformIndexPath(merged.desktop) } : {},
965
+ appName: merged.appName ?? this.app.name,
966
+ appId: merged.appId ?? appId,
967
+ version: merged.version ?? version,
968
+ buildNum: merged.buildNum ?? buildNum,
969
+ ...updates ? { updates } : {}
624
970
  };
625
- return [name, resolved];
971
+ return [name, target];
626
972
  }));
627
- return {
628
- ...rawMobile,
629
- appName,
630
- appId,
631
- version,
632
- buildNum,
633
- targets,
634
- plugins: rawMobile.plugins
973
+ return { appName, appId, ...root.fileName ? { fileName: root.fileName } : {}, version, buildNum, targets };
974
+ }
975
+ static #platformIndexPath(section) {
976
+ const { indexPath: rawIndexPath, ...rest } = section;
977
+ const indexPath = normalizeIndexPath(rawIndexPath);
978
+ return { ...rest, ...indexPath ? { indexPath } : {} };
979
+ }
980
+ static #overlay(base, own) {
981
+ const merge = (kept, value, top, key) => {
982
+ if (!AkanAppConfig.#isPlainObject(kept) || !AkanAppConfig.#isPlainObject(value))
983
+ return value;
984
+ if (top && (key === "icon" || key === "splash"))
985
+ return value;
986
+ const out = { ...kept };
987
+ for (const [inner, next] of Object.entries(value))
988
+ if (next !== undefined)
989
+ out[inner] = merge(out[inner], next, false, inner);
990
+ return out;
635
991
  };
636
- }
637
- #defaultMobileTargetName(rawTargets) {
638
- if (rawTargets && Object.keys(rawTargets).length > 0)
639
- return Object.keys(rawTargets)[0];
640
- return this.basePaths.has(this.app.name) ? this.app.name : "default";
992
+ const out = { ...base };
993
+ for (const [key, value] of Object.entries(own))
994
+ if (value !== undefined)
995
+ out[key] = merge(out[key], value, true, key);
996
+ return out;
997
+ }
998
+ static #isPlainObject(value) {
999
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1000
+ }
1001
+ static #nativeKeys = {
1002
+ native: [
1003
+ "basePath",
1004
+ "indexPath",
1005
+ "appName",
1006
+ "appId",
1007
+ "fileName",
1008
+ "version",
1009
+ "buildNum",
1010
+ "icon",
1011
+ "splash",
1012
+ "permissions",
1013
+ "plugins",
1014
+ "deepLinks",
1015
+ "updates",
1016
+ "ios",
1017
+ "android",
1018
+ "desktop"
1019
+ ],
1020
+ ios: ["indexPath", "teamId", "infoPlist", "entitlements", "privacy", "files"],
1021
+ android: [
1022
+ "indexPath",
1023
+ "sha256CertFingerprints",
1024
+ "googleServices",
1025
+ "push",
1026
+ "autoplay",
1027
+ "manifest",
1028
+ "application",
1029
+ "activity",
1030
+ "files"
1031
+ ],
1032
+ desktop: ["indexPath", "server", "recovery", "window", "screenCapture"],
1033
+ deepLinks: ["schemes", "domains"]
1034
+ };
1035
+ static #movedKeys = {
1036
+ native: "the platform sections and plugins sit directly in the native section: ios, android, desktop, plugins",
1037
+ assets: "icon and splash sit directly in the native section",
1038
+ files: "ios.files (keyed by the path in the app bundle) or android.files (keyed res/<type>/<file> or assets/<path>)",
1039
+ push: "android.push",
1040
+ privacy: "ios.privacy",
1041
+ "deepLinks.ios": "ios.teamId",
1042
+ "deepLinks.android": "android.sha256CertFingerprints"
1043
+ };
1044
+ static #assertNativeKeys(section, where, configPath) {
1045
+ const check = (value, known, at, kind) => {
1046
+ if (!AkanAppConfig.#isPlainObject(value))
1047
+ return;
1048
+ for (const key of Object.keys(value)) {
1049
+ if (known.includes(key))
1050
+ continue;
1051
+ const moved = AkanAppConfig.#movedKeys[kind === "native" ? key : `${kind}.${key}`];
1052
+ throw new Error(moved ? `${at}.${key} in ${configPath} has moved: ${moved}.` : `${at}.${key} in ${configPath} is not a native setting. Known: ${known.join(", ")}.`);
1053
+ }
1054
+ };
1055
+ check(section, AkanAppConfig.#nativeKeys.native, where, "native");
1056
+ const settings = section;
1057
+ check(settings.ios, AkanAppConfig.#nativeKeys.ios, `${where}.ios`, "ios");
1058
+ check(settings.android, AkanAppConfig.#nativeKeys.android, `${where}.android`, "android");
1059
+ check(settings.desktop, AkanAppConfig.#nativeKeys.desktop, `${where}.desktop`, "desktop");
1060
+ check(settings.deepLinks, AkanAppConfig.#nativeKeys.deepLinks, `${where}.deepLinks`, "deepLinks");
1061
+ }
1062
+ static #androidFilePattern = /^(?:res\/[^/]+\/[^/]+|assets\/.+)$/;
1063
+ static #assertFiles(settings, where, configPath) {
1064
+ const check = (files, at, valid, shape) => {
1065
+ for (const [to, from] of Object.entries(files ?? {})) {
1066
+ if (typeof from !== "string")
1067
+ throw new Error(`${at}["${to}"] in ${configPath} must name its source file, relative to the app folder, e.g. { "${shape}": "assets/chime.mp3" }.`);
1068
+ if (!valid(to))
1069
+ throw new Error(`${at}["${to}"] in ${configPath} must land at ${shape}.`);
1070
+ }
1071
+ };
1072
+ check(settings.ios?.files, `${where}.ios.files`, (to) => to.length > 0 && !to.startsWith("/") && !to.split("/").includes(".."), "<path in the app bundle>");
1073
+ check(settings.android?.files, `${where}.android.files`, (to) => AkanAppConfig.#androidFilePattern.test(to), "res/<type>/<file> or assets/<path>");
641
1074
  }
642
1075
  #applyRoutes(routes = []) {
643
1076
  for (const route of routes) {
644
- if (route.basePath) {
645
- const basePath = route.basePath.replace(/^\/+|\/+$/g, "");
1077
+ const basePath = route.basePath ? route.basePath.replace(/^\/+|\/+$/g, "") : null;
1078
+ if (basePath !== null)
646
1079
  this.basePaths.add(basePath);
647
- const domains = this.subRoutes.getOrInsert(basePath, new Set);
648
- Object.keys(route.domains).forEach((branch) => void this.branches.add(branch));
649
- Object.values(route.domains).flat().forEach((domain) => {
650
- if (domain)
651
- domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
652
- });
653
- } else {
654
- Object.keys(route.domains).forEach((branch) => void this.branches.add(branch));
655
- Object.values(route.domains).flat().forEach((domain) => {
656
- if (domain)
657
- this.domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
658
- });
659
- }
1080
+ const domains = basePath !== null ? this.subRoutes.getOrInsert(basePath, new Set) : this.domains;
1081
+ for (const branch of Object.keys(route.domains))
1082
+ this.branches.add(branch);
1083
+ for (const domain of Object.values(route.domains).flat())
1084
+ if (domain)
1085
+ domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
660
1086
  }
661
1087
  const appName = this.app.name.toLowerCase();
662
1088
  const serveDomain = this.baseDevEnv.serveDomain.toLowerCase();
@@ -693,18 +1119,37 @@ class AkanAppConfig {
693
1119
  command: docker?.command ?? ["bun", "main.js"]
694
1120
  };
695
1121
  }
696
- #makeDockerfile() {
1122
+ getProductionEnv(environment = this.baseDevEnv.env) {
1123
+ return {
1124
+ PORT: "8282",
1125
+ NODE_ENV: "production",
1126
+ AKAN_PUBLIC_REPO_NAME: this.baseDevEnv.repoName,
1127
+ AKAN_PUBLIC_SERVE_DOMAIN: this.baseDevEnv.serveDomain,
1128
+ AKAN_PUBLIC_APP_NAME: this.app.name,
1129
+ AKAN_PUBLIC_ENV: environment,
1130
+ ...this.basePaths.size ? { AKAN_PUBLIC_BASE_PATHS: [...this.basePaths].join(",") } : {},
1131
+ AKAN_PUBLIC_DEFAULT_LOCALE: this.i18n.defaultLocale,
1132
+ AKAN_PUBLIC_LOCALES: this.i18n.locales.join(","),
1133
+ AKAN_PUBLIC_API_PREFIX: this.api.prefix,
1134
+ AKAN_PUBLIC_WS_PREFIX: this.api.websocketPrefix,
1135
+ AKAN_PUBLIC_OPERATION_MODE: "cloud",
1136
+ AKAN_DATABASE_MODES: this.database.modes.join(","),
1137
+ AKAN_LOG_TO_FILE: "0",
1138
+ ...this.web.ssr ? {} : { AKAN_SSR: "false" },
1139
+ ...this.web.csr ? {} : { AKAN_CSR: "false" }
1140
+ };
1141
+ }
1142
+ dockerfileFor(environment) {
1143
+ return this.#makeDockerfile(environment);
1144
+ }
1145
+ #makeDockerfile(environment) {
697
1146
  if (typeof this.docker === "string")
698
1147
  return this.docker;
699
1148
  const { image, preRuns, postRuns, command } = this.docker;
700
1149
  const preRunScripts = this.#getDockerRunScripts(preRuns);
701
1150
  const postRunScripts = this.#getDockerRunScripts(postRuns);
702
1151
  const imageScript = this.#getDockerImageScript(image, DEFAULT_DOCKER_IMAGE);
703
- const webEnvLines = [
704
- ...this.web.ssr ? [] : ["ENV AKAN_SSR=false"],
705
- ...this.web.csr ? [] : ["ENV AKAN_CSR=false"]
706
- ].join(`
707
- `);
1152
+ const envLines = Object.entries(this.getProductionEnv(environment)).map(([key, value]) => `ENV ${key}=${value}`);
708
1153
  return `${imageScript}
709
1154
  RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
710
1155
  RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
@@ -718,20 +1163,8 @@ RUN bun install --production
718
1163
  ${postRunScripts.join(`
719
1164
  `)}
720
1165
  COPY . .
721
- ENV PORT=8282
722
- ENV NODE_ENV=production
723
- ENV AKAN_PUBLIC_REPO_NAME=${this.baseDevEnv.repoName}
724
- ENV AKAN_PUBLIC_SERVE_DOMAIN=${this.baseDevEnv.serveDomain}
725
- ENV AKAN_PUBLIC_APP_NAME=${this.app.name}
726
- ENV AKAN_PUBLIC_ENV=${this.baseDevEnv.env}
727
- ${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join(",")}` : ""}
728
- ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
729
- ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
730
- ENV AKAN_PUBLIC_API_PREFIX=${this.api.prefix}
731
- ENV AKAN_PUBLIC_WS_PREFIX=${this.api.websocketPrefix}
732
- ENV AKAN_PUBLIC_OPERATION_MODE=cloud
733
- ENV AKAN_LOG_TO_FILE=0
734
- ${webEnvLines}
1166
+ ${envLines.join(`
1167
+ `)}
735
1168
  CMD [${command.map((c) => `"${c}"`).join(",")}]`;
736
1169
  }
737
1170
  static #importGeneration = 0;
@@ -743,7 +1176,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
743
1176
  static async from(app, { bustImportCache = false } = {}) {
744
1177
  const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
745
1178
  AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
746
- WorkspaceExecutor.getBaseDevEnv(path2.join(app.workspace.workspaceRoot, ".env")),
1179
+ WorkspaceExecutor.getBaseDevEnv(path5.join(app.workspace.workspaceRoot, ".env")),
747
1180
  app.workspace.getLibs(),
748
1181
  app.workspace.getPackageJson()
749
1182
  ]);
@@ -753,503 +1186,159 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
753
1186
  return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
754
1187
  }
755
1188
  static async#collectLibContributions(app, libs, bustImportCache) {
756
- const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
757
- app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
758
- return null;
759
- })));
760
- return {
761
- externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
762
- docker: {
763
- preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
764
- postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? [])
765
- },
766
- keepFonts: libConfigs.flatMap((libConfig) => (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`))
767
- };
768
- }
769
- #resolveProductionDependencyVersion(lib) {
770
- const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
771
- if (rootVersion)
772
- return rootVersion;
773
- const akanPackageJson = getAkanPackageJson();
774
- return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
775
- }
776
- #getProductionRuntimePackages() {
777
- return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
778
- }
779
- getDatabaseModeRuntimePackages(databaseMode = this.defaultDatabaseMode) {
780
- return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
781
- }
782
- getMissingDatabaseModeDependencySpecs(databaseMode = this.defaultDatabaseMode) {
783
- return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
784
- }
785
- getMobileRuntimePackages() {
786
- return [...new Set([...MOBILE_RUNTIME_PACKAGES, ...MOBILE_APP_CAPACITOR_PLUGINS])];
787
- }
788
- getMissingMobileDependencySpecs() {
789
- return this.#getMissingDependencySpecs(this.getMobileRuntimePackages());
790
- }
791
- getMobileAppCapacitorPlugins() {
792
- return [...MOBILE_APP_CAPACITOR_PLUGINS];
793
- }
794
- #getMissingDependencySpecs(libs) {
795
- const rootDependencies = {
796
- ...this.rootPackageJson.dependencies,
797
- ...this.rootPackageJson.devDependencies
798
- };
799
- return libs.filter((lib) => !rootDependencies[lib]).map((lib) => {
800
- const version = this.#resolveProductionDependencyVersion(lib);
801
- if (!version)
802
- throw new Error(`Dependency ${lib} not found in package.json`);
803
- return `${lib}@${version}`;
804
- });
805
- }
806
- getProductionPackageJson(data = {}) {
807
- return {
808
- name: this.app.name,
809
- description: this.app.name,
810
- version: "1.0.0",
811
- main: "./main.js",
812
- dependencies: Object.fromEntries([...new Set(this.#getProductionRuntimePackages())].map((lib) => {
813
- const version = this.#resolveProductionDependencyVersion(lib);
814
- if (!version)
815
- throw new Error(`Dependency ${lib} not found in package.json`);
816
- return [lib, version];
817
- })),
818
- ...data
819
- };
820
- }
821
- }
822
- var akanPackageJson = null;
823
- function getAkanPackageJson() {
824
- if (akanPackageJson)
825
- return akanPackageJson;
826
- const sourceDir = path2.dirname(fileURLToPath(import.meta.url));
827
- const packageJsonPaths = [
828
- path2.join(sourceDir, "../../../akanjs/package.json"),
829
- path2.join(process.cwd(), "pkgs/akanjs/package.json"),
830
- path2.join(path2.dirname(Bun.main), "node_modules/akanjs/package.json")
831
- ];
832
- try {
833
- packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
834
- } catch {}
835
- for (const packageJsonPath of packageJsonPaths) {
836
- try {
837
- akanPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
838
- return akanPackageJson;
839
- } catch {}
840
- }
841
- akanPackageJson = {
842
- name: "akanjs",
843
- version: "0.0.0",
844
- description: "akanjs",
845
- dependencies: {}
846
- };
847
- return akanPackageJson;
848
- }
849
- function mergeImageConfig(config = {}) {
850
- return {
851
- ...DEFAULT_AKAN_IMAGE_CONFIG,
852
- ...config,
853
- deviceSizes: config.deviceSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.deviceSizes,
854
- imageSizes: config.imageSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.imageSizes,
855
- formats: config.formats ?? DEFAULT_AKAN_IMAGE_CONFIG.formats,
856
- qualities: config.qualities ?? DEFAULT_AKAN_IMAGE_CONFIG.qualities,
857
- remotePatterns: config.remotePatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.remotePatterns,
858
- localPatterns: config.localPatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.localPatterns
859
- };
860
- }
861
-
862
- class AkanLibConfig {
863
- lib;
864
- externalLibs;
865
- docker;
866
- assets;
867
- plugins;
868
- constructor(lib, config, plugins = []) {
869
- this.lib = lib;
870
- this.externalLibs = config?.externalLibs ?? [];
871
- this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
872
- this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
873
- this.plugins = plugins;
874
- }
875
- static async from(lib, { bustImportCache = false } = {}) {
876
- const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
877
- const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
878
- const { plugins, ...config } = resolved ?? {};
879
- return new AkanLibConfig(lib, config, plugins ?? []);
880
- }
881
- }
882
- //! need to refactor
883
- var increaseBuildNum = async (app) => {
884
- const appConfig = await AkanAppConfig.from(app);
885
- const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
886
- const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
887
- const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum + 1}`);
888
- fs.writeFileSync(akanConfigPath, akanConfigContent);
889
- };
890
- var decreaseBuildNum = async (app) => {
891
- const appConfig = await AkanAppConfig.from(app);
892
- const akanConfigPath = path2.join(app.cwdPath, "akan.config.ts");
893
- const akanConfig = fs.readFileSync(akanConfigPath, "utf8");
894
- const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
895
- fs.writeFileSync(akanConfigPath, akanConfigContent);
896
- };
897
- // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
898
- import { mkdir as mkdir2 } from "fs/promises";
899
- import path4 from "path";
900
-
901
- // pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
902
- import fs2 from "fs";
903
- import path3 from "path";
904
-
905
- class AsyncDefaultExportDetector {
906
- static #typescriptLoad;
907
- static #loadTypescript() {
908
- AsyncDefaultExportDetector.#typescriptLoad ??= import("typescript").then((mod) => mod.default ?? mod);
909
- return AsyncDefaultExportDetector.#typescriptLoad;
910
- }
911
- static async detect(moduleAbsPath) {
912
- try {
913
- const typescript = await AsyncDefaultExportDetector.#loadTypescript();
914
- const source = fs2.readFileSync(path3.resolve(moduleAbsPath), "utf8");
915
- const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, AsyncDefaultExportDetector.#scriptKind(typescript, moduleAbsPath));
916
- return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
917
- } catch {
918
- return false;
919
- }
920
- }
921
- #ts;
922
- constructor(typescript) {
923
- this.#ts = typescript;
924
- }
925
- detectInSourceFile(sourceFile) {
926
- const ts = this.#ts;
927
- const asyncBindings = new Map;
928
- let defaultIdentifier = null;
929
- for (const statement of sourceFile.statements) {
930
- if (ts.isFunctionDeclaration(statement)) {
931
- if (this.#hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
932
- return this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword);
933
- }
934
- if (statement.name) {
935
- asyncBindings.set(statement.name.text, this.#hasModifier(statement, ts.SyntaxKind.AsyncKeyword));
936
- }
937
- continue;
938
- }
939
- if (ts.isVariableStatement(statement)) {
940
- for (const declaration of statement.declarationList.declarations) {
941
- if (!ts.isIdentifier(declaration.name))
942
- continue;
943
- asyncBindings.set(declaration.name.text, this.#isAsyncFunctionExpression(declaration.initializer));
944
- }
945
- continue;
946
- }
947
- if (ts.isExportAssignment(statement)) {
948
- if (this.#isAsyncFunctionExpression(statement.expression))
949
- return true;
950
- if (ts.isCallExpression(statement.expression))
951
- return true;
952
- if (ts.isIdentifier(statement.expression))
953
- defaultIdentifier = statement.expression.text;
954
- continue;
955
- }
956
- if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
957
- const exportClause = statement.exportClause;
958
- for (const specifier of exportClause.elements) {
959
- if (specifier.name.text !== "default")
960
- continue;
961
- defaultIdentifier = specifier.propertyName?.text ?? specifier.name.text;
962
- }
963
- }
964
- }
965
- return defaultIdentifier ? asyncBindings.get(defaultIdentifier) === true : false;
1189
+ const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
1190
+ app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
1191
+ return null;
1192
+ })));
1193
+ return {
1194
+ externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
1195
+ trustedDependencies: libConfigs.flatMap((libConfig) => libConfig?.trustedDependencies ?? []),
1196
+ bin: libConfigs.flatMap((libConfig) => libConfig && Object.keys(libConfig.bin).length ? [{ lib: libConfig.lib.name, bin: libConfig.bin }] : []),
1197
+ docker: {
1198
+ preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
1199
+ postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? [])
1200
+ },
1201
+ keepFonts: libConfigs.flatMap((libConfig) => (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`))
1202
+ };
966
1203
  }
967
- #hasModifier(node, kind) {
968
- const ts = this.#ts;
969
- return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
1204
+ #resolveProductionDependencyVersion(lib) {
1205
+ const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
1206
+ if (rootVersion)
1207
+ return rootVersion;
1208
+ const akanPackageJson = getAkanPackageJson();
1209
+ const version = akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
1210
+ if (!version)
1211
+ throw new Error(`Dependency ${lib} not found in package.json`);
1212
+ return version;
970
1213
  }
971
- #isAsyncFunctionExpression(node) {
972
- const ts = this.#ts;
973
- return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
1214
+ #getProductionRuntimePackages() {
1215
+ return [
1216
+ ...this.externalLibs,
1217
+ ...SSR_RUNTIME_PACKAGES,
1218
+ ...this.database.modes.flatMap((mode) => this.getDatabaseModeRuntimePackages(mode))
1219
+ ];
974
1220
  }
975
- static #scriptKind(typescript, moduleAbsPath) {
976
- return moduleAbsPath.endsWith(".tsx") || moduleAbsPath.endsWith(".jsx") ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS;
1221
+ getDatabaseModeRuntimePackages(databaseMode) {
1222
+ return [...DatabaseModes.drivers[databaseMode]];
977
1223
  }
978
- }
979
-
980
- // pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
981
- var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
982
- async function appHasStModule(appCwdPath) {
983
- return Bun.file(path4.join(appCwdPath, "lib", "st.ts")).exists();
984
- }
985
- var IMPLICIT_LAYOUT_DIR = path4.join(".akan", "generated", "root-layouts");
986
- var IMPLICIT_DICT_DIR = path4.join(".akan", "generated", "dict");
987
- var IMPLICIT_OVERRIDES_DIR = path4.join(".akan", "generated", "overrides");
988
- var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
989
- async function writeGeneratedOverridesLayoutFile(opts) {
990
- const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
991
- const absPath = path4.join(path4.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
992
- const userRel = path4.relative(path4.dirname(absPath), opts.userAbsPath).split(path4.sep).join("/");
993
- const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
994
- const source = `"use client";
995
- import { UiOverrideProvider } from "akanjs/ui";
996
- import { createElement, type ReactNode } from "react";
997
- import value from ${JSON.stringify(userSpecifier)};
998
-
999
- export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
1000
- return createElement(UiOverrideProvider, { value }, children);
1001
- }
1002
- `;
1003
- await Bun.write(absPath, source);
1004
- return absPath;
1005
- }
1006
- function getRootBoundarySegments(key) {
1007
- const match = LAYOUT_KEY_RE.exec(key);
1008
- if (!match)
1009
- return null;
1010
- const prefix = match[1]?.replace(/\/$/, "");
1011
- if (!prefix)
1012
- return [];
1013
- return prefix.split("/").filter(Boolean);
1014
- }
1015
- function implicitRootLayoutKey(segments) {
1016
- return `./${[...segments, "__root_layout"].join("/")}.tsx`;
1017
- }
1018
- function implicitRootLayoutAbsPath(appCwdPath, segments) {
1019
- const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
1020
- return path4.join(path4.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
1021
- }
1022
- function implicitDictionaryMacroAbsPath(appCwdPath) {
1023
- return path4.join(path4.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
1024
- }
1025
- function isRootBoundarySegments(segments, basePaths) {
1026
- const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
1027
- if (firstVisibleIndex === -1)
1028
- return segments.length <= 1;
1029
- if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
1030
- return false;
1031
- const visible = segments.slice(firstVisibleIndex);
1032
- const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
1033
- return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
1034
- }
1035
- function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
1036
- const boundaries = new Map;
1037
- for (const key of pageKeys) {
1038
- const segments = getRootBoundarySegments(key);
1039
- if (!segments)
1040
- continue;
1041
- if (!isRootBoundarySegments(segments, basePaths))
1042
- continue;
1043
- const id = segments.join("/");
1044
- boundaries.set(id, {
1045
- sourceKey: key,
1046
- sourceAbsPath: path4.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
1047
- segments
1224
+ getMissingDatabaseModeDependencySpecs(databaseMode) {
1225
+ return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
1226
+ }
1227
+ resolveDatabaseMode() {
1228
+ return DatabaseModes.resolve({
1229
+ requested: process.env.AKAN_DATABASE_MODE,
1230
+ declared: this.database.modes.join(","),
1231
+ local: true
1048
1232
  });
1049
1233
  }
1050
- const hasExplicitRootBoundary = [...boundaries.values()].some((boundary) => boundary.segments.length === 0);
1051
- if (!hasExplicitRootBoundary && boundaries.size === 0) {
1052
- boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
1234
+ static #database(app, config) {
1235
+ const declared = config?.database?.modes?.filter((mode) => !!mode);
1236
+ const modes = declared?.length ? declared : ["single"];
1237
+ return { modes: DatabaseModes.parseList(modes.join(","), `database.modes in apps/${app.name}/akan.config.ts`) };
1053
1238
  }
1054
- return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
1055
- }
1056
- function hasAncestorRootBoundary(boundary, boundaries) {
1057
- return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
1058
- }
1059
- function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
1060
- const rootLayoutKey = pageKeys.find((key) => {
1061
- const segments = getRootBoundarySegments(key);
1062
- return segments !== null && segments.length === 0;
1063
- });
1064
- return rootLayoutKey ? path4.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
1065
- }
1066
- function routePrefixForSegments(segments) {
1067
- const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
1068
- return visible[0] ?? null;
1069
- }
1070
- async function assertEnvClientConvention(appCwdPath, appName) {
1071
- const envPath = path4.join(appCwdPath, "env", "env.client.ts");
1072
- if (!await Bun.file(envPath).exists()) {
1073
- throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
1239
+ #getMissingDependencySpecs(libs) {
1240
+ const rootDependencies = {
1241
+ ...this.rootPackageJson.dependencies,
1242
+ ...this.rootPackageJson.devDependencies
1243
+ };
1244
+ return libs.filter((lib) => !rootDependencies[lib]).map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
1245
+ }
1246
+ get akanVersion() {
1247
+ return getAkanPackageJson().version;
1248
+ }
1249
+ getProductionPackageJson(data = {}) {
1250
+ return {
1251
+ name: this.app.name,
1252
+ description: this.app.name,
1253
+ version: "1.0.0",
1254
+ main: "./main.js",
1255
+ dependencies: Object.fromEntries([...new Set(this.#getProductionRuntimePackages())].map((lib) => [
1256
+ lib,
1257
+ this.#resolveProductionDependencyVersion(lib)
1258
+ ])),
1259
+ ...this.trustedDependencies.length ? { trustedDependencies: this.trustedDependencies } : {},
1260
+ ...data
1261
+ };
1074
1262
  }
1075
1263
  }
1076
- async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
1077
- const absPath = implicitDictionaryMacroAbsPath(appCwdPath);
1078
- await mkdir2(path4.dirname(absPath), { recursive: true });
1079
- await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
1080
-
1081
- export const allDictionary = getAllDictionary();
1082
- `);
1083
- return absPath;
1084
- }
1085
- async function writeGeneratedRootLayoutFile(opts) {
1086
- await assertEnvClientConvention(opts.appCwdPath, opts.appName);
1087
- const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
1088
- const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
1089
- await mkdir2(path4.dirname(absPath), { recursive: true });
1090
- const dictMacroRel = dictMacroAbsPath ? path4.relative(path4.dirname(absPath), dictMacroAbsPath).split(path4.sep).join("/") : null;
1091
- const dictMacroSpecifier = dictMacroRel ? dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}` : null;
1092
- const sourceRel = opts.boundary.sourceAbsPath ? path4.relative(path4.dirname(absPath), opts.boundary.sourceAbsPath).split(path4.sep).join("/") : null;
1093
- const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
1094
- const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
1095
- const inheritedSourceRel = inheritedSourceAbsPath ? path4.relative(path4.dirname(absPath), inheritedSourceAbsPath).split(path4.sep).join("/") : null;
1096
- const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
1097
- const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
1098
- void st;
1099
- ` : `import "@apps/${opts.appName}/client";
1100
- `;
1101
- const inheritedLabel = inheritedSourceAbsPath ? path4.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
1102
- const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
1103
- const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
1104
- ` : `const inheritedLayout: LayoutModule = {};
1105
- `;
1106
- const prefix = routePrefixForSegments(opts.boundary.segments);
1107
- const userLabel = opts.boundary.sourceAbsPath ? path4.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
1108
- const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
1109
- const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
1110
- const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
1111
- ` : `const UserLayout = ({ children }: LayoutProps) => children;
1112
- const userLayout: LayoutModule = {};
1113
- `;
1114
- const isAsyncUserLayout = opts.boundary.sourceAbsPath ? await AsyncDefaultExportDetector.detect(opts.boundary.sourceAbsPath) : false;
1115
- const userLayoutElement = "<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>";
1116
- const layoutSignature = isAsyncUserLayout ? "export default async function GeneratedLayout" : "export default function GeneratedLayout";
1117
- const layoutBinding = isAsyncUserLayout ? ` const layout =
1118
- process.env.AKAN_PUBLIC_RENDER_ENV === "csr"
1119
- ? await UserLayout({ params, searchParams, children })
1120
- : ${userLayoutElement};
1121
- ` : "";
1122
- const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
1123
- const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
1124
- const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
1125
- import { loadFonts, resolveRouteModule } from "akanjs/client";
1126
- import type { ReactNode } from "react";
1127
- import { System } from "akanjs/ui";
1128
- import { env } from "@apps/${opts.appName}/env/env.client";
1129
- import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
1130
- ${clientImport}${inheritedImport}${userImport}
1131
- const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
1132
- const defaultFonts = userFonts.filter((font) => font.default);
1133
- if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
1134
- const defaultFont = defaultFonts[0];
1135
- const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
1136
-
1137
- export async function generateHead(props: PageProps) {
1138
- if (userLayout.generateHead) return userLayout.generateHead(props);
1139
- if (userLayout.head !== undefined) return userLayout.head;
1140
- if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
1141
- return inheritedLayout.head;
1142
- }
1143
-
1144
- export async function generateMetadata(props: PageProps) {
1145
- if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
1146
- if (userLayout.metadata !== undefined) return userLayout.metadata;
1147
- if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
1148
- return inheritedLayout.metadata;
1149
- }
1150
-
1151
- export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
1152
- export const Error = userLayout.Error ?? inheritedLayout.Error;
1153
- export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
1154
-
1155
- ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
1156
- ${layoutBinding} return (
1157
- <System.Provider
1158
- of={GeneratedLayout as never}
1159
- appName=${JSON.stringify(opts.appName)}
1160
- ${prefix ? `prefix=${JSON.stringify(prefix)}
1161
- ` : ""}params={params}
1162
- manifest={userLayout.manifest ?? inheritedLayout.manifest}
1163
- env={env}
1164
- theme={userLayout.theme ?? inheritedLayout.theme}
1165
- fonts={loadFonts(userFonts)}
1166
- className={defaultFontClassName}
1167
- gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
1168
- layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
1169
- reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
1170
- wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
1171
- allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
1172
- >
1173
- ${layoutChild}
1174
- </System.Provider>
1175
- );
1176
- }
1177
- ` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
1178
- import { resolveRouteModule } from "akanjs/client";
1179
- import type { ReactNode } from "react";
1180
- ${inheritedImport}${userImport}
1181
- export async function generateHead(props: PageProps) {
1182
- if (userLayout.generateHead) return userLayout.generateHead(props);
1183
- if (userLayout.head !== undefined) return userLayout.head;
1184
- if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
1185
- return inheritedLayout.head;
1186
- }
1187
-
1188
- export async function generateMetadata(props: PageProps) {
1189
- if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
1190
- if (userLayout.metadata !== undefined) return userLayout.metadata;
1191
- if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
1192
- return inheritedLayout.metadata;
1193
- }
1194
-
1195
- export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
1196
- export const Error = userLayout.Error ?? inheritedLayout.Error;
1197
- export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
1198
-
1199
- ${layoutSignature}({ children, params, searchParams }: LayoutProps) {
1200
- ${layoutBinding} return ${layoutReturn};
1201
- }
1202
- `;
1203
- await Bun.write(absPath, source);
1204
- return absPath;
1205
- }
1206
- async function resolveSsrPageEntries(opts) {
1207
- const absPageDir = path4.resolve(opts.appCwdPath, "page");
1208
- const hasSt = await appHasStModule(opts.appCwdPath);
1209
- const basePaths = opts.basePaths ?? [];
1210
- const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
1211
- const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
1212
- const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
1213
- const segments = getRootBoundarySegments(key);
1214
- return segments !== null && isRootBoundarySegments(segments, basePaths);
1215
- }));
1216
- const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
1217
- const userAbsPath = path4.resolve(absPageDir, key);
1218
- if (OVERRIDES_KEY_RE.test(key)) {
1219
- const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
1220
- appCwdPath: opts.appCwdPath,
1221
- key,
1222
- userAbsPath
1223
- });
1224
- return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
1225
- }
1226
- return { key, moduleAbsPath: userAbsPath };
1227
- }));
1228
- const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
1229
- key: implicitRootLayoutKey(boundary.segments),
1230
- moduleAbsPath: await writeGeneratedRootLayoutFile({
1231
- appCwdPath: opts.appCwdPath,
1232
- appName: opts.appName,
1233
- boundary,
1234
- rootSourceAbsPath,
1235
- includeStInit: hasSt && boundary.segments.length === 0,
1236
- includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
1237
- }),
1238
- seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
1239
- })));
1240
- const entries = [...base, ...generated];
1241
- entries.sort((a, b) => a.key.localeCompare(b.key));
1242
- return entries;
1264
+ var akanPackageJson = null;
1265
+ function getAkanPackageJson() {
1266
+ if (akanPackageJson)
1267
+ return akanPackageJson;
1268
+ const sourceDir = path5.dirname(fileURLToPath(import.meta.url));
1269
+ const packageJsonPaths = [
1270
+ path5.join(sourceDir, "../../../akanjs/package.json"),
1271
+ path5.join(process.cwd(), "pkgs/akanjs/package.json"),
1272
+ path5.join(path5.dirname(Bun.main), "node_modules/akanjs/package.json")
1273
+ ];
1274
+ try {
1275
+ packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
1276
+ } catch {}
1277
+ for (const packageJsonPath of packageJsonPaths) {
1278
+ try {
1279
+ akanPackageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
1280
+ return akanPackageJson;
1281
+ } catch {}
1282
+ }
1283
+ akanPackageJson = {
1284
+ name: "akanjs",
1285
+ version: "0.0.0",
1286
+ description: "akanjs",
1287
+ dependencies: {}
1288
+ };
1289
+ return akanPackageJson;
1243
1290
  }
1244
- async function resolveSsrPageEntriesForApp(app, pageKeys) {
1245
- const config = await app.getConfig();
1246
- return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
1291
+ function mergeImageConfig(config = {}) {
1292
+ return {
1293
+ ...DEFAULT_AKAN_IMAGE_CONFIG,
1294
+ ...config,
1295
+ deviceSizes: config.deviceSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.deviceSizes,
1296
+ imageSizes: config.imageSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.imageSizes,
1297
+ formats: config.formats ?? DEFAULT_AKAN_IMAGE_CONFIG.formats,
1298
+ qualities: config.qualities ?? DEFAULT_AKAN_IMAGE_CONFIG.qualities,
1299
+ remotePatterns: config.remotePatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.remotePatterns,
1300
+ localPatterns: config.localPatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.localPatterns
1301
+ };
1247
1302
  }
1248
1303
 
1304
+ class AkanLibConfig {
1305
+ lib;
1306
+ externalLibs;
1307
+ trustedDependencies;
1308
+ bin;
1309
+ docker;
1310
+ assets;
1311
+ plugins;
1312
+ constructor(lib, config, plugins = []) {
1313
+ this.lib = lib;
1314
+ this.externalLibs = config?.externalLibs ?? [];
1315
+ const owner = `libs/${lib.name}/akan.config.ts`;
1316
+ this.trustedDependencies = normalizePackageNames(config?.trustedDependencies, owner);
1317
+ this.bin = AkanBin.parse(config?.bin, owner, lib.cwdPath);
1318
+ this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
1319
+ this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
1320
+ this.plugins = plugins;
1321
+ }
1322
+ static async from(lib, { bustImportCache = false } = {}) {
1323
+ const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
1324
+ const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
1325
+ const { plugins, ...config } = resolved ?? {};
1326
+ return new AkanLibConfig(lib, config, plugins ?? []);
1327
+ }
1328
+ }
1329
+ //! need to refactor
1330
+ var shiftBuildNum = async (app, delta) => {
1331
+ const { buildNum } = (await AkanAppConfig.from(app)).native;
1332
+ const akanConfigPath = path5.join(app.cwdPath, "akan.config.ts");
1333
+ const akanConfig = fs2.readFileSync(akanConfigPath, "utf8");
1334
+ fs2.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
1335
+ };
1336
+ var increaseBuildNum = async (app) => await shiftBuildNum(app, 1);
1337
+ var decreaseBuildNum = async (app) => await shiftBuildNum(app, -1);
1249
1338
  // pkgs/@akanjs/devkit/linter.ts
1250
1339
  import { spawn } from "child_process";
1251
1340
  import { existsSync, readFileSync } from "fs";
1252
- import path5 from "path";
1341
+ import path6 from "path";
1253
1342
  import chalk from "chalk";
1254
1343
  var parseBiomeReport = (output) => {
1255
1344
  for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
@@ -1299,20 +1388,29 @@ var asBiomeReport = (candidate) => {
1299
1388
  }
1300
1389
  };
1301
1390
  var BIOME_CONFIG_FILES = ["biome.json", "biome.jsonc"];
1391
+ var emptyResult = (filePath) => ({
1392
+ filePath,
1393
+ messages: [],
1394
+ errorCount: 0,
1395
+ warningCount: 0,
1396
+ fixableErrorCount: 0,
1397
+ fixableWarningCount: 0
1398
+ });
1302
1399
 
1303
1400
  class Linter {
1401
+ static biomeBinName = process.platform === "win32" ? "biome.exe" : "biome";
1304
1402
  lintRoot;
1305
1403
  configPath;
1306
1404
  #biomeBin;
1307
1405
  constructor(cwdPath) {
1308
1406
  this.lintRoot = this.#findBiomeRootPath(cwdPath);
1309
- this.configPath = Linter.#configPathIn(this.lintRoot) ?? path5.join(this.lintRoot, "biome.json");
1310
- const localBiomeBin = path5.join(this.lintRoot, "node_modules/.bin/biome");
1407
+ this.configPath = Linter.#configPathIn(this.lintRoot) ?? path6.join(this.lintRoot, "biome.json");
1408
+ const localBiomeBin = path6.join(this.lintRoot, "node_modules", ".bin", Linter.biomeBinName);
1311
1409
  this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
1312
1410
  }
1313
1411
  static #configPathIn(dir) {
1314
1412
  for (const fileName of BIOME_CONFIG_FILES) {
1315
- const configPath = path5.join(dir, fileName);
1413
+ const configPath = path6.join(dir, fileName);
1316
1414
  if (existsSync(configPath))
1317
1415
  return configPath;
1318
1416
  }
@@ -1321,19 +1419,19 @@ class Linter {
1321
1419
  #findBiomeRootPath(dir) {
1322
1420
  if (Linter.#configPathIn(dir))
1323
1421
  return dir;
1324
- const parentDir = path5.dirname(dir);
1422
+ const parentDir = path6.dirname(dir);
1325
1423
  if (parentDir === dir)
1326
1424
  throw new Error(`biome.json not found from ${dir}`);
1327
1425
  return this.#findBiomeRootPath(parentDir);
1328
1426
  }
1329
1427
  #toBiomePath(filePath) {
1330
- const relativePath = path5.relative(this.lintRoot, filePath);
1331
- if (!relativePath.startsWith("..") && !path5.isAbsolute(relativePath))
1428
+ const relativePath = path6.relative(this.lintRoot, filePath);
1429
+ if (!relativePath.startsWith("..") && !path6.isAbsolute(relativePath))
1332
1430
  return relativePath;
1333
1431
  return filePath;
1334
1432
  }
1335
1433
  #resolveFilePath(filePath) {
1336
- return path5.isAbsolute(filePath) ? filePath : path5.join(this.lintRoot, filePath);
1434
+ return path6.isAbsolute(filePath) ? filePath : path6.join(this.lintRoot, filePath);
1337
1435
  }
1338
1436
  async#runBiome(args, input) {
1339
1437
  return await new Promise((resolve, reject) => {
@@ -1358,7 +1456,7 @@ class Linter {
1358
1456
  const diagnosticPath = diagnostic.location?.path;
1359
1457
  if (!diagnosticPath)
1360
1458
  return fallbackFilePath;
1361
- return path5.isAbsolute(diagnosticPath) ? diagnosticPath : path5.join(this.lintRoot, diagnosticPath);
1459
+ return path6.isAbsolute(diagnosticPath) ? diagnosticPath : path6.join(this.lintRoot, diagnosticPath);
1362
1460
  }
1363
1461
  #createLintMessage(diagnostic) {
1364
1462
  const start = diagnostic.location?.start;
@@ -1379,14 +1477,7 @@ class Linter {
1379
1477
  if (diagnostic.severity !== "error" && diagnostic.severity !== "warning")
1380
1478
  continue;
1381
1479
  const diagnosticFilePath = this.#diagnosticFilePath(diagnostic, filePath);
1382
- const result = resultsByPath.get(diagnosticFilePath) ?? {
1383
- filePath: diagnosticFilePath,
1384
- messages: [],
1385
- errorCount: 0,
1386
- warningCount: 0,
1387
- fixableErrorCount: 0,
1388
- fixableWarningCount: 0
1389
- };
1480
+ const result = resultsByPath.get(diagnosticFilePath) ?? emptyResult(diagnosticFilePath);
1390
1481
  const message = this.#createLintMessage(diagnostic);
1391
1482
  result.messages.push(message);
1392
1483
  if (message.severity === 2)
@@ -1396,14 +1487,7 @@ class Linter {
1396
1487
  resultsByPath.set(diagnosticFilePath, result);
1397
1488
  }
1398
1489
  return [
1399
- resultsByPath.get(filePath) ?? {
1400
- filePath,
1401
- messages: [],
1402
- errorCount: 0,
1403
- warningCount: 0,
1404
- fixableErrorCount: 0,
1405
- fixableWarningCount: 0
1406
- },
1490
+ resultsByPath.get(filePath) ?? emptyResult(filePath),
1407
1491
  ...[...resultsByPath.entries()].filter(([resultPath]) => resultPath !== filePath).map(([, result]) => result)
1408
1492
  ];
1409
1493
  }
@@ -1513,12 +1597,7 @@ ${errorText}, ${warningText} found`];
1513
1597
  warningCount: acc.warningCount + result.warningCount,
1514
1598
  fixableErrorCount: acc.fixableErrorCount + result.fixableErrorCount,
1515
1599
  fixableWarningCount: acc.fixableWarningCount + result.fixableWarningCount
1516
- }), {
1517
- errorCount: 0,
1518
- warningCount: 0,
1519
- fixableErrorCount: 0,
1520
- fixableWarningCount: 0
1521
- });
1600
+ }), { errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 });
1522
1601
  return { results, details, stats };
1523
1602
  }
1524
1603
  async hasNoLintErrors(filePath) {
@@ -1576,19 +1655,16 @@ ${errorText}, ${warningText} found`];
1576
1655
  async getProblematicRules(filePath) {
1577
1656
  const { results } = await this.lintFile(filePath);
1578
1657
  const ruleCounts = {};
1579
- results.forEach((result) => {
1580
- result.messages.forEach((message) => {
1581
- if (message.ruleId)
1582
- ruleCounts[message.ruleId] = (ruleCounts[message.ruleId] || 0) + 1;
1583
- });
1584
- });
1658
+ for (const message of results.flatMap((result) => result.messages))
1659
+ if (message.ruleId)
1660
+ ruleCounts[message.ruleId] = (ruleCounts[message.ruleId] || 0) + 1;
1585
1661
  return ruleCounts;
1586
1662
  }
1587
1663
  }
1588
1664
 
1589
1665
  // pkgs/@akanjs/devkit/repoIdentity.ts
1590
1666
  import { execFileSync } from "child_process";
1591
- import path6 from "path";
1667
+ import path7 from "path";
1592
1668
  var resolved = new Map;
1593
1669
  var readRemoteName = (workspaceRoot) => {
1594
1670
  try {
@@ -1606,14 +1682,13 @@ var resolveRepoName = (workspaceRoot) => {
1606
1682
  const cached = resolved.get(workspaceRoot);
1607
1683
  if (cached)
1608
1684
  return cached;
1609
- const repoName = readRemoteName(workspaceRoot) ?? path6.basename(workspaceRoot);
1685
+ const repoName = readRemoteName(workspaceRoot) ?? path7.basename(workspaceRoot);
1610
1686
  resolved.set(workspaceRoot, repoName);
1611
1687
  return repoName;
1612
1688
  };
1613
1689
 
1614
1690
  // pkgs/@akanjs/devkit/scanInfo.ts
1615
- import { rm as rm2 } from "fs/promises";
1616
- import path7 from "path";
1691
+ import path8 from "path";
1617
1692
 
1618
1693
  // pkgs/@akanjs/devkit/workspaceLayout.ts
1619
1694
  var appRootAllowedFiles = new Set([
@@ -1621,7 +1696,6 @@ var appRootAllowedFiles = new Set([
1621
1696
  "CLAUDE.md",
1622
1697
  "akan.app.json",
1623
1698
  "akan.config.ts",
1624
- "capacitor.config.ts",
1625
1699
  "client.ts",
1626
1700
  "main.ts",
1627
1701
  "package.json",
@@ -1631,12 +1705,10 @@ var appRootAllowedFiles = new Set([
1631
1705
  ]);
1632
1706
  var appRootAllowedDirs = new Set([
1633
1707
  ".akan",
1634
- "android",
1635
1708
  "common",
1636
1709
  "env",
1637
- "ios",
1638
1710
  "lib",
1639
- "mobile",
1711
+ "native",
1640
1712
  "page",
1641
1713
  "plugin",
1642
1714
  "private",
@@ -1665,6 +1737,7 @@ var libRootAllowedDirs = new Set([
1665
1737
  "common",
1666
1738
  "env",
1667
1739
  "lib",
1740
+ "native",
1668
1741
  "page",
1669
1742
  "plugin",
1670
1743
  "private",
@@ -1688,13 +1761,20 @@ var libFacetRootAllowedFiles = new Set([
1688
1761
  ]);
1689
1762
  var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
1690
1763
  var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
1764
+ var retiredAppRootEntries = new Set([
1765
+ "android",
1766
+ "ios",
1767
+ "mobile",
1768
+ "capacitor.config.ts",
1769
+ "capacitor.config.js",
1770
+ "capacitor.config.json"
1771
+ ]);
1772
+ var rootEntryHintOf = (type, name) => type === "app" && retiredAppRootEntries.has(name) ? "a Capacitor-era entry; the native runtime generates its projects under .akan/native/<target>, so delete it" : null;
1691
1773
  var isScannedRootEntry = (type, name) => !name.startsWith(".") || rootAllowedDirs[type].has(name);
1692
1774
 
1693
1775
  // pkgs/@akanjs/devkit/scanInfo.ts
1694
- var databaseFileTypes = [
1695
- "constant",
1776
+ var serviceFileTypes = [
1696
1777
  "dictionary",
1697
- "document",
1698
1778
  "service",
1699
1779
  "signal",
1700
1780
  "store",
@@ -1704,7 +1784,9 @@ var databaseFileTypes = [
1704
1784
  "view",
1705
1785
  "zone"
1706
1786
  ];
1707
- var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
1787
+ var databaseFileTypes = ["constant", "dictionary", "document", ...serviceFileTypes.slice(1)];
1788
+ var uiFileTypes = new Set(["template", "unit", "util", "view", "zone"]);
1789
+ var fileTypeOf = (filename) => databaseFileTypes.find((type) => filename.endsWith(uiFileTypes.has(type) ? `.${type[0].toUpperCase()}${type.slice(1)}.tsx` : `.${type}.ts`));
1708
1790
  var internalLibDirs = new Set(["__lib", "__scalar"]);
1709
1791
  var moduleNonUiFileTypes = {
1710
1792
  database: new Set(["constant", "dictionary", "document", "service", "signal", "store"]),
@@ -1716,21 +1798,13 @@ var moduleUiFileTypes = {
1716
1798
  service: new Set(["Util", "Zone"]),
1717
1799
  scalar: new Set(["Template", "Unit"])
1718
1800
  };
1719
- var testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
1720
- var createDependencyScanner = async (exec) => (await import("./dependencyScanner-shs82ynp.js")).TypeScriptDependencyScanner.from(exec);
1721
- var isAllowedTestFile = (filename) => testFilePattern.test(filename);
1722
- var getScanPath = (exec, relativePath) => path7.posix.join(`${exec.type}s`, exec.name, relativePath.split(path7.sep).join("/"));
1723
- async function clearGeneratedRootCapacitorConfigs(exec) {
1724
- if (exec.type !== "app")
1725
- return;
1726
- await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm2(exec.getPath(filename), { force: true })));
1727
- }
1801
+ var createDependencyScanner = async (exec) => (await import("./dependencyScanner-06z2dmrr.js")).TypeScriptDependencyScanner.from(exec);
1802
+ var getScanPath = (exec, relativePath) => path8.posix.join(`${exec.type}s`, exec.name, relativePath.split(path8.sep).join("/"));
1728
1803
  var getModuleNameFromPath = (kind, modulePath) => {
1729
- const dirname = path7.basename(modulePath);
1804
+ const dirname = path8.basename(modulePath);
1730
1805
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
1731
1806
  };
1732
1807
  async function assertScanConvention(exec, libRoot) {
1733
- await clearGeneratedRootCapacitorConfigs(exec);
1734
1808
  const violations = [];
1735
1809
  const addViolation = (relativePath, reason) => {
1736
1810
  violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
@@ -1738,28 +1812,28 @@ async function assertScanConvention(exec, libRoot) {
1738
1812
  const allowedRootFiles = rootAllowedFiles[exec.type];
1739
1813
  const allowedRootDirs = rootAllowedDirs[exec.type];
1740
1814
  const { files, dirs } = await exec.getFilesAndDirs(".");
1741
- files.filter((filename) => !allowedRootFiles.has(filename)).forEach((filename) => {
1742
- addViolation(filename, `unsupported ${exec.type} root file`);
1743
- });
1744
- dirs.filter((dirname) => !allowedRootDirs.has(dirname)).forEach((dirname) => {
1745
- addViolation(dirname, `unsupported ${exec.type} root folder`);
1746
- });
1815
+ for (const filename of files)
1816
+ if (!allowedRootFiles.has(filename))
1817
+ addViolation(filename, rootEntryHintOf(exec.type, filename) ?? `unsupported ${exec.type} root file`);
1818
+ for (const dirname of dirs)
1819
+ if (!allowedRootDirs.has(dirname))
1820
+ addViolation(dirname, rootEntryHintOf(exec.type, dirname) ?? `unsupported ${exec.type} root folder`);
1747
1821
  if (exec.type === "lib")
1748
1822
  for (const { relativePath, reason } of await exec.getPageConventionViolations())
1749
1823
  addViolation(relativePath, reason);
1750
- libRoot.files.filter((filename) => !isAllowedLibFacetRootFile(filename)).forEach((filename) => {
1751
- addViolation(path7.join("lib", filename), "unsupported lib facet root file");
1752
- });
1753
- libRoot.dirs.filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname)).forEach((dirname) => {
1754
- addViolation(path7.join("lib", dirname), "unsupported internal lib folder");
1755
- });
1824
+ for (const filename of libRoot.files)
1825
+ if (!isAllowedLibFacetRootFile(filename))
1826
+ addViolation(path8.join("lib", filename), "unsupported lib facet root file");
1827
+ for (const dirname of libRoot.dirs)
1828
+ if (dirname.startsWith("__") && !internalLibDirs.has(dirname))
1829
+ addViolation(path8.join("lib", dirname), "unsupported internal lib folder");
1756
1830
  const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
1757
1831
  const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
1758
1832
  const scalarDirs = await exec.readdir("lib/__scalar");
1759
1833
  await Promise.all([
1760
- ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path7.join("lib", dirname))),
1761
- ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path7.join("lib", dirname))),
1762
- ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path7.join("lib/__scalar", dirname)))
1834
+ ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path8.join("lib", dirname))),
1835
+ ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path8.join("lib", dirname))),
1836
+ ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path8.join("lib/__scalar", dirname)))
1763
1837
  ]);
1764
1838
  if (violations.length > 0) {
1765
1839
  throw new Error(`[scan-convention]
@@ -1771,41 +1845,28 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
1771
1845
  const { files, dirs } = await exec.getFilesAndDirs(modulePath);
1772
1846
  const moduleName = getModuleNameFromPath(kind, modulePath);
1773
1847
  dirs.forEach((dirname) => {
1774
- violations.push(`${getScanPath(exec, path7.join(modulePath, dirname))}: unsupported module folder`);
1848
+ violations.push(`${getScanPath(exec, path8.join(modulePath, dirname))}: unsupported module folder`);
1775
1849
  });
1776
1850
  const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
1777
- files.forEach((filename) => {
1778
- const filePath = path7.join(modulePath, filename);
1779
- if (filename === "index.ts" || filename === "index.tsx" || isAllowedTestFile(filename))
1780
- return;
1851
+ for (const filename of files) {
1852
+ const scanPath = getScanPath(exec, path8.join(modulePath, filename));
1853
+ if (filename === "index.ts" || filename === "index.tsx" || /\.(test|spec)\.(ts|tsx)$/.test(filename))
1854
+ continue;
1781
1855
  if (filename === `${moduleName}.abstract.md`)
1782
- return;
1856
+ continue;
1783
1857
  const uiMatch = filename.match(/^([A-Z][A-Za-z0-9]+)\.([A-Z][A-Za-z0-9]*)\.tsx$/);
1784
- if (uiMatch) {
1785
- const fileModuleName = uiMatch[1];
1786
- const fileType = uiMatch[2];
1787
- if (fileModuleName !== uiModuleName) {
1788
- violations.push(`${getScanPath(exec, filePath)}: module name mismatch: expected '${uiModuleName}', got '${fileModuleName}'`);
1789
- }
1790
- if (!moduleUiFileTypes[kind].has(fileType)) {
1791
- violations.push(`${getScanPath(exec, filePath)}: unsupported ${kind} UI file`);
1792
- }
1793
- return;
1794
- }
1795
- const nonUiMatch = filename.match(/^([a-z][a-zA-Z0-9]*)\.([a-z][a-z0-9]*)\.ts$/);
1796
- if (nonUiMatch) {
1797
- const fileModuleName = nonUiMatch[1];
1798
- const fileType = nonUiMatch[2];
1799
- if (fileModuleName !== moduleName) {
1800
- violations.push(`${getScanPath(exec, filePath)}: module name mismatch: expected '${moduleName}', got '${fileModuleName}'`);
1801
- }
1802
- if (!moduleNonUiFileTypes[kind].has(fileType)) {
1803
- violations.push(`${getScanPath(exec, filePath)}: unsupported ${kind} file`);
1804
- }
1805
- return;
1858
+ const match = uiMatch ?? filename.match(/^([a-z][a-zA-Z0-9]*)\.([a-z][a-z0-9]*)\.ts$/);
1859
+ if (!match) {
1860
+ violations.push(`${scanPath}: unsupported module file`);
1861
+ continue;
1806
1862
  }
1807
- violations.push(`${getScanPath(exec, filePath)}: unsupported module file`);
1808
- });
1863
+ const [, fileModuleName, fileType] = match;
1864
+ const expectedName = uiMatch ? uiModuleName : moduleName;
1865
+ if (fileModuleName !== expectedName)
1866
+ violations.push(`${scanPath}: module name mismatch: expected '${expectedName}', got '${fileModuleName}'`);
1867
+ if (!(uiMatch ? moduleUiFileTypes : moduleNonUiFileTypes)[kind].has(fileType))
1868
+ violations.push(`${scanPath}: unsupported ${kind}${uiMatch ? " UI" : ""} file`);
1869
+ }
1809
1870
  }
1810
1871
 
1811
1872
  class ScanInfo {
@@ -1853,86 +1914,24 @@ class ScanInfo {
1853
1914
  } else
1854
1915
  databaseDirs.push(name);
1855
1916
  });
1917
+ const collect = async (dir, name, kind) => {
1918
+ for (const filename of await exec.readdir(dir)) {
1919
+ const type = fileTypeOf(filename);
1920
+ if (type)
1921
+ files[type][kind]?.push(name);
1922
+ }
1923
+ };
1856
1924
  await Promise.all([
1857
- ...databaseDirs.map(async (name) => {
1858
- const filenames = await exec.readdir(path7.join("lib", name));
1859
- filenames.forEach((filename) => {
1860
- if (filename.endsWith(".constant.ts"))
1861
- files.constant.databases.push(name);
1862
- else if (filename.endsWith(".dictionary.ts"))
1863
- files.dictionary.databases.push(name);
1864
- else if (filename.endsWith(".document.ts"))
1865
- files.document.databases.push(name);
1866
- else if (filename.endsWith(".service.ts"))
1867
- files.service.databases.push(name);
1868
- else if (filename.endsWith(".signal.ts"))
1869
- files.signal.databases.push(name);
1870
- else if (filename.endsWith(".store.ts"))
1871
- files.store.databases.push(name);
1872
- else if (filename.endsWith(".Template.tsx"))
1873
- files.template.databases.push(name);
1874
- else if (filename.endsWith(".Unit.tsx"))
1875
- files.unit.databases.push(name);
1876
- else if (filename.endsWith(".Util.tsx"))
1877
- files.util.databases.push(name);
1878
- else if (filename.endsWith(".View.tsx"))
1879
- files.view.databases.push(name);
1880
- else if (filename.endsWith(".Zone.tsx"))
1881
- files.zone.databases.push(name);
1882
- });
1883
- }),
1884
- ...serviceDirs.map(async (dirname) => {
1885
- const name = dirname.slice(1);
1886
- const filenames = await exec.readdir(path7.join("lib", dirname));
1887
- filenames.forEach((filename) => {
1888
- if (filename.endsWith(".dictionary.ts"))
1889
- files.dictionary.services.push(name);
1890
- else if (filename.endsWith(".service.ts"))
1891
- files.service.services.push(name);
1892
- else if (filename.endsWith(".signal.ts"))
1893
- files.signal.services.push(name);
1894
- else if (filename.endsWith(".store.ts"))
1895
- files.store.services.push(name);
1896
- else if (filename.endsWith(".Template.tsx"))
1897
- files.template.services.push(name);
1898
- else if (filename.endsWith(".Unit.tsx"))
1899
- files.unit.services.push(name);
1900
- else if (filename.endsWith(".Util.tsx"))
1901
- files.util.services.push(name);
1902
- else if (filename.endsWith(".View.tsx"))
1903
- files.view.services.push(name);
1904
- else if (filename.endsWith(".Zone.tsx"))
1905
- files.zone.services.push(name);
1906
- });
1907
- }),
1908
- ...scalarDirs.map(async (name) => {
1909
- const filenames = await exec.readdir(path7.join("lib/__scalar", name));
1910
- filenames.forEach((filename) => {
1911
- if (filename.endsWith(".constant.ts"))
1912
- files.constant.scalars.push(name);
1913
- else if (filename.endsWith(".dictionary.ts"))
1914
- files.dictionary.scalars.push(name);
1915
- else if (filename.endsWith(".document.ts"))
1916
- files.document.scalars.push(name);
1917
- else if (filename.endsWith(".Template.tsx"))
1918
- files.template.scalars.push(name);
1919
- else if (filename.endsWith(".Unit.tsx"))
1920
- files.unit.scalars.push(name);
1921
- else if (filename.endsWith(".Util.tsx"))
1922
- files.util.scalars.push(name);
1923
- else if (filename.endsWith(".View.tsx"))
1924
- files.view.scalars.push(name);
1925
- else if (filename.endsWith(".Zone.tsx"))
1926
- files.zone.scalars.push(name);
1927
- });
1928
- })
1925
+ ...databaseDirs.map((name) => collect(path8.join("lib", name), name, "databases")),
1926
+ ...serviceDirs.map((dirname) => collect(path8.join("lib", dirname), dirname.slice(1), "services")),
1927
+ ...scalarDirs.map((name) => collect(path8.join("lib/__scalar", name), name, "scalars"))
1929
1928
  ]);
1930
1929
  const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
1931
1930
  const common = {
1932
1931
  name: exec.name,
1933
1932
  type: exec.type,
1934
1933
  repoName: exec.workspace.repoName,
1935
- serveDomain: WorkspaceExecutor.getBaseDevEnv(path7.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
1934
+ serveDomain: WorkspaceExecutor.getBaseDevEnv(path8.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
1936
1935
  files,
1937
1936
  libDeps,
1938
1937
  pkgDeps,
@@ -1946,31 +1945,14 @@ class ScanInfo {
1946
1945
  constructor(scanResult) {
1947
1946
  this.name = scanResult.name;
1948
1947
  this.scanResult = scanResult;
1949
- Object.entries(scanResult.files).forEach(([_key, value]) => {
1950
- const key = _key;
1951
- const { databases, services, scalars } = value;
1952
- databases.forEach((modelName) => {
1953
- const model = this.database.get(modelName) ?? new Set;
1954
- model.add(key);
1955
- this.database.set(modelName, model);
1956
- this.file[key].all.add(modelName);
1957
- this.file[key].databases.add(modelName);
1958
- });
1959
- services?.forEach((serviceName) => {
1960
- const service = this.service.get(serviceName) ?? new Set;
1961
- service.add(key);
1962
- this.service.set(serviceName, service);
1963
- this.file[key].all.add(serviceName);
1964
- this.file[key].services.add(serviceName);
1965
- });
1966
- scalars?.forEach((scalarName) => {
1967
- const scalar = this.scalar.get(scalarName) ?? new Set;
1968
- scalar.add(key);
1969
- this.scalar.set(scalarName, scalar);
1970
- this.file[key].all.add(scalarName);
1971
- this.file[key].scalars.add(scalarName);
1972
- });
1973
- });
1948
+ const modulesOf = { databases: this.database, services: this.service, scalars: this.scalar };
1949
+ for (const [key, groups] of Object.entries(scanResult.files))
1950
+ for (const kind of ["databases", "services", "scalars"])
1951
+ for (const name of groups[kind] ?? []) {
1952
+ modulesOf[kind].set(name, (modulesOf[kind].get(name) ?? new Set).add(key));
1953
+ this.file[key].all.add(name);
1954
+ this.file[key][kind].add(name);
1955
+ }
1974
1956
  }
1975
1957
  getScanResult() {
1976
1958
  return this.scanResult;
@@ -1984,6 +1966,28 @@ class ScanInfo {
1984
1966
  getScalarModules() {
1985
1967
  return [...this.scalar.keys()];
1986
1968
  }
1969
+ #sortedLibs = null;
1970
+ sortedLibs(libDeps) {
1971
+ if (this.#sortedLibs)
1972
+ return this.#sortedLibs;
1973
+ const libIndices = LibInfo.getSortedLibIndices();
1974
+ this.#sortedLibs = libDeps.sort((libNameA, libNameB) => {
1975
+ const indexA = libIndices.get(libNameA);
1976
+ const indexB = libIndices.get(libNameB);
1977
+ if (indexA === undefined || indexB === undefined)
1978
+ throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
1979
+ return indexA - indexB;
1980
+ });
1981
+ return this.#sortedLibs;
1982
+ }
1983
+ sortedLibInfos(libDeps) {
1984
+ return new Map(this.sortedLibs(libDeps).map((libName) => {
1985
+ const libInfo = LibInfo.libInfos.get(libName);
1986
+ if (!libInfo)
1987
+ throw new Error(`LibInfo not found: ${libName}`);
1988
+ return [libName, libInfo];
1989
+ }));
1990
+ }
1987
1991
  }
1988
1992
  var isAkanFrameworkDependency = (dep) => dep === "akanjs" || dep.startsWith("akanjs/");
1989
1993
 
@@ -2033,30 +2037,11 @@ class AppInfo extends ScanInfo {
2033
2037
  }));
2034
2038
  return [...libSet];
2035
2039
  }
2036
- #sortedLibs = null;
2037
- #getSortedLibs() {
2038
- if (this.#sortedLibs)
2039
- return this.#sortedLibs;
2040
- const libIndices = LibInfo.getSortedLibIndices();
2041
- this.#sortedLibs = this.libDeps.sort((libNameA, libNameB) => {
2042
- const indexA = libIndices.get(libNameA);
2043
- const indexB = libIndices.get(libNameB);
2044
- if (indexA === undefined || indexB === undefined)
2045
- throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
2046
- return indexA - indexB;
2047
- });
2048
- return this.#sortedLibs;
2049
- }
2050
2040
  getLibs() {
2051
- return this.#getSortedLibs();
2041
+ return this.sortedLibs(this.libDeps);
2052
2042
  }
2053
2043
  getLibInfos() {
2054
- return new Map(this.#getSortedLibs().map((libName) => {
2055
- const libInfo = LibInfo.libInfos.get(libName);
2056
- if (!libInfo)
2057
- throw new Error(`LibInfo not found: ${libName}`);
2058
- return [libName, libInfo];
2059
- }));
2044
+ return this.sortedLibInfos(this.libDeps);
2060
2045
  }
2061
2046
  }
2062
2047
 
@@ -2096,38 +2081,18 @@ class LibInfo extends ScanInfo {
2096
2081
  getScanResult() {
2097
2082
  return this.scanResult;
2098
2083
  }
2099
- #sortedLibs = null;
2100
- #getSortedLibs() {
2101
- if (this.#sortedLibs)
2102
- return this.#sortedLibs;
2103
- const libs = LibInfo.getSortedLibIndices();
2104
- this.#sortedLibs = this.scanResult.libDeps.sort((libNameA, libNameB) => {
2105
- const indexA = libs.get(libNameA);
2106
- const indexB = libs.get(libNameB);
2107
- if (indexA === undefined || indexB === undefined)
2108
- throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
2109
- return indexA - indexB;
2110
- });
2111
- return this.#sortedLibs;
2112
- }
2113
2084
  getLibs() {
2114
- return this.#getSortedLibs();
2085
+ return this.sortedLibs(this.scanResult.libDeps);
2115
2086
  }
2116
2087
  getLibInfo(libName) {
2117
2088
  if (!this.getScanResult().libDeps.includes(libName))
2118
2089
  return;
2119
- const libSet = new Set(this.#getSortedLibs());
2120
- if (!libSet.has(libName))
2090
+ if (!this.getLibs().includes(libName))
2121
2091
  throw new Error(`LibInfo is invalid: ${libName}`);
2122
2092
  return LibInfo.libInfos.get(libName);
2123
2093
  }
2124
2094
  getLibInfos() {
2125
- return new Map(this.#getSortedLibs().map((libName) => {
2126
- const libInfo = LibInfo.libInfos.get(libName);
2127
- if (!libInfo)
2128
- throw new Error(`LibInfo not found: ${libName}`);
2129
- return [libName, libInfo];
2130
- }));
2095
+ return this.sortedLibInfos(this.scanResult.libDeps);
2131
2096
  }
2132
2097
  }
2133
2098
 
@@ -2139,18 +2104,14 @@ class PkgInfo {
2139
2104
  const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
2140
2105
  const scanner = await createDependencyScanner(exec);
2141
2106
  const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
2142
- const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path) => tsconfig.compilerOptions.paths?.[path]?.some((resolve) => resolve.startsWith("pkgs/"))).map((path) => path.replace("/*", "")));
2107
+ const workspacePathOf = (resolve) => resolve.replace(/^\.\//, "");
2108
+ const pkgPathSet = new Set(Object.keys(tsconfig.compilerOptions.paths ?? {}).filter((path) => tsconfig.compilerOptions.paths?.[path]?.some((resolve) => workspacePathOf(resolve).startsWith("pkgs/"))).map((path) => path.replace("/*", "")));
2143
2109
  const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
2144
2110
  const pkgDeps = [...pkgPathDepSet].map((path) => {
2145
2111
  const pathSplitLength = path.split("/").length;
2146
- return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
2112
+ return workspacePathOf(tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
2147
2113
  }).filter((pkg) => pkg !== exec.name);
2148
- const pkgScanResult = {
2149
- name: exec.name,
2150
- pkgDeps,
2151
- dependencies: [...npmDepSet]
2152
- };
2153
- return pkgScanResult;
2114
+ return { name: exec.name, pkgDeps, dependencies: [...npmDepSet] };
2154
2115
  }
2155
2116
  static #pkgInfos = new Map;
2156
2117
  static async fromExecutor(exec, options = {}) {
@@ -2188,17 +2149,9 @@ class WorkspaceInfo {
2188
2149
  return existingWorkspaceInfo;
2189
2150
  const [appNames, libNames, pkgNames] = await Promise.all([exec.getApps(), exec.getLibs(), exec.getPkgs()]);
2190
2151
  const [appInfos, libInfos, pkgInfos] = await Promise.all([
2191
- Promise.all(appNames.map(async (appName) => {
2192
- const app = AppExecutor.from(exec, appName);
2193
- return await app.scan();
2194
- })),
2195
- Promise.all(libNames.map(async (libName) => {
2196
- const lib = LibExecutor.from(exec, libName);
2197
- return await lib.scan();
2198
- })),
2199
- Promise.all(pkgNames.map(async (pkgName) => {
2200
- return await PkgExecutor.from(exec, pkgName).scan();
2201
- }))
2152
+ Promise.all(appNames.map(async (appName) => await AppExecutor.from(exec, appName).scan())),
2153
+ Promise.all(libNames.map(async (libName) => await LibExecutor.from(exec, libName).scan())),
2154
+ Promise.all(pkgNames.map(async (pkgName) => await PkgExecutor.from(exec, pkgName).scan()))
2202
2155
  ]);
2203
2156
  const workspaceInfo = new WorkspaceInfo(new Map(appInfos.map((app) => [app.exec.name, app])), new Map(libInfos.map((lib) => [lib.exec.name, lib])), new Map(pkgInfos.map((pkg) => [pkg.exec.name, pkg])));
2204
2157
  WorkspaceInfo.#workspaceInfos.set(exec.name, workspaceInfo);
@@ -2207,40 +2160,9 @@ class WorkspaceInfo {
2207
2160
  }
2208
2161
 
2209
2162
  // pkgs/@akanjs/devkit/executors.ts
2210
- var staticTemplateFileExtensions = new Set([
2211
- ".avif",
2212
- ".bmp",
2213
- ".cjs",
2214
- ".css",
2215
- ".eot",
2216
- ".gif",
2217
- ".html",
2218
- ".ico",
2219
- ".jpeg",
2220
- ".jpg",
2221
- ".js",
2222
- ".json",
2223
- ".map",
2224
- ".md",
2225
- ".mjs",
2226
- ".mp3",
2227
- ".mp4",
2228
- ".ogg",
2229
- ".otf",
2230
- ".pdf",
2231
- ".png",
2232
- ".svg",
2233
- ".ttf",
2234
- ".txt",
2235
- ".wasm",
2236
- ".wav",
2237
- ".webm",
2238
- ".webp",
2239
- ".woff",
2240
- ".woff2",
2241
- ".xml"
2242
- ]);
2243
- var formatCommandArg = (value) => /^[\w@%+=:,./-]+$/.test(value) ? value : JSON.stringify(value);
2163
+ var staticTemplateFileExtensions = new Set(".avif .bmp .cjs .css .eot .gif .html .ico .jpeg .jpg .js .json .map .md .mjs .mp3 .mp4 .ogg .otf .pdf .png .svg".split(" ").concat(".ttf .txt .wasm .wav .webm .webp .woff .woff2 .xml".split(" ")));
2164
+ var plainCommandArgPattern = process.platform === "win32" ? /^[\w@%+=:,./\\-]+$/ : /^[\w@%+=:,./-]+$/;
2165
+ var formatCommandArg = (value) => plainCommandArgPattern.test(value) ? value : JSON.stringify(value);
2244
2166
  var formatCommandForDisplay = (command, args = []) => [command, ...args].map(formatCommandArg).join(" ");
2245
2167
 
2246
2168
  class CommandExecutionError extends Error {
@@ -2314,6 +2236,10 @@ var parseEnvFile = (envPath) => {
2314
2236
  }
2315
2237
  return env;
2316
2238
  };
2239
+ var withCapitalizedDict = (dict = {}) => ({
2240
+ ...dict,
2241
+ ...Object.fromEntries(Object.entries(dict).map(([key, value]) => [capitalize(key), capitalize(value)]))
2242
+ });
2317
2243
 
2318
2244
  class Executor {
2319
2245
  static verbose = false;
@@ -2340,150 +2266,57 @@ class Executor {
2340
2266
  #stderr(data) {
2341
2267
  Logger2.raw(chalk2.red(data.toString()));
2342
2268
  }
2343
- exec(command, options = {}) {
2344
- const cwd = options.cwd?.toString() ?? this.cwdPath;
2345
- const proc = exec(command, { cwd: this.cwdPath, ...options });
2269
+ #settle(proc, target, { onClose = false, keepLogs = false, redStderr = false } = {}) {
2346
2270
  let stdout = "";
2347
2271
  let stderr = "";
2348
2272
  proc.stdout?.on("data", (data) => {
2349
2273
  stdout += data.toString();
2274
+ if (keepLogs)
2275
+ this.logs.push(data.toString());
2350
2276
  this.#stdout(data);
2351
2277
  });
2352
2278
  proc.stderr?.on("data", (data) => {
2353
2279
  stderr += data.toString();
2354
- this.#stdout(data);
2280
+ if (keepLogs)
2281
+ this.logs.push(data.toString());
2282
+ if (redStderr)
2283
+ this.#stderr(data);
2284
+ else
2285
+ this.#stdout(data);
2355
2286
  });
2356
2287
  return new Promise((resolve, reject) => {
2357
- proc.on("error", (error) => {
2358
- reject(new CommandExecutionError({
2359
- command,
2360
- cwd,
2361
- code: null,
2362
- signal: null,
2363
- stdout,
2364
- stderr,
2365
- cause: error
2366
- }));
2367
- });
2368
- proc.on("exit", (code, signal) => {
2369
- if (code || signal)
2370
- reject(new CommandExecutionError({
2371
- command,
2372
- cwd,
2373
- code,
2374
- signal,
2375
- stdout,
2376
- stderr
2377
- }));
2288
+ proc.on("error", (cause) => reject(new CommandExecutionError({ ...target, code: null, signal: null, stdout, stderr, cause })));
2289
+ proc.on(onClose ? "close" : "exit", (code, signal) => {
2290
+ if ((onClose ? code !== 0 : code) || signal)
2291
+ reject(new CommandExecutionError({ ...target, code, signal, stdout, stderr }));
2378
2292
  else
2379
- resolve({ code, signal });
2293
+ resolve({ code, signal, stdout });
2380
2294
  });
2381
2295
  });
2382
2296
  }
2297
+ exec(command, options = {}) {
2298
+ const target = { command, cwd: options.cwd?.toString() ?? this.cwdPath };
2299
+ const proc = exec(command, { cwd: this.cwdPath, ...options });
2300
+ return this.#settle(proc, target).then(({ code, signal }) => ({ code, signal }));
2301
+ }
2383
2302
  spawn(command, args = [], options = {}) {
2384
- const cwd = options.cwd?.toString() ?? this.cwdPath;
2385
- const proc = spawn2(command, args, {
2386
- cwd: this.cwdPath,
2387
- ...options
2388
- });
2389
- let stdout = "";
2390
- let stderr = "";
2391
- proc.stdout?.on("data", (data) => {
2392
- stdout += data.toString();
2393
- this.logs.push(data.toString());
2394
- this.#stdout(data);
2395
- });
2396
- proc.stderr?.on("data", (data) => {
2397
- stderr += data.toString();
2398
- this.logs.push(data.toString());
2399
- this.#stdout(data);
2400
- });
2401
- return new Promise((resolve, reject) => {
2402
- proc.on("error", (error) => {
2403
- reject(new CommandExecutionError({
2404
- command,
2405
- args,
2406
- cwd,
2407
- code: null,
2408
- signal: null,
2409
- stdout,
2410
- stderr,
2411
- cause: error
2412
- }));
2413
- });
2414
- proc.on("close", (code, signal) => {
2415
- if (code !== 0 || signal)
2416
- reject(new CommandExecutionError({
2417
- command,
2418
- args,
2419
- cwd,
2420
- code,
2421
- signal,
2422
- stdout,
2423
- stderr
2424
- }));
2425
- else
2426
- resolve(stdout);
2427
- });
2428
- });
2303
+ const target = { command, args, cwd: options.cwd?.toString() ?? this.cwdPath };
2304
+ const proc = spawn2(command, args, { cwd: this.cwdPath, ...options });
2305
+ return this.#settle(proc, target, { onClose: true, keepLogs: true }).then(({ stdout }) => stdout);
2429
2306
  }
2430
2307
  spawnSync(command, args = [], options = {}) {
2431
- const proc = spawn2(command, args, {
2432
- cwd: this.cwdPath,
2433
- ...options
2434
- });
2435
- return proc;
2308
+ return spawn2(command, args, { cwd: this.cwdPath, ...options });
2436
2309
  }
2437
2310
  fork(modulePath, args = [], options = {}) {
2438
- const cwd = options.cwd?.toString() ?? this.cwdPath;
2439
- const proc = fork(modulePath, args, {
2440
- cwd: this.cwdPath,
2441
- ...options
2442
- });
2443
- let stdout = "";
2444
- let stderr = "";
2445
- proc.stdout?.on("data", (data) => {
2446
- stdout += data.toString();
2447
- this.#stdout(data);
2448
- });
2449
- proc.stderr?.on("data", (data) => {
2450
- stderr += data.toString();
2451
- this.#stderr(data);
2452
- });
2453
- return new Promise((resolve, reject) => {
2454
- proc.on("error", (error) => {
2455
- reject(new CommandExecutionError({
2456
- command: modulePath,
2457
- args,
2458
- cwd,
2459
- code: null,
2460
- signal: null,
2461
- stdout,
2462
- stderr,
2463
- cause: error
2464
- }));
2465
- });
2466
- proc.on("exit", (code, signal) => {
2467
- if (code || signal)
2468
- reject(new CommandExecutionError({
2469
- command: modulePath,
2470
- args,
2471
- cwd,
2472
- code,
2473
- signal,
2474
- stdout,
2475
- stderr
2476
- }));
2477
- else
2478
- resolve({ code, signal });
2479
- });
2480
- });
2311
+ const target = { command: modulePath, args, cwd: options.cwd?.toString() ?? this.cwdPath };
2312
+ const proc = fork(modulePath, args, { cwd: this.cwdPath, ...options });
2313
+ return this.#settle(proc, target, { redStderr: true }).then(({ code, signal }) => ({ code, signal }));
2481
2314
  }
2482
2315
  getPath(filePath) {
2483
- if (path8.isAbsolute(filePath))
2316
+ if (path9.isAbsolute(filePath))
2484
2317
  return filePath;
2485
2318
  if (filePath.startsWith("."))
2486
- return path8.join(this.cwdPath, filePath);
2319
+ return path9.join(this.cwdPath, filePath);
2487
2320
  const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
2488
2321
  const targetParts = filePath.split(/[\\/]/).filter(Boolean);
2489
2322
  let overlapLength = 0;
@@ -2497,12 +2330,12 @@ class Executor {
2497
2330
  if (isOverlap)
2498
2331
  overlapLength = i;
2499
2332
  }
2500
- return path8.join(this.cwdPath, ...targetParts.slice(overlapLength));
2333
+ return path9.join(this.cwdPath, ...targetParts.slice(overlapLength));
2501
2334
  }
2502
2335
  async mkdir(dirPath) {
2503
2336
  const writePath = this.getPath(dirPath);
2504
- if (!await FileSys.dirExists(writePath))
2505
- await mkdir3(writePath, { recursive: true });
2337
+ if (!await FileSys2.dirExists(writePath))
2338
+ await mkdir2(writePath, { recursive: true });
2506
2339
  this.logger.verbose(`Make directory ${writePath}`);
2507
2340
  return this;
2508
2341
  }
@@ -2521,50 +2354,48 @@ class Executor {
2521
2354
  }
2522
2355
  async getFilesAndDirs(dirPath) {
2523
2356
  const fullDirPath = this.getPath(dirPath);
2524
- const fileGlob = new Bun.Glob("*");
2525
- const files = Array.from(fileGlob.scanSync({ cwd: fullDirPath, onlyFiles: true }));
2526
- const dirGlob = new Bun.Glob("*");
2527
- const allEntries = Array.from(dirGlob.scanSync({ cwd: fullDirPath, onlyFiles: false }));
2357
+ const glob = new Bun.Glob("*");
2358
+ const files = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: true }));
2359
+ const allEntries = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: false }));
2528
2360
  const dirs = allEntries.filter((entry) => !files.includes(entry));
2529
2361
  return { files, dirs };
2530
2362
  }
2531
2363
  async exists(filePath) {
2532
- const readPath = this.getPath(filePath);
2533
- return await FileSys.exists(readPath);
2364
+ return await FileSys2.exists(this.getPath(filePath));
2534
2365
  }
2535
2366
  async remove(filePath) {
2536
2367
  const readPath = this.getPath(filePath);
2537
- if (await FileSys.fileExists(readPath))
2538
- await FileSys.delete(readPath);
2368
+ if (await FileSys2.fileExists(readPath))
2369
+ await FileSys2.delete(readPath);
2539
2370
  this.logger.verbose(`Remove file ${readPath}`);
2540
2371
  return this;
2541
2372
  }
2542
2373
  async removeDir(dirPath) {
2543
- const readPath = path8.join(this.getPath(dirPath), ".");
2544
- if (await FileSys.entryExists(readPath))
2374
+ const readPath = path9.join(this.getPath(dirPath), ".");
2375
+ if (await FileSys2.entryExists(readPath))
2545
2376
  await rm3(readPath, { recursive: true, force: true });
2546
2377
  this.logger.verbose(`Remove directory ${readPath}`);
2547
2378
  return this;
2548
2379
  }
2549
2380
  async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
2550
2381
  const writePath = this.getPath(filePath);
2551
- const dir = path8.dirname(writePath);
2552
- if (!await FileSys.dirExists(dir))
2553
- await mkdir3(dir, { recursive: true });
2382
+ const dir = path9.dirname(writePath);
2383
+ if (!await FileSys2.dirExists(dir))
2384
+ await mkdir2(dir, { recursive: true });
2554
2385
  let contentStr = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}
2555
2386
  `;
2556
- if (await FileSys.fileExists(writePath)) {
2557
- const currentContent = await FileSys.readText(writePath);
2387
+ if (await FileSys2.fileExists(writePath)) {
2388
+ const currentContent = await FileSys2.readText(writePath);
2558
2389
  if (currentContent === contentStr || !overwrite) {
2559
2390
  this.logger.verbose(`File ${writePath} is unchanged`);
2560
2391
  contentStr = currentContent;
2561
2392
  } else {
2562
- await FileSys.writeText(writePath, contentStr);
2393
+ await FileSys2.writeText(writePath, contentStr);
2563
2394
  if (Logger2.isVerbose())
2564
2395
  this.logger.rawLog(chalk2.yellow(`File Update: ${filePath}`));
2565
2396
  }
2566
2397
  } else {
2567
- await FileSys.writeText(writePath, contentStr);
2398
+ await FileSys2.writeText(writePath, contentStr);
2568
2399
  if (!silent)
2569
2400
  this.logger.rawLog(chalk2.green(`File Create: ${filePath}`));
2570
2401
  }
@@ -2574,27 +2405,25 @@ class Executor {
2574
2405
  await this.writeFile(filePath, content);
2575
2406
  }
2576
2407
  async getLocalFile(targetPath) {
2577
- const filePath = path8.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
2408
+ const filePath = path9.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
2578
2409
  const content = await this.readFile(filePath);
2579
2410
  return { filePath, content };
2580
2411
  }
2581
2412
  async readFile(filePath) {
2582
- const readPath = this.getPath(filePath);
2583
- return await FileSys.readText(readPath);
2413
+ return await FileSys2.readText(this.getPath(filePath));
2584
2414
  }
2585
2415
  async readJson(filePath) {
2586
- const readPath = this.getPath(filePath);
2587
- return await FileSys.readJson(readPath);
2416
+ return await FileSys2.readJson(this.getPath(filePath));
2588
2417
  }
2589
2418
  async cp(srcPath, destPath, { dereference = false } = {}) {
2590
2419
  const src = this.getPath(srcPath);
2591
2420
  const dest = this.getPath(destPath);
2592
- if (!await FileSys.exists(src))
2421
+ if (!await FileSys2.exists(src))
2593
2422
  return;
2594
2423
  const isDirectory = (await stat2(src)).isDirectory();
2595
- if (!await FileSys.exists(dest) && isDirectory)
2596
- await mkdir3(dest, { recursive: true });
2597
- if (dereference)
2424
+ if (!await FileSys2.exists(dest) && isDirectory)
2425
+ await mkdir2(dest, { recursive: true });
2426
+ if (dereference || process.platform === "win32")
2598
2427
  await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
2599
2428
  else
2600
2429
  await $`cp -r ${src}${isDirectory ? "/." : ""} ${dest}`;
@@ -2618,19 +2447,10 @@ class Executor {
2618
2447
  async getTsConfig(pathname = "tsconfig.json", { refresh } = {}) {
2619
2448
  if (this.#tsconfig && !refresh)
2620
2449
  return this.#tsconfig;
2621
- const tsconfig = await this.readJson(pathname);
2450
+ let tsconfig = await this.readJson(pathname);
2622
2451
  if (tsconfig.extends) {
2623
- const extendsTsconfig = await this.getTsConfig(tsconfig.extends);
2624
- const result = {
2625
- ...extendsTsconfig,
2626
- ...tsconfig,
2627
- compilerOptions: {
2628
- ...extendsTsconfig.compilerOptions,
2629
- ...tsconfig.compilerOptions
2630
- }
2631
- };
2632
- this.#tsconfig = result;
2633
- return result;
2452
+ const base = await this.getTsConfig(tsconfig.extends, { refresh });
2453
+ tsconfig = { ...base, ...tsconfig, compilerOptions: { ...base.compilerOptions, ...tsconfig.compilerOptions } };
2634
2454
  }
2635
2455
  this.#tsconfig = tsconfig;
2636
2456
  return tsconfig;
@@ -2666,31 +2486,29 @@ class Executor {
2666
2486
  scanInfo,
2667
2487
  overwrite = true
2668
2488
  }, dict = {}, options = {}) {
2489
+ const convertPath = (target) => Object.entries(dict).reduce((converted, [key, value]) => converted.replace(new RegExp(`__${key}__`, "g"), value), target);
2669
2490
  if (targetPath.endsWith(".ts") || targetPath.endsWith(".tsx")) {
2670
2491
  const getContent = await import(templatePath);
2671
2492
  const result = await getContent.default(scanInfo ?? null, dict, options);
2672
2493
  if (result === null)
2673
2494
  return null;
2674
- const filename = typeof result === "object" ? result.filename : path8.basename(targetPath).replace(".js", ".ts");
2495
+ const filename = typeof result === "object" ? result.filename : path9.basename(targetPath).replace(".js", ".ts");
2675
2496
  const content = typeof result === "object" ? result.content : result;
2676
- const dirname = path8.dirname(targetPath);
2677
- const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), `${dirname}/${filename}`);
2497
+ const convertedTargetPath = convertPath(`${path9.dirname(targetPath)}/${filename}`);
2678
2498
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2679
2499
  return this.writeFile(convertedTargetPath, content, { overwrite });
2680
2500
  } else if (targetPath.endsWith(".template")) {
2681
- const content = await FileSys.readText(templatePath);
2682
- const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
2501
+ const content = await FileSys2.readText(templatePath);
2502
+ const convertedTargetPath = convertPath(targetPath.slice(0, -9));
2683
2503
  const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
2684
2504
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2685
- return this.writeFile(convertedTargetPath, convertedContent, {
2686
- overwrite
2687
- });
2688
- } else if (staticTemplateFileExtensions.has(path8.extname(targetPath).toLowerCase())) {
2689
- const convertedTargetPath = Object.entries(dict).reduce((path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
2505
+ return this.writeFile(convertedTargetPath, convertedContent, { overwrite });
2506
+ } else if (staticTemplateFileExtensions.has(path9.extname(targetPath).toLowerCase())) {
2507
+ const convertedTargetPath = convertPath(targetPath);
2690
2508
  const writePath = this.getPath(convertedTargetPath);
2691
- const dirname = path8.dirname(writePath);
2692
- if (!await FileSys.dirExists(dirname))
2693
- await mkdir3(dirname, { recursive: true });
2509
+ const dirname = path9.dirname(writePath);
2510
+ if (!await FileSys2.dirExists(dirname))
2511
+ await mkdir2(dirname, { recursive: true });
2694
2512
  await copyFile(templatePath, writePath);
2695
2513
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2696
2514
  return { filePath: writePath, content: "" };
@@ -2707,12 +2525,10 @@ class Executor {
2707
2525
  }) {
2708
2526
  const templateRoot = await this.#resolveTemplateRoot();
2709
2527
  const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
2710
- const prefixTemplatePath = templatePath;
2711
- if ((await stat2(prefixTemplatePath)).isFile()) {
2712
- const filename = path8.basename(prefixTemplatePath);
2528
+ if ((await stat2(templatePath)).isFile()) {
2713
2529
  const fileContent = await this.#applyTemplateFile({
2714
- templatePath: prefixTemplatePath,
2715
- targetPath: path8.join(basePath, filename),
2530
+ templatePath,
2531
+ targetPath: path9.join(basePath, path9.basename(templatePath)),
2716
2532
  scanInfo,
2717
2533
  overwrite
2718
2534
  }, dict, options);
@@ -2720,19 +2536,19 @@ class Executor {
2720
2536
  } else {
2721
2537
  const subdirs = await readDirEntries(templatePath);
2722
2538
  const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
2723
- const subpath = path8.join(templatePath, subdir);
2539
+ const subpath = path9.join(templatePath, subdir);
2724
2540
  if ((await stat2(subpath)).isFile()) {
2725
2541
  const fileContent = await this.#applyTemplateFile({
2726
2542
  templatePath: subpath,
2727
- targetPath: path8.join(basePath, subdir),
2543
+ targetPath: path9.join(basePath, subdir),
2728
2544
  scanInfo,
2729
2545
  overwrite
2730
2546
  }, dict, options);
2731
2547
  return fileContent ? [fileContent] : [];
2732
2548
  } else
2733
2549
  return await this._applyTemplate({
2734
- basePath: path8.join(basePath, subdir),
2735
- template: path8.join(template, subdir),
2550
+ basePath: path9.join(basePath, subdir),
2551
+ template: path9.join(template, subdir),
2736
2552
  scanInfo,
2737
2553
  dict,
2738
2554
  overwrite,
@@ -2744,21 +2560,17 @@ class Executor {
2744
2560
  }
2745
2561
  async#resolveTemplateRoot() {
2746
2562
  const candidates = [
2747
- path8.resolve(getDirname2(import.meta.url), "templates"),
2748
- path8.resolve(getDirname2(import.meta.url), "../cli/templates")
2563
+ path9.resolve(getDirname2(import.meta.url), "templates"),
2564
+ path9.resolve(getDirname2(import.meta.url), "../cli/templates")
2749
2565
  ];
2750
2566
  for (const candidate of candidates) {
2751
- if (await FileSys.dirExists(candidate))
2567
+ if (await FileSys2.dirExists(candidate))
2752
2568
  return candidate;
2753
2569
  }
2754
2570
  return candidates[0];
2755
2571
  }
2756
2572
  async applyTemplate(options) {
2757
- const dict = {
2758
- ...options.dict ?? {},
2759
- ...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
2760
- };
2761
- const fileContents = await this._applyTemplate({ ...options, dict });
2573
+ const fileContents = await this._applyTemplate({ ...options, dict: withCapitalizedDict(options.dict) });
2762
2574
  await this.#formatAppliedTemplate(fileContents);
2763
2575
  return fileContents;
2764
2576
  }
@@ -2775,7 +2587,7 @@ class Executor {
2775
2587
  }
2776
2588
  }
2777
2589
  async getTypeChecker() {
2778
- const { TypeChecker } = await import("./typeChecker-3xgrzc2v.js");
2590
+ const { TypeChecker } = await import("./typeChecker-px49mezc.js");
2779
2591
  this.typeChecker ??= new TypeChecker(this);
2780
2592
  return this.typeChecker;
2781
2593
  }
@@ -2817,11 +2629,11 @@ class Executor {
2817
2629
  async#resolveTypecheckWorkerEntry() {
2818
2630
  const dirname = getDirname2(import.meta.url);
2819
2631
  const candidates = [
2820
- path8.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2821
- path8.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2822
- path8.join(dirname, "typecheck/typecheck.proc.ts"),
2823
- path8.join(dirname, "typecheck.proc.js"),
2824
- path8.join(dirname, "typecheck.proc.ts")
2632
+ path9.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2633
+ path9.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
2634
+ path9.join(dirname, "typecheck/typecheck.proc.ts"),
2635
+ path9.join(dirname, "typecheck.proc.js"),
2636
+ path9.join(dirname, "typecheck.proc.ts")
2825
2637
  ];
2826
2638
  for (const candidate of candidates)
2827
2639
  if (await Bun.file(candidate).exists())
@@ -2853,12 +2665,11 @@ class WorkspaceExecutor extends Executor {
2853
2665
  this.workspaceRoot = workspaceRoot;
2854
2666
  this.repoName = repoName;
2855
2667
  }
2856
- static #execs = new Map;
2857
2668
  static fromRoot({
2858
2669
  workspaceRoot = process.cwd(),
2859
2670
  repoName = resolveRepoName(workspaceRoot)
2860
2671
  } = {}) {
2861
- return WorkspaceExecutor.#execs.get(repoName) ?? new WorkspaceExecutor({ workspaceRoot, repoName });
2672
+ return new WorkspaceExecutor({ workspaceRoot, repoName });
2862
2673
  }
2863
2674
  static getBaseDevEnv(envPath) {
2864
2675
  const sourceEnv = envPath ? { ...process.env, ...parseEnvFile(envPath) } : process.env;
@@ -2897,13 +2708,9 @@ class WorkspaceExecutor extends Executor {
2897
2708
  return await WorkspaceInfo.fromExecutor(this);
2898
2709
  }
2899
2710
  async getApps() {
2900
- if (!await FileSys.dirExists(`${this.workspaceRoot}/apps`))
2901
- return [];
2902
2711
  return await this.#getDirHasFile(`${this.workspaceRoot}/apps`, "akan.config.ts");
2903
2712
  }
2904
2713
  async getLibs() {
2905
- if (!await FileSys.dirExists(`${this.workspaceRoot}/libs`))
2906
- return [];
2907
2714
  return await this.#getDirHasFile(`${this.workspaceRoot}/libs`, "akan.config.ts");
2908
2715
  }
2909
2716
  async getSyss() {
@@ -2911,8 +2718,6 @@ class WorkspaceExecutor extends Executor {
2911
2718
  return [appNames, libNames];
2912
2719
  }
2913
2720
  async getPkgs() {
2914
- if (!await FileSys.dirExists(`${this.workspaceRoot}/pkgs`))
2915
- return [];
2916
2721
  return await this.#getDirHasFile(`${this.workspaceRoot}/pkgs`, "package.json");
2917
2722
  }
2918
2723
  async getExecs() {
@@ -2948,7 +2753,7 @@ class WorkspaceExecutor extends Executor {
2948
2753
  await Promise.all(dirs.map(async (dir) => {
2949
2754
  if (dir.includes("_") || AVOID_DIRS.includes(dir))
2950
2755
  return;
2951
- const dirPath = path8.join(dirname, dir);
2756
+ const dirPath = path9.join(dirname, dir);
2952
2757
  if ((await stat2(dirPath)).isDirectory()) {
2953
2758
  results.push(`${prefix}${dir}`);
2954
2759
  if (maxDepth > 0)
@@ -2977,15 +2782,17 @@ class WorkspaceExecutor extends Executor {
2977
2782
  return stdout.split("\x00").filter((file) => !!file).sort();
2978
2783
  }
2979
2784
  async#getDirHasFile(basePath, targetFilename) {
2980
- const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
2785
+ if (!await FileSys2.dirExists(basePath))
2786
+ return [];
2787
+ const AVOID_DIRS = ["node_modules", "dist", "public", "webkit", "local"];
2981
2788
  const getDirs = async (dirname, maxDepth = 3, results = [], prefix = "") => {
2982
2789
  const dirs = await this.readdir(dirname);
2983
2790
  await Promise.all(dirs.map(async (dir) => {
2984
2791
  if (AVOID_DIRS.includes(dir))
2985
2792
  return;
2986
- const dirPath = path8.join(dirname, dir);
2987
- if (await FileSys.dirExists(dirPath)) {
2988
- const hasTargetFile = await FileSys.fileExists(path8.join(dirPath, targetFilename));
2793
+ const dirPath = path9.join(dirname, dir);
2794
+ if (await FileSys2.dirExists(dirPath)) {
2795
+ const hasTargetFile = await FileSys2.fileExists(path9.join(dirPath, targetFilename));
2989
2796
  if (hasTargetFile)
2990
2797
  results.push(`${prefix}${dir}`);
2991
2798
  if (maxDepth > 0)
@@ -2994,39 +2801,25 @@ class WorkspaceExecutor extends Executor {
2994
2801
  }));
2995
2802
  return results;
2996
2803
  };
2997
- return await getDirs(basePath);
2804
+ return (await getDirs(basePath)).sort((a, b) => a.localeCompare(b));
2998
2805
  }
2999
- async getScalarConstantFiles() {
2806
+ async#fromEverySys(read) {
3000
2807
  const [appNames, libNames] = await this.getSyss();
3001
- const scalarConstantExampleFiles = [
3002
- ...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getScalarConstantFiles()))).flat(),
3003
- ...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getScalarConstantFiles()))).flat()
3004
- ];
3005
- return scalarConstantExampleFiles;
2808
+ const fromApps = await Promise.all(appNames.map((appName) => read(AppExecutor.from(this, appName))));
2809
+ const fromLibs = await Promise.all(libNames.map((libName) => read(LibExecutor.from(this, libName))));
2810
+ return [...fromApps.flat(), ...fromLibs.flat()];
2811
+ }
2812
+ async getScalarConstantFiles() {
2813
+ return await this.#fromEverySys((sys) => sys.getScalarConstantFiles());
3006
2814
  }
3007
2815
  async getConstantFiles() {
3008
- const [appNames, libNames] = await this.getSyss();
3009
- const moduleConstantExampleFiles = [
3010
- ...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getConstantFiles()))).flat(),
3011
- ...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getConstantFiles()))).flat()
3012
- ];
3013
- return moduleConstantExampleFiles;
2816
+ return await this.#fromEverySys((sys) => sys.getConstantFiles());
3014
2817
  }
3015
2818
  async getDictionaryFiles() {
3016
- const [appNames, libNames] = await this.getSyss();
3017
- const moduleDictionaryExampleFiles = [
3018
- ...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getDictionaryFiles()))).flat(),
3019
- ...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getDictionaryFiles()))).flat()
3020
- ];
3021
- return moduleDictionaryExampleFiles;
2819
+ return await this.#fromEverySys((sys) => sys.getDictionaryFiles());
3022
2820
  }
3023
2821
  async getViewFiles() {
3024
- const [appNames, libNames] = await this.getSyss();
3025
- const viewExampleFiles = [
3026
- ...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getViewsSourceCode()))).flat(),
3027
- ...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getViewsSourceCode()))).flat()
3028
- ];
3029
- return viewExampleFiles;
2822
+ return await this.#fromEverySys((sys) => sys.getViewsSourceCode());
3030
2823
  }
3031
2824
  }
3032
2825
  var scanFacetDirs = ["ui", "webkit", "srvkit", "common", "plugin"];
@@ -3059,7 +2852,7 @@ class SysExecutor extends Executor {
3059
2852
  return [];
3060
2853
  const violations = [];
3061
2854
  for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
3062
- const relativePath = entry.split(path8.sep).join("/");
2855
+ const relativePath = entry.split(path9.sep).join("/");
3063
2856
  const reason = getPageSourceFileViolation(relativePath);
3064
2857
  if (reason)
3065
2858
  violations.push({ relativePath: `page/${relativePath}`, reason });
@@ -3090,24 +2883,11 @@ class SysExecutor extends Executor {
3090
2883
  scanInfo,
3091
2884
  options: { exec: this, facet }
3092
2885
  })),
3093
- ...scanInfo.getDatabaseModules().map((model) => this._applyTemplate({
3094
- basePath: `lib/${model}`,
3095
- template: "moduleRoot",
3096
- scanInfo,
3097
- dict: { model, Model: capitalize(model) }
3098
- })),
3099
- ...scanInfo.getServiceModules().map((model) => this._applyTemplate({
3100
- basePath: `lib/_${model}`,
3101
- template: "moduleRoot",
3102
- scanInfo,
3103
- dict: { model, Model: capitalize(model) }
3104
- })),
3105
- ...scanInfo.getScalarModules().map((model) => this._applyTemplate({
3106
- basePath: `lib/__scalar/${model}`,
3107
- template: "moduleRoot",
3108
- scanInfo,
3109
- dict: { model, Model: capitalize(model) }
3110
- }))
2886
+ ...[
2887
+ ...scanInfo.getDatabaseModules().map((model) => [model, `lib/${model}`]),
2888
+ ...scanInfo.getServiceModules().map((model) => [model, `lib/_${model}`]),
2889
+ ...scanInfo.getScalarModules().map((model) => [model, `lib/__scalar/${model}`])
2890
+ ].map(([model, basePath]) => this._applyTemplate({ basePath, template: "moduleRoot", scanInfo, dict: { model, Model: capitalize(model) } }))
3111
2891
  ];
3112
2892
  }
3113
2893
  async scan({
@@ -3169,45 +2949,43 @@ class SysExecutor extends Executor {
3169
2949
  await this.setPackageJson(libPkgJsonWithDeps);
3170
2950
  }
3171
2951
  async getLocalFile(targetPath) {
3172
- const filePath = path8.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
2952
+ const filePath = path9.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
3173
2953
  const content = await this.workspace.readFile(filePath);
3174
2954
  return { filePath, content };
3175
2955
  }
3176
2956
  async getDatabaseModules() {
3177
- const databaseModules = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.constant.ts`).exists());
3178
- return databaseModules;
2957
+ return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${name}.constant.ts`));
3179
2958
  }
3180
2959
  async getServiceModules() {
3181
- const serviceModules = (await this.readdir("lib")).filter((name) => name.startsWith("_") && !name.startsWith("__")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.service.ts`).exists());
3182
- return serviceModules;
2960
+ return (await this.readdir("lib")).filter((name) => name.startsWith("_") && !name.startsWith("__")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${name.slice(1)}.service.ts`));
3183
2961
  }
3184
2962
  async getScalarModules() {
3185
- const scalarModules = (await this.readdir("lib/__scalar")).filter((name) => !name.startsWith("_")).filter((name) => Bun.file(`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`).exists());
3186
- return scalarModules;
2963
+ return (await this.readdir("lib/__scalar")).filter((name) => !name.startsWith("_")).filter((name) => existsSync2(`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`));
2964
+ }
2965
+ async#getComponentModules(role) {
2966
+ return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${capitalize(name)}.${role}.tsx`));
2967
+ }
2968
+ async#getComponentSources(role) {
2969
+ const modules = await this.#getComponentModules(role);
2970
+ return Promise.all(modules.map((name) => this.getLocalFile(`lib/${name}/${capitalize(name)}.${role}.tsx`)));
3187
2971
  }
3188
2972
  async getViewComponents() {
3189
- const viewComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.View.tsx`).exists());
3190
- return viewComponents;
2973
+ return await this.#getComponentModules("View");
3191
2974
  }
3192
2975
  async getUnitComponents() {
3193
- const unitComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Unit.tsx`).exists());
3194
- return unitComponents;
2976
+ return await this.#getComponentModules("Unit");
3195
2977
  }
3196
2978
  async getTemplateComponents() {
3197
- const templateComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${capitalize(name)}.Template.tsx`).exists());
3198
- return templateComponents;
2979
+ return await this.#getComponentModules("Template");
3199
2980
  }
3200
2981
  async getViewsSourceCode() {
3201
- const viewComponents = await this.getViewComponents();
3202
- return Promise.all(viewComponents.map((viewComponent) => this.getLocalFile(`lib/${viewComponent}/${capitalize(viewComponent)}.View.tsx`)));
2982
+ return await this.#getComponentSources("View");
3203
2983
  }
3204
2984
  async getUnitsSourceCode() {
3205
- const unitComponents = await this.getUnitComponents();
3206
- return Promise.all(unitComponents.map((unitComponent) => this.getLocalFile(`lib/${unitComponent}/${capitalize(unitComponent)}.Unit.tsx`)));
2985
+ return await this.#getComponentSources("Unit");
3207
2986
  }
3208
2987
  async getTemplatesSourceCode() {
3209
- const templateComponents = await this.getTemplateComponents();
3210
- return Promise.all(templateComponents.map((templateComponent) => this.getLocalFile(`lib/${templateComponent}/${capitalize(templateComponent)}.Template.tsx`)));
2988
+ return await this.#getComponentSources("Template");
3211
2989
  }
3212
2990
  async getScalarConstantFiles() {
3213
2991
  const scalarModules = await this.getScalarModules();
@@ -3215,7 +2993,7 @@ class SysExecutor extends Executor {
3215
2993
  }
3216
2994
  async getScalarDictionaryFiles() {
3217
2995
  const scalarModules = await this.getScalarModules();
3218
- return Promise.all(scalarModules.map((scalarModule) => this.getLocalFile(`lib/${scalarModule}/${scalarModule}.dictionary.ts`)));
2996
+ return Promise.all(scalarModules.map((scalarModule) => this.getLocalFile(`lib/__scalar/${scalarModule}/${scalarModule}.dictionary.ts`)));
3219
2997
  }
3220
2998
  async getConstantFiles() {
3221
2999
  const modules = await this.getModules();
@@ -3237,16 +3015,8 @@ class SysExecutor extends Executor {
3237
3015
  return Promise.all(modules.map((module) => this.getLocalFile(`lib/${module}/${module}.dictionary.ts`)));
3238
3016
  }
3239
3017
  async applyTemplate(options) {
3240
- const dict = {
3241
- ...options.dict ?? {},
3242
- ...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
3243
- };
3244
3018
  const scanInfo = await this.scan();
3245
- const fileContents = await this._applyTemplate({
3246
- ...options,
3247
- scanInfo,
3248
- dict
3249
- });
3019
+ const fileContents = await this._applyTemplate({ ...options, scanInfo, dict: withCapitalizedDict(options.dict) });
3250
3020
  await this.scan();
3251
3021
  return fileContents;
3252
3022
  }
@@ -3255,7 +3025,7 @@ class SysExecutor extends Executor {
3255
3025
  class AppExecutor extends SysExecutor {
3256
3026
  static #routeSourceValidator = null;
3257
3027
  static async#getRouteSourceValidator() {
3258
- AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-apyqw7wk.js")).RouteSourceValidator;
3028
+ AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-7ycnfz0e.js")).RouteSourceValidator;
3259
3029
  return AppExecutor.#routeSourceValidator;
3260
3030
  }
3261
3031
  dist;
@@ -3264,15 +3034,20 @@ class AppExecutor extends SysExecutor {
3264
3034
  super({ workspace, name, type: "app" });
3265
3035
  this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/apps/${name}`);
3266
3036
  }
3267
- static #execs = new Map;
3268
3037
  static from(executor, name) {
3269
- const exec = AppExecutor.#execs.get(name);
3270
- if (exec)
3271
- return exec;
3272
- else if (executor instanceof WorkspaceExecutor)
3273
- return new AppExecutor({ workspace: executor, name });
3274
- else
3275
- return new AppExecutor({ workspace: executor.workspace, name });
3038
+ return new AppExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
3039
+ }
3040
+ static #keptOnStart = [path9.join(".akan", "native"), path9.join(".akan", "cache", "bin")];
3041
+ async#removeDevOutput(dir = ".akan") {
3042
+ const entries = await readDirEntries(this.getPath(dir)).catch(() => []);
3043
+ await Promise.all(entries.map(async (name) => {
3044
+ const entry = path9.join(dir, name);
3045
+ if (AppExecutor.#keptOnStart.includes(entry))
3046
+ return;
3047
+ if (AppExecutor.#keptOnStart.some((kept) => kept.startsWith(`${entry}${path9.sep}`)))
3048
+ return await this.#removeDevOutput(entry);
3049
+ await this.removeDir(entry);
3050
+ }));
3276
3051
  }
3277
3052
  getEnv() {
3278
3053
  return WorkspaceExecutor.getBaseDevEnv().env;
@@ -3288,10 +3063,8 @@ class AppExecutor extends SysExecutor {
3288
3063
  return basePort + appIndex + portOffset;
3289
3064
  }
3290
3065
  getCommandEnv(env = {}) {
3291
- const basePort = 8282;
3292
- const portOffset = WorkspaceExecutor.getBaseDevEnv().portOffset;
3293
- const PORT = (basePort + portOffset).toString();
3294
- const AKAN_PUBLIC_SERVER_PORT = portOffset ? (8282 + portOffset).toString() : undefined;
3066
+ const { portOffset } = WorkspaceExecutor.getBaseDevEnv();
3067
+ const PORT = (8282 + portOffset).toString();
3295
3068
  return {
3296
3069
  ...process.env,
3297
3070
  AKAN_PUBLIC_APP_NAME: this.name,
@@ -3299,18 +3072,24 @@ class AppExecutor extends SysExecutor {
3299
3072
  NODE_NO_WARNINGS: "1",
3300
3073
  PORT,
3301
3074
  AKAN_PUBLIC_CLIENT_PORT: PORT,
3302
- ...AKAN_PUBLIC_SERVER_PORT ? { AKAN_PUBLIC_SERVER_PORT } : {},
3075
+ ...portOffset ? { AKAN_PUBLIC_SERVER_PORT: PORT } : {},
3303
3076
  ...env
3304
3077
  };
3305
3078
  }
3079
+ async getDatabaseModeEnv() {
3080
+ const akanConfig = await this.getConfig();
3081
+ return {
3082
+ AKAN_DATABASE_MODE: akanConfig.resolveDatabaseMode(),
3083
+ AKAN_DATABASE_MODES: akanConfig.database.modes.join(",")
3084
+ };
3085
+ }
3306
3086
  async prepareCommand(type) {
3307
3087
  const akanConfig = await this.getConfig();
3308
- const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
3309
3088
  const routeEnv = {
3310
3089
  AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
3311
3090
  AKAN_PUBLIC_API_PREFIX: akanConfig.api.prefix,
3312
3091
  AKAN_PUBLIC_WS_PREFIX: akanConfig.api.websocketPrefix,
3313
- AKAN_DATABASE_MODE: databaseMode
3092
+ ...await this.getDatabaseModeEnv()
3314
3093
  };
3315
3094
  Object.assign(process.env, routeEnv);
3316
3095
  if (type === "build") {
@@ -3323,7 +3102,7 @@ class AppExecutor extends SysExecutor {
3323
3102
  ...akanConfig.web.ssr ? [this.cp("public", `${this.dist.cwdPath}/public`, { dereference: true })] : []
3324
3103
  ]);
3325
3104
  } else {
3326
- await this.removeDir(".akan");
3105
+ await this.#removeDevOutput();
3327
3106
  if (!akanConfig.web.ssr || !akanConfig.web.csr)
3328
3107
  this.logger.verbose(`akan.config.ts disables web.${!akanConfig.web.ssr ? "ssr" : "csr"}; \`akan start\` still serves it, \`akan build\` will not`);
3329
3108
  }
@@ -3339,6 +3118,8 @@ class AppExecutor extends SysExecutor {
3339
3118
  const buildEnv = { ...env };
3340
3119
  delete buildEnv.AKAN_PUBLIC_CLIENT_PORT;
3341
3120
  delete buildEnv.AKAN_PUBLIC_SERVER_PORT;
3121
+ delete buildEnv.AKAN_PUBLIC_OPERATION_MODE;
3122
+ delete process.env.AKAN_PUBLIC_OPERATION_MODE;
3342
3123
  Object.assign(process.env, buildEnv);
3343
3124
  }
3344
3125
  return { env };
@@ -3348,16 +3129,7 @@ class AppExecutor extends SysExecutor {
3348
3129
  if (this.#publicEnv)
3349
3130
  return this.#publicEnv;
3350
3131
  const searchPatterns = [...patterns, "AKAN_PUBLIC_*"];
3351
- const regexes = searchPatterns.map((pattern) => {
3352
- let body = "";
3353
- for (const ch of pattern) {
3354
- if (ch === "*")
3355
- body += ".*";
3356
- else
3357
- body += ch.replace(/[.+^${}()|[\]\\?]/g, "\\$&");
3358
- }
3359
- return new RegExp(`^${body}$`);
3360
- });
3132
+ const regexes = searchPatterns.map((pattern) => new RegExp(`^${pattern.split("*").map((part) => part.replace(/[.+^${}()|[\]\\?]/g, "\\$&")).join(".*")}$`));
3361
3133
  const publicEnv = {};
3362
3134
  for (const [k, v] of Object.entries(process.env)) {
3363
3135
  if (typeof v !== "string")
@@ -3386,19 +3158,23 @@ class AppExecutor extends SysExecutor {
3386
3158
  const owners = new Map;
3387
3159
  const devOnlyKeys = new Set;
3388
3160
  const devOnlyDirs = [];
3389
- for (const root of await this.getPageRoots()) {
3390
- if (!await FileSys.dirExists(root.dir))
3161
+ const pageRoots = await this.getPageRoots();
3162
+ const libKeyPrefixes = pageRoots.map((root) => root.keyPrefix).filter(Boolean);
3163
+ for (const root of pageRoots) {
3164
+ if (!await FileSys2.dirExists(root.dir))
3391
3165
  continue;
3392
3166
  for await (const rel of glob.scan({
3393
3167
  cwd: root.dir,
3394
3168
  absolute: false,
3395
3169
  onlyFiles: true
3396
3170
  })) {
3397
- const segments = rel.split(path8.sep);
3171
+ const segments = rel.split(path9.sep);
3398
3172
  if (segments.some((s) => s === "node_modules"))
3399
3173
  continue;
3400
3174
  const posix = `${root.keyPrefix}${segments.join("/")}`;
3401
- const absPath = path8.join(root.dir, ...segments);
3175
+ if (!root.keyPrefix && libKeyPrefixes.some((prefix) => posix.startsWith(prefix)))
3176
+ continue;
3177
+ const absPath = path9.join(root.dir, ...segments);
3402
3178
  validatePageSourceFile(posix, { filePath: absPath });
3403
3179
  if (!isRouteSourceFile(posix))
3404
3180
  continue;
@@ -3462,7 +3238,7 @@ class AppExecutor extends SysExecutor {
3462
3238
  const entries = await readDirEntries(libsDir).catch(() => []);
3463
3239
  for (const entry of entries.sort()) {
3464
3240
  const dir = `${libsDir}/${entry}`;
3465
- if (!await FileSys.dirExists(dir))
3241
+ if (!await FileSys2.dirExists(dir))
3466
3242
  continue;
3467
3243
  roots.push({ dir, realDir: await realpath(dir), keyPrefix: `${parent}${AppExecutor.#pageLibsDir}/${entry}/` });
3468
3244
  }
@@ -3487,7 +3263,8 @@ class AppExecutor extends SysExecutor {
3487
3263
  return entries.map((entry) => `${parent}${AppExecutor.#pageLibsDir}/${entry}/`);
3488
3264
  }))).flat();
3489
3265
  const wanted = parents.flatMap((parent) => libs.map((lib) => `${parent}${AppExecutor.#pageLibsDir}/(${lib})/`));
3490
- if (linked.sort().join(",") === wanted.sort().join(","))
3266
+ const isLink = async (key) => (await lstat(`${this.cwdPath}/page/${key.slice(0, -1)}`).catch(() => null))?.isSymbolicLink() ?? false;
3267
+ if (linked.sort().join(",") === wanted.sort().join(",") && (await Promise.all(linked.map(isLink))).every(Boolean))
3491
3268
  return false;
3492
3269
  await Promise.all(parents.map((parent) => this.removeDir(`${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`)));
3493
3270
  for (const parent of parents) {
@@ -3503,7 +3280,7 @@ class AppExecutor extends SysExecutor {
3503
3280
  async#resolvePageLibs(syncPageLibs, libDeps) {
3504
3281
  if (!syncPageLibs)
3505
3282
  return [];
3506
- const hasPageDir = async (lib) => await FileSys.dirExists(`${this.workspace.workspaceRoot}/libs/${lib}/page`);
3283
+ const hasPageDir = async (lib) => await FileSys2.dirExists(`${this.workspace.workspaceRoot}/libs/${lib}/page`);
3507
3284
  if (syncPageLibs === true) {
3508
3285
  const libs = [];
3509
3286
  for (const lib of libDeps)
@@ -3538,12 +3315,12 @@ class AppExecutor extends SysExecutor {
3538
3315
  static async#linkLibAsset(targetPath, linkPath) {
3539
3316
  const isWindows = process.platform === "win32";
3540
3317
  try {
3541
- const target = isWindows ? targetPath : path8.relative(path8.dirname(linkPath), targetPath);
3318
+ const target = isWindows ? targetPath : path9.relative(path9.dirname(linkPath), targetPath);
3542
3319
  await symlink(target, linkPath, isWindows ? "junction" : "dir");
3543
3320
  } catch (error) {
3544
3321
  if (!isWindows)
3545
3322
  throw error;
3546
- await mkdir3(linkPath, { recursive: true });
3323
+ await mkdir2(linkPath, { recursive: true });
3547
3324
  await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
3548
3325
  }
3549
3326
  }
@@ -3575,13 +3352,14 @@ class AppExecutor extends SysExecutor {
3575
3352
  appPath: this.cwdPath,
3576
3353
  executor: this,
3577
3354
  getPath: (rel) => this.getPath(rel),
3578
- fileExists: (rel) => FileSys.fileExists(this.getPath(rel)),
3355
+ fileExists: (rel) => FileSys2.fileExists(this.getPath(rel)),
3356
+ readFile: (rel) => this.readFile(rel),
3579
3357
  writeFile: async (rel, content, opts) => {
3580
3358
  await this.writeFile(rel, content, opts);
3581
3359
  },
3582
3360
  readEnvClient: async () => {
3583
- const envClientPath = path8.join(this.cwdPath, "env", "env.client.ts");
3584
- if (!await FileSys.fileExists(envClientPath))
3361
+ const envClientPath = path9.join(this.cwdPath, "env", "env.client.ts");
3362
+ if (!await FileSys2.fileExists(envClientPath))
3585
3363
  return null;
3586
3364
  try {
3587
3365
  const envUrl = pathToFileURL(envClientPath);
@@ -3612,16 +3390,6 @@ class AppExecutor extends SysExecutor {
3612
3390
  return true;
3613
3391
  });
3614
3392
  }
3615
- async getPluginRuntimePackages() {
3616
- const plugins = await this.collectPlugins();
3617
- const appConfig = await this.getConfig();
3618
- const ctx = {
3619
- appName: this.name,
3620
- mobile: appConfig.mobile,
3621
- hasMobilePermission: (permission) => Object.values(appConfig.mobile.targets).some((target) => target.permissions?.includes(permission) ?? false)
3622
- };
3623
- return [...new Set(plugins.flatMap((plugin) => plugin.runtimePackages?.(ctx) ?? []))];
3624
- }
3625
3393
  async increaseBuildNum() {
3626
3394
  await increaseBuildNum(this);
3627
3395
  }
@@ -3637,15 +3405,8 @@ class LibExecutor extends SysExecutor {
3637
3405
  super({ workspace, name, type: "lib" });
3638
3406
  this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/libs/${name}`);
3639
3407
  }
3640
- static #execs = new Map;
3641
3408
  static from(executor, name) {
3642
- const exec = LibExecutor.#execs.get(name);
3643
- if (exec)
3644
- return exec;
3645
- else if (executor instanceof WorkspaceExecutor)
3646
- return new LibExecutor({ workspace: executor, name });
3647
- else
3648
- return new LibExecutor({ workspace: executor.workspace, name });
3409
+ return new LibExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
3649
3410
  }
3650
3411
  #akanConfig = null;
3651
3412
  async getConfig({ refresh } = {}) {
@@ -3668,9 +3429,7 @@ class PkgExecutor extends Executor {
3668
3429
  this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/pkgs/${name}`);
3669
3430
  }
3670
3431
  static from(executor, name) {
3671
- if (executor instanceof WorkspaceExecutor)
3672
- return new PkgExecutor({ workspace: executor, name });
3673
- return new PkgExecutor({ workspace: executor.workspace, name });
3432
+ return new PkgExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
3674
3433
  }
3675
3434
  #scanInfo = null;
3676
3435
  async scan({ refresh } = {}) {
@@ -3688,9 +3447,12 @@ class PkgExecutor extends Executor {
3688
3447
  const rootVersion = rootDeps[dep];
3689
3448
  if (rootVersion)
3690
3449
  return rootVersion;
3450
+ const overrideVersion = rootPackageJson.overrides?.[dep];
3451
+ if (typeof overrideVersion === "string")
3452
+ return overrideVersion;
3691
3453
  try {
3692
3454
  const packageJsonPath = `pkgs/${dep}/package.json`;
3693
- if (!await Bun.file(path8.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
3455
+ if (!await Bun.file(path9.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
3694
3456
  return;
3695
3457
  const packageJson = await this.workspace.readJson(packageJsonPath);
3696
3458
  if (packageJson.name === dep)
@@ -3713,16 +3475,8 @@ class PkgExecutor extends Executor {
3713
3475
  }
3714
3476
  if (missingDeps.length > 0)
3715
3477
  throw new Error(`Missing dependency versions in root package.json: ${missingDeps.join(", ")}`);
3716
- const toDependencyEntries = (names) => names.map((dep) => {
3717
- const version = dependencyVersions.get(dep);
3718
- if (!version)
3719
- throw new Error(`Missing dependency versions in root package.json: ${dep}`);
3720
- return [dep, version];
3721
- });
3722
- return {
3723
- dependencies: Object.fromEntries(toDependencyEntries(dependencyNames)),
3724
- devDependencies: Object.fromEntries(toDependencyEntries(devDependencyNames))
3725
- };
3478
+ const toDependencyMap = (names) => Object.fromEntries(names.map((dep) => [dep, dependencyVersions.get(dep)]));
3479
+ return { dependencies: toDependencyMap(dependencyNames), devDependencies: toDependencyMap(devDependencyNames) };
3726
3480
  }
3727
3481
  async updatePackageJsonDependencies(dependencies = [], devDependencies = []) {
3728
3482
  const [rootPackageJson, pkgJson] = await Promise.all([this.workspace.getPackageJson(), this.getPackageJson()]);
@@ -3791,4 +3545,4 @@ class ModuleExecutor extends Executor {
3791
3545
  }
3792
3546
  }
3793
3547
 
3794
- export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, isScannedRootEntry, AppInfo, Spinner, CommandExecutionError, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
3548
+ export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, binPlatforms, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };