@akanjs/devkit 3.0.0-beta.9 → 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 (341) hide show
  1. package/DEV_RUNTIME_KNOBS.md +44 -26
  2. package/README.ko.md +21 -7
  3. package/README.md +17 -8
  4. package/abstractDoc.test.ts +6 -15
  5. package/abstractDoc.ts +6 -8
  6. package/agentsIndex.test.ts +4 -7
  7. package/agentsIndex.ts +6 -22
  8. package/akanApp/BackendImportGraph.test.ts +8 -22
  9. package/akanApp/BackendImportGraph.ts +1 -20
  10. package/akanApp/akanApp.host.test.ts +56 -3
  11. package/akanApp/akanApp.host.ts +218 -278
  12. package/akanApp/devBootLatch.test.ts +39 -0
  13. package/akanApp/devBootLatch.ts +39 -0
  14. package/akanApp/devHostPolicy.test.ts +81 -35
  15. package/akanApp/devHostPolicy.ts +41 -102
  16. package/akanConfig/akanBin.test.ts +62 -0
  17. package/akanConfig/akanBin.ts +78 -0
  18. package/akanConfig/akanConfig.test.ts +439 -141
  19. package/akanConfig/akanConfig.ts +359 -275
  20. package/akanConfig/index.ts +1 -0
  21. package/akanConfig/types.ts +22 -8
  22. package/akanContext.ts +110 -160
  23. package/akanMcpContract.ts +28 -73
  24. package/appSelectionMemory.ts +1 -4
  25. package/applicationBuildReporter.ts +37 -5
  26. package/applicationBuildRunner.test.ts +41 -5
  27. package/applicationBuildRunner.ts +71 -63
  28. package/applicationTestPreload.ts +3 -12
  29. package/artifact/implicitRootLayout.test.ts +32 -30
  30. package/artifact/implicitRootLayout.ts +58 -106
  31. package/artifact/routeSeedIndex.test.ts +7 -6
  32. package/artifact/routeSeedIndex.ts +11 -44
  33. package/biome.base.json +35 -2
  34. package/biomeBase.ts +2 -5
  35. package/cloud/cloudApi.test.ts +202 -0
  36. package/cloud/cloudApi.ts +77 -39
  37. package/cloud/configLock.test.ts +64 -0
  38. package/cloud/configLock.ts +96 -0
  39. package/cloud/constants.ts +26 -5
  40. package/cloud/globalConfig.ts +71 -32
  41. package/codeAgent/agent/AkanCodeServices.ts +97 -0
  42. package/codeAgent/agent/AkanContextFiles.ts +94 -0
  43. package/codeAgent/agent/CodeAgent.ts +551 -0
  44. package/codeAgent/agent/CodeAgentAsks.ts +66 -0
  45. package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
  46. package/codeAgent/agent/CodeAgentGate.ts +93 -0
  47. package/codeAgent/agent/CodeAgentProxy.ts +82 -0
  48. package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
  49. package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
  50. package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
  51. package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
  52. package/codeAgent/agent/CodeAgentUi.ts +47 -0
  53. package/codeAgent/agent/CodeMailbox.ts +148 -0
  54. package/codeAgent/agent/CodeSessionFork.ts +56 -0
  55. package/codeAgent/agent/CodeSessionIndex.ts +78 -0
  56. package/codeAgent/agent/SubagentPool.ts +214 -0
  57. package/codeAgent/agent/akanCodeModel.ts +47 -0
  58. package/codeAgent/agent/akanCodePaths.ts +31 -0
  59. package/codeAgent/agent/akanContextFiles.test.ts +114 -0
  60. package/codeAgent/agent/akanSystemPrompt.ts +46 -0
  61. package/codeAgent/agent/codeAgent.test.ts +178 -0
  62. package/codeAgent/agent/codeAgentContract.test.ts +523 -0
  63. package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
  64. package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
  65. package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
  66. package/codeAgent/agent/codeMailbox.test.ts +76 -0
  67. package/codeAgent/agent/codeSessionFork.test.ts +58 -0
  68. package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
  69. package/codeAgent/agent/consoleToStderr.ts +8 -0
  70. package/codeAgent/agent/jsonLines.ts +29 -0
  71. package/codeAgent/agent/stringArg.ts +5 -0
  72. package/codeAgent/feedback/DevLogFeedback.ts +82 -0
  73. package/codeAgent/feedback/PreviewChrome.ts +82 -0
  74. package/codeAgent/feedback/PreviewFeedback.ts +46 -0
  75. package/codeAgent/feedback/PreviewView.ts +95 -0
  76. package/codeAgent/feedback/TurnFeedback.ts +98 -0
  77. package/codeAgent/feedback/VerifyFeedback.ts +40 -0
  78. package/codeAgent/feedback/previewChrome.test.ts +73 -0
  79. package/codeAgent/index.ts +34 -0
  80. package/codeAgent/skills/akan-code/SKILL.md +130 -0
  81. package/codeAgent/skills/akan-debug/SKILL.md +42 -0
  82. package/codeAgent/skills/akan-deps/SKILL.md +50 -0
  83. package/codeAgent/skills/akan-err/SKILL.md +39 -0
  84. package/codeAgent/skills/akan-module/SKILL.md +45 -0
  85. package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
  86. package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
  87. package/codeAgent/skills/akan-service/SKILL.md +34 -0
  88. package/codeAgent/skills/akan-store/SKILL.md +67 -0
  89. package/codeAgent/skills/akan-ui/SKILL.md +63 -0
  90. package/codeAgent/skills/akan-validate/SKILL.md +44 -0
  91. package/codeAgent/tools/AkanCli.ts +50 -0
  92. package/codeAgent/tools/AkanCodePlugins.ts +136 -0
  93. package/codeAgent/tools/AkanEditScope.ts +48 -0
  94. package/codeAgent/tools/AkanEnvKeys.ts +50 -0
  95. package/codeAgent/tools/AkanToolPack.ts +92 -0
  96. package/codeAgent/tools/AkanVerifier.ts +48 -0
  97. package/codeAgent/tools/AskToolPack.ts +99 -0
  98. package/codeAgent/tools/MailToolPack.ts +76 -0
  99. package/codeAgent/tools/McpClient.ts +187 -0
  100. package/codeAgent/tools/McpOAuth.ts +233 -0
  101. package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
  102. package/codeAgent/tools/McpServerConfig.ts +129 -0
  103. package/codeAgent/tools/McpSignIn.ts +158 -0
  104. package/codeAgent/tools/McpTokenStore.ts +81 -0
  105. package/codeAgent/tools/McpToolPack.ts +199 -0
  106. package/codeAgent/tools/SessionToolPack.ts +91 -0
  107. package/codeAgent/tools/WebToolPack.ts +147 -0
  108. package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
  109. package/codeAgent/tools/askToolPack.test.ts +116 -0
  110. package/codeAgent/tools/mcpOAuth.test.ts +612 -0
  111. package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
  112. package/codegenLock.test.ts +4 -13
  113. package/codegenLock.ts +3 -17
  114. package/commandDecorators/argMeta.ts +1 -5
  115. package/commandDecorators/camelToKebabCase.ts +1 -0
  116. package/commandDecorators/command.ts +46 -102
  117. package/commandDecorators/commandBuilder.ts +30 -61
  118. package/commandDecorators/commandDecorators.test.ts +2 -10
  119. package/commandDecorators/dependencyBuilder.ts +10 -1
  120. package/commandDecorators/helpFormatter.ts +68 -145
  121. package/commandDecorators/index.ts +0 -2
  122. package/commandDecorators/targetMeta.ts +9 -7
  123. package/createTunnel.ts +7 -14
  124. package/csrE2e/csrE2eHarness.fixture.ts +505 -0
  125. package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
  126. package/csrE2e/csrPageStack.e2e.test.ts +252 -0
  127. package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
  128. package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
  129. package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
  130. package/csrE2e/registryVendor.e2e.test.ts +221 -0
  131. package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
  132. package/dependencyScanner.test.ts +4 -16
  133. package/dependencyScanner.ts +31 -80
  134. package/devkitUtils.test.ts +48 -20
  135. package/executors.linkFallback.test.ts +103 -0
  136. package/executors.test.ts +176 -106
  137. package/executors.ts +212 -498
  138. package/fileEditor.ts +9 -37
  139. package/{getDirname.test.ts → fileSys.test.ts} +5 -3
  140. package/fileSys.ts +22 -9
  141. package/formSetterScanner.ts +3 -16
  142. package/frontendBuild/allRoutesBuilder.ts +7 -21
  143. package/frontendBuild/autoImportSync.test.ts +27 -38
  144. package/frontendBuild/autoImportSync.ts +31 -65
  145. package/frontendBuild/buildRouteClient.test.ts +123 -37
  146. package/frontendBuild/bundleDefine.test.ts +12 -0
  147. package/frontendBuild/bundleDefine.ts +16 -0
  148. package/frontendBuild/clientBuildTypes.ts +5 -36
  149. package/frontendBuild/clientEntriesBundler.ts +9 -40
  150. package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
  151. package/frontendBuild/clientEntryDiscovery.ts +122 -110
  152. package/frontendBuild/csrArtifactBuilder.ts +9 -31
  153. package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
  154. package/frontendBuild/csrDevArtifactWriter.ts +307 -0
  155. package/frontendBuild/csrDevBundler.test.ts +66 -0
  156. package/frontendBuild/csrDevBundler.ts +95 -0
  157. package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
  158. package/frontendBuild/csrDevFactoryCheck.ts +39 -0
  159. package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
  160. package/frontendBuild/csrDevModuleCompiler.ts +422 -0
  161. package/frontendBuild/csrDevPatcher.test.ts +572 -0
  162. package/frontendBuild/csrDevPatcher.ts +548 -0
  163. package/frontendBuild/csrDevPaths.ts +145 -0
  164. package/frontendBuild/csrDevResolver.test.ts +239 -0
  165. package/frontendBuild/csrDevResolver.ts +363 -0
  166. package/frontendBuild/csrDevTypes.ts +61 -0
  167. package/frontendBuild/csrEntryFiles.ts +24 -0
  168. package/frontendBuild/cssCandidateCache.test.ts +0 -6
  169. package/frontendBuild/cssCandidateCache.ts +6 -41
  170. package/frontendBuild/cssCompiler.ts +31 -82
  171. package/frontendBuild/cssImportResolver.ts +6 -19
  172. package/frontendBuild/devChangePlanner.ts +7 -10
  173. package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
  174. package/frontendBuild/devGeneratedIndexSync.ts +1 -4
  175. package/frontendBuild/devRegistryBundler.ts +192 -0
  176. package/frontendBuild/fontOptimizer.test.ts +6 -14
  177. package/frontendBuild/fontOptimizer.ts +15 -72
  178. package/frontendBuild/fontPruner.test.ts +5 -10
  179. package/frontendBuild/fontPruner.ts +5 -20
  180. package/frontendBuild/frontendBuild.test.ts +57 -70
  181. package/frontendBuild/hmrChangeClassifier.ts +2 -2
  182. package/frontendBuild/hmrWatcher.test.ts +59 -32
  183. package/frontendBuild/hmrWatcher.ts +31 -74
  184. package/frontendBuild/index.ts +4 -0
  185. package/frontendBuild/pagesBundleBuilder.ts +28 -30
  186. package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
  187. package/frontendBuild/precompressArtifacts.ts +5 -18
  188. package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
  189. package/frontendBuild/registryMetadataFingerprint.ts +28 -0
  190. package/frontendBuild/routeClientBuilder.ts +100 -62
  191. package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
  192. package/frontendBuild/serverGraphFile.test.ts +77 -0
  193. package/frontendBuild/serverGraphFile.ts +93 -0
  194. package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
  195. package/frontendBuild/sourceMtimeIndex.ts +19 -130
  196. package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
  197. package/frontendBuild/ssrDevBundler.ts +85 -0
  198. package/frontendBuild/styleContract.ts +1 -8
  199. package/frontendBuild/themeValidator.ts +4 -26
  200. package/frontendBuild/watchRootResolver.test.ts +23 -42
  201. package/frontendBuild/watchRootResolver.ts +5 -27
  202. package/getRelatedCnsts.ts +12 -92
  203. package/incrementalBuilder/buildBatch.proc.ts +147 -63
  204. package/incrementalBuilder/buildBatchProtocol.ts +13 -38
  205. package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
  206. package/incrementalBuilder/buildBatchRunner.ts +26 -38
  207. package/incrementalBuilder/builderChannel.test.ts +24 -49
  208. package/incrementalBuilder/builderChannel.ts +6 -37
  209. package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
  210. package/incrementalBuilder/builderRequestRouter.ts +5 -24
  211. package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
  212. package/incrementalBuilder/builderWorkQueue.ts +126 -0
  213. package/incrementalBuilder/devWatchBatch.test.ts +21 -24
  214. package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
  215. package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
  216. package/incrementalBuilder/incrementalBuilder.proc.test.ts +270 -0
  217. package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
  218. package/index.ts +5 -19
  219. package/integration/devResourceProbe.ts +50 -56
  220. package/integration/devStability.integration.test.ts +105 -199
  221. package/integration/devStabilityHarness.test.ts +13 -28
  222. package/integration/devStabilityHarness.ts +65 -330
  223. package/integration/ssrMemoryProbe.ts +41 -167
  224. package/libSource.test.ts +4 -15
  225. package/libSource.ts +19 -31
  226. package/lint/__fixtures__/no-document-cookie/bad.tsx +4 -0
  227. package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
  228. package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
  229. package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
  230. package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
  231. package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
  232. package/lint/gritRules.test.ts +8 -24
  233. package/lint/no-deep-internal-import.grit +2 -2
  234. package/lint/no-document-cookie.grit +29 -0
  235. package/lint/no-redeclare-predefined-endpoint.grit +2 -1
  236. package/lint/no-web-only-api-outside-webkit.grit +44 -0
  237. package/lint/no-web-storage.grit +30 -0
  238. package/linter.test.ts +1 -2
  239. package/linter.ts +26 -116
  240. package/mobile/desktopBin.test.ts +182 -0
  241. package/mobile/desktopBin.ts +144 -0
  242. package/mobile/desktopServerStage.test.ts +252 -0
  243. package/mobile/desktopServerStage.ts +178 -0
  244. package/mobile/index.ts +9 -0
  245. package/mobile/mobileTarget.test.ts +57 -8
  246. package/mobile/mobileTarget.ts +45 -26
  247. package/mobile/nativeApi.test.ts +30 -0
  248. package/mobile/nativeApi.ts +40 -0
  249. package/mobile/nativeApp.test.ts +184 -0
  250. package/mobile/nativeApp.ts +454 -0
  251. package/mobile/nativeAppLine.test.ts +69 -0
  252. package/mobile/nativeAppLine.ts +71 -0
  253. package/mobile/nativeConfig.test.ts +438 -0
  254. package/mobile/nativeConfig.ts +294 -0
  255. package/mobile/nativePluginFolders.test.ts +81 -0
  256. package/mobile/nativePluginFolders.ts +60 -0
  257. package/mobile/nativeWebDir.test.ts +80 -0
  258. package/mobile/nativeWebDir.ts +66 -0
  259. package/mobile/placeholderAppId.test.ts +13 -0
  260. package/mobile/placeholderAppId.ts +16 -0
  261. package/mobile/usageDescriptions.test.ts +27 -0
  262. package/mobile/usageDescriptions.ts +24 -0
  263. package/package.json +11 -11
  264. package/packageExports.test.ts +3 -18
  265. package/packageExportsMap.ts +3 -19
  266. package/piDependencyPins.test.ts +46 -0
  267. package/platformTest/BunTestReport.test.ts +60 -0
  268. package/platformTest/BunTestReport.ts +65 -0
  269. package/platformTest/IdleWatchedProcess.test.ts +56 -0
  270. package/platformTest/IdleWatchedProcess.ts +73 -0
  271. package/platformTest/LinuxDockerTarget.ts +105 -0
  272. package/platformTest/PlatformTestRun.test.ts +64 -0
  273. package/platformTest/PlatformTestRun.ts +141 -0
  274. package/platformTest/PlatformTestTarget.ts +142 -0
  275. package/platformTest/TestSnapshot.test.ts +72 -0
  276. package/platformTest/TestSnapshot.ts +103 -0
  277. package/platformTest/WindowsSshTarget.ts +151 -0
  278. package/prompter.ts +3 -13
  279. package/qualityScanner.test.ts +9 -26
  280. package/qualityScanner.ts +200 -286
  281. package/recipeScanner.test.ts +7 -12
  282. package/recipeScanner.ts +9 -38
  283. package/repoIdentity.ts +2 -9
  284. package/routeSourceValidator.test.ts +0 -1
  285. package/routeSourceValidator.ts +7 -42
  286. package/scanInfo.ts +91 -225
  287. package/selectModel.ts +1 -2
  288. package/semver.ts +3 -5
  289. package/slicePlanner.test.ts +6 -24
  290. package/slicePlanner.ts +11 -32
  291. package/spinner.test.ts +3 -8
  292. package/spinner.ts +4 -16
  293. package/sshTunnel.ts +1 -6
  294. package/ssrScanner.test.ts +1 -6
  295. package/ssrScanner.ts +103 -113
  296. package/stripAnsi.ts +3 -0
  297. package/subspace.test.ts +9 -25
  298. package/subspace.ts +48 -115
  299. package/subspaceConfig.ts +3 -11
  300. package/testHelpers.ts +194 -0
  301. package/transforms/asyncDefaultExportDetector.ts +3 -11
  302. package/transforms/barrelAnalyzer.ts +43 -135
  303. package/transforms/barrelImportsPlugin.ts +38 -175
  304. package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
  305. package/transforms/externalizeFrameworkPlugin.ts +13 -87
  306. package/transforms/moduleSyntax.ts +32 -0
  307. package/transforms/rscUseClientTransform.ts +42 -20
  308. package/transforms/serverEnvPlugin.test.ts +103 -0
  309. package/transforms/serverEnvPlugin.ts +41 -0
  310. package/transforms/transforms.test.ts +166 -44
  311. package/transforms/tsconfigPackageResolver.test.ts +6 -24
  312. package/transforms/useClientBundlePlugin.ts +6 -23
  313. package/typeChecker.ts +7 -63
  314. package/types.ts +2 -0
  315. package/uploadRelease.ts +10 -25
  316. package/workflow/artifacts.ts +18 -0
  317. package/workflow/executor.test.ts +1 -12
  318. package/workflow/executor.ts +101 -110
  319. package/workflow/index.ts +0 -1
  320. package/workflow/moduleIndex.test.ts +4 -15
  321. package/workflow/moduleIndex.ts +9 -49
  322. package/workflow/plan.ts +18 -25
  323. package/workflow/render.ts +80 -96
  324. package/workflow/rolloutGate.test.ts +1 -1
  325. package/workflow/source.ts +52 -69
  326. package/workflow/sourceAst.ts +50 -0
  327. package/workspaceLayout.test.ts +18 -3
  328. package/workspaceLayout.ts +22 -23
  329. package/abstractCompactor.ts +0 -112
  330. package/aiEditor.test.ts +0 -60
  331. package/aiEditor.ts +0 -387
  332. package/applicationReleasePackager.ts +0 -209
  333. package/capacitorApp.test.ts +0 -372
  334. package/capacitorApp.ts +0 -1648
  335. package/commandDecorators/commandMeta.ts +0 -7
  336. package/commandDecorators/types.ts +0 -10
  337. package/getDirname.ts +0 -4
  338. package/guideline.ts +0 -19
  339. package/local/optimize-resource/ipcprobe/child.ts +0 -1
  340. package/local/optimize-resource/ipcprobe/parent.ts +0 -11
  341. package/workflow/primitive.ts +0 -59
@@ -0,0 +1,252 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { AkanAppConfig } from "../akanConfig";
5
+ import type { App } from "../commandDecorators";
6
+ import { tempDirs, writeText as write } from "../testHelpers";
7
+ import type { PackageJson } from "../types";
8
+ import { DesktopServerStage } from "./desktopServerStage";
9
+ import { NativeApi } from "./nativeApi";
10
+ import { NativeApp } from "./nativeApp";
11
+ import { NativeConfig } from "./nativeConfig";
12
+
13
+ const makeTempRoot = tempDirs("akan-desktop-server-");
14
+ const repoApp = path.resolve(import.meta.dir, "../../../../apps/minimal");
15
+ const baseDevEnv = {
16
+ repoName: "akanjs",
17
+ serveDomain: "akanjs.com",
18
+ env: "debug" as const,
19
+ portOffset: 0,
20
+ workspaceRoot: "/w",
21
+ };
22
+ const rootPackageJson: PackageJson = {
23
+ name: "repo",
24
+ version: "1.0.0",
25
+ description: "repo",
26
+ dependencies: { "@external/runtime": "2.0.0", pg: "8.0.0", rclnodejs: "1.0.0" },
27
+ };
28
+ const appConfig = (config: object = {}) =>
29
+ new AkanAppConfig({ name: "portal" } as never, [], rootPackageJson, config, baseDevEnv);
30
+
31
+ describe("DesktopServerStage", () => {
32
+ test("starts from the image's env, as an API-only edge server on SQLite that the launcher gives a port", () => {
33
+ const env = DesktopServerStage.env(appConfig({ database: { modes: ["single", "cluster"] } }), "main");
34
+
35
+ expect(env).toMatchObject({
36
+ NODE_ENV: "production",
37
+ AKAN_PUBLIC_APP_NAME: "portal",
38
+ AKAN_PUBLIC_ENV: "main",
39
+ AKAN_PUBLIC_OPERATION_MODE: "edge",
40
+ AKAN_DATABASE_MODE: "single",
41
+ AKAN_DATABASE_MODES: "single",
42
+ AKAN_SSR: "false",
43
+ AKAN_CSR: "false",
44
+ AKAN_MCP: "false",
45
+ AKAN_SHUTDOWN_TIMEOUT_MS: "1000",
46
+ });
47
+ expect(env.PORT).toBeUndefined();
48
+ expect(env.AKAN_LOG_TO_FILE).toBeUndefined();
49
+ });
50
+
51
+ test("installs what the image does, minus the RSC renderer and the other modes' drivers", () => {
52
+ const config = appConfig({ database: { modes: ["single", "cluster"] }, externalLibs: ["@external/runtime"] });
53
+ const built = config.getProductionPackageJson();
54
+ const drivers = config.getDatabaseModeRuntimePackages("cluster");
55
+
56
+ const { dependencies } = DesktopServerStage.packageJson(config, built);
57
+
58
+ expect(Object.keys(dependencies ?? {}).sort()).toEqual(["@external/runtime", "react", "react-dom"]);
59
+ for (const driver of drivers) expect(Object.keys(built.dependencies ?? {})).toContain(driver);
60
+ });
61
+
62
+ test("leaves out what desktop.server.omit names, an external lib included, while the image keeps it", () => {
63
+ const config = appConfig({ externalLibs: ["rclnodejs", "@external/runtime"] });
64
+ const built = config.getProductionPackageJson();
65
+
66
+ const { dependencies } = DesktopServerStage.packageJson(config, built, ["rclnodejs"]);
67
+
68
+ expect(Object.keys(dependencies ?? {})).toContain("@external/runtime");
69
+ expect(Object.keys(dependencies ?? {})).not.toContain("rclnodejs");
70
+ expect(Object.keys(config.getProductionPackageJson().dependencies ?? {})).toContain("rclnodejs");
71
+ });
72
+
73
+ test("keeps the image's trusted packages, so their install scripts run in the app's server too", () => {
74
+ const config = appConfig({ trustedDependencies: ["@external/runtime"], externalLibs: ["@external/runtime"] });
75
+ expect(DesktopServerStage.packageJson(config, config.getProductionPackageJson()).trustedDependencies).toEqual([
76
+ "@external/runtime",
77
+ ]);
78
+ });
79
+
80
+ test("says what the image installs that the desktop app's server goes without", () => {
81
+ const notice = (docker: unknown, carried: string[] = []) =>
82
+ DesktopServerStage.imageStepsNotice({ app: { name: "portal" }, docker } as never, carried);
83
+ const steps = { image: "oven/bun", preRuns: ["apt-get install -y ffmpeg"], postRuns: [], command: [] };
84
+ expect(notice({ ...steps, preRuns: [] })).toBeNull();
85
+ expect(notice(steps)).toEqual({
86
+ level: "warn",
87
+ message: expect.stringContaining(
88
+ "The image runs 1 docker step from the app and its libs, and a desktop app's server runs none of them",
89
+ ),
90
+ });
91
+ expect(notice("FROM ros:humble-ros-core")?.message).toStartWith(
92
+ "apps/portal/akan.config.ts writes its own Dockerfile",
93
+ );
94
+ expect(notice(steps, ["ffmpeg"])).toEqual({
95
+ level: "info",
96
+ message: expect.stringContaining("carries only bin: ffmpeg"),
97
+ });
98
+ });
99
+
100
+ const stageApp = async (spawn: (...args: unknown[]) => Promise<string> = async () => "") => {
101
+ const root = await makeTempRoot();
102
+ const dist = path.join(root, "dist/apps/portal");
103
+ const config = appConfig();
104
+ const app = {
105
+ cwdPath: path.join(root, "apps/portal"),
106
+ dist: { cwdPath: dist },
107
+ getConfig: async () => config,
108
+ getScanInfo: () => ({ getLibs: () => [] }),
109
+ logger: { info: () => undefined, warn: () => undefined },
110
+ spawn,
111
+ } as unknown as App;
112
+ return { dist, app, stage: new DesktopServerStage(app) };
113
+ };
114
+
115
+ test("carries everything the backend build wrote beside main.js but what only the image reads", async () => {
116
+ const { dist, stage } = await stageApp();
117
+ const carried = [
118
+ "main.js",
119
+ "server.js",
120
+ "chunk-5eap2n9b.js",
121
+ "akan.build.json",
122
+ "better-sqlite3-a1b2c3d4.node",
123
+ "engine-e5f6a7b8.wasm",
124
+ "model-c9d0e1f2.bin",
125
+ "private/service-account.json",
126
+ ];
127
+ const imageOnly = [
128
+ "Dockerfile",
129
+ "package.json",
130
+ "rscWorker.js",
131
+ "console.js",
132
+ "console-runtime.js",
133
+ "csr/index.html",
134
+ "public/favicon.ico",
135
+ ".akan/artifact/base-artifact.json",
136
+ ];
137
+ for (const file of [...carried, ...imageOnly]) await write(path.join(dist, file), file);
138
+
139
+ await stage.prepare("main");
140
+
141
+ const staged = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: stage.dir, dot: true }));
142
+ expect(staged.map((file) => file.split(path.sep).join("/")).sort()).toEqual(carried.sort());
143
+ expect(await Bun.file(path.join(stage.dir, "better-sqlite3-a1b2c3d4.node")).text()).toBe(
144
+ "better-sqlite3-a1b2c3d4.node",
145
+ );
146
+ });
147
+
148
+ test("installs from the config alone, offline when Bun's cache holds the packages", async () => {
149
+ const spawned: unknown[][] = [];
150
+ const { stage } = await stageApp(async (...args) => {
151
+ spawned.push(args);
152
+ return "";
153
+ });
154
+
155
+ await stage.install();
156
+
157
+ expect(spawned).toEqual([[process.execPath, ["install", "--production", "--prefer-offline"], { cwd: stage.dir }]]);
158
+ const installed = (await Bun.file(path.join(stage.dir, "package.json")).json()) as PackageJson;
159
+ expect(Object.keys(installed.dependencies ?? {}).sort()).toEqual(["react", "react-dom"]);
160
+ });
161
+
162
+ test("an omitted package another dependency still installs stops the stage, naming who needs it", async () => {
163
+ const installing =
164
+ (pulled: boolean) =>
165
+ async (...args: unknown[]) => {
166
+ const nodeModules = path.join((args[2] as { cwd: string }).cwd, "node_modules");
167
+ await write(path.join(nodeModules, "react/package.json"), JSON.stringify({ name: "react" }));
168
+ if (pulled) {
169
+ await write(path.join(nodeModules, "rclnodejs/package.json"), JSON.stringify({ name: "rclnodejs" }));
170
+ await write(
171
+ path.join(nodeModules, "@robot/bridge/package.json"),
172
+ JSON.stringify({ name: "@robot/bridge", dependencies: { rclnodejs: "^1" } }),
173
+ );
174
+ }
175
+ return "";
176
+ };
177
+ await (await stageApp(installing(false))).stage.install(undefined, ["rclnodejs"]);
178
+ await expect((await stageApp(installing(true))).stage.install(undefined, ["rclnodejs"])).rejects.toThrow(
179
+ "another dependency still installs: rclnodejs (needed by @robot/bridge)",
180
+ );
181
+ });
182
+
183
+ test("installs another CPU's optional packages for that CPU", () => {
184
+ const other = process.arch === "arm64" ? "x64" : "arm64";
185
+ expect(DesktopServerStage.installArgs()).toEqual(["install", "--production", "--prefer-offline"]);
186
+ expect(DesktopServerStage.installArgs(process.arch as "arm64" | "x64")).toEqual([
187
+ "install",
188
+ "--production",
189
+ "--prefer-offline",
190
+ ]);
191
+ expect(DesktopServerStage.installArgs(other)).toEqual([
192
+ "install",
193
+ "--production",
194
+ "--prefer-offline",
195
+ `--cpu=${other}`,
196
+ ]);
197
+ });
198
+
199
+ test("an install that fails says it needs the registry or a Bun cache that holds the packages", async () => {
200
+ const { stage } = await stageApp(async () => {
201
+ throw new Error("error: GET https://registry.npmjs.org/scheduler - ConnectionRefused");
202
+ });
203
+
204
+ const failed = stage.install();
205
+
206
+ await expect(failed).rejects.toThrow(
207
+ "it needs the npm registry, or a Bun cache that already holds every one of them.\nerror: GET https://registry.npmjs.org/scheduler",
208
+ );
209
+ });
210
+
211
+ test("refuses an app whose database modes leave out single", () => {
212
+ expect(() => DesktopServerStage.assertCarriable(appConfig({ database: { modes: ["cluster"] } }))).toThrow(
213
+ "only database mode single runs",
214
+ );
215
+ expect(() =>
216
+ DesktopServerStage.assertCarriable(appConfig({ database: { modes: ["single", "cluster"] } })),
217
+ ).not.toThrow();
218
+ });
219
+
220
+ test("the runtime accepts a desktop config that carries the server, with single-instance on", async () => {
221
+ const root = await makeTempRoot();
222
+ await mkdir(path.join(root, "web"), { recursive: true });
223
+ await writeFile(path.join(root, "web/index.html"), "<html><head></head><body></body></html>");
224
+ await mkdir(path.join(root, "server"), { recursive: true });
225
+ await writeFile(path.join(root, "server/main.js"), "");
226
+ const server = { dir: path.join(root, "server"), entry: "main.js", env: { AKAN_PUBLIC_ENV: "main" } };
227
+
228
+ const { config } = NativeConfig.build({
229
+ appPath: root,
230
+ target: { name: "default", appName: "Portal", appId: "com.portal.app", version: "1.0.0", buildNum: 1 },
231
+ webDir: path.join(root, "web"),
232
+ contributions: [],
233
+ locales: ["en"],
234
+ platform: "macos",
235
+ desktopServer: server,
236
+ });
237
+
238
+ expect(config.plugins).toContain("single-instance");
239
+ expect(config.desktop).toEqual({ server });
240
+ expect((await NativeApi.load(repoApp)).validateConfig(config, { appDir: root })).toEqual([]);
241
+ });
242
+
243
+ test("a phone build cannot carry the server", async () => {
244
+ const app = new NativeApp({ cwdPath: "/repo/apps/portal" } as never, {
245
+ name: "default",
246
+ config: { name: "default", appName: "Portal", appId: "com.portal.app", version: "1.0.0", buildNum: 1 },
247
+ });
248
+ const server = { dir: "/tmp/server", entry: "main.js", env: {} };
249
+ await expect(app.build("ios", { server })).rejects.toThrow("Only a desktop app carries its server");
250
+ await expect(app.run("android", { server })).rejects.toThrow("Only a desktop app carries its server");
251
+ });
252
+ });
@@ -0,0 +1,178 @@
1
+ import { cp, mkdir, readdir, rm, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import type { AkanAppConfig, NativeEnv } from "../akanConfig";
4
+ import type { App } from "../commandDecorators";
5
+ import type { PackageJson } from "../types";
6
+ import { DesktopBin } from "./desktopBin";
7
+
8
+ /** What a desktop build hands the native runtime as `desktop.server`. */
9
+ export interface DesktopServerBundle {
10
+ dir: string;
11
+ entry: string;
12
+ env: Record<string, string>;
13
+ }
14
+
15
+ //* A desktop app's carried server is the backend `akan build` wrote into dist, installed on its own as the image's
16
+ //* `bun install --production` does. It is staged outside dist: installing there would put host packages into the
17
+ //* image's `COPY . .`.
18
+ export class DesktopServerStage {
19
+ //? An API-only server starts neither the RSC worker nor the akan console and serves no CSR, public/ or SSR artifact,
20
+ //? and `install` wrote its own package.json. The rest of dist is the server's: its chunks, a `.node`, a `.wasm`.
21
+ static readonly uncarried = new Set([
22
+ "Dockerfile",
23
+ "package.json",
24
+ "rscWorker.js",
25
+ "console.js",
26
+ "console-runtime.js",
27
+ "csr",
28
+ "public",
29
+ ".akan",
30
+ ]);
31
+ //? Only the RSC worker loads it, and its peer webpack makes 35 of the 43 MB an install would add.
32
+ static readonly rscRenderer = "react-server-dom-webpack";
33
+
34
+ readonly dir: string;
35
+
36
+ constructor(readonly app: App) {
37
+ this.dir = path.join(app.cwdPath, ".akan", "desktop", "server");
38
+ }
39
+
40
+ static assertCarriable(config: AkanAppConfig) {
41
+ if (!config.database.modes.includes("single"))
42
+ throw new Error(
43
+ `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(", ")}.`,
44
+ );
45
+ }
46
+
47
+ static env(config: AkanAppConfig, environment: NativeEnv): Record<string, string> {
48
+ //? The launcher picks the port, and the log file stays on: a user's computer has no log collector.
49
+ const { PORT: _port, AKAN_LOG_TO_FILE: _fileLog, ...image } = config.getProductionEnv(environment);
50
+ return {
51
+ ...image,
52
+ //? Neither the developer's machine (local, which derives a forgeable JWT secret) nor a deployment (cloud).
53
+ AKAN_PUBLIC_OPERATION_MODE: "edge",
54
+ AKAN_DATABASE_MODE: "single",
55
+ AKAN_DATABASE_MODES: "single",
56
+ //? API only: the shell serves the CSR bundle, and an RSC worker would need a bun on the user's PATH.
57
+ AKAN_SSR: "false",
58
+ AKAN_CSR: "false",
59
+ AKAN_MCP: "false",
60
+ //? Ahead of the launcher's 1.5 s grace before it signals, which sits inside the shell's 2 s quit budget.
61
+ AKAN_SHUTDOWN_TIMEOUT_MS: "1000",
62
+ };
63
+ }
64
+
65
+ //? The image's steps install into a Linux image the desktop app never runs in, so its server has none of it.
66
+ static imageStepsNotice(
67
+ config: Pick<AkanAppConfig, "app" | "docker">,
68
+ carried: string[],
69
+ ): { level: "warn" | "info"; message: string } | null {
70
+ const steps =
71
+ typeof config.docker === "string" ? null : config.docker.preRuns.length + config.docker.postRuns.length;
72
+ if (steps === 0) return null;
73
+ const image =
74
+ steps === null
75
+ ? `apps/${config.app.name}/akan.config.ts writes its own Dockerfile`
76
+ : `The image runs ${steps} docker step${steps === 1 ? "" : "s"} from the app and its libs`;
77
+ return carried.length
78
+ ? {
79
+ level: "info",
80
+ message: `${image}; the desktop app's server runs none of them and carries only bin: ${carried.join(", ")}.`,
81
+ }
82
+ : {
83
+ level: "warn",
84
+ 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.`,
85
+ };
86
+ }
87
+
88
+ //? `omit` (the target's `desktop.server.omit`) wins over externalLibs: the image keeps those packages, the app does not.
89
+ static packageJson(config: AkanAppConfig, built: PackageJson, omit: string[] = []): PackageJson {
90
+ const otherDrivers = new Set(
91
+ config.database.modes
92
+ .filter((mode) => mode !== "single")
93
+ .flatMap((mode) => config.getDatabaseModeRuntimePackages(mode)),
94
+ );
95
+ const dependencies = Object.entries(built.dependencies ?? {}).filter(
96
+ ([name]) =>
97
+ !omit.includes(name) &&
98
+ (config.externalLibs.includes(name) || (name !== DesktopServerStage.rscRenderer && !otherDrivers.has(name))),
99
+ );
100
+ return { ...built, dependencies: Object.fromEntries(dependencies) };
101
+ }
102
+
103
+ /** The packages in `nodeModules` that depend on `name`: what still pulls an omitted package in. */
104
+ static async dependentsOf(nodeModules: string, name: string): Promise<string[]> {
105
+ const dependents = new Set<string>();
106
+ for await (const manifest of new Bun.Glob("**/package.json").scan({ cwd: nodeModules, onlyFiles: true })) {
107
+ const json = (await Bun.file(path.join(nodeModules, manifest))
108
+ .json()
109
+ .catch(() => null)) as PackageJson | null;
110
+ if (!json?.name || json.name === name) continue;
111
+ const declared = { ...json.dependencies, ...json.optionalDependencies, ...json.peerDependencies };
112
+ if (name in declared) dependents.add(json.name);
113
+ }
114
+ return [...dependents].sort();
115
+ }
116
+
117
+ //? `--cpu` installs another CPU's optional packages: an addon's prebuilt binary for the app's CPU, not this computer's.
118
+ static installArgs(arch?: "arm64" | "x64"): string[] {
119
+ return ["install", "--production", "--prefer-offline", ...(arch && arch !== process.arch ? [`--cpu=${arch}`] : [])];
120
+ }
121
+
122
+ //* The packages come from the config alone, so they install before `akan build` runs: a machine that cannot install
123
+ //* them stops at once instead of after the whole build.
124
+ async install(arch?: "arm64" | "x64", omit: string[] = []) {
125
+ const config = await this.app.getConfig();
126
+ DesktopServerStage.assertCarriable(config);
127
+ await rm(this.dir, { recursive: true, force: true });
128
+ await mkdir(this.dir, { recursive: true });
129
+ const built = config.getProductionPackageJson();
130
+ for (const name of omit.filter((name) => !(name in (built.dependencies ?? {}))))
131
+ this.app.logger.warn(`desktop.server.omit names ${name}, which the server does not depend on directly.`);
132
+ await writeFile(
133
+ path.join(this.dir, "package.json"),
134
+ JSON.stringify(DesktopServerStage.packageJson(config, built, omit), null, 2),
135
+ );
136
+ try {
137
+ await this.app.spawn(process.execPath, DesktopServerStage.installArgs(arch), { cwd: this.dir });
138
+ } catch (error) {
139
+ throw new Error(
140
+ `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.\n${error instanceof Error ? error.message : String(error)}`,
141
+ { cause: error },
142
+ );
143
+ }
144
+ //? Launcher links only: a link out of the app bundle breaks its signature, and the server runs none of them.
145
+ await rm(path.join(this.dir, "node_modules", ".bin"), { recursive: true, force: true });
146
+ await this.#assertOmitted(omit);
147
+ }
148
+
149
+ //? Leaving a package out of package.json keeps out only what nothing else needs; another dependency still installs it.
150
+ async #assertOmitted(omit: string[]) {
151
+ const nodeModules = path.join(this.dir, "node_modules");
152
+ const kept: string[] = [];
153
+ for (const name of omit) {
154
+ if (!(await Bun.file(path.join(nodeModules, name, "package.json")).exists())) continue;
155
+ const dependents = await DesktopServerStage.dependentsOf(nodeModules, name);
156
+ kept.push(`${name} (needed by ${dependents.join(", ") || "a package that names it"})`);
157
+ }
158
+ if (kept.length)
159
+ throw new Error(
160
+ `desktop.server.omit leaves out packages another dependency still installs: ${kept.join("; ")}. Omit those too, or keep the package.`,
161
+ );
162
+ }
163
+
164
+ //? Copies the build beside what `install` put in the stage, so it runs after both.
165
+ async prepare(environment: NativeEnv): Promise<DesktopServerBundle> {
166
+ const config = await this.app.getConfig();
167
+ const dist = this.app.dist.cwdPath;
168
+ await mkdir(this.dir, { recursive: true });
169
+ for (const name of await readdir(dist))
170
+ if (!DesktopServerStage.uncarried.has(name))
171
+ await cp(path.join(dist, name), path.join(this.dir, name), { recursive: true });
172
+ const scanInfo = this.app.getScanInfo({ allowEmpty: true }) ?? (await this.app.scan({ write: false }));
173
+ const carried = [...DesktopBin.select(config, scanInfo.getLibs()).keys()];
174
+ const notice = DesktopServerStage.imageStepsNotice(config, carried);
175
+ if (notice) this.app.logger[notice.level](notice.message);
176
+ return { dir: this.dir, entry: "main.js", env: DesktopServerStage.env(config, environment) };
177
+ }
178
+ }
package/mobile/index.ts CHANGED
@@ -1 +1,10 @@
1
+ export * from "./desktopBin";
2
+ export * from "./desktopServerStage";
1
3
  export * from "./mobileTarget";
4
+ export * from "./nativeApi";
5
+ export * from "./nativeApp";
6
+ export * from "./nativeConfig";
7
+ export * from "./nativePluginFolders";
8
+ export * from "./nativeWebDir";
9
+ export * from "./placeholderAppId";
10
+ export * from "./usageDescriptions";
@@ -1,7 +1,14 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import type { AkanMobileTargetConfig } from "../akanConfig";
2
+ import type { AkanNativeTarget } from "../akanConfig";
3
3
  import type { App } from "../commandDecorators";
4
- import { getMobileTargetChoices, resolveMobilePath, resolveMobileTargets, targetHtmlFilename } from "./mobileTarget";
4
+ import {
5
+ appIdsOf,
6
+ getMobileTargetChoices,
7
+ resolveAppId,
8
+ resolveMobilePath,
9
+ resolveMobileTargets,
10
+ targetHtmlFilename,
11
+ } from "./mobileTarget";
5
12
 
6
13
  const target = {
7
14
  name: "akanjs",
@@ -10,25 +17,36 @@ const target = {
10
17
  appId: "com.akanjs.app",
11
18
  version: "1.0.0",
12
19
  buildNum: 1,
13
- } as AkanMobileTargetConfig;
20
+ } as AkanNativeTarget;
14
21
 
15
- describe("mobile target helpers", () => {
22
+ describe("native target helpers", () => {
16
23
  test("maps deep links into target base paths without duplicating prefixes", () => {
17
24
  expect(resolveMobilePath(target, "/order/123")).toBe("/akanjs/order/123");
18
25
  expect(resolveMobilePath(target, "/akanjs/order/123")).toBe("/akanjs/order/123");
19
26
  expect(resolveMobilePath({ ...target, basePath: undefined }, "/order/123")).toBe("/order/123");
20
27
  });
21
28
 
29
+ test("an app id per platform falls back to default, and a platform with neither is refused", () => {
30
+ const appId = { default: "com.yeollege", ios: "com.puffinplanet.yeollege" };
31
+ expect(resolveAppId(appId, "ios")).toBe("com.puffinplanet.yeollege");
32
+ expect(resolveAppId(appId, "android")).toBe("com.yeollege");
33
+ expect(resolveAppId("com.one.app", "macos")).toBe("com.one.app");
34
+ expect(() => resolveAppId({ ios: "com.puffinplanet.yeollege" }, "android")).toThrow(
35
+ "native.appId names no id for android",
36
+ );
37
+ expect(appIdsOf(appId)).toEqual(["com.yeollege", "com.puffinplanet.yeollege"]);
38
+ });
39
+
22
40
  test("selects the self-contained html file for a target", () => {
23
41
  expect(targetHtmlFilename(target)).toBe("akanjs.html");
24
42
  expect(targetHtmlFilename({ ...target, basePath: undefined })).toBe("index.html");
25
43
  });
26
44
 
27
- test("uses the configured mobile target as the only choice when one target is configured", async () => {
45
+ test("uses the configured native target as the only choice when one target is configured", async () => {
28
46
  const app = {
29
47
  getConfig: async () => ({
30
48
  basePaths: new Set(["akanjs", "soft", "office"]),
31
- mobile: { targets: { akanjs: target } },
49
+ native: { targets: { akanjs: target } },
32
50
  }),
33
51
  } as unknown as App;
34
52
 
@@ -36,11 +54,42 @@ describe("mobile target helpers", () => {
36
54
  await expect(resolveMobileTargets(app, undefined)).resolves.toEqual([{ name: "akanjs", config: target }]);
37
55
  });
38
56
 
39
- test("resolves a route base path onto the default mobile target config", async () => {
57
+ test("a target naming no basePath is a template in an app with basePaths, never a page of its own", async () => {
58
+ const template = { ...target, name: "default", basePath: undefined };
59
+ const appWith = (basePaths: string[]) =>
60
+ ({
61
+ name: "angelo",
62
+ getConfig: async () => ({ basePaths: new Set(basePaths), native: { targets: { default: template } } }),
63
+ }) as unknown as App;
64
+
65
+ const two = appWith(["soft", "office"]);
66
+ await expect(getMobileTargetChoices(two)).resolves.toEqual(["soft", "office"]);
67
+ await expect(resolveMobileTargets(two, undefined)).rejects.toThrow(
68
+ "Multiple native targets found for angelo. Pass --target <soft|office|all>.",
69
+ );
70
+ await expect(resolveMobileTargets(two, "office")).resolves.toEqual([
71
+ { name: "office", config: { ...template, name: "office", basePath: "office" } },
72
+ ]);
73
+ await expect(resolveMobileTargets(two, "all")).resolves.toEqual([
74
+ { name: "soft", config: { ...template, name: "soft", basePath: "soft" } },
75
+ { name: "office", config: { ...template, name: "office", basePath: "office" } },
76
+ ]);
77
+
78
+ const one = appWith(["soft"]);
79
+ const soft = [{ name: "soft", config: { ...template, name: "soft", basePath: "soft" } }];
80
+ await expect(resolveMobileTargets(one, undefined)).resolves.toEqual(soft);
81
+ await expect(resolveMobileTargets(one, "all")).resolves.toEqual(soft);
82
+
83
+ await expect(resolveMobileTargets(appWith([]), undefined)).resolves.toEqual([
84
+ { name: "default", config: template },
85
+ ]);
86
+ });
87
+
88
+ test("resolves a route base path onto the default native target config", async () => {
40
89
  const app = {
41
90
  getConfig: async () => ({
42
91
  basePaths: new Set(["akanjs", "soft", "office"]),
43
- mobile: { targets: { akanjs: target } },
92
+ native: { targets: { akanjs: target } },
44
93
  }),
45
94
  } as unknown as App;
46
95