@akanjs/cli 3.0.0-beta.8 → 3.0.0

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 (224) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-2vmp9qe0.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-5ed6bg2a.js +1457 -0
  6. package/README.ko.md +83 -28
  7. package/README.md +70 -27
  8. package/TunnelShare-cwh9ezsm.js +9 -0
  9. package/{agent.command-0q17pnrs.js → agent.command-gn73rhe7.js} +8 -9
  10. package/akanCode.js +102 -0
  11. package/application.command-e968t0hw.js +246 -0
  12. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-64taxseg.js} +121 -77
  13. package/buildBatch.proc.js +120 -32
  14. package/cloud.command-0jm301rr.js +95 -0
  15. package/cloud.script-nemnzjpv.js +20 -0
  16. package/code.command-18kcm7hq.js +154 -0
  17. package/commandManifest.json +1 -1
  18. package/consoleToStderr-h5bryj94.js +7 -0
  19. package/context.command-trd9bn18.js +48 -0
  20. package/context.runner-y5hwh673.js +21 -0
  21. package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
  22. package/{devTui-m1atgkc9.js → devTui-q7k7bakd.js} +122 -40
  23. package/fileSys-wzzrntkw.js +9 -0
  24. package/guideline.command-bdmdn2de.js +42 -0
  25. package/guidelines/agentRule/agentRule.instruction.md +154 -26
  26. package/guidelines/conventions/conventions.instruction.md +134 -48
  27. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  28. package/guidelines/queryRule/queryRule.instruction.md +21 -7
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +291 -6
  30. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  31. package/guidelines/transportRule/transportRule.instruction.md +10 -8
  32. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
  33. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  34. package/incrementalBuilder.proc.js +564 -109
  35. package/index-0wpybxtr.js +2729 -0
  36. package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
  37. package/index-2wc41k5w.js +49 -0
  38. package/index-3hx1z96c.js +87 -0
  39. package/{index-2tv3fdx4.js → index-3w94e7n3.js} +1228 -1458
  40. package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
  41. package/index-4npz05nm.js +187 -0
  42. package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
  43. package/index-6he6gvfg.js +4300 -0
  44. package/index-7md2g9vw.js +6 -0
  45. package/{index-04bk599g.js → index-93j60em0.js} +10 -17
  46. package/index-94s080qm.js +266 -0
  47. package/index-9dp031by.js +12 -0
  48. package/{index-ktb5njgy.js → index-9z771ptz.js} +49 -99
  49. package/{index-y6n8nmny.js → index-abxpehf8.js} +75 -203
  50. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  51. package/{index-9077wzd2.js → index-by9pxkr0.js} +43 -26
  52. package/index-c6eggbee.js +123 -0
  53. package/index-d2242ksy.js +99 -0
  54. package/index-drehwpdt.js +1119 -0
  55. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  56. package/{index-ffvk10zz.js → index-f3c78nd6.js} +107 -241
  57. package/index-gbggbqcp.js +1058 -0
  58. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  59. package/index-hsmtysz4.js +53 -0
  60. package/{index-j9yhg404.js → index-jmrn54tt.js} +4 -7
  61. package/{index-a64esmtb.js → index-kcdj1z5v.js} +523 -1022
  62. package/{index-d43r08tf.js → index-kqabytj7.js} +77 -150
  63. package/{index-vyvre9en.js → index-mbxbhay5.js} +6 -2
  64. package/{index-vxtad5bd.js → index-mgxpx3f2.js} +5 -8
  65. package/index-p6pbz9n5.js +25 -0
  66. package/{index-sj5vxr8f.js → index-p993efkq.js} +981 -897
  67. package/{index-2xp0kfkt.js → index-pmhqttv3.js} +37 -25
  68. package/index-tqm7jjcn.js +700 -0
  69. package/{index-0g7r8xz3.js → index-vpj42xwt.js} +114 -232
  70. package/index-wa5jbxj1.js +760 -0
  71. package/{index-k2j1hc4r.js → index-wprnsjms.js} +12 -12
  72. package/{index-gyd220p2.js → index-y3mphr1m.js} +554 -577
  73. package/index-yg7jhz8x.js +69 -0
  74. package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
  75. package/index.js +23 -22
  76. package/{library.command-qzzbj6d8.js → library.command-9h749a1x.js} +4 -5
  77. package/{localRegistry.command-vyrgg4xw.js → localRegistry.command-2bqqhxyz.js} +23 -43
  78. package/{module.command-rq5hq3ax.js → module.command-nv8nzgft.js} +8 -12
  79. package/{package.command-d9hd6v9n.js → package.command-mq4bggp6.js} +6 -7
  80. package/package.json +15 -7
  81. package/{page.command-126d56te.js → page.command-j639n707.js} +4 -5
  82. package/{primitive.command-taxd1538.js → primitive.command-kz6849th.js} +9 -23
  83. package/prompter-76k56b37.js +8 -0
  84. package/{quality.command-6gsna0a3.js → quality.command-4hyabwkb.js} +205 -377
  85. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  86. package/{repair.command-tq2dgy72.js → repair.command-a9awpbqr.js} +11 -35
  87. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  88. package/{scalar.command-3bcmrrkv.js → scalar.command-ma4fpm9m.js} +7 -11
  89. package/skills/akan-code/SKILL.md +130 -0
  90. package/skills/akan-debug/SKILL.md +42 -0
  91. package/skills/akan-deps/SKILL.md +50 -0
  92. package/skills/akan-err/SKILL.md +39 -0
  93. package/skills/akan-module/SKILL.md +45 -0
  94. package/skills/akan-scaffold/SKILL.md +76 -0
  95. package/skills/akan-scalar/SKILL.md +52 -0
  96. package/skills/akan-service/SKILL.md +34 -0
  97. package/skills/akan-store/SKILL.md +67 -0
  98. package/skills/akan-ui/SKILL.md +63 -0
  99. package/skills/akan-validate/SKILL.md +44 -0
  100. package/{subspace.command-eddzqpx3.js → subspace.command-3k2pmrs4.js} +53 -62
  101. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  102. package/templates/app/page/_index.tsx +257 -258
  103. package/templates/app/page/_layout.tsx +5 -1
  104. package/templates/app/page/_overrides.tsx +12 -0
  105. package/templates/app/page/styles.css.template +316 -14
  106. package/templates/app/public/apple-touch-icon.png +0 -0
  107. package/templates/app/public/favicon.ico +0 -0
  108. package/templates/app/public/jelly/cloud.webp +0 -0
  109. package/templates/app/public/jelly/comet.webp +0 -0
  110. package/templates/app/public/jelly/moon.webp +0 -0
  111. package/templates/app/public/jelly/planet.webp +0 -0
  112. package/templates/app/public/jelly/rocket.webp +0 -0
  113. package/templates/app/public/jelly/star.webp +0 -0
  114. package/templates/app/public/jelly/sugar.webp +0 -0
  115. package/templates/app/public/logo.png +0 -0
  116. package/templates/app/ui/Recipe/index.ts +9 -0
  117. package/templates/app/ui/Recipe/jellyButton.ts +63 -0
  118. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  119. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  120. package/templates/appSample/lib/task/Task.Unit.tsx +1 -1
  121. package/templates/appSample/lib/task/Task.Util.tsx +10 -13
  122. package/templates/appSample/lib/task/Task.View.tsx +2 -2
  123. package/templates/appSample/lib/task/Task.Zone.tsx +5 -2
  124. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  125. package/templates/appSample/lib/task/task.signal.spec.ts +1 -15
  126. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  127. package/templates/appSample/lib/task/task.signal.ts +10 -8
  128. package/templates/appSample/page/task/[taskId]/_index.tsx +6 -6
  129. package/templates/appSample/page/task/[taskId]/edit.tsx +5 -4
  130. package/templates/appSample/page/task/_index.tsx +4 -3
  131. package/templates/appSample/page/task/_layout.tsx +1 -1
  132. package/templates/appSample/page/task/new.tsx +5 -4
  133. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  134. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  135. package/templates/appSample/ui/GlobalLoading.tsx +2 -1
  136. package/templates/appSample/ui/QuantityControl.tsx +3 -3
  137. package/templates/client.ts +7 -30
  138. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  139. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  140. package/templates/crudPages/new/page.tsx +2 -1
  141. package/templates/crudPages/page.tsx +2 -1
  142. package/templates/crudSinglePage/page.tsx +2 -1
  143. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  144. package/templates/lib/__lib/lib.document.ts +0 -2
  145. package/templates/lib/sig.ts +8 -30
  146. package/templates/localDev/docker-compose.yaml.template +3 -14
  147. package/templates/module/__model__.signal.ts +12 -4
  148. package/templates/pkgRoot/tsconfig.json.template +2 -7
  149. package/templates/server.ts +1 -3
  150. package/templates/service/__model__.abstract.md.template +8 -0
  151. package/templates/workspaceRoot/.gitignore.template +0 -1
  152. package/templates/workspaceRoot/AGENTS.md.template +6 -0
  153. package/templates/workspaceRoot/package.json.template +1 -3
  154. package/tunnel.command-xwr46b66.js +129 -0
  155. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  156. package/typecheck.proc.js +1 -1
  157. package/workflow.command-t7wqe4h7.js +79 -0
  158. package/{workspace.command-06wvstgr.js → workspace.command-4derhzrh.js} +79 -109
  159. package/abstractCompactor-gmkc0z8j.js +0 -102
  160. package/application.command-18y0s485.js +0 -204
  161. package/applicationReleasePackager-4yasxwv9.js +0 -244
  162. package/capacitorApp-36rbr858.js +0 -12
  163. package/cloud.command-av97ndh6.js +0 -94
  164. package/cloud.script-cm11xjdz.js +0 -20
  165. package/context.command-5v37ef3n.js +0 -81
  166. package/devTuiView-0hc2emhe.js +0 -9
  167. package/getDirname-dr3jz1xg.js +0 -7
  168. package/guideline.command-4pz9g1rt.js +0 -360
  169. package/guidelines/agentRule/agentRule.generate.json +0 -31
  170. package/guidelines/componentRule/componentRule.generate.json +0 -31
  171. package/guidelines/conventions/conventions.generate.json +0 -25
  172. package/guidelines/cssRule/cssRule.generate.json +0 -31
  173. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  174. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  175. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  176. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  177. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  178. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  179. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  180. package/guidelines/framework/framework.generate.json +0 -32
  181. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  182. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  183. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  184. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  185. package/guidelines/modelService/modelService.generate.json +0 -31
  186. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  187. package/guidelines/modelStore/modelStore.generate.json +0 -31
  188. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  189. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  190. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  191. package/guidelines/modelView/modelView.generate.json +0 -31
  192. package/guidelines/modelZone/modelZone.generate.json +0 -31
  193. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  194. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  195. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  196. package/guidelines/queryRule/queryRule.generate.json +0 -31
  197. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  198. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  199. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  200. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  201. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  202. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  203. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  204. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  205. package/guidelines/transportRule/transportRule.generate.json +0 -31
  206. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  207. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  208. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  209. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  210. package/index-47pj3aee.js +0 -76
  211. package/index-5ystnsd5.js +0 -841
  212. package/index-6wwzs1bc.js +0 -7
  213. package/index-a17x49vg.js +0 -229
  214. package/index-m0xccmmm.js +0 -228
  215. package/index-meh558jy.js +0 -65
  216. package/index-rtxxan88.js +0 -471
  217. package/index-te54x9zm.js +0 -338
  218. package/index-w8kmx1b6.js +0 -2176
  219. package/index-wt7qrtd4.js +0 -1516
  220. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  221. package/templates/module/__model__.abstract.md +0 -28
  222. package/templates/service/__model__.abstract.md +0 -28
  223. package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
  224. package/workflow.command-gtdnzvbz.js +0 -108
@@ -0,0 +1,1058 @@
1
+ // @bun
2
+ import {
3
+ binPlatforms,
4
+ Executor
5
+ } from "./index-3w94e7n3.js";
6
+
7
+ // pkgs/@akanjs/devkit/mobile/mobileTarget.ts
8
+ var trimSlashes = (value) => value.replace(/^\/+|\/+$/g, "");
9
+ var getMobileTargets = async (app) => {
10
+ const config = await app.getConfig();
11
+ return Object.entries(config.native.targets).map(([name, target]) => ({ name, config: target }));
12
+ };
13
+ var isTemplateOnly = ({ basePaths, native }) => basePaths.size > 0 && Object.values(native.targets).every((target) => !target.basePath);
14
+ var getMobileTargetChoices = async (app) => {
15
+ const config = await app.getConfig();
16
+ const targetNames = Object.keys(config.native.targets);
17
+ if (targetNames.length > 0 && !isTemplateOnly(config))
18
+ return targetNames;
19
+ return [...config.basePaths];
20
+ };
21
+ var resolveMobileTargetByBasePath = (targets, basePath) => {
22
+ const normalizedBasePath = trimSlashes(basePath);
23
+ const byBasePath = targets.find(({ config }) => config.basePath !== undefined && trimSlashes(config.basePath) === normalizedBasePath);
24
+ if (byBasePath)
25
+ return byBasePath;
26
+ const [template] = targets;
27
+ if (!template)
28
+ return;
29
+ return {
30
+ name: normalizedBasePath,
31
+ config: { ...template.config, name: normalizedBasePath, basePath: normalizedBasePath }
32
+ };
33
+ };
34
+ var resolveMobileTargets = async (app, selection) => {
35
+ const config = await app.getConfig();
36
+ const targets = await getMobileTargets(app);
37
+ if (targets.length === 0)
38
+ throw new Error(`No native targets configured for ${app.name}`);
39
+ if (!selection && targets.length === 1 && !isTemplateOnly(config))
40
+ return targets;
41
+ if (!selection) {
42
+ const choices = await getMobileTargetChoices(app);
43
+ if (choices.length === 1)
44
+ return resolveMobileTargets(app, choices[0]);
45
+ throw new Error(`Multiple native targets found for ${app.name}. Pass --target <${choices.join("|")}|all>.`);
46
+ }
47
+ if (selection === "all") {
48
+ if (Object.keys(config.native.targets).length > 1)
49
+ return targets;
50
+ const basePaths = [...config.basePaths];
51
+ if (basePaths.length > 1 || isTemplateOnly(config)) {
52
+ return basePaths.flatMap((basePath) => {
53
+ const resolved = resolveMobileTargetByBasePath(targets, basePath);
54
+ return resolved ? [resolved] : [];
55
+ });
56
+ }
57
+ return targets;
58
+ }
59
+ const target = targets.find((candidate) => candidate.name === selection);
60
+ if (target)
61
+ return [target];
62
+ const basePathTarget = resolveMobileTargetByBasePath(targets, selection);
63
+ if (basePathTarget && config.basePaths.has(trimSlashes(selection)))
64
+ return [basePathTarget];
65
+ const choices = await getMobileTargetChoices(app);
66
+ throw new Error(`Native target '${selection}' was not found. Available: ${choices.join(", ")}`);
67
+ };
68
+ var resolveAppId = (appId, platform) => {
69
+ if (typeof appId === "string")
70
+ return appId;
71
+ const id = appId[platform] ?? appId.default;
72
+ if (!id)
73
+ throw new Error(`native.appId names no id for ${platform}; give it ${platform} or default.`);
74
+ return id;
75
+ };
76
+ var appIdsOf = (appId) => typeof appId === "string" ? [appId] : Object.values(appId).filter((id) => !!id);
77
+ var targetHtmlFilename = (target) => {
78
+ const basePath = trimSlashes(target.basePath ?? "");
79
+ return basePath ? `${basePath}.html` : "index.html";
80
+ };
81
+
82
+ // pkgs/@akanjs/devkit/mobile/nativeApp.ts
83
+ import path6 from "path";
84
+
85
+ // pkgs/@akanjs/devkit/mobile/desktopBin.ts
86
+ import { chmod, mkdir, rename, rm, writeFile } from "fs/promises";
87
+ import path from "path";
88
+ class DesktopBin {
89
+ app;
90
+ config;
91
+ constructor(app, config) {
92
+ this.app = app;
93
+ this.config = config;
94
+ }
95
+ static platform(arch = process.arch) {
96
+ const platform = `${process.platform}-${arch}`;
97
+ return binPlatforms.includes(platform) ? platform : null;
98
+ }
99
+ static select(config, libs) {
100
+ const chosen = new Map;
101
+ for (const [name, platforms] of Object.entries(config.bin))
102
+ chosen.set(name, { owner: `apps/${config.app.name}/akan.config.ts`, platforms });
103
+ for (const { lib, bin } of config.libBins) {
104
+ if (!libs.includes(lib))
105
+ continue;
106
+ const owner = `libs/${lib}/akan.config.ts`;
107
+ for (const [name, platforms] of Object.entries(bin)) {
108
+ const held = chosen.get(name);
109
+ if (!held)
110
+ chosen.set(name, { owner, platforms });
111
+ else if (held.owner.startsWith("libs/") && JSON.stringify(held.platforms) !== JSON.stringify(platforms))
112
+ throw new Error(`${held.owner} and ${owner} declare bin.${name} differently; declare it in apps/${config.app.name}/akan.config.ts to pick one.`);
113
+ }
114
+ }
115
+ return chosen;
116
+ }
117
+ static fileName(name, executable, platform = process.platform) {
118
+ if (platform !== "win32" || /\.(exe|cmd|bat|com)$/i.test(name))
119
+ return name;
120
+ return `${name}${path.extname(executable).toLowerCase() || ".exe"}`;
121
+ }
122
+ static downloadName(url) {
123
+ const extension = /(?:\.tar)?\.[A-Za-z0-9]{1,8}$/.exec(path.posix.basename(new URL(url).pathname))?.[0] ?? "";
124
+ return `download${extension.toLowerCase()}`;
125
+ }
126
+ static extractCommand(archive, dir, platform = process.platform) {
127
+ if (platform === "linux" && /\.zip$/i.test(archive))
128
+ return ["unzip", "-q", "-o", archive, "-d", dir];
129
+ const tar = platform === "win32" ? path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe") : "tar";
130
+ return [tar, "-xf", archive, "-C", dir];
131
+ }
132
+ static async sha256(file) {
133
+ return new Bun.CryptoHasher("sha256").update(await Bun.file(file).arrayBuffer()).digest("hex");
134
+ }
135
+ get #cacheDir() {
136
+ return path.join(this.app.cwdPath, ".akan", "cache", "bin");
137
+ }
138
+ async stage(dir, arch = process.arch) {
139
+ const scanInfo = this.app.getScanInfo({ allowEmpty: true }) ?? await this.app.scan({ write: false });
140
+ const chosen = DesktopBin.select(this.config, scanInfo.getLibs());
141
+ if (!chosen.size)
142
+ return [];
143
+ const platform = DesktopBin.platform(arch);
144
+ if (!platform)
145
+ throw new Error(`bin: this desktop app is for ${process.platform}-${arch}, and a bin names only ${binPlatforms.join(", ")}.`);
146
+ await rm(dir, { recursive: true, force: true });
147
+ await mkdir(dir, { recursive: true });
148
+ for (const [name, { owner, platforms }] of chosen) {
149
+ const source = platforms[platform];
150
+ if (!source)
151
+ throw new Error(`${owner}: bin.${name} names no ${platform} file, the platform this desktop app is built for (it names ${Object.keys(platforms).join(", ") || "none"}).`);
152
+ const executable = await this.#executable(`${owner}: bin.${name}.${platform}`, source);
153
+ const target = path.join(dir, DesktopBin.fileName(name, executable));
154
+ await Bun.write(target, Bun.file(executable));
155
+ await chmod(target, 493);
156
+ this.app.logger.info(`The desktop app carries ${name} from ${"url" in source ? source.url : source.path}`);
157
+ }
158
+ return [...chosen.keys()];
159
+ }
160
+ async#executable(at, source) {
161
+ const file = "url" in source ? await this.#download(at, source) : source.path;
162
+ if (!await Bun.file(file).exists())
163
+ throw new Error(`${at}: ${file} does not exist`);
164
+ if (!source.file)
165
+ return file;
166
+ const key = "url" in source ? source.sha256 : await DesktopBin.sha256(file);
167
+ const executable = path.join(await this.#extract(file, key), ...source.file.split("/"));
168
+ if (!await Bun.file(executable).exists())
169
+ throw new Error(`${at}: the archive holds no ${source.file}`);
170
+ return executable;
171
+ }
172
+ async#download(at, { url, sha256 }) {
173
+ const dir = path.join(this.#cacheDir, sha256);
174
+ const file = path.join(dir, DesktopBin.downloadName(url));
175
+ if (await Bun.file(file).exists())
176
+ return file;
177
+ await mkdir(dir, { recursive: true });
178
+ this.app.logger.info(`Downloading ${url}`);
179
+ const res = await fetch(url, { signal: AbortSignal.timeout(600000) });
180
+ if (!res.ok)
181
+ throw new Error(`${at}: ${url} answered ${res.status}`);
182
+ const bytes = new Uint8Array(await res.arrayBuffer());
183
+ const digest = new Bun.CryptoHasher("sha256").update(bytes).digest("hex");
184
+ if (digest !== sha256)
185
+ throw new Error(`${at}: ${url} hashes to ${digest}, not the declared ${sha256}`);
186
+ await Bun.write(`${file}.partial`, bytes);
187
+ await rename(`${file}.partial`, file);
188
+ return file;
189
+ }
190
+ async#extract(archive, key) {
191
+ const dir = path.join(this.#cacheDir, key, "extracted");
192
+ if (await Bun.file(`${dir}.done`).exists())
193
+ return dir;
194
+ await rm(dir, { recursive: true, force: true });
195
+ await mkdir(dir, { recursive: true });
196
+ const [command = "tar", ...args] = DesktopBin.extractCommand(archive, dir);
197
+ if (command === "unzip" && !Bun.which("unzip"))
198
+ throw new Error(`${archive} is a zip, and this computer has no unzip: install it, or use a .tar.gz or .tar.xz.`);
199
+ await this.app.spawn(command, args, { cwd: dir });
200
+ await writeFile(`${dir}.done`, "");
201
+ return dir;
202
+ }
203
+ }
204
+
205
+ // pkgs/@akanjs/devkit/mobile/nativeApi.ts
206
+ import path2 from "path";
207
+
208
+ class NativeApi {
209
+ static supportedMajor = 0;
210
+ static entryOf(appDir) {
211
+ const workspaceEntry = NativeApi.#resolve("@akanjs/native/api", appDir);
212
+ if (workspaceEntry)
213
+ return workspaceEntry;
214
+ const akanjsPackage = NativeApi.#resolve("akanjs/package.json", appDir);
215
+ if (!akanjsPackage)
216
+ throw new Error(`akanjs is not installed for ${appDir}; the native build ships inside it.`);
217
+ return path2.join(path2.dirname(akanjsPackage), "vendor", "@akanjs", "native", "packages", "cli", "src", "api.ts");
218
+ }
219
+ static async load(appDir) {
220
+ const entry = NativeApi.entryOf(appDir);
221
+ if (!await Bun.file(entry).exists())
222
+ throw new Error(`The installed akanjs has no native build at ${entry}; update akanjs.`);
223
+ const api = await import(entry);
224
+ const major = Number(api.API_VERSION.split(".")[0]);
225
+ if (major !== NativeApi.supportedMajor)
226
+ throw new Error(`The native build API is ${api.API_VERSION}, and this akan CLI speaks ${NativeApi.supportedMajor}.x; install matching versions of akanjs and @akanjs/cli.`);
227
+ return api;
228
+ }
229
+ static #resolve(specifier, from) {
230
+ try {
231
+ return Bun.resolveSync(specifier, from);
232
+ } catch {
233
+ return null;
234
+ }
235
+ }
236
+ }
237
+
238
+ // pkgs/@akanjs/devkit/mobile/nativeAppLine.ts
239
+ class NativeAppLine {
240
+ static #page = /^\[page(\+)?(?:#(\d+))? (trace|verbose|debug|log|info|warn|error)\] ?(.*)$/;
241
+ static #logcat = /^([VDIWEFA])\/([^(]+?)\s*\(\s*\d+\):\s?(.*)$/;
242
+ static #named = /^\[([^\]\s]+)\] ([\s\S]*)$/;
243
+ static #server = /^\[server\] /;
244
+ static #serverLevel = /\b(TRACE|VERBOSE|DEBUG|INFO|WARN|ERROR)\b/;
245
+ static #logcatLevels = {
246
+ V: "verbose",
247
+ D: "debug",
248
+ I: "info",
249
+ W: "warn",
250
+ E: "error",
251
+ F: "error",
252
+ A: "error"
253
+ };
254
+ static read(line, { verbose = false } = {}) {
255
+ const logcat = NativeAppLine.#logcat.exec(line);
256
+ if (logcat) {
257
+ const [, priority = "I", tag = "", text = ""] = logcat;
258
+ const level = NativeAppLine.#logcatLevels[priority] ?? "info";
259
+ if (tag === "AkanNativeConsole")
260
+ return NativeAppLine.#pageLine(level, text, { continuation: false });
261
+ if (tag === "AkanNative")
262
+ return NativeAppLine.#hostLine(`[akan-native] ${text}`, level);
263
+ return { level, message: `[${tag}] ${text}` };
264
+ }
265
+ const page = NativeAppLine.#page.exec(line);
266
+ if (page) {
267
+ const [, continuation, window, level = "info", text = ""] = page;
268
+ return NativeAppLine.#pageLine(level === "log" ? "info" : level, text, {
269
+ continuation: continuation === "+",
270
+ window
271
+ });
272
+ }
273
+ if (line.startsWith("[akan-native"))
274
+ return NativeAppLine.#hostLine(line, "info");
275
+ if (NativeAppLine.#server.test(line)) {
276
+ const word = NativeAppLine.#serverLevel.exec(Bun.stripANSI(line))?.[1];
277
+ return { level: word ? word.toLowerCase() : "info", message: line };
278
+ }
279
+ return { level: verbose ? "info" : "debug", message: line };
280
+ }
281
+ static #pageLine(level, text, { continuation, window }) {
282
+ if (continuation)
283
+ return { level, message: ` ${text}` };
284
+ const named = NativeAppLine.#named.exec(text);
285
+ const scope = `page${window ? `#${window}` : ""}${named ? `:${named[1]}` : ""}`;
286
+ return { level, message: `[${scope}] ${named ? named[2] : text}` };
287
+ }
288
+ static #hostLine(line, level) {
289
+ return { level: /\bpages from\b/.test(line) ? "debug" : level, message: line };
290
+ }
291
+ }
292
+
293
+ // pkgs/@akanjs/devkit/mobile/nativeConfig.ts
294
+ import { readFileSync } from "fs";
295
+ import path3 from "path";
296
+
297
+ // pkgs/@akanjs/devkit/mobile/usageDescriptions.ts
298
+ import { capitalize } from "akanjs/common";
299
+ var iosUsageDescriptionKeys = {
300
+ cameraUsageDescription: ["NSCameraUsageDescription"],
301
+ photoAddUsageDescription: ["NSPhotoLibraryAddUsageDescription"],
302
+ photoUsageDescription: ["NSPhotoLibraryUsageDescription"],
303
+ contactsUsageDescription: ["NSContactsUsageDescription"],
304
+ locationAlwaysUsageDescription: ["NSLocationAlwaysAndWhenInUseUsageDescription", "NSLocationAlwaysUsageDescription"],
305
+ locationWhenInUseUsageDescription: ["NSLocationWhenInUseUsageDescription"],
306
+ microphoneUsageDescription: ["NSMicrophoneUsageDescription"],
307
+ speechRecognitionUsageDescription: ["NSSpeechRecognitionUsageDescription"]
308
+ };
309
+ var toIosInfoPlistUsageDescriptions = (descriptions) => Object.fromEntries(Object.entries(descriptions).flatMap(([key, value]) => {
310
+ const plistKeys = Object.hasOwn(iosUsageDescriptionKeys, key) ? iosUsageDescriptionKeys[key] : [`NS${capitalize(key)}`];
311
+ return plistKeys.map((plistKey) => [plistKey, value]);
312
+ }));
313
+
314
+ // pkgs/@akanjs/devkit/mobile/nativeConfig.ts
315
+ class NativeConfig {
316
+ static basePlugins = [
317
+ "app",
318
+ "app-state",
319
+ "browser",
320
+ "opener",
321
+ "auth-session",
322
+ "device",
323
+ "dialog",
324
+ "keyboard",
325
+ "preferences",
326
+ "secure-storage",
327
+ "haptics"
328
+ ];
329
+ static builtinContributions = {
330
+ camera: {
331
+ permission: "camera",
332
+ plugins: ["camera"],
333
+ usageDescriptions: {
334
+ cameraUsageDescription: "$(PRODUCT_NAME) requires access to the camera to take photos.",
335
+ photoAddUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
336
+ photoUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos."
337
+ }
338
+ },
339
+ contacts: {
340
+ permission: "contacts",
341
+ plugins: ["contacts"],
342
+ usageDescriptions: {
343
+ contactsUsageDescription: "$(PRODUCT_NAME) requires access to the contacts to find people you know."
344
+ }
345
+ },
346
+ location: {
347
+ permission: "location",
348
+ plugins: ["geolocation"],
349
+ usageDescriptions: {
350
+ locationAlwaysUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
351
+ locationWhenInUseUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location."
352
+ }
353
+ },
354
+ push: { permission: "push", plugins: ["push"] },
355
+ speech: { permission: "speech" }
356
+ };
357
+ static build({
358
+ appPath,
359
+ target,
360
+ webDir,
361
+ contributions,
362
+ locales,
363
+ env,
364
+ platform,
365
+ desktopServer,
366
+ nativePlugins = [],
367
+ desktopBin
368
+ }) {
369
+ const warnings = [];
370
+ const applied = (target.permissions ?? []).flatMap((permission) => {
371
+ const claimed = contributions.filter((contribution) => contribution.permission === permission);
372
+ const used = claimed.length ? claimed : [NativeConfig.builtinContributions[permission]];
373
+ if (!used.some((contribution) => contribution.plugins?.length))
374
+ warnings.push(`Permission '${permission}' has no native plugin yet; the app ships without it.`);
375
+ return used;
376
+ });
377
+ const abs = (relative) => path3.resolve(appPath, relative);
378
+ const updates = NativeConfig.#updates(target, env);
379
+ const folders = new Set(nativePlugins.map((plugin) => path3.resolve(plugin.dir)));
380
+ const folderIds = new Set(nativePlugins.map((plugin) => plugin.id));
381
+ const listed = (target.plugins ?? []).filter((spec) => NativeConfig.#isId(spec) ? !folderIds.has(spec) : !folders.has(path3.resolve(appPath, spec)));
382
+ const plugins = [
383
+ ...new Set([
384
+ ...NativeConfig.basePlugins,
385
+ ...target.deepLinks?.schemes?.length ? ["single-instance"] : [],
386
+ ...applied.flatMap((contribution) => contribution.plugins ?? []),
387
+ ...desktopServer ? ["single-instance"] : [],
388
+ ...updates ? ["updates"] : [],
389
+ ...listed,
390
+ ...nativePlugins.map((plugin) => plugin.dir)
391
+ ])
392
+ ];
393
+ const pluginIds = plugins.map((spec) => nativePlugins.find((plugin) => plugin.dir === spec)?.id ?? NativeConfig.#pluginId(appPath, spec));
394
+ const usageDescriptions = Object.fromEntries(Object.entries(toIosInfoPlistUsageDescriptions(Object.assign({}, ...applied.map((contribution) => contribution.usageDescriptions ?? {})))).map(([key, text]) => [key, text.replaceAll("$(PRODUCT_NAME)", target.appName)]));
395
+ const ios = NativeConfig.#compact({
396
+ infoPlist: NativeConfig.#merged(applied.map((c) => c.infoPlist), target.ios?.infoPlist),
397
+ entitlements: NativeConfig.#merged(applied.map((c) => c.entitlements), target.ios?.entitlements)
398
+ });
399
+ const manifest = [
400
+ ...[...new Set(applied.flatMap((c) => c.androidPermissions ?? []))].map((name) => `<uses-permission android:name="android.permission.${name}" />`),
401
+ ...[...new Set(applied.flatMap((c) => c.androidFeatures ?? []))].map((name) => `<uses-feature android:name="${name}" android:required="false" />`),
402
+ ...target.android?.manifest ?? []
403
+ ];
404
+ const android = NativeConfig.#compact({
405
+ manifest,
406
+ application: target.android?.application ?? [],
407
+ activity: target.android?.activity ?? []
408
+ });
409
+ const resources = [
410
+ ...Object.entries(target.ios?.files ?? {}).map(([to, from]) => ({ from: abs(from), to: `ios/${to}` })),
411
+ ...Object.entries(target.android?.files ?? {}).map(([to, from]) => ({ from: abs(from), to: `android/${to}` }))
412
+ ];
413
+ const macos = NativeConfig.#compact({ entitlements: target.desktop?.entitlements });
414
+ const native = NativeConfig.#compact({ ios, macos, android, resources });
415
+ const googleServices = target.android?.googleServices;
416
+ const pushAndroid = target.android?.push;
417
+ const desktop = NativeConfig.#compact({
418
+ recovery: target.desktop?.recovery,
419
+ screenCapture: target.desktop?.screenCapture,
420
+ window: NativeConfig.#compact({ ...target.desktop?.window }),
421
+ server: desktopServer,
422
+ bin: desktopBin
423
+ });
424
+ const config = {
425
+ app: {
426
+ id: resolveAppId(target.appId, platform),
427
+ name: target.appName,
428
+ fileName: target.fileName ?? NativeConfig.fileNameOf(path3.basename(appPath)),
429
+ version: target.version,
430
+ build: target.buildNum
431
+ },
432
+ web: { dir: webDir },
433
+ plugins,
434
+ capabilities: [
435
+ {
436
+ identifier: "app",
437
+ description: `The plugins ${target.appName} ships, each with its default permissions`,
438
+ permissions: pluginIds.map((id) => `${id}:default`)
439
+ }
440
+ ],
441
+ ...Object.keys(usageDescriptions).length ? { usageDescriptions } : {},
442
+ ...target.deepLinks?.schemes?.length || target.deepLinks?.domains?.length ? { deepLinks: NativeConfig.#deepLinks(target, locales) } : {},
443
+ ...native ? { native } : {},
444
+ ...updates ? { updates } : {},
445
+ ...pushAndroid ? {
446
+ push: {
447
+ android: { ...pushAndroid, ...pushAndroid.smallIcon ? { smallIcon: abs(pushAndroid.smallIcon) } : {} }
448
+ }
449
+ } : {},
450
+ ...target.ios?.privacy ? { privacy: target.ios.privacy } : {},
451
+ android: {
452
+ debugAppIdSuffix: ".debug",
453
+ ...googleServices ? { googleServices: abs(googleServices) } : {},
454
+ ...target.android?.autoplay ? { autoplay: true } : {}
455
+ },
456
+ keyboard: { resize: "none" },
457
+ ...desktop ? { desktop } : {},
458
+ ...target.icon ? { icon: NativeConfig.#icon(target.icon, abs) } : {},
459
+ ...target.splash ? { splash: NativeConfig.#splash(target.splash, abs) } : {}
460
+ };
461
+ return { config, warnings };
462
+ }
463
+ static #icon(icon, abs) {
464
+ return typeof icon === "string" ? abs(icon) : { ...icon, image: abs(icon.image) };
465
+ }
466
+ static #splash(splash, abs) {
467
+ if (typeof splash === "string")
468
+ return { image: abs(splash) };
469
+ return { ...splash, ...splash.image ? { image: abs(splash.image) } : {} };
470
+ }
471
+ static #updates(target, env) {
472
+ const { url, publicKey, channel = env, ...rest } = target.updates ?? {};
473
+ return url && publicKey ? { ...rest, url, publicKey, ...channel ? { channel } : {} } : undefined;
474
+ }
475
+ static fileNameOf(name) {
476
+ return name.replace(/[^A-Za-z0-9._-]/g, "-").replace(/^[-.]+|[-.]+$/g, "") || "app";
477
+ }
478
+ static #pluginId(appPath, spec) {
479
+ if (NativeConfig.#isId(spec))
480
+ return spec;
481
+ try {
482
+ const { id } = JSON.parse(readFileSync(path3.join(path3.resolve(appPath, spec), "native-plugin.json"), "utf8"));
483
+ return typeof id === "string" ? id : spec;
484
+ } catch {
485
+ return spec;
486
+ }
487
+ }
488
+ static #isId(spec) {
489
+ return !spec.startsWith(".") && !path3.isAbsolute(spec);
490
+ }
491
+ static #compact(value) {
492
+ const kept = Object.entries(value).filter(([, entry]) => Array.isArray(entry) ? entry.length > 0 : entry && typeof entry === "object" ? Object.keys(entry).length > 0 : entry !== undefined);
493
+ return kept.length ? Object.fromEntries(kept) : undefined;
494
+ }
495
+ static #merged(contributed, own) {
496
+ return Object.assign({}, ...contributed, own);
497
+ }
498
+ static #deepLinks(target, locales) {
499
+ const basePath = target.basePath?.replace(/^\/+|\/+$/g, "");
500
+ const pathPrefixes = basePath ? locales.map((locale) => `/${locale}/${basePath}`) : undefined;
501
+ return {
502
+ ...target.deepLinks?.schemes?.length ? { schemes: target.deepLinks.schemes } : {},
503
+ ...target.deepLinks?.domains?.length ? { domains: target.deepLinks.domains.map((host) => pathPrefixes ? { host, pathPrefixes } : host) } : {}
504
+ };
505
+ }
506
+ }
507
+
508
+ // pkgs/@akanjs/devkit/mobile/nativePluginFolders.ts
509
+ import { readdir } from "fs/promises";
510
+ import path4 from "path";
511
+
512
+ class NativePluginFolders {
513
+ static folder = "native";
514
+ static async in(root, owner) {
515
+ const base = path4.join(root, NativePluginFolders.folder);
516
+ const entries = await readdir(base, { withFileTypes: true }).catch(() => []);
517
+ const folders = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
518
+ return await Promise.all(folders.sort().map(async (name) => {
519
+ const where = `${owner}/${NativePluginFolders.folder}/${name}`;
520
+ const manifest = Bun.file(path4.join(base, name, "native-plugin.json"));
521
+ if (!await manifest.exists())
522
+ throw new Error(`${where} has no native-plugin.json; a folder in native/ is one native plugin.`);
523
+ const { id } = await manifest.json();
524
+ if (id !== name)
525
+ throw new Error(`${where}/native-plugin.json has id ${JSON.stringify(id)}; name the folder after its id.`);
526
+ return { id, dir: path4.join(base, name), owner };
527
+ }));
528
+ }
529
+ static select(own, fromLibs) {
530
+ const chosen = new Map(own.map((plugin) => [plugin.id, plugin]));
531
+ for (const plugin of fromLibs) {
532
+ const held = chosen.get(plugin.id);
533
+ if (!held)
534
+ chosen.set(plugin.id, plugin);
535
+ else if (held.owner.startsWith("libs/"))
536
+ throw new Error(`${held.owner} and ${plugin.owner} both hold native plugin "${plugin.id}"; give the app its own native/${plugin.id} to pick one.`);
537
+ }
538
+ return [...chosen.values()];
539
+ }
540
+ static async of(app) {
541
+ const scanInfo = app.getScanInfo({ allowEmpty: true }) ?? await app.scan({ write: false });
542
+ const root = app.workspace.workspaceRoot;
543
+ const [own, ...fromLibs] = await Promise.all([
544
+ NativePluginFolders.in(app.cwdPath, `apps/${app.name}`),
545
+ ...scanInfo.getLibs().map((lib) => NativePluginFolders.in(path4.join(root, "libs", lib), `libs/${lib}`))
546
+ ]);
547
+ return NativePluginFolders.select(own ?? [], fromLibs.flat());
548
+ }
549
+ }
550
+
551
+ // pkgs/@akanjs/devkit/mobile/nativeWebDir.ts
552
+ import { cp, mkdir as mkdir2, rm as rm2, stat } from "fs/promises";
553
+ import path5 from "path";
554
+
555
+ class NativeWebDir {
556
+ dir;
557
+ static reservedEntry = "__akan_native";
558
+ constructor(dir) {
559
+ this.dir = dir;
560
+ }
561
+ static injectTarget(html, target) {
562
+ if (html.includes(NativeWebDir.#targetTag))
563
+ return html;
564
+ const basePath = target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
565
+ const script = `${NativeWebDir.#targetTag}${JSON.stringify({
566
+ name: target.name,
567
+ basePath,
568
+ indexPath: target.indexPath
569
+ })};</script>`;
570
+ const head = /<head(?:\s[^>]*)?>/i.exec(html);
571
+ if (!head)
572
+ return `${script}
573
+ ${html}`;
574
+ const at = head.index + head[0].length;
575
+ return `${html.slice(0, at)}
576
+ ${script}${html.slice(at)}`;
577
+ }
578
+ static #targetTag = "<script>window.__AKAN_MOBILE_TARGET__=";
579
+ async clear() {
580
+ await rm2(this.dir, { recursive: true, force: true });
581
+ await mkdir2(this.dir, { recursive: true });
582
+ }
583
+ async assemble(target, { html, publicDir, fontsDir }) {
584
+ if (!await Bun.file(html).exists())
585
+ throw new Error(`CSR html for native target '${target.name}' not found: ${html}`);
586
+ await this.clear();
587
+ if (publicDir && await NativeWebDir.#isDir(publicDir))
588
+ await cp(publicDir, this.dir, { recursive: true });
589
+ if (fontsDir && await NativeWebDir.#isDir(fontsDir))
590
+ await cp(fontsDir, path5.join(this.dir, "_akan", "fonts"), { recursive: true });
591
+ if (await NativeWebDir.#isDir(path5.join(this.dir, NativeWebDir.reservedEntry)))
592
+ throw new Error(`public/${NativeWebDir.reservedEntry} is reserved for the native runtime.`);
593
+ await Bun.write(path5.join(this.dir, "index.html"), NativeWebDir.injectTarget(await Bun.file(html).text(), target));
594
+ const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: this.dir, onlyFiles: true, dot: true }));
595
+ return files.map((file) => file.split(path5.sep).join("/")).sort();
596
+ }
597
+ static async#isDir(dir) {
598
+ try {
599
+ return (await stat(dir)).isDirectory();
600
+ } catch {
601
+ return false;
602
+ }
603
+ }
604
+ }
605
+
606
+ // pkgs/@akanjs/devkit/mobile/nativeApp.ts
607
+ class NativeApp {
608
+ app;
609
+ target;
610
+ env;
611
+ targetRoot;
612
+ web;
613
+ #boot = null;
614
+ constructor(app, target, env) {
615
+ this.app = app;
616
+ this.target = target;
617
+ this.env = env;
618
+ this.targetRoot = path6.join(app.cwdPath, ".akan", "native", target.name);
619
+ this.web = new NativeWebDir(path6.join(this.targetRoot, "web"));
620
+ }
621
+ outDir(platform) {
622
+ return path6.join(this.targetRoot, "build", platform);
623
+ }
624
+ devOutDir(platform) {
625
+ return path6.join(this.targetRoot, "dev", platform);
626
+ }
627
+ indexPath(platform) {
628
+ const { config } = this.target;
629
+ const section = platform === "ios" ? config.ios : platform === "android" ? config.android : config.desktop;
630
+ return section?.indexPath ?? config.indexPath;
631
+ }
632
+ async assembleWeb(platform) {
633
+ const dist = this.app.dist.cwdPath;
634
+ return await this.web.assemble({ ...this.target.config, indexPath: this.indexPath(platform) }, {
635
+ html: path6.join(dist, "csr", targetHtmlFilename(this.target.config)),
636
+ publicDir: path6.join(dist, "public"),
637
+ fontsDir: path6.join(dist, ".akan", "artifact", "fonts")
638
+ });
639
+ }
640
+ get binDir() {
641
+ return path6.join(this.targetRoot, "bin");
642
+ }
643
+ async config({
644
+ server,
645
+ platform,
646
+ stageBin = true,
647
+ arch
648
+ }) {
649
+ const [appConfig, plugins, nativePlugins] = await Promise.all([
650
+ this.app.getConfig(),
651
+ this.app.collectPlugins(),
652
+ NativePluginFolders.of(this.app)
653
+ ]);
654
+ const desktop = platform === "macos" || platform === "windows" || platform === "linux";
655
+ const carried = desktop && stageBin ? await new DesktopBin(this.app, appConfig).stage(this.binDir, arch) : [];
656
+ return NativeConfig.build({
657
+ appPath: this.app.cwdPath,
658
+ target: this.target.config,
659
+ webDir: this.web.dir,
660
+ contributions: plugins.flatMap((plugin) => plugin.native ? [plugin.native] : []),
661
+ locales: appConfig.i18n.locales,
662
+ env: this.env,
663
+ platform,
664
+ nativePlugins,
665
+ ...server ? { desktopServer: server } : {},
666
+ ...carried.length ? { desktopBin: this.binDir } : {}
667
+ });
668
+ }
669
+ async prepare(platform, server, arch) {
670
+ const [api, { config, warnings }] = await Promise.all([
671
+ NativeApi.load(this.app.cwdPath),
672
+ this.config({ platform, ...server ? { server } : {}, ...arch ? { arch } : {} })
673
+ ]);
674
+ for (const warning of warnings)
675
+ this.app.logger.warn(warning);
676
+ this.#assertValid(api, config);
677
+ return { api, config };
678
+ }
679
+ #assertValid(api, config) {
680
+ const problems = api.validateConfig(config, { appDir: this.app.cwdPath });
681
+ if (problems.length)
682
+ throw new Error(`Native target '${this.target.name}' makes an invalid native config:
683
+ - ${problems.join(`
684
+ - `)}`);
685
+ }
686
+ #appLine = (line) => {
687
+ const read = NativeAppLine.read(line, { verbose: Executor.verbose });
688
+ if (this.#boot)
689
+ this.#boot.lines.push(read);
690
+ else
691
+ this.app.logger[read.level](read.message);
692
+ };
693
+ #task(platform, config, outDir = this.outDir(platform)) {
694
+ return {
695
+ appDir: this.app.cwdPath,
696
+ config,
697
+ platform,
698
+ outDir,
699
+ envFiles: false,
700
+ skipWebBuild: true,
701
+ log: (event) => {
702
+ if (event.level === "error")
703
+ this.app.logger.error(event.message);
704
+ else if (event.level === "warn")
705
+ this.app.logger.warn(event.message);
706
+ else if (this.#boot)
707
+ this.#boot.steps.push(event.message);
708
+ else if (event.level === "tool")
709
+ this.app.logger.verbose(event.message);
710
+ else
711
+ this.app.logger.info(event.message);
712
+ }
713
+ };
714
+ }
715
+ async build(platform, { profile = "release", server, installer = false, arch } = {}) {
716
+ NativeApp.#assertServerPlatform(platform, server);
717
+ NativeApp.assertInstaller(platform, installer);
718
+ await this.assembleWeb(platform);
719
+ const { api, config } = await this.prepare(platform, server, arch);
720
+ return await api.build({
721
+ ...this.#task(platform, config),
722
+ profile,
723
+ ...arch ? { arch } : {},
724
+ ...platform === "windows" ? {
725
+ windows: {
726
+ ...installer ? { installer } : {},
727
+ ...profile === "release" ? NativeApp.#windowsSigning(api) : {}
728
+ }
729
+ } : {},
730
+ ...platform === "macos" ? {
731
+ macos: {
732
+ ...profile === "release" ? api.macosDistributionFromEnv(process.env) : {},
733
+ ...installer ? { dmg: true } : {}
734
+ }
735
+ } : {},
736
+ ...platform === "linux" && installer ? { linux: { appImage: true } } : {}
737
+ });
738
+ }
739
+ async run(platform, { device, teamId, profile = "debug", server } = {}) {
740
+ NativeApp.#assertServerPlatform(platform, server);
741
+ await this.assembleWeb(platform);
742
+ const { api, config } = await this.prepare(platform, server);
743
+ return await api.run({
744
+ ...this.#task(platform, config),
745
+ profile,
746
+ onLine: this.#appLine,
747
+ ...device ? { device } : {},
748
+ ...teamId ? { ios: { signing: { teamId } } } : {}
749
+ });
750
+ }
751
+ async dev(platform, { upstream, lang, device, teamId }) {
752
+ const startedAt = performance.now();
753
+ const boot = { steps: [], lines: [] };
754
+ this.#boot = boot;
755
+ try {
756
+ await this.web.clear();
757
+ const [{ api, config }, { api: routes }] = await Promise.all([this.prepare(platform), this.app.getConfig()]);
758
+ const session = await api.dev({
759
+ ...this.#task(platform, config, this.devOutDir(platform)),
760
+ upstream,
761
+ hmrPath: "/_akan/hmr",
762
+ wsPaths: [`${routes.prefix}${routes.websocketPrefix}`],
763
+ reversePorts: [Number(new URL(upstream).port)],
764
+ onLine: this.#appLine,
765
+ startPath: this.startPath(lang, platform),
766
+ ...device ? { device } : {},
767
+ ...teamId ? { ios: { signing: { teamId } } } : {}
768
+ });
769
+ this.#boot = null;
770
+ this.app.logger.info(this.readyLine(platform, { upstream, device: session.device, startedAt }));
771
+ for (const step of [...boot.steps, `pages through ${session.gateway}`])
772
+ this.app.logger.debug(step);
773
+ for (const line of boot.lines)
774
+ this.app.logger[line.level](line.message);
775
+ return session;
776
+ } catch (error) {
777
+ this.#boot = null;
778
+ for (const step of boot.steps)
779
+ this.app.logger.info(step);
780
+ for (const line of boot.lines)
781
+ this.app.logger[line.level === "debug" ? "info" : line.level](line.message);
782
+ throw error;
783
+ }
784
+ }
785
+ readyLine(platform, { upstream, device, startedAt }) {
786
+ const label = platform === "ios" || platform === "android" ? platform : "desktop";
787
+ const target = this.target.name === "default" ? "" : `/${this.target.name}`;
788
+ const seconds = ((performance.now() - startedAt) / 1000).toFixed(1);
789
+ return [`${this.app.name}${target} ${label} ready`, ...device ? [device.name] : [], upstream, `${seconds}s`].join(" \xB7 ");
790
+ }
791
+ async releaseIos({ teamId, adHoc = false } = {}) {
792
+ await this.assembleWeb("ios");
793
+ const { api, config } = await this.prepare("ios");
794
+ return await api.release({
795
+ ...this.#task("ios", config),
796
+ platform: "ios",
797
+ signing: { ...teamId ? { teamId } : {}, ...adHoc ? { distribution: "ad-hoc" } : {} }
798
+ });
799
+ }
800
+ get updatesDir() {
801
+ return path6.join(this.targetRoot, "updates");
802
+ }
803
+ async publishUpdate(platform, { channel, server } = {}) {
804
+ NativeApp.#assertServerPlatform(platform, server);
805
+ this.#assertUpdates();
806
+ await this.assembleWeb(platform);
807
+ const { api, config } = await this.prepare(platform, server);
808
+ return await api.publishUpdate({
809
+ ...this.#task(platform, config),
810
+ platform,
811
+ out: this.updatesDir,
812
+ ...channel ? { channel } : {},
813
+ ...platform === "macos" ? { macos: api.macosDistributionFromEnv(process.env) } : {},
814
+ ...platform === "windows" ? { windows: NativeApp.#windowsSigning(api) } : {}
815
+ });
816
+ }
817
+ async assertPublishable(platform, { channel, server = false } = {}) {
818
+ this.#assertUpdates();
819
+ const [api, { config }] = await Promise.all([
820
+ NativeApi.load(this.app.cwdPath),
821
+ this.config({ platform, stageBin: false })
822
+ ]);
823
+ this.#assertValid(api, config);
824
+ try {
825
+ api.checkPublishUpdate({
826
+ appDir: this.app.cwdPath,
827
+ config,
828
+ platform,
829
+ out: this.updatesDir,
830
+ server,
831
+ ...channel ? { channel } : {}
832
+ });
833
+ } catch (error) {
834
+ throw new Error(`Native target '${this.target.name}': ${error instanceof Error ? error.message : String(error)}`, {
835
+ cause: error
836
+ });
837
+ }
838
+ }
839
+ #assertUpdates() {
840
+ if (!this.target.config.updates)
841
+ throw new Error(`Native target '${this.target.name}' has no updates: add native.updates: { url, publicKey } to akan.config.ts; \`akan update-keygen ${this.app.name}\` prints the key.`);
842
+ }
843
+ async updateKeygen(platform) {
844
+ const [api, { config }] = await Promise.all([
845
+ NativeApi.load(this.app.cwdPath),
846
+ this.config({ platform, stageBin: false })
847
+ ]);
848
+ return api.updateKeygen({ config });
849
+ }
850
+ async releaseAndroid({ formats = ["aab"] } = {}) {
851
+ const signing = NativeApp.androidSigning();
852
+ await this.assembleWeb("android");
853
+ const { api, config } = await this.prepare("android");
854
+ return await api.release({ ...this.#task("android", config), platform: "android", signing, formats });
855
+ }
856
+ async packUpdate(platform, { out } = {}) {
857
+ await this.assembleWeb(platform);
858
+ const { api, config } = await this.prepare(platform);
859
+ if (!config.updates)
860
+ throw new Error(`Native target '${this.target.name}' takes no updates: add native.updates: { url, publicKey } to akan.config.ts first.`);
861
+ const packed = await api.packUpdate({
862
+ ...this.#task(platform, config),
863
+ platform,
864
+ out: out ?? path6.join(this.targetRoot, "updates", platform)
865
+ });
866
+ return { ...packed, channel: config.updates.channel ?? "production" };
867
+ }
868
+ static async compareBundles(appDir, shipped, bundle) {
869
+ return (await NativeApi.load(appDir)).compareBundles(shipped, bundle);
870
+ }
871
+ static androidSigning(env = process.env) {
872
+ const keystore = env.MYAPP_RELEASE_STORE_FILE;
873
+ const storePassword = env.MYAPP_RELEASE_STORE_PASSWORD;
874
+ const alias = env.MYAPP_RELEASE_KEY_ALIAS;
875
+ const keyPassword = env.MYAPP_RELEASE_KEY_PASSWORD;
876
+ if (!keystore || !storePassword || !alias) {
877
+ const missing = Object.entries({
878
+ MYAPP_RELEASE_STORE_FILE: keystore,
879
+ MYAPP_RELEASE_STORE_PASSWORD: storePassword,
880
+ MYAPP_RELEASE_KEY_ALIAS: alias
881
+ }).flatMap(([key, value]) => value ? [] : [key]);
882
+ throw new Error(`An Android release is signed with the upload key; set ${missing.join(", ")} in the environment.`);
883
+ }
884
+ return { keystore, storePassword, alias, ...keyPassword ? { keyPassword } : {} };
885
+ }
886
+ startPath(lang, platform) {
887
+ const basePath = this.target.config.basePath?.replace(/^\/+|\/+$/g, "");
888
+ const indexPath = this.indexPath(platform);
889
+ const params = new URLSearchParams({ csr: "true", akanMobileTarget: this.target.name });
890
+ if (basePath)
891
+ params.set("akanMobileBasePath", basePath);
892
+ if (indexPath)
893
+ params.set("akanMobileIndexPath", indexPath);
894
+ const home = [lang, basePath, indexPath].flatMap((part) => (part ?? "").split("/")).filter((segment) => segment.length > 0);
895
+ return `/${home.join("/")}?${params}`;
896
+ }
897
+ static #windowsSigning(api) {
898
+ const signing = api.windowsSigningFromEnv(process.env);
899
+ return signing ? { signing } : {};
900
+ }
901
+ static assertInstaller(platform, installer) {
902
+ if (installer && (platform === "ios" || platform === "android"))
903
+ throw new Error(`An installer is built for a desktop app (Windows, macOS, Linux), not for ${platform}.`);
904
+ }
905
+ static #assertServerPlatform(platform, server) {
906
+ if (server && (platform === "ios" || platform === "android"))
907
+ throw new Error(`Only a desktop app carries its server; ${platform} cannot run one.`);
908
+ }
909
+ static desktopPlatform(host = process.platform) {
910
+ if (host === "darwin")
911
+ return "macos";
912
+ if (host === "win32")
913
+ return "windows";
914
+ if (host === "linux")
915
+ return "linux";
916
+ throw new Error(`A desktop app builds on macOS, Windows or Linux, not on ${host}.`);
917
+ }
918
+ static async devices(appDir, platform) {
919
+ return await (await NativeApi.load(appDir)).devices({ platform });
920
+ }
921
+ static async doctor(appDir, platforms) {
922
+ return await (await NativeApi.load(appDir)).doctor({ platforms });
923
+ }
924
+ }
925
+
926
+ // pkgs/@akanjs/devkit/mobile/placeholderAppId.ts
927
+ var PLACEHOLDER_APP_IDS = [
928
+ "com.myapp.app",
929
+ "com.myorg.myapp",
930
+ "com.example.app",
931
+ "com.example.myapp"
932
+ ];
933
+ var placeholderAppIdSegment = /^(example|examples|myorg|myapp|mycompany|myorganization|changeme|todo|sample|test)$/;
934
+ var isPlaceholderAppId = (appId) => {
935
+ const normalized = appId?.trim().toLowerCase() ?? "";
936
+ if (!normalized)
937
+ return true;
938
+ if (PLACEHOLDER_APP_IDS.includes(normalized))
939
+ return true;
940
+ return normalized.split(".").some((segment) => placeholderAppIdSegment.test(segment));
941
+ };
942
+ // pkgs/@akanjs/devkit/mobile/desktopServerStage.ts
943
+ import { cp as cp2, mkdir as mkdir3, readdir as readdir2, rm as rm3, writeFile as writeFile2 } from "fs/promises";
944
+ import path7 from "path";
945
+ class DesktopServerStage {
946
+ app;
947
+ static uncarried = new Set([
948
+ "Dockerfile",
949
+ "package.json",
950
+ "rscWorker.js",
951
+ "console.js",
952
+ "console-runtime.js",
953
+ "csr",
954
+ "public",
955
+ ".akan"
956
+ ]);
957
+ static rscRenderer = "react-server-dom-webpack";
958
+ dir;
959
+ constructor(app) {
960
+ this.app = app;
961
+ this.dir = path7.join(app.cwdPath, ".akan", "desktop", "server");
962
+ }
963
+ static assertCarriable(config) {
964
+ if (!config.database.modes.includes("single"))
965
+ throw new Error(`desktop.server in native puts the server in the app, where only database mode single runs (no Redis or Postgres); apps/${config.app.name}/akan.config.ts declares ${config.database.modes.join(", ")}.`);
966
+ }
967
+ static env(config, environment) {
968
+ const { PORT: _port, AKAN_LOG_TO_FILE: _fileLog, ...image } = config.getProductionEnv(environment);
969
+ return {
970
+ ...image,
971
+ AKAN_PUBLIC_OPERATION_MODE: "edge",
972
+ AKAN_DATABASE_MODE: "single",
973
+ AKAN_DATABASE_MODES: "single",
974
+ AKAN_SSR: "false",
975
+ AKAN_CSR: "false",
976
+ AKAN_MCP: "false",
977
+ AKAN_SHUTDOWN_TIMEOUT_MS: "1000"
978
+ };
979
+ }
980
+ static imageStepsNotice(config, carried) {
981
+ const steps = typeof config.docker === "string" ? null : config.docker.preRuns.length + config.docker.postRuns.length;
982
+ if (steps === 0)
983
+ return null;
984
+ const image = steps === null ? `apps/${config.app.name}/akan.config.ts writes its own Dockerfile` : `The image runs ${steps} docker step${steps === 1 ? "" : "s"} from the app and its libs`;
985
+ return carried.length ? {
986
+ level: "info",
987
+ message: `${image}; the desktop app's server runs none of them and carries only bin: ${carried.join(", ")}.`
988
+ } : {
989
+ level: "warn",
990
+ message: `${image}, and a desktop app's server runs none of them: declare in bin the executables its code spawns, or those calls fail on the user's computer.`
991
+ };
992
+ }
993
+ static packageJson(config, built, omit = []) {
994
+ const otherDrivers = new Set(config.database.modes.filter((mode) => mode !== "single").flatMap((mode) => config.getDatabaseModeRuntimePackages(mode)));
995
+ const dependencies = Object.entries(built.dependencies ?? {}).filter(([name]) => !omit.includes(name) && (config.externalLibs.includes(name) || name !== DesktopServerStage.rscRenderer && !otherDrivers.has(name)));
996
+ return { ...built, dependencies: Object.fromEntries(dependencies) };
997
+ }
998
+ static async dependentsOf(nodeModules, name) {
999
+ const dependents = new Set;
1000
+ for await (const manifest of new Bun.Glob("**/package.json").scan({ cwd: nodeModules, onlyFiles: true })) {
1001
+ const json = await Bun.file(path7.join(nodeModules, manifest)).json().catch(() => null);
1002
+ if (!json?.name || json.name === name)
1003
+ continue;
1004
+ const declared = { ...json.dependencies, ...json.optionalDependencies, ...json.peerDependencies };
1005
+ if (name in declared)
1006
+ dependents.add(json.name);
1007
+ }
1008
+ return [...dependents].sort();
1009
+ }
1010
+ static installArgs(arch) {
1011
+ return ["install", "--production", "--prefer-offline", ...arch && arch !== process.arch ? [`--cpu=${arch}`] : []];
1012
+ }
1013
+ async install(arch, omit = []) {
1014
+ const config = await this.app.getConfig();
1015
+ DesktopServerStage.assertCarriable(config);
1016
+ await rm3(this.dir, { recursive: true, force: true });
1017
+ await mkdir3(this.dir, { recursive: true });
1018
+ const built = config.getProductionPackageJson();
1019
+ for (const name of omit.filter((name) => !(name in (built.dependencies ?? {}))))
1020
+ this.app.logger.warn(`desktop.server.omit names ${name}, which the server does not depend on directly.`);
1021
+ await writeFile2(path7.join(this.dir, "package.json"), JSON.stringify(DesktopServerStage.packageJson(config, built, omit), null, 2));
1022
+ try {
1023
+ await this.app.spawn(process.execPath, DesktopServerStage.installArgs(arch), { cwd: this.dir });
1024
+ } catch (error) {
1025
+ throw new Error(`The desktop app's server could not install its packages in ${this.dir}: it needs the npm registry, or a Bun cache that already holds every one of them.
1026
+ ${error instanceof Error ? error.message : String(error)}`, { cause: error });
1027
+ }
1028
+ await rm3(path7.join(this.dir, "node_modules", ".bin"), { recursive: true, force: true });
1029
+ await this.#assertOmitted(omit);
1030
+ }
1031
+ async#assertOmitted(omit) {
1032
+ const nodeModules = path7.join(this.dir, "node_modules");
1033
+ const kept = [];
1034
+ for (const name of omit) {
1035
+ if (!await Bun.file(path7.join(nodeModules, name, "package.json")).exists())
1036
+ continue;
1037
+ const dependents = await DesktopServerStage.dependentsOf(nodeModules, name);
1038
+ kept.push(`${name} (needed by ${dependents.join(", ") || "a package that names it"})`);
1039
+ }
1040
+ if (kept.length)
1041
+ throw new Error(`desktop.server.omit leaves out packages another dependency still installs: ${kept.join("; ")}. Omit those too, or keep the package.`);
1042
+ }
1043
+ async prepare(environment) {
1044
+ const config = await this.app.getConfig();
1045
+ const dist = this.app.dist.cwdPath;
1046
+ await mkdir3(this.dir, { recursive: true });
1047
+ for (const name of await readdir2(dist))
1048
+ if (!DesktopServerStage.uncarried.has(name))
1049
+ await cp2(path7.join(dist, name), path7.join(this.dir, name), { recursive: true });
1050
+ const scanInfo = this.app.getScanInfo({ allowEmpty: true }) ?? await this.app.scan({ write: false });
1051
+ const carried = [...DesktopBin.select(config, scanInfo.getLibs()).keys()];
1052
+ const notice = DesktopServerStage.imageStepsNotice(config, carried);
1053
+ if (notice)
1054
+ this.app.logger[notice.level](notice.message);
1055
+ return { dir: this.dir, entry: "main.js", env: DesktopServerStage.env(config, environment) };
1056
+ }
1057
+ }
1058
+ export { DesktopServerStage, getMobileTargets, getMobileTargetChoices, resolveMobileTargets, resolveAppId, appIdsOf, NativeApp, isPlaceholderAppId };