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

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 (190) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-q2m47rqp.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-pdqhc3sn.js +1457 -0
  6. package/README.md +3 -0
  7. package/TunnelShare-scf4e4pd.js +9 -0
  8. package/{agent.command-d2gfxfck.js → agent.command-ttn09rx3.js} +8 -9
  9. package/akanCode.js +102 -0
  10. package/application.command-7gweenv7.js +179 -0
  11. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-38exmejd.js} +63 -64
  12. package/buildBatch.proc.js +26 -25
  13. package/cloud.command-bhztw7c2.js +95 -0
  14. package/cloud.script-aabyhgba.js +20 -0
  15. package/code.command-6s8c0k91.js +154 -0
  16. package/commandManifest.json +1 -1
  17. package/consoleToStderr-h5bryj94.js +7 -0
  18. package/context.command-phmgywt2.js +48 -0
  19. package/context.runner-yvyw5g2d.js +21 -0
  20. package/{dependencyScanner-shs82ynp.js → dependencyScanner-ye2e6pdz.js} +2 -2
  21. package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
  22. package/fileSys-5ngz93ga.js +9 -0
  23. package/guideline.command-7jk47fgq.js +42 -0
  24. package/guidelines/agentRule/agentRule.instruction.md +209 -28
  25. package/guidelines/conventions/conventions.instruction.md +96 -29
  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 +17 -7
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +77 -3
  30. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  31. package/guidelines/transportRule/transportRule.instruction.md +51 -0
  32. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  33. package/incrementalBuilder.proc.js +31 -48
  34. package/{index-h7yw2z9n.js → index-0bq14vr1.js} +8 -14
  35. package/{index-259dsnz3.js → index-1db2cw0y.js} +5 -8
  36. package/index-22d57v4c.js +266 -0
  37. package/index-3hx1z96c.js +87 -0
  38. package/{index-kcxmv48c.js → index-3t7g9e4r.js} +45 -79
  39. package/{index-ne2qy0nk.js → index-4dn1p115.js} +12 -12
  40. package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
  41. package/index-7cqjvraf.js +69 -0
  42. package/index-7md2g9vw.js +6 -0
  43. package/{index-zry5nz81.js → index-8kb5p54p.js} +51 -30
  44. package/{index-ktb5njgy.js → index-9629w7ns.js} +49 -99
  45. package/index-9dp031by.js +12 -0
  46. package/{index-a64esmtb.js → index-aadpbnt2.js} +172 -308
  47. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  48. package/index-c6eggbee.js +123 -0
  49. package/index-d0arnrm8.js +700 -0
  50. package/index-d2242ksy.js +99 -0
  51. package/{index-600zhnve.js → index-dg97qp24.js} +70 -200
  52. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  53. package/{index-w8kmx1b6.js → index-fgq75haa.js} +198 -290
  54. package/{index-2tv3fdx4.js → index-ga6hygxr.js} +944 -1333
  55. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  56. package/index-hr5n7t4x.js +465 -0
  57. package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
  58. package/{index-7737gxcp.js → index-m0ms3n1c.js} +107 -241
  59. package/{index-ws5e0j51.js → index-mgfx3mfs.js} +43 -26
  60. package/index-p6pbz9n5.js +25 -0
  61. package/index-pp54p5zk.js +53 -0
  62. package/{index-j9yhg404.js → index-q44jbw48.js} +4 -7
  63. package/{index-mak3cd28.js → index-rmcw8yc8.js} +137 -255
  64. package/{index-pnqbkf5p.js → index-rnt66vym.js} +6 -2
  65. package/index-smme4r47.js +49 -0
  66. package/index-tgjy8xf3.js +187 -0
  67. package/index-v5eayqqw.js +2729 -0
  68. package/index-wa5jbxj1.js +760 -0
  69. package/{index-4edzf1gr.js → index-wp43xrjv.js} +77 -150
  70. package/{index-fz5q3gd7.js → index-ya5qbmyg.js} +10 -17
  71. package/index-yjkte9aj.js +1119 -0
  72. package/{index-gyd220p2.js → index-yy4s1j72.js} +554 -577
  73. package/{index-v0qh106m.js → index-z50k041j.js} +580 -862
  74. package/index.js +23 -22
  75. package/{library.command-nbsky672.js → library.command-ccb1cp92.js} +4 -5
  76. package/{localRegistry.command-yeq57wjr.js → localRegistry.command-ajb9n87j.js} +23 -42
  77. package/{module.command-29vqzrh8.js → module.command-5k6h407d.js} +8 -11
  78. package/{package.command-axv0abmn.js → package.command-e6ntvpez.js} +6 -7
  79. package/package.json +14 -6
  80. package/{page.command-vs9s3zrh.js → page.command-v4f90wb8.js} +4 -5
  81. package/{primitive.command-cbxmqbtc.js → primitive.command-g6km57q5.js} +9 -22
  82. package/prompter-ptaa3nk0.js +8 -0
  83. package/{quality.command-gmbs7nws.js → quality.command-de3p36pz.js} +205 -377
  84. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  85. package/{repair.command-bqsjptsj.js → repair.command-d3x1javq.js} +11 -35
  86. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  87. package/{scalar.command-6k1vhe9s.js → scalar.command-7ezrac2b.js} +7 -10
  88. package/skills/akan-code/SKILL.md +130 -0
  89. package/skills/akan-debug/SKILL.md +42 -0
  90. package/skills/akan-deps/SKILL.md +50 -0
  91. package/skills/akan-err/SKILL.md +39 -0
  92. package/skills/akan-module/SKILL.md +45 -0
  93. package/skills/akan-scaffold/SKILL.md +76 -0
  94. package/skills/akan-scalar/SKILL.md +52 -0
  95. package/skills/akan-service/SKILL.md +34 -0
  96. package/skills/akan-store/SKILL.md +67 -0
  97. package/skills/akan-ui/SKILL.md +63 -0
  98. package/skills/akan-validate/SKILL.md +44 -0
  99. package/{subspace.command-fwjfcts7.js → subspace.command-ps71ynp6.js} +138 -67
  100. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  101. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  102. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  103. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  104. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  105. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  106. package/templates/appSample/lib/task/task.signal.ts +1 -1
  107. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  108. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  109. package/templates/client.ts +7 -30
  110. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  111. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  112. package/templates/crudPages/new/page.tsx +2 -1
  113. package/templates/crudPages/page.tsx +2 -1
  114. package/templates/crudSinglePage/page.tsx +2 -1
  115. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  116. package/templates/lib/__lib/lib.document.ts +0 -2
  117. package/templates/lib/sig.ts +8 -30
  118. package/templates/localDev/docker-compose.yaml.template +3 -14
  119. package/templates/module/__model__.signal.ts +12 -4
  120. package/templates/pkgRoot/tsconfig.json.template +2 -7
  121. package/templates/server.ts +0 -2
  122. package/templates/service/__model__.abstract.md.template +8 -0
  123. package/templates/workspaceRoot/.gitignore.template +0 -1
  124. package/tunnel.command-hsrv2mny.js +129 -0
  125. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  126. package/typecheck.proc.js +1 -1
  127. package/workflow.command-z2p32nmf.js +79 -0
  128. package/{workspace.command-8s050m0h.js → workspace.command-tmdqkrh0.js} +77 -109
  129. package/abstractCompactor-mjh5ppsq.js +0 -101
  130. package/application.command-pj2m7584.js +0 -204
  131. package/applicationReleasePackager-4yasxwv9.js +0 -244
  132. package/capacitorApp-36rbr858.js +0 -12
  133. package/cloud.command-jshwdq22.js +0 -93
  134. package/context.command-ez8a0zyz.js +0 -80
  135. package/devTuiView-c08ccbat.js +0 -9
  136. package/getDirname-dr3jz1xg.js +0 -7
  137. package/guideline.command-xmqwx8zm.js +0 -359
  138. package/guidelines/agentRule/agentRule.generate.json +0 -31
  139. package/guidelines/componentRule/componentRule.generate.json +0 -31
  140. package/guidelines/conventions/conventions.generate.json +0 -25
  141. package/guidelines/cssRule/cssRule.generate.json +0 -31
  142. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  143. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  144. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  145. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  146. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  147. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  148. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  149. package/guidelines/framework/framework.generate.json +0 -32
  150. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  151. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  152. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  153. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  154. package/guidelines/modelService/modelService.generate.json +0 -31
  155. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  156. package/guidelines/modelStore/modelStore.generate.json +0 -31
  157. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  158. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  159. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  160. package/guidelines/modelView/modelView.generate.json +0 -31
  161. package/guidelines/modelZone/modelZone.generate.json +0 -31
  162. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  163. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  164. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  165. package/guidelines/queryRule/queryRule.generate.json +0 -31
  166. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  167. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  168. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  169. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  170. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  171. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  172. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  173. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  174. package/guidelines/transportRule/transportRule.generate.json +0 -31
  175. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  176. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  177. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  178. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  179. package/index-2hq52jq3.js +0 -465
  180. package/index-47pj3aee.js +0 -76
  181. package/index-6wwzs1bc.js +0 -7
  182. package/index-8pm30m73.js +0 -841
  183. package/index-kn1h7p1x.js +0 -560
  184. package/index-meh558jy.js +0 -65
  185. package/index-rwg2aa4f.js +0 -229
  186. package/index-wt7qrtd4.js +0 -1516
  187. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  188. package/templates/module/__model__.abstract.md +0 -28
  189. package/templates/service/__model__.abstract.md +0 -28
  190. 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-5b6tavv3.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,11 +504,12 @@ 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,
@@ -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-5ngz93ga.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;
@@ -355,10 +669,10 @@ ${CLAUDE_COMMENT_RULE}
355
669
  `;
356
670
 
357
671
  // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
358
- import fs from "fs";
359
- import path2 from "path";
672
+ import fs2 from "fs";
673
+ import path4 from "path";
360
674
  import { fileURLToPath } from "url";
361
- import { normalizeRoutePrefix } from "akanjs/base";
675
+ import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
362
676
  import { resolveAkanI18nConfig } from "akanjs/common";
363
677
 
364
678
  // pkgs/@akanjs/devkit/akanConfig/types.ts
@@ -367,66 +681,14 @@ import { archs } from "akanjs";
367
681
  // pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
368
682
  var DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui", "akanjs/server"];
369
683
  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
- ];
684
+ "lucide-react date-fns lodash-es ramda antd react-bootstrap ahooks @ant-design/icons @headlessui/react",
685
+ "@headlessui-float/react @heroicons/react/20/solid @heroicons/react/24/solid @heroicons/react/24/outline",
686
+ "@visx/visx @tremor/react rxjs @mui/material @mui/icons-material recharts react-use @material-ui/core",
687
+ "@material-ui/icons @tabler/icons-react mui-core react-icons/*"
688
+ ].flatMap((line) => line.split(" "));
396
689
  var WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"];
397
690
  var DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
398
691
  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
692
  var DEFAULT_AKAN_IMAGE_CONFIG = {
431
693
  deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
432
694
  imageSizes: [32, 48, 64, 96, 128, 256, 384],
@@ -506,7 +768,7 @@ class AkanAppConfig {
506
768
  rootPackageJson;
507
769
  docker;
508
770
  dockerfile;
509
- defaultDatabaseMode;
771
+ database;
510
772
  web;
511
773
  externalLibs;
512
774
  barrelImports;
@@ -534,7 +796,7 @@ class AkanAppConfig {
534
796
  this.baseDevEnv = baseDevEnv;
535
797
  this.plugins = plugins;
536
798
  this.#applyRoutes(config?.routes);
537
- this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
799
+ this.database = AkanAppConfig.#database(app, config);
538
800
  this.externalLibs = [...new Set([...config?.externalLibs ?? [], ...libContributions.externalLibs])];
539
801
  this.barrelImports = [
540
802
  ...DEFAULT_BARREL_IMPORTS,
@@ -572,7 +834,7 @@ class AkanAppConfig {
572
834
  #resolveWebConfig(web) {
573
835
  const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
574
836
  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.`);
837
+ throw new Error(`apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares mobile targets; the mobile apps ship that bundle. Drop the mobile section or leave CSR on.`);
576
838
  return resolved;
577
839
  }
578
840
  #resolveMobileConfig(mobile) {
@@ -581,57 +843,113 @@ class AkanAppConfig {
581
843
  indexPath: _indexPath,
582
844
  ...rawMobile
583
845
  } = mobile ?? {};
846
+ const configPath = `apps/${this.app.name}/akan.config.ts`;
847
+ AkanAppConfig.#assertMobileKeys(rawMobile, AkanAppConfig.#mobileKeys, "mobile", configPath);
584
848
  const appName = rawMobile.appName ?? this.app.name;
585
849
  const appId = rawMobile.appId ?? deriveDefaultAppId(this.baseDevEnv.repoName, this.app.name);
586
850
  const version = rawMobile.version ?? "0.0.1";
587
851
  const buildNum = rawMobile.buildNum ?? 1;
852
+ const files = AkanAppConfig.#resolveMobileFiles(rawMobile.files, "mobile", configPath);
853
+ const native = rawMobile.native;
588
854
  const defaultTargetName = this.#defaultMobileTargetName(rawTargets);
589
855
  const targetEntries = Object.entries(rawTargets ?? {
590
856
  [defaultTargetName]: {}
591
857
  });
592
858
  const targets = Object.fromEntries(targetEntries.map(([name, rawTarget]) => {
593
- const target = rawTarget;
859
+ const target = rawTarget ?? {};
860
+ const where = `mobile.targets.${name}`;
861
+ AkanAppConfig.#assertMobileKeys(target, AkanAppConfig.#targetKeys, where, configPath);
594
862
  const fallbackBasePath = !rawTargets && this.basePaths.has(name) ? name : undefined;
595
863
  const basePath = (target.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
596
864
  const indexPath = normalizeIndexPath(target.indexPath);
597
865
  const deepLinks = normalizeDeepLinks(target.deepLinks);
598
866
  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`);
867
+ throw new Error(`Mobile target '${name}' uses unknown basePath '${basePath}' in ${configPath}`);
600
868
  }
869
+ const targetFiles = AkanAppConfig.#resolveMobileFiles(target.files, where, configPath);
870
+ const fileName = target.fileName ?? rawMobile.fileName;
601
871
  const resolved = {
602
- ...rawMobile,
603
- ...target,
604
872
  name,
605
873
  basePath,
606
874
  indexPath,
607
875
  deepLinks,
608
876
  appName: target.appName ?? appName,
609
877
  appId: target.appId ?? appId,
878
+ ...fileName ? { fileName } : {},
610
879
  version: target.version ?? version,
611
880
  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
- }
881
+ ...target.assets ? { assets: target.assets } : {},
882
+ ...target.permissions ? { permissions: target.permissions } : {},
883
+ ...files || targetFiles ? { files: { ...files, ...targetFiles } } : {},
884
+ ...native || target.native ? { native: AkanAppConfig.#mergeNative(native, target.native) } : {}
624
885
  };
625
886
  return [name, resolved];
626
887
  }));
627
888
  return {
628
- ...rawMobile,
629
889
  appName,
630
890
  appId,
891
+ ...rawMobile.fileName ? { fileName: rawMobile.fileName } : {},
631
892
  version,
632
893
  buildNum,
633
- targets,
634
- plugins: rawMobile.plugins
894
+ ...files ? { files } : {},
895
+ ...native ? { native } : {},
896
+ targets
897
+ };
898
+ }
899
+ static #mobileKeys = ["appName", "appId", "fileName", "version", "buildNum", "files", "native"];
900
+ static #targetKeys = [
901
+ "basePath",
902
+ "indexPath",
903
+ "appName",
904
+ "appId",
905
+ "fileName",
906
+ "version",
907
+ "buildNum",
908
+ "assets",
909
+ "permissions",
910
+ "deepLinks",
911
+ "files",
912
+ "native"
913
+ ];
914
+ static #assertMobileKeys(section, known, where, configPath) {
915
+ for (const key of Object.keys(section)) {
916
+ if (known.includes(key))
917
+ continue;
918
+ if (key === "plugins" || key === "ios" || key === "android")
919
+ throw new Error(`${where}.${key} in ${configPath} is a Capacitor setting. The native runtime reads native.plugins, native.ios.{infoPlist,entitlements} and native.android.{manifest,application,activity,googleServices} instead.`);
920
+ throw new Error(`${where}.${key} in ${configPath} is not a mobile setting. Known: ${known.join(", ")}.`);
921
+ }
922
+ }
923
+ static #fileTargetPattern = /^(?:ios\/.+|android\/res\/[^/]+\/[^/]+|android\/assets\/.+)$/;
924
+ static #resolveMobileFiles(files, where, configPath) {
925
+ if (!files)
926
+ return;
927
+ for (const [to, from] of Object.entries(files)) {
928
+ if (typeof from !== "string")
929
+ throw new Error(`${where}.files in ${configPath} maps where a file lands to its source now, e.g. { "android/res/raw/chime.mp3": "assets/chime.mp3" }. google-services.json goes to native.android.googleServices, and GoogleService-Info.plist is no longer needed.`);
930
+ if (!AkanAppConfig.#fileTargetPattern.test(to))
931
+ throw new Error(`${where}.files["${to}"] in ${configPath} must land under ios/<path>, android/res/<type>/<file> or android/assets/<path>.`);
932
+ }
933
+ return files;
934
+ }
935
+ static #mergeNative(base, override) {
936
+ const plugins = [...new Set([...base?.plugins ?? [], ...override?.plugins ?? []])];
937
+ const ios = { ...base?.ios, ...override?.ios };
938
+ const android = {
939
+ ...base?.android,
940
+ ...override?.android
941
+ };
942
+ for (const key of ["manifest", "application", "activity"]) {
943
+ const merged = [...base?.android?.[key] ?? [], ...override?.android?.[key] ?? []];
944
+ if (merged.length)
945
+ android[key] = merged;
946
+ else
947
+ delete android[key];
948
+ }
949
+ return {
950
+ ...plugins.length ? { plugins } : {},
951
+ ...Object.keys(ios).length ? { ios } : {},
952
+ ...Object.keys(android).length ? { android } : {}
635
953
  };
636
954
  }
637
955
  #defaultMobileTargetName(rawTargets) {
@@ -641,22 +959,15 @@ class AkanAppConfig {
641
959
  }
642
960
  #applyRoutes(routes = []) {
643
961
  for (const route of routes) {
644
- if (route.basePath) {
645
- const basePath = route.basePath.replace(/^\/+|\/+$/g, "");
962
+ const basePath = route.basePath ? route.basePath.replace(/^\/+|\/+$/g, "") : null;
963
+ if (basePath !== null)
646
964
  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
- }
965
+ const domains = basePath !== null ? this.subRoutes.getOrInsert(basePath, new Set) : this.domains;
966
+ for (const branch of Object.keys(route.domains))
967
+ this.branches.add(branch);
968
+ for (const domain of Object.values(route.domains).flat())
969
+ if (domain)
970
+ domains.add(domain.toLowerCase().replace(/:\d+$/, ""));
660
971
  }
661
972
  const appName = this.app.name.toLowerCase();
662
973
  const serveDomain = this.baseDevEnv.serveDomain.toLowerCase();
@@ -730,6 +1041,7 @@ ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
730
1041
  ENV AKAN_PUBLIC_API_PREFIX=${this.api.prefix}
731
1042
  ENV AKAN_PUBLIC_WS_PREFIX=${this.api.websocketPrefix}
732
1043
  ENV AKAN_PUBLIC_OPERATION_MODE=cloud
1044
+ ENV AKAN_DATABASE_MODES=${this.database.modes.join(",")}
733
1045
  ENV AKAN_LOG_TO_FILE=0
734
1046
  ${webEnvLines}
735
1047
  CMD [${command.map((c) => `"${c}"`).join(",")}]`;
@@ -743,509 +1055,157 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
743
1055
  static async from(app, { bustImportCache = false } = {}) {
744
1056
  const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
745
1057
  AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
746
- WorkspaceExecutor.getBaseDevEnv(path2.join(app.workspace.workspaceRoot, ".env")),
1058
+ WorkspaceExecutor.getBaseDevEnv(path4.join(app.workspace.workspaceRoot, ".env")),
747
1059
  app.workspace.getLibs(),
748
1060
  app.workspace.getPackageJson()
749
1061
  ]);
750
- const resolved = typeof configImp === "function" ? configImp(app) : configImp;
751
- const { plugins, ...config } = resolved ?? {};
752
- const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
753
- return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
754
- }
755
- static async#collectLibContributions(app, libs, bustImportCache) {
756
- const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
757
- app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
758
- return null;
759
- })));
760
- return {
761
- externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
762
- docker: {
763
- preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
764
- postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? [])
765
- },
766
- keepFonts: libConfigs.flatMap((libConfig) => (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`))
767
- };
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;
1062
+ const resolved = typeof configImp === "function" ? configImp(app) : configImp;
1063
+ const { plugins, ...config } = resolved ?? {};
1064
+ const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
1065
+ return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
966
1066
  }
967
- #hasModifier(node, kind) {
968
- const ts = this.#ts;
969
- return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
1067
+ static async#collectLibContributions(app, libs, bustImportCache) {
1068
+ const libConfigs = await Promise.all(libs.map(async (libName) => LibExecutor.from(app, libName).getConfig({ refresh: bustImportCache }).catch((error) => {
1069
+ app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
1070
+ return null;
1071
+ })));
1072
+ return {
1073
+ externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
1074
+ docker: {
1075
+ preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
1076
+ postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? [])
1077
+ },
1078
+ keepFonts: libConfigs.flatMap((libConfig) => (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`))
1079
+ };
970
1080
  }
971
- #isAsyncFunctionExpression(node) {
972
- const ts = this.#ts;
973
- return Boolean(node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && this.#hasModifier(node, ts.SyntaxKind.AsyncKeyword));
1081
+ #resolveProductionDependencyVersion(lib) {
1082
+ const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
1083
+ if (rootVersion)
1084
+ return rootVersion;
1085
+ const akanPackageJson = getAkanPackageJson();
1086
+ const version = akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
1087
+ if (!version)
1088
+ throw new Error(`Dependency ${lib} not found in package.json`);
1089
+ return version;
974
1090
  }
975
- static #scriptKind(typescript, moduleAbsPath) {
976
- return moduleAbsPath.endsWith(".tsx") || moduleAbsPath.endsWith(".jsx") ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS;
1091
+ #getProductionRuntimePackages() {
1092
+ return [
1093
+ ...this.externalLibs,
1094
+ ...SSR_RUNTIME_PACKAGES,
1095
+ ...this.database.modes.flatMap((mode) => this.getDatabaseModeRuntimePackages(mode))
1096
+ ];
977
1097
  }
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
1098
+ getDatabaseModeRuntimePackages(databaseMode) {
1099
+ return [...DatabaseModes.drivers[databaseMode]];
1100
+ }
1101
+ getMissingDatabaseModeDependencySpecs(databaseMode) {
1102
+ return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
1103
+ }
1104
+ resolveDatabaseMode() {
1105
+ return DatabaseModes.resolve({
1106
+ requested: process.env.AKAN_DATABASE_MODE,
1107
+ declared: this.database.modes.join(","),
1108
+ local: true
1048
1109
  });
1049
1110
  }
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: [] });
1111
+ static #database(app, config) {
1112
+ const declared = config?.database?.modes?.filter((mode) => !!mode);
1113
+ const modes = declared?.length ? declared : ["single"];
1114
+ return { modes: DatabaseModes.parseList(modes.join(","), `database.modes in apps/${app.name}/akan.config.ts`) };
1053
1115
  }
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`);
1116
+ #getMissingDependencySpecs(libs) {
1117
+ const rootDependencies = {
1118
+ ...this.rootPackageJson.dependencies,
1119
+ ...this.rootPackageJson.devDependencies
1120
+ };
1121
+ return libs.filter((lib) => !rootDependencies[lib]).map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
1122
+ }
1123
+ get akanVersion() {
1124
+ return getAkanPackageJson().version;
1125
+ }
1126
+ getProductionPackageJson(data = {}) {
1127
+ return {
1128
+ name: this.app.name,
1129
+ description: this.app.name,
1130
+ version: "1.0.0",
1131
+ main: "./main.js",
1132
+ dependencies: Object.fromEntries([...new Set(this.#getProductionRuntimePackages())].map((lib) => [
1133
+ lib,
1134
+ this.#resolveProductionDependencyVersion(lib)
1135
+ ])),
1136
+ ...data
1137
+ };
1074
1138
  }
1075
1139
  }
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;
1243
- }
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 });
1140
+ var akanPackageJson = null;
1141
+ function getAkanPackageJson() {
1142
+ if (akanPackageJson)
1143
+ return akanPackageJson;
1144
+ const sourceDir = path4.dirname(fileURLToPath(import.meta.url));
1145
+ const packageJsonPaths = [
1146
+ path4.join(sourceDir, "../../../akanjs/package.json"),
1147
+ path4.join(process.cwd(), "pkgs/akanjs/package.json"),
1148
+ path4.join(path4.dirname(Bun.main), "node_modules/akanjs/package.json")
1149
+ ];
1150
+ try {
1151
+ packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
1152
+ } catch {}
1153
+ for (const packageJsonPath of packageJsonPaths) {
1154
+ try {
1155
+ akanPackageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
1156
+ return akanPackageJson;
1157
+ } catch {}
1158
+ }
1159
+ akanPackageJson = {
1160
+ name: "akanjs",
1161
+ version: "0.0.0",
1162
+ description: "akanjs",
1163
+ dependencies: {}
1164
+ };
1165
+ return akanPackageJson;
1166
+ }
1167
+ function mergeImageConfig(config = {}) {
1168
+ return {
1169
+ ...DEFAULT_AKAN_IMAGE_CONFIG,
1170
+ ...config,
1171
+ deviceSizes: config.deviceSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.deviceSizes,
1172
+ imageSizes: config.imageSizes ?? DEFAULT_AKAN_IMAGE_CONFIG.imageSizes,
1173
+ formats: config.formats ?? DEFAULT_AKAN_IMAGE_CONFIG.formats,
1174
+ qualities: config.qualities ?? DEFAULT_AKAN_IMAGE_CONFIG.qualities,
1175
+ remotePatterns: config.remotePatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.remotePatterns,
1176
+ localPatterns: config.localPatterns ?? DEFAULT_AKAN_IMAGE_CONFIG.localPatterns
1177
+ };
1247
1178
  }
1248
1179
 
1180
+ class AkanLibConfig {
1181
+ lib;
1182
+ externalLibs;
1183
+ docker;
1184
+ assets;
1185
+ plugins;
1186
+ constructor(lib, config, plugins = []) {
1187
+ this.lib = lib;
1188
+ this.externalLibs = config?.externalLibs ?? [];
1189
+ this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
1190
+ this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts) };
1191
+ this.plugins = plugins;
1192
+ }
1193
+ static async from(lib, { bustImportCache = false } = {}) {
1194
+ const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
1195
+ const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
1196
+ const { plugins, ...config } = resolved ?? {};
1197
+ return new AkanLibConfig(lib, config, plugins ?? []);
1198
+ }
1199
+ }
1200
+ //! need to refactor
1201
+ var shiftBuildNum = async (app, delta) => {
1202
+ const { buildNum } = (await AkanAppConfig.from(app)).mobile;
1203
+ const akanConfigPath = path4.join(app.cwdPath, "akan.config.ts");
1204
+ const akanConfig = fs2.readFileSync(akanConfigPath, "utf8");
1205
+ fs2.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
1206
+ };
1207
+ var increaseBuildNum = async (app) => await shiftBuildNum(app, 1);
1208
+ var decreaseBuildNum = async (app) => await shiftBuildNum(app, -1);
1249
1209
  // pkgs/@akanjs/devkit/linter.ts
1250
1210
  import { spawn } from "child_process";
1251
1211
  import { existsSync, readFileSync } from "fs";
@@ -1299,15 +1259,24 @@ var asBiomeReport = (candidate) => {
1299
1259
  }
1300
1260
  };
1301
1261
  var BIOME_CONFIG_FILES = ["biome.json", "biome.jsonc"];
1262
+ var emptyResult = (filePath) => ({
1263
+ filePath,
1264
+ messages: [],
1265
+ errorCount: 0,
1266
+ warningCount: 0,
1267
+ fixableErrorCount: 0,
1268
+ fixableWarningCount: 0
1269
+ });
1302
1270
 
1303
1271
  class Linter {
1272
+ static biomeBinName = process.platform === "win32" ? "biome.exe" : "biome";
1304
1273
  lintRoot;
1305
1274
  configPath;
1306
1275
  #biomeBin;
1307
1276
  constructor(cwdPath) {
1308
1277
  this.lintRoot = this.#findBiomeRootPath(cwdPath);
1309
1278
  this.configPath = Linter.#configPathIn(this.lintRoot) ?? path5.join(this.lintRoot, "biome.json");
1310
- const localBiomeBin = path5.join(this.lintRoot, "node_modules/.bin/biome");
1279
+ const localBiomeBin = path5.join(this.lintRoot, "node_modules", ".bin", Linter.biomeBinName);
1311
1280
  this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
1312
1281
  }
1313
1282
  static #configPathIn(dir) {
@@ -1379,14 +1348,7 @@ class Linter {
1379
1348
  if (diagnostic.severity !== "error" && diagnostic.severity !== "warning")
1380
1349
  continue;
1381
1350
  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
- };
1351
+ const result = resultsByPath.get(diagnosticFilePath) ?? emptyResult(diagnosticFilePath);
1390
1352
  const message = this.#createLintMessage(diagnostic);
1391
1353
  result.messages.push(message);
1392
1354
  if (message.severity === 2)
@@ -1396,14 +1358,7 @@ class Linter {
1396
1358
  resultsByPath.set(diagnosticFilePath, result);
1397
1359
  }
1398
1360
  return [
1399
- resultsByPath.get(filePath) ?? {
1400
- filePath,
1401
- messages: [],
1402
- errorCount: 0,
1403
- warningCount: 0,
1404
- fixableErrorCount: 0,
1405
- fixableWarningCount: 0
1406
- },
1361
+ resultsByPath.get(filePath) ?? emptyResult(filePath),
1407
1362
  ...[...resultsByPath.entries()].filter(([resultPath]) => resultPath !== filePath).map(([, result]) => result)
1408
1363
  ];
1409
1364
  }
@@ -1513,12 +1468,7 @@ ${errorText}, ${warningText} found`];
1513
1468
  warningCount: acc.warningCount + result.warningCount,
1514
1469
  fixableErrorCount: acc.fixableErrorCount + result.fixableErrorCount,
1515
1470
  fixableWarningCount: acc.fixableWarningCount + result.fixableWarningCount
1516
- }), {
1517
- errorCount: 0,
1518
- warningCount: 0,
1519
- fixableErrorCount: 0,
1520
- fixableWarningCount: 0
1521
- });
1471
+ }), { errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 });
1522
1472
  return { results, details, stats };
1523
1473
  }
1524
1474
  async hasNoLintErrors(filePath) {
@@ -1576,12 +1526,9 @@ ${errorText}, ${warningText} found`];
1576
1526
  async getProblematicRules(filePath) {
1577
1527
  const { results } = await this.lintFile(filePath);
1578
1528
  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
- });
1529
+ for (const message of results.flatMap((result) => result.messages))
1530
+ if (message.ruleId)
1531
+ ruleCounts[message.ruleId] = (ruleCounts[message.ruleId] || 0) + 1;
1585
1532
  return ruleCounts;
1586
1533
  }
1587
1534
  }
@@ -1612,7 +1559,6 @@ var resolveRepoName = (workspaceRoot) => {
1612
1559
  };
1613
1560
 
1614
1561
  // pkgs/@akanjs/devkit/scanInfo.ts
1615
- import { rm as rm2 } from "fs/promises";
1616
1562
  import path7 from "path";
1617
1563
 
1618
1564
  // pkgs/@akanjs/devkit/workspaceLayout.ts
@@ -1621,7 +1567,6 @@ var appRootAllowedFiles = new Set([
1621
1567
  "CLAUDE.md",
1622
1568
  "akan.app.json",
1623
1569
  "akan.config.ts",
1624
- "capacitor.config.ts",
1625
1570
  "client.ts",
1626
1571
  "main.ts",
1627
1572
  "package.json",
@@ -1631,12 +1576,9 @@ var appRootAllowedFiles = new Set([
1631
1576
  ]);
1632
1577
  var appRootAllowedDirs = new Set([
1633
1578
  ".akan",
1634
- "android",
1635
1579
  "common",
1636
1580
  "env",
1637
- "ios",
1638
1581
  "lib",
1639
- "mobile",
1640
1582
  "page",
1641
1583
  "plugin",
1642
1584
  "private",
@@ -1688,13 +1630,20 @@ var libFacetRootAllowedFiles = new Set([
1688
1630
  ]);
1689
1631
  var libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
1690
1632
  var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
1633
+ var retiredAppRootEntries = new Set([
1634
+ "android",
1635
+ "ios",
1636
+ "mobile",
1637
+ "capacitor.config.ts",
1638
+ "capacitor.config.js",
1639
+ "capacitor.config.json"
1640
+ ]);
1641
+ var rootEntryHintOf = (type, name) => type === "app" && retiredAppRootEntries.has(name) ? "a Capacitor-era entry; the native runtime generates its projects under .akan/mobile/<target>, so delete it" : null;
1691
1642
  var isScannedRootEntry = (type, name) => !name.startsWith(".") || rootAllowedDirs[type].has(name);
1692
1643
 
1693
1644
  // pkgs/@akanjs/devkit/scanInfo.ts
1694
- var databaseFileTypes = [
1695
- "constant",
1645
+ var serviceFileTypes = [
1696
1646
  "dictionary",
1697
- "document",
1698
1647
  "service",
1699
1648
  "signal",
1700
1649
  "store",
@@ -1704,7 +1653,9 @@ var databaseFileTypes = [
1704
1653
  "view",
1705
1654
  "zone"
1706
1655
  ];
1707
- var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
1656
+ var databaseFileTypes = ["constant", "dictionary", "document", ...serviceFileTypes.slice(1)];
1657
+ var uiFileTypes = new Set(["template", "unit", "util", "view", "zone"]);
1658
+ var fileTypeOf = (filename) => databaseFileTypes.find((type) => filename.endsWith(uiFileTypes.has(type) ? `.${type[0].toUpperCase()}${type.slice(1)}.tsx` : `.${type}.ts`));
1708
1659
  var internalLibDirs = new Set(["__lib", "__scalar"]);
1709
1660
  var moduleNonUiFileTypes = {
1710
1661
  database: new Set(["constant", "dictionary", "document", "service", "signal", "store"]),
@@ -1716,21 +1667,13 @@ var moduleUiFileTypes = {
1716
1667
  service: new Set(["Util", "Zone"]),
1717
1668
  scalar: new Set(["Template", "Unit"])
1718
1669
  };
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);
1670
+ var createDependencyScanner = async (exec) => (await import("./dependencyScanner-ye2e6pdz.js")).TypeScriptDependencyScanner.from(exec);
1722
1671
  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
- }
1728
1672
  var getModuleNameFromPath = (kind, modulePath) => {
1729
1673
  const dirname = path7.basename(modulePath);
1730
1674
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
1731
1675
  };
1732
1676
  async function assertScanConvention(exec, libRoot) {
1733
- await clearGeneratedRootCapacitorConfigs(exec);
1734
1677
  const violations = [];
1735
1678
  const addViolation = (relativePath, reason) => {
1736
1679
  violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
@@ -1738,21 +1681,21 @@ async function assertScanConvention(exec, libRoot) {
1738
1681
  const allowedRootFiles = rootAllowedFiles[exec.type];
1739
1682
  const allowedRootDirs = rootAllowedDirs[exec.type];
1740
1683
  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
- });
1684
+ for (const filename of files)
1685
+ if (!allowedRootFiles.has(filename))
1686
+ addViolation(filename, rootEntryHintOf(exec.type, filename) ?? `unsupported ${exec.type} root file`);
1687
+ for (const dirname of dirs)
1688
+ if (!allowedRootDirs.has(dirname))
1689
+ addViolation(dirname, rootEntryHintOf(exec.type, dirname) ?? `unsupported ${exec.type} root folder`);
1747
1690
  if (exec.type === "lib")
1748
1691
  for (const { relativePath, reason } of await exec.getPageConventionViolations())
1749
1692
  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
- });
1693
+ for (const filename of libRoot.files)
1694
+ if (!isAllowedLibFacetRootFile(filename))
1695
+ addViolation(path7.join("lib", filename), "unsupported lib facet root file");
1696
+ for (const dirname of libRoot.dirs)
1697
+ if (dirname.startsWith("__") && !internalLibDirs.has(dirname))
1698
+ addViolation(path7.join("lib", dirname), "unsupported internal lib folder");
1756
1699
  const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
1757
1700
  const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
1758
1701
  const scalarDirs = await exec.readdir("lib/__scalar");
@@ -1774,38 +1717,25 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
1774
1717
  violations.push(`${getScanPath(exec, path7.join(modulePath, dirname))}: unsupported module folder`);
1775
1718
  });
1776
1719
  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;
1720
+ for (const filename of files) {
1721
+ const scanPath = getScanPath(exec, path7.join(modulePath, filename));
1722
+ if (filename === "index.ts" || filename === "index.tsx" || /\.(test|spec)\.(ts|tsx)$/.test(filename))
1723
+ continue;
1781
1724
  if (filename === `${moduleName}.abstract.md`)
1782
- return;
1725
+ continue;
1783
1726
  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;
1727
+ const match = uiMatch ?? filename.match(/^([a-z][a-zA-Z0-9]*)\.([a-z][a-z0-9]*)\.ts$/);
1728
+ if (!match) {
1729
+ violations.push(`${scanPath}: unsupported module file`);
1730
+ continue;
1806
1731
  }
1807
- violations.push(`${getScanPath(exec, filePath)}: unsupported module file`);
1808
- });
1732
+ const [, fileModuleName, fileType] = match;
1733
+ const expectedName = uiMatch ? uiModuleName : moduleName;
1734
+ if (fileModuleName !== expectedName)
1735
+ violations.push(`${scanPath}: module name mismatch: expected '${expectedName}', got '${fileModuleName}'`);
1736
+ if (!(uiMatch ? moduleUiFileTypes : moduleNonUiFileTypes)[kind].has(fileType))
1737
+ violations.push(`${scanPath}: unsupported ${kind}${uiMatch ? " UI" : ""} file`);
1738
+ }
1809
1739
  }
1810
1740
 
1811
1741
  class ScanInfo {
@@ -1853,79 +1783,17 @@ class ScanInfo {
1853
1783
  } else
1854
1784
  databaseDirs.push(name);
1855
1785
  });
1786
+ const collect = async (dir, name, kind) => {
1787
+ for (const filename of await exec.readdir(dir)) {
1788
+ const type = fileTypeOf(filename);
1789
+ if (type)
1790
+ files[type][kind]?.push(name);
1791
+ }
1792
+ };
1856
1793
  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
- })
1794
+ ...databaseDirs.map((name) => collect(path7.join("lib", name), name, "databases")),
1795
+ ...serviceDirs.map((dirname) => collect(path7.join("lib", dirname), dirname.slice(1), "services")),
1796
+ ...scalarDirs.map((name) => collect(path7.join("lib/__scalar", name), name, "scalars"))
1929
1797
  ]);
1930
1798
  const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
1931
1799
  const common = {
@@ -1946,31 +1814,14 @@ class ScanInfo {
1946
1814
  constructor(scanResult) {
1947
1815
  this.name = scanResult.name;
1948
1816
  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
- });
1817
+ const modulesOf = { databases: this.database, services: this.service, scalars: this.scalar };
1818
+ for (const [key, groups] of Object.entries(scanResult.files))
1819
+ for (const kind of ["databases", "services", "scalars"])
1820
+ for (const name of groups[kind] ?? []) {
1821
+ modulesOf[kind].set(name, (modulesOf[kind].get(name) ?? new Set).add(key));
1822
+ this.file[key].all.add(name);
1823
+ this.file[key][kind].add(name);
1824
+ }
1974
1825
  }
1975
1826
  getScanResult() {
1976
1827
  return this.scanResult;
@@ -1984,6 +1835,28 @@ class ScanInfo {
1984
1835
  getScalarModules() {
1985
1836
  return [...this.scalar.keys()];
1986
1837
  }
1838
+ #sortedLibs = null;
1839
+ sortedLibs(libDeps) {
1840
+ if (this.#sortedLibs)
1841
+ return this.#sortedLibs;
1842
+ const libIndices = LibInfo.getSortedLibIndices();
1843
+ this.#sortedLibs = libDeps.sort((libNameA, libNameB) => {
1844
+ const indexA = libIndices.get(libNameA);
1845
+ const indexB = libIndices.get(libNameB);
1846
+ if (indexA === undefined || indexB === undefined)
1847
+ throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
1848
+ return indexA - indexB;
1849
+ });
1850
+ return this.#sortedLibs;
1851
+ }
1852
+ sortedLibInfos(libDeps) {
1853
+ return new Map(this.sortedLibs(libDeps).map((libName) => {
1854
+ const libInfo = LibInfo.libInfos.get(libName);
1855
+ if (!libInfo)
1856
+ throw new Error(`LibInfo not found: ${libName}`);
1857
+ return [libName, libInfo];
1858
+ }));
1859
+ }
1987
1860
  }
1988
1861
  var isAkanFrameworkDependency = (dep) => dep === "akanjs" || dep.startsWith("akanjs/");
1989
1862
 
@@ -2033,30 +1906,11 @@ class AppInfo extends ScanInfo {
2033
1906
  }));
2034
1907
  return [...libSet];
2035
1908
  }
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
1909
  getLibs() {
2051
- return this.#getSortedLibs();
1910
+ return this.sortedLibs(this.libDeps);
2052
1911
  }
2053
1912
  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
- }));
1913
+ return this.sortedLibInfos(this.libDeps);
2060
1914
  }
2061
1915
  }
2062
1916
 
@@ -2096,38 +1950,18 @@ class LibInfo extends ScanInfo {
2096
1950
  getScanResult() {
2097
1951
  return this.scanResult;
2098
1952
  }
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
1953
  getLibs() {
2114
- return this.#getSortedLibs();
1954
+ return this.sortedLibs(this.scanResult.libDeps);
2115
1955
  }
2116
1956
  getLibInfo(libName) {
2117
1957
  if (!this.getScanResult().libDeps.includes(libName))
2118
1958
  return;
2119
- const libSet = new Set(this.#getSortedLibs());
2120
- if (!libSet.has(libName))
1959
+ if (!this.getLibs().includes(libName))
2121
1960
  throw new Error(`LibInfo is invalid: ${libName}`);
2122
1961
  return LibInfo.libInfos.get(libName);
2123
1962
  }
2124
1963
  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
- }));
1964
+ return this.sortedLibInfos(this.scanResult.libDeps);
2131
1965
  }
2132
1966
  }
2133
1967
 
@@ -2139,18 +1973,14 @@ class PkgInfo {
2139
1973
  const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
2140
1974
  const scanner = await createDependencyScanner(exec);
2141
1975
  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("/*", "")));
1976
+ const workspacePathOf = (resolve) => resolve.replace(/^\.\//, "");
1977
+ 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
1978
  const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
2144
1979
  const pkgDeps = [...pkgPathDepSet].map((path) => {
2145
1980
  const pathSplitLength = path.split("/").length;
2146
- return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
1981
+ return workspacePathOf(tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*").split("/").slice(1, 1 + pathSplitLength).join("/");
2147
1982
  }).filter((pkg) => pkg !== exec.name);
2148
- const pkgScanResult = {
2149
- name: exec.name,
2150
- pkgDeps,
2151
- dependencies: [...npmDepSet]
2152
- };
2153
- return pkgScanResult;
1983
+ return { name: exec.name, pkgDeps, dependencies: [...npmDepSet] };
2154
1984
  }
2155
1985
  static #pkgInfos = new Map;
2156
1986
  static async fromExecutor(exec, options = {}) {
@@ -2188,17 +2018,9 @@ class WorkspaceInfo {
2188
2018
  return existingWorkspaceInfo;
2189
2019
  const [appNames, libNames, pkgNames] = await Promise.all([exec.getApps(), exec.getLibs(), exec.getPkgs()]);
2190
2020
  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
- }))
2021
+ Promise.all(appNames.map(async (appName) => await AppExecutor.from(exec, appName).scan())),
2022
+ Promise.all(libNames.map(async (libName) => await LibExecutor.from(exec, libName).scan())),
2023
+ Promise.all(pkgNames.map(async (pkgName) => await PkgExecutor.from(exec, pkgName).scan()))
2202
2024
  ]);
2203
2025
  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
2026
  WorkspaceInfo.#workspaceInfos.set(exec.name, workspaceInfo);
@@ -2207,40 +2029,9 @@ class WorkspaceInfo {
2207
2029
  }
2208
2030
 
2209
2031
  // 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);
2032
+ 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(" ")));
2033
+ var plainCommandArgPattern = process.platform === "win32" ? /^[\w@%+=:,./\\-]+$/ : /^[\w@%+=:,./-]+$/;
2034
+ var formatCommandArg = (value) => plainCommandArgPattern.test(value) ? value : JSON.stringify(value);
2244
2035
  var formatCommandForDisplay = (command, args = []) => [command, ...args].map(formatCommandArg).join(" ");
2245
2036
 
2246
2037
  class CommandExecutionError extends Error {
@@ -2314,6 +2105,10 @@ var parseEnvFile = (envPath) => {
2314
2105
  }
2315
2106
  return env;
2316
2107
  };
2108
+ var withCapitalizedDict = (dict = {}) => ({
2109
+ ...dict,
2110
+ ...Object.fromEntries(Object.entries(dict).map(([key, value]) => [capitalize(key), capitalize(value)]))
2111
+ });
2317
2112
 
2318
2113
  class Executor {
2319
2114
  static verbose = false;
@@ -2340,144 +2135,51 @@ class Executor {
2340
2135
  #stderr(data) {
2341
2136
  Logger2.raw(chalk2.red(data.toString()));
2342
2137
  }
2343
- exec(command, options = {}) {
2344
- const cwd = options.cwd?.toString() ?? this.cwdPath;
2345
- const proc = exec(command, { cwd: this.cwdPath, ...options });
2138
+ #settle(proc, target, { onClose = false, keepLogs = false, redStderr = false } = {}) {
2346
2139
  let stdout = "";
2347
2140
  let stderr = "";
2348
2141
  proc.stdout?.on("data", (data) => {
2349
2142
  stdout += data.toString();
2143
+ if (keepLogs)
2144
+ this.logs.push(data.toString());
2350
2145
  this.#stdout(data);
2351
2146
  });
2352
2147
  proc.stderr?.on("data", (data) => {
2353
2148
  stderr += data.toString();
2354
- this.#stdout(data);
2149
+ if (keepLogs)
2150
+ this.logs.push(data.toString());
2151
+ if (redStderr)
2152
+ this.#stderr(data);
2153
+ else
2154
+ this.#stdout(data);
2355
2155
  });
2356
2156
  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
- }));
2157
+ proc.on("error", (cause) => reject(new CommandExecutionError({ ...target, code: null, signal: null, stdout, stderr, cause })));
2158
+ proc.on(onClose ? "close" : "exit", (code, signal) => {
2159
+ if ((onClose ? code !== 0 : code) || signal)
2160
+ reject(new CommandExecutionError({ ...target, code, signal, stdout, stderr }));
2378
2161
  else
2379
- resolve({ code, signal });
2162
+ resolve({ code, signal, stdout });
2380
2163
  });
2381
2164
  });
2382
2165
  }
2166
+ exec(command, options = {}) {
2167
+ const target = { command, cwd: options.cwd?.toString() ?? this.cwdPath };
2168
+ const proc = exec(command, { cwd: this.cwdPath, ...options });
2169
+ return this.#settle(proc, target).then(({ code, signal }) => ({ code, signal }));
2170
+ }
2383
2171
  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
- });
2172
+ const target = { command, args, cwd: options.cwd?.toString() ?? this.cwdPath };
2173
+ const proc = spawn2(command, args, { cwd: this.cwdPath, ...options });
2174
+ return this.#settle(proc, target, { onClose: true, keepLogs: true }).then(({ stdout }) => stdout);
2429
2175
  }
2430
2176
  spawnSync(command, args = [], options = {}) {
2431
- const proc = spawn2(command, args, {
2432
- cwd: this.cwdPath,
2433
- ...options
2434
- });
2435
- return proc;
2177
+ return spawn2(command, args, { cwd: this.cwdPath, ...options });
2436
2178
  }
2437
2179
  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
- });
2180
+ const target = { command: modulePath, args, cwd: options.cwd?.toString() ?? this.cwdPath };
2181
+ const proc = fork(modulePath, args, { cwd: this.cwdPath, ...options });
2182
+ return this.#settle(proc, target, { redStderr: true }).then(({ code, signal }) => ({ code, signal }));
2481
2183
  }
2482
2184
  getPath(filePath) {
2483
2185
  if (path8.isAbsolute(filePath))
@@ -2501,8 +2203,8 @@ class Executor {
2501
2203
  }
2502
2204
  async mkdir(dirPath) {
2503
2205
  const writePath = this.getPath(dirPath);
2504
- if (!await FileSys.dirExists(writePath))
2505
- await mkdir3(writePath, { recursive: true });
2206
+ if (!await FileSys2.dirExists(writePath))
2207
+ await mkdir2(writePath, { recursive: true });
2506
2208
  this.logger.verbose(`Make directory ${writePath}`);
2507
2209
  return this;
2508
2210
  }
@@ -2521,27 +2223,25 @@ class Executor {
2521
2223
  }
2522
2224
  async getFilesAndDirs(dirPath) {
2523
2225
  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 }));
2226
+ const glob = new Bun.Glob("*");
2227
+ const files = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: true }));
2228
+ const allEntries = Array.from(glob.scanSync({ cwd: fullDirPath, onlyFiles: false }));
2528
2229
  const dirs = allEntries.filter((entry) => !files.includes(entry));
2529
2230
  return { files, dirs };
2530
2231
  }
2531
2232
  async exists(filePath) {
2532
- const readPath = this.getPath(filePath);
2533
- return await FileSys.exists(readPath);
2233
+ return await FileSys2.exists(this.getPath(filePath));
2534
2234
  }
2535
2235
  async remove(filePath) {
2536
2236
  const readPath = this.getPath(filePath);
2537
- if (await FileSys.fileExists(readPath))
2538
- await FileSys.delete(readPath);
2237
+ if (await FileSys2.fileExists(readPath))
2238
+ await FileSys2.delete(readPath);
2539
2239
  this.logger.verbose(`Remove file ${readPath}`);
2540
2240
  return this;
2541
2241
  }
2542
2242
  async removeDir(dirPath) {
2543
2243
  const readPath = path8.join(this.getPath(dirPath), ".");
2544
- if (await FileSys.entryExists(readPath))
2244
+ if (await FileSys2.entryExists(readPath))
2545
2245
  await rm3(readPath, { recursive: true, force: true });
2546
2246
  this.logger.verbose(`Remove directory ${readPath}`);
2547
2247
  return this;
@@ -2549,22 +2249,22 @@ class Executor {
2549
2249
  async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
2550
2250
  const writePath = this.getPath(filePath);
2551
2251
  const dir = path8.dirname(writePath);
2552
- if (!await FileSys.dirExists(dir))
2553
- await mkdir3(dir, { recursive: true });
2252
+ if (!await FileSys2.dirExists(dir))
2253
+ await mkdir2(dir, { recursive: true });
2554
2254
  let contentStr = typeof content === "string" ? content : `${JSON.stringify(content, null, 2)}
2555
2255
  `;
2556
- if (await FileSys.fileExists(writePath)) {
2557
- const currentContent = await FileSys.readText(writePath);
2256
+ if (await FileSys2.fileExists(writePath)) {
2257
+ const currentContent = await FileSys2.readText(writePath);
2558
2258
  if (currentContent === contentStr || !overwrite) {
2559
2259
  this.logger.verbose(`File ${writePath} is unchanged`);
2560
2260
  contentStr = currentContent;
2561
2261
  } else {
2562
- await FileSys.writeText(writePath, contentStr);
2262
+ await FileSys2.writeText(writePath, contentStr);
2563
2263
  if (Logger2.isVerbose())
2564
2264
  this.logger.rawLog(chalk2.yellow(`File Update: ${filePath}`));
2565
2265
  }
2566
2266
  } else {
2567
- await FileSys.writeText(writePath, contentStr);
2267
+ await FileSys2.writeText(writePath, contentStr);
2568
2268
  if (!silent)
2569
2269
  this.logger.rawLog(chalk2.green(`File Create: ${filePath}`));
2570
2270
  }
@@ -2579,22 +2279,20 @@ class Executor {
2579
2279
  return { filePath, content };
2580
2280
  }
2581
2281
  async readFile(filePath) {
2582
- const readPath = this.getPath(filePath);
2583
- return await FileSys.readText(readPath);
2282
+ return await FileSys2.readText(this.getPath(filePath));
2584
2283
  }
2585
2284
  async readJson(filePath) {
2586
- const readPath = this.getPath(filePath);
2587
- return await FileSys.readJson(readPath);
2285
+ return await FileSys2.readJson(this.getPath(filePath));
2588
2286
  }
2589
2287
  async cp(srcPath, destPath, { dereference = false } = {}) {
2590
2288
  const src = this.getPath(srcPath);
2591
2289
  const dest = this.getPath(destPath);
2592
- if (!await FileSys.exists(src))
2290
+ if (!await FileSys2.exists(src))
2593
2291
  return;
2594
2292
  const isDirectory = (await stat2(src)).isDirectory();
2595
- if (!await FileSys.exists(dest) && isDirectory)
2596
- await mkdir3(dest, { recursive: true });
2597
- if (dereference)
2293
+ if (!await FileSys2.exists(dest) && isDirectory)
2294
+ await mkdir2(dest, { recursive: true });
2295
+ if (dereference || process.platform === "win32")
2598
2296
  await cpEntry(src, dest, { recursive: isDirectory, dereference: true, force: true });
2599
2297
  else
2600
2298
  await $`cp -r ${src}${isDirectory ? "/." : ""} ${dest}`;
@@ -2618,19 +2316,10 @@ class Executor {
2618
2316
  async getTsConfig(pathname = "tsconfig.json", { refresh } = {}) {
2619
2317
  if (this.#tsconfig && !refresh)
2620
2318
  return this.#tsconfig;
2621
- const tsconfig = await this.readJson(pathname);
2319
+ let tsconfig = await this.readJson(pathname);
2622
2320
  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;
2321
+ const base = await this.getTsConfig(tsconfig.extends, { refresh });
2322
+ tsconfig = { ...base, ...tsconfig, compilerOptions: { ...base.compilerOptions, ...tsconfig.compilerOptions } };
2634
2323
  }
2635
2324
  this.#tsconfig = tsconfig;
2636
2325
  return tsconfig;
@@ -2666,6 +2355,7 @@ class Executor {
2666
2355
  scanInfo,
2667
2356
  overwrite = true
2668
2357
  }, dict = {}, options = {}) {
2358
+ const convertPath = (target) => Object.entries(dict).reduce((converted, [key, value]) => converted.replace(new RegExp(`__${key}__`, "g"), value), target);
2669
2359
  if (targetPath.endsWith(".ts") || targetPath.endsWith(".tsx")) {
2670
2360
  const getContent = await import(templatePath);
2671
2361
  const result = await getContent.default(scanInfo ?? null, dict, options);
@@ -2673,24 +2363,21 @@ class Executor {
2673
2363
  return null;
2674
2364
  const filename = typeof result === "object" ? result.filename : path8.basename(targetPath).replace(".js", ".ts");
2675
2365
  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}`);
2366
+ const convertedTargetPath = convertPath(`${path8.dirname(targetPath)}/${filename}`);
2678
2367
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2679
2368
  return this.writeFile(convertedTargetPath, content, { overwrite });
2680
2369
  } 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));
2370
+ const content = await FileSys2.readText(templatePath);
2371
+ const convertedTargetPath = convertPath(targetPath.slice(0, -9));
2683
2372
  const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
2684
2373
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2685
- return this.writeFile(convertedTargetPath, convertedContent, {
2686
- overwrite
2687
- });
2374
+ return this.writeFile(convertedTargetPath, convertedContent, { overwrite });
2688
2375
  } 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);
2376
+ const convertedTargetPath = convertPath(targetPath);
2690
2377
  const writePath = this.getPath(convertedTargetPath);
2691
2378
  const dirname = path8.dirname(writePath);
2692
- if (!await FileSys.dirExists(dirname))
2693
- await mkdir3(dirname, { recursive: true });
2379
+ if (!await FileSys2.dirExists(dirname))
2380
+ await mkdir2(dirname, { recursive: true });
2694
2381
  await copyFile(templatePath, writePath);
2695
2382
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2696
2383
  return { filePath: writePath, content: "" };
@@ -2707,12 +2394,10 @@ class Executor {
2707
2394
  }) {
2708
2395
  const templateRoot = await this.#resolveTemplateRoot();
2709
2396
  const templatePath = `${templateRoot}${template ? `/${template}` : ""}`;
2710
- const prefixTemplatePath = templatePath;
2711
- if ((await stat2(prefixTemplatePath)).isFile()) {
2712
- const filename = path8.basename(prefixTemplatePath);
2397
+ if ((await stat2(templatePath)).isFile()) {
2713
2398
  const fileContent = await this.#applyTemplateFile({
2714
- templatePath: prefixTemplatePath,
2715
- targetPath: path8.join(basePath, filename),
2399
+ templatePath,
2400
+ targetPath: path8.join(basePath, path8.basename(templatePath)),
2716
2401
  scanInfo,
2717
2402
  overwrite
2718
2403
  }, dict, options);
@@ -2748,17 +2433,13 @@ class Executor {
2748
2433
  path8.resolve(getDirname2(import.meta.url), "../cli/templates")
2749
2434
  ];
2750
2435
  for (const candidate of candidates) {
2751
- if (await FileSys.dirExists(candidate))
2436
+ if (await FileSys2.dirExists(candidate))
2752
2437
  return candidate;
2753
2438
  }
2754
2439
  return candidates[0];
2755
2440
  }
2756
2441
  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 });
2442
+ const fileContents = await this._applyTemplate({ ...options, dict: withCapitalizedDict(options.dict) });
2762
2443
  await this.#formatAppliedTemplate(fileContents);
2763
2444
  return fileContents;
2764
2445
  }
@@ -2775,7 +2456,7 @@ class Executor {
2775
2456
  }
2776
2457
  }
2777
2458
  async getTypeChecker() {
2778
- const { TypeChecker } = await import("./typeChecker-3xgrzc2v.js");
2459
+ const { TypeChecker } = await import("./typeChecker-px49mezc.js");
2779
2460
  this.typeChecker ??= new TypeChecker(this);
2780
2461
  return this.typeChecker;
2781
2462
  }
@@ -2853,12 +2534,11 @@ class WorkspaceExecutor extends Executor {
2853
2534
  this.workspaceRoot = workspaceRoot;
2854
2535
  this.repoName = repoName;
2855
2536
  }
2856
- static #execs = new Map;
2857
2537
  static fromRoot({
2858
2538
  workspaceRoot = process.cwd(),
2859
2539
  repoName = resolveRepoName(workspaceRoot)
2860
2540
  } = {}) {
2861
- return WorkspaceExecutor.#execs.get(repoName) ?? new WorkspaceExecutor({ workspaceRoot, repoName });
2541
+ return new WorkspaceExecutor({ workspaceRoot, repoName });
2862
2542
  }
2863
2543
  static getBaseDevEnv(envPath) {
2864
2544
  const sourceEnv = envPath ? { ...process.env, ...parseEnvFile(envPath) } : process.env;
@@ -2897,13 +2577,9 @@ class WorkspaceExecutor extends Executor {
2897
2577
  return await WorkspaceInfo.fromExecutor(this);
2898
2578
  }
2899
2579
  async getApps() {
2900
- if (!await FileSys.dirExists(`${this.workspaceRoot}/apps`))
2901
- return [];
2902
2580
  return await this.#getDirHasFile(`${this.workspaceRoot}/apps`, "akan.config.ts");
2903
2581
  }
2904
2582
  async getLibs() {
2905
- if (!await FileSys.dirExists(`${this.workspaceRoot}/libs`))
2906
- return [];
2907
2583
  return await this.#getDirHasFile(`${this.workspaceRoot}/libs`, "akan.config.ts");
2908
2584
  }
2909
2585
  async getSyss() {
@@ -2911,8 +2587,6 @@ class WorkspaceExecutor extends Executor {
2911
2587
  return [appNames, libNames];
2912
2588
  }
2913
2589
  async getPkgs() {
2914
- if (!await FileSys.dirExists(`${this.workspaceRoot}/pkgs`))
2915
- return [];
2916
2590
  return await this.#getDirHasFile(`${this.workspaceRoot}/pkgs`, "package.json");
2917
2591
  }
2918
2592
  async getExecs() {
@@ -2977,6 +2651,8 @@ class WorkspaceExecutor extends Executor {
2977
2651
  return stdout.split("\x00").filter((file) => !!file).sort();
2978
2652
  }
2979
2653
  async#getDirHasFile(basePath, targetFilename) {
2654
+ if (!await FileSys2.dirExists(basePath))
2655
+ return [];
2980
2656
  const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
2981
2657
  const getDirs = async (dirname, maxDepth = 3, results = [], prefix = "") => {
2982
2658
  const dirs = await this.readdir(dirname);
@@ -2984,8 +2660,8 @@ class WorkspaceExecutor extends Executor {
2984
2660
  if (AVOID_DIRS.includes(dir))
2985
2661
  return;
2986
2662
  const dirPath = path8.join(dirname, dir);
2987
- if (await FileSys.dirExists(dirPath)) {
2988
- const hasTargetFile = await FileSys.fileExists(path8.join(dirPath, targetFilename));
2663
+ if (await FileSys2.dirExists(dirPath)) {
2664
+ const hasTargetFile = await FileSys2.fileExists(path8.join(dirPath, targetFilename));
2989
2665
  if (hasTargetFile)
2990
2666
  results.push(`${prefix}${dir}`);
2991
2667
  if (maxDepth > 0)
@@ -2994,39 +2670,25 @@ class WorkspaceExecutor extends Executor {
2994
2670
  }));
2995
2671
  return results;
2996
2672
  };
2997
- return await getDirs(basePath);
2673
+ return (await getDirs(basePath)).sort((a, b) => a.localeCompare(b));
2998
2674
  }
2999
- async getScalarConstantFiles() {
2675
+ async#fromEverySys(read) {
3000
2676
  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;
2677
+ const fromApps = await Promise.all(appNames.map((appName) => read(AppExecutor.from(this, appName))));
2678
+ const fromLibs = await Promise.all(libNames.map((libName) => read(LibExecutor.from(this, libName))));
2679
+ return [...fromApps.flat(), ...fromLibs.flat()];
2680
+ }
2681
+ async getScalarConstantFiles() {
2682
+ return await this.#fromEverySys((sys) => sys.getScalarConstantFiles());
3006
2683
  }
3007
2684
  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;
2685
+ return await this.#fromEverySys((sys) => sys.getConstantFiles());
3014
2686
  }
3015
2687
  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;
2688
+ return await this.#fromEverySys((sys) => sys.getDictionaryFiles());
3022
2689
  }
3023
2690
  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;
2691
+ return await this.#fromEverySys((sys) => sys.getViewsSourceCode());
3030
2692
  }
3031
2693
  }
3032
2694
  var scanFacetDirs = ["ui", "webkit", "srvkit", "common", "plugin"];
@@ -3090,24 +2752,11 @@ class SysExecutor extends Executor {
3090
2752
  scanInfo,
3091
2753
  options: { exec: this, facet }
3092
2754
  })),
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
- }))
2755
+ ...[
2756
+ ...scanInfo.getDatabaseModules().map((model) => [model, `lib/${model}`]),
2757
+ ...scanInfo.getServiceModules().map((model) => [model, `lib/_${model}`]),
2758
+ ...scanInfo.getScalarModules().map((model) => [model, `lib/__scalar/${model}`])
2759
+ ].map(([model, basePath]) => this._applyTemplate({ basePath, template: "moduleRoot", scanInfo, dict: { model, Model: capitalize(model) } }))
3111
2760
  ];
3112
2761
  }
3113
2762
  async scan({
@@ -3174,40 +2823,38 @@ class SysExecutor extends Executor {
3174
2823
  return { filePath, content };
3175
2824
  }
3176
2825
  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;
2826
+ return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${name}.constant.ts`));
3179
2827
  }
3180
2828
  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;
2829
+ return (await this.readdir("lib")).filter((name) => name.startsWith("_") && !name.startsWith("__")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${name.slice(1)}.service.ts`));
3183
2830
  }
3184
2831
  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;
2832
+ return (await this.readdir("lib/__scalar")).filter((name) => !name.startsWith("_")).filter((name) => existsSync2(`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`));
2833
+ }
2834
+ async#getComponentModules(role) {
2835
+ return (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.endsWith(".ts")).filter((name) => existsSync2(`${this.cwdPath}/lib/${name}/${capitalize(name)}.${role}.tsx`));
2836
+ }
2837
+ async#getComponentSources(role) {
2838
+ const modules = await this.#getComponentModules(role);
2839
+ return Promise.all(modules.map((name) => this.getLocalFile(`lib/${name}/${capitalize(name)}.${role}.tsx`)));
3187
2840
  }
3188
2841
  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;
2842
+ return await this.#getComponentModules("View");
3191
2843
  }
3192
2844
  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;
2845
+ return await this.#getComponentModules("Unit");
3195
2846
  }
3196
2847
  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;
2848
+ return await this.#getComponentModules("Template");
3199
2849
  }
3200
2850
  async getViewsSourceCode() {
3201
- const viewComponents = await this.getViewComponents();
3202
- return Promise.all(viewComponents.map((viewComponent) => this.getLocalFile(`lib/${viewComponent}/${capitalize(viewComponent)}.View.tsx`)));
2851
+ return await this.#getComponentSources("View");
3203
2852
  }
3204
2853
  async getUnitsSourceCode() {
3205
- const unitComponents = await this.getUnitComponents();
3206
- return Promise.all(unitComponents.map((unitComponent) => this.getLocalFile(`lib/${unitComponent}/${capitalize(unitComponent)}.Unit.tsx`)));
2854
+ return await this.#getComponentSources("Unit");
3207
2855
  }
3208
2856
  async getTemplatesSourceCode() {
3209
- const templateComponents = await this.getTemplateComponents();
3210
- return Promise.all(templateComponents.map((templateComponent) => this.getLocalFile(`lib/${templateComponent}/${capitalize(templateComponent)}.Template.tsx`)));
2857
+ return await this.#getComponentSources("Template");
3211
2858
  }
3212
2859
  async getScalarConstantFiles() {
3213
2860
  const scalarModules = await this.getScalarModules();
@@ -3215,7 +2862,7 @@ class SysExecutor extends Executor {
3215
2862
  }
3216
2863
  async getScalarDictionaryFiles() {
3217
2864
  const scalarModules = await this.getScalarModules();
3218
- return Promise.all(scalarModules.map((scalarModule) => this.getLocalFile(`lib/${scalarModule}/${scalarModule}.dictionary.ts`)));
2865
+ return Promise.all(scalarModules.map((scalarModule) => this.getLocalFile(`lib/__scalar/${scalarModule}/${scalarModule}.dictionary.ts`)));
3219
2866
  }
3220
2867
  async getConstantFiles() {
3221
2868
  const modules = await this.getModules();
@@ -3237,16 +2884,8 @@ class SysExecutor extends Executor {
3237
2884
  return Promise.all(modules.map((module) => this.getLocalFile(`lib/${module}/${module}.dictionary.ts`)));
3238
2885
  }
3239
2886
  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
2887
  const scanInfo = await this.scan();
3245
- const fileContents = await this._applyTemplate({
3246
- ...options,
3247
- scanInfo,
3248
- dict
3249
- });
2888
+ const fileContents = await this._applyTemplate({ ...options, scanInfo, dict: withCapitalizedDict(options.dict) });
3250
2889
  await this.scan();
3251
2890
  return fileContents;
3252
2891
  }
@@ -3255,7 +2894,7 @@ class SysExecutor extends Executor {
3255
2894
  class AppExecutor extends SysExecutor {
3256
2895
  static #routeSourceValidator = null;
3257
2896
  static async#getRouteSourceValidator() {
3258
- AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-apyqw7wk.js")).RouteSourceValidator;
2897
+ AppExecutor.#routeSourceValidator ??= (await import("./routeSourceValidator-7ycnfz0e.js")).RouteSourceValidator;
3259
2898
  return AppExecutor.#routeSourceValidator;
3260
2899
  }
3261
2900
  dist;
@@ -3264,15 +2903,8 @@ class AppExecutor extends SysExecutor {
3264
2903
  super({ workspace, name, type: "app" });
3265
2904
  this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/apps/${name}`);
3266
2905
  }
3267
- static #execs = new Map;
3268
2906
  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 });
2907
+ return new AppExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
3276
2908
  }
3277
2909
  getEnv() {
3278
2910
  return WorkspaceExecutor.getBaseDevEnv().env;
@@ -3288,10 +2920,8 @@ class AppExecutor extends SysExecutor {
3288
2920
  return basePort + appIndex + portOffset;
3289
2921
  }
3290
2922
  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;
2923
+ const { portOffset } = WorkspaceExecutor.getBaseDevEnv();
2924
+ const PORT = (8282 + portOffset).toString();
3295
2925
  return {
3296
2926
  ...process.env,
3297
2927
  AKAN_PUBLIC_APP_NAME: this.name,
@@ -3299,18 +2929,24 @@ class AppExecutor extends SysExecutor {
3299
2929
  NODE_NO_WARNINGS: "1",
3300
2930
  PORT,
3301
2931
  AKAN_PUBLIC_CLIENT_PORT: PORT,
3302
- ...AKAN_PUBLIC_SERVER_PORT ? { AKAN_PUBLIC_SERVER_PORT } : {},
2932
+ ...portOffset ? { AKAN_PUBLIC_SERVER_PORT: PORT } : {},
3303
2933
  ...env
3304
2934
  };
3305
2935
  }
2936
+ async getDatabaseModeEnv() {
2937
+ const akanConfig = await this.getConfig();
2938
+ return {
2939
+ AKAN_DATABASE_MODE: akanConfig.resolveDatabaseMode(),
2940
+ AKAN_DATABASE_MODES: akanConfig.database.modes.join(",")
2941
+ };
2942
+ }
3306
2943
  async prepareCommand(type) {
3307
2944
  const akanConfig = await this.getConfig();
3308
- const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
3309
2945
  const routeEnv = {
3310
2946
  AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
3311
2947
  AKAN_PUBLIC_API_PREFIX: akanConfig.api.prefix,
3312
2948
  AKAN_PUBLIC_WS_PREFIX: akanConfig.api.websocketPrefix,
3313
- AKAN_DATABASE_MODE: databaseMode
2949
+ ...await this.getDatabaseModeEnv()
3314
2950
  };
3315
2951
  Object.assign(process.env, routeEnv);
3316
2952
  if (type === "build") {
@@ -3339,6 +2975,8 @@ class AppExecutor extends SysExecutor {
3339
2975
  const buildEnv = { ...env };
3340
2976
  delete buildEnv.AKAN_PUBLIC_CLIENT_PORT;
3341
2977
  delete buildEnv.AKAN_PUBLIC_SERVER_PORT;
2978
+ delete buildEnv.AKAN_PUBLIC_OPERATION_MODE;
2979
+ delete process.env.AKAN_PUBLIC_OPERATION_MODE;
3342
2980
  Object.assign(process.env, buildEnv);
3343
2981
  }
3344
2982
  return { env };
@@ -3348,16 +2986,7 @@ class AppExecutor extends SysExecutor {
3348
2986
  if (this.#publicEnv)
3349
2987
  return this.#publicEnv;
3350
2988
  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
- });
2989
+ const regexes = searchPatterns.map((pattern) => new RegExp(`^${pattern.split("*").map((part) => part.replace(/[.+^${}()|[\]\\?]/g, "\\$&")).join(".*")}$`));
3361
2990
  const publicEnv = {};
3362
2991
  for (const [k, v] of Object.entries(process.env)) {
3363
2992
  if (typeof v !== "string")
@@ -3386,8 +3015,10 @@ class AppExecutor extends SysExecutor {
3386
3015
  const owners = new Map;
3387
3016
  const devOnlyKeys = new Set;
3388
3017
  const devOnlyDirs = [];
3389
- for (const root of await this.getPageRoots()) {
3390
- if (!await FileSys.dirExists(root.dir))
3018
+ const pageRoots = await this.getPageRoots();
3019
+ const libKeyPrefixes = pageRoots.map((root) => root.keyPrefix).filter(Boolean);
3020
+ for (const root of pageRoots) {
3021
+ if (!await FileSys2.dirExists(root.dir))
3391
3022
  continue;
3392
3023
  for await (const rel of glob.scan({
3393
3024
  cwd: root.dir,
@@ -3398,6 +3029,8 @@ class AppExecutor extends SysExecutor {
3398
3029
  if (segments.some((s) => s === "node_modules"))
3399
3030
  continue;
3400
3031
  const posix = `${root.keyPrefix}${segments.join("/")}`;
3032
+ if (!root.keyPrefix && libKeyPrefixes.some((prefix) => posix.startsWith(prefix)))
3033
+ continue;
3401
3034
  const absPath = path8.join(root.dir, ...segments);
3402
3035
  validatePageSourceFile(posix, { filePath: absPath });
3403
3036
  if (!isRouteSourceFile(posix))
@@ -3462,7 +3095,7 @@ class AppExecutor extends SysExecutor {
3462
3095
  const entries = await readDirEntries(libsDir).catch(() => []);
3463
3096
  for (const entry of entries.sort()) {
3464
3097
  const dir = `${libsDir}/${entry}`;
3465
- if (!await FileSys.dirExists(dir))
3098
+ if (!await FileSys2.dirExists(dir))
3466
3099
  continue;
3467
3100
  roots.push({ dir, realDir: await realpath(dir), keyPrefix: `${parent}${AppExecutor.#pageLibsDir}/${entry}/` });
3468
3101
  }
@@ -3487,7 +3120,8 @@ class AppExecutor extends SysExecutor {
3487
3120
  return entries.map((entry) => `${parent}${AppExecutor.#pageLibsDir}/${entry}/`);
3488
3121
  }))).flat();
3489
3122
  const wanted = parents.flatMap((parent) => libs.map((lib) => `${parent}${AppExecutor.#pageLibsDir}/(${lib})/`));
3490
- if (linked.sort().join(",") === wanted.sort().join(","))
3123
+ const isLink = async (key) => (await lstat(`${this.cwdPath}/page/${key.slice(0, -1)}`).catch(() => null))?.isSymbolicLink() ?? false;
3124
+ if (linked.sort().join(",") === wanted.sort().join(",") && (await Promise.all(linked.map(isLink))).every(Boolean))
3491
3125
  return false;
3492
3126
  await Promise.all(parents.map((parent) => this.removeDir(`${this.cwdPath}/page/${parent}${AppExecutor.#pageLibsDir}`)));
3493
3127
  for (const parent of parents) {
@@ -3503,7 +3137,7 @@ class AppExecutor extends SysExecutor {
3503
3137
  async#resolvePageLibs(syncPageLibs, libDeps) {
3504
3138
  if (!syncPageLibs)
3505
3139
  return [];
3506
- const hasPageDir = async (lib) => await FileSys.dirExists(`${this.workspace.workspaceRoot}/libs/${lib}/page`);
3140
+ const hasPageDir = async (lib) => await FileSys2.dirExists(`${this.workspace.workspaceRoot}/libs/${lib}/page`);
3507
3141
  if (syncPageLibs === true) {
3508
3142
  const libs = [];
3509
3143
  for (const lib of libDeps)
@@ -3543,7 +3177,7 @@ class AppExecutor extends SysExecutor {
3543
3177
  } catch (error) {
3544
3178
  if (!isWindows)
3545
3179
  throw error;
3546
- await mkdir3(linkPath, { recursive: true });
3180
+ await mkdir2(linkPath, { recursive: true });
3547
3181
  await cpEntry(targetPath, linkPath, { recursive: true, dereference: true, force: true });
3548
3182
  }
3549
3183
  }
@@ -3575,13 +3209,14 @@ class AppExecutor extends SysExecutor {
3575
3209
  appPath: this.cwdPath,
3576
3210
  executor: this,
3577
3211
  getPath: (rel) => this.getPath(rel),
3578
- fileExists: (rel) => FileSys.fileExists(this.getPath(rel)),
3212
+ fileExists: (rel) => FileSys2.fileExists(this.getPath(rel)),
3213
+ readFile: (rel) => this.readFile(rel),
3579
3214
  writeFile: async (rel, content, opts) => {
3580
3215
  await this.writeFile(rel, content, opts);
3581
3216
  },
3582
3217
  readEnvClient: async () => {
3583
3218
  const envClientPath = path8.join(this.cwdPath, "env", "env.client.ts");
3584
- if (!await FileSys.fileExists(envClientPath))
3219
+ if (!await FileSys2.fileExists(envClientPath))
3585
3220
  return null;
3586
3221
  try {
3587
3222
  const envUrl = pathToFileURL(envClientPath);
@@ -3612,16 +3247,6 @@ class AppExecutor extends SysExecutor {
3612
3247
  return true;
3613
3248
  });
3614
3249
  }
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
3250
  async increaseBuildNum() {
3626
3251
  await increaseBuildNum(this);
3627
3252
  }
@@ -3637,15 +3262,8 @@ class LibExecutor extends SysExecutor {
3637
3262
  super({ workspace, name, type: "lib" });
3638
3263
  this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/libs/${name}`);
3639
3264
  }
3640
- static #execs = new Map;
3641
3265
  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 });
3266
+ return new LibExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
3649
3267
  }
3650
3268
  #akanConfig = null;
3651
3269
  async getConfig({ refresh } = {}) {
@@ -3668,9 +3286,7 @@ class PkgExecutor extends Executor {
3668
3286
  this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/pkgs/${name}`);
3669
3287
  }
3670
3288
  static from(executor, name) {
3671
- if (executor instanceof WorkspaceExecutor)
3672
- return new PkgExecutor({ workspace: executor, name });
3673
- return new PkgExecutor({ workspace: executor.workspace, name });
3289
+ return new PkgExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
3674
3290
  }
3675
3291
  #scanInfo = null;
3676
3292
  async scan({ refresh } = {}) {
@@ -3688,6 +3304,9 @@ class PkgExecutor extends Executor {
3688
3304
  const rootVersion = rootDeps[dep];
3689
3305
  if (rootVersion)
3690
3306
  return rootVersion;
3307
+ const overrideVersion = rootPackageJson.overrides?.[dep];
3308
+ if (typeof overrideVersion === "string")
3309
+ return overrideVersion;
3691
3310
  try {
3692
3311
  const packageJsonPath = `pkgs/${dep}/package.json`;
3693
3312
  if (!await Bun.file(path8.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
@@ -3713,16 +3332,8 @@ class PkgExecutor extends Executor {
3713
3332
  }
3714
3333
  if (missingDeps.length > 0)
3715
3334
  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
- };
3335
+ const toDependencyMap = (names) => Object.fromEntries(names.map((dep) => [dep, dependencyVersions.get(dep)]));
3336
+ return { dependencies: toDependencyMap(dependencyNames), devDependencies: toDependencyMap(devDependencyNames) };
3726
3337
  }
3727
3338
  async updatePackageJsonDependencies(dependencies = [], devDependencies = []) {
3728
3339
  const [rootPackageJson, pkgJson] = await Promise.all([this.workspace.getPackageJson(), this.getPackageJson()]);
@@ -3791,4 +3402,4 @@ class ModuleExecutor extends Executor {
3791
3402
  }
3792
3403
  }
3793
3404
 
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 };
3405
+ export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };