@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
@@ -1,209 +0,0 @@
1
- import { cp, mkdir, rm } from "node:fs/promises";
2
- import path from "node:path";
3
- import type { App } from "./commandDecorators";
4
- import { FileSys } from "./fileSys";
5
- import { uploadRelease } from "./uploadRelease";
6
-
7
- export interface ReleaseSourceOptions {
8
- rebuild?: boolean;
9
- buildNum?: number;
10
- environment?: string;
11
- local?: boolean;
12
- }
13
-
14
- interface ApplicationReleasePackagerOptions {
15
- build: () => Promise<void>;
16
- }
17
-
18
- export class ApplicationReleasePackager {
19
- #app: App;
20
- #build: () => Promise<void>;
21
-
22
- constructor(app: App, options: ApplicationReleasePackagerOptions) {
23
- this.#app = app;
24
- this.#build = options.build;
25
- }
26
-
27
- async releaseSource({
28
- rebuild,
29
- buildNum = 0,
30
- environment = "debug",
31
- local = true,
32
- }: ReleaseSourceOptions = {}): Promise<void> {
33
- const { platformVersion } = await this.#prepareReleaseBuild({ rebuild, buildNum });
34
- await this.#writeSourceArchive({ readme: this.#confidentialReadme() });
35
- await uploadRelease(this.#app.name, {
36
- local,
37
- buildNum,
38
- environment,
39
- platformVersion,
40
- workspaceRoot: this.#app.workspace.cwdPath,
41
- });
42
- }
43
-
44
- async compressProjectFiles({ rebuild, buildNum = 0 }: ReleaseSourceOptions = {}): Promise<void> {
45
- await this.#prepareReleaseBuild({ rebuild, buildNum });
46
- await this.#writeSourceArchive({ readme: this.#publicReadme() });
47
- }
48
-
49
- async #prepareReleaseBuild({ rebuild, buildNum }: { rebuild?: boolean; buildNum: number }) {
50
- const akanConfig = await this.#app.getConfig();
51
- const platformVersion = akanConfig.mobile.version;
52
- const buildRoot = `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}`;
53
- if (await FileSys.dirExists(buildRoot)) await rm(buildRoot, { recursive: true, force: true });
54
- await mkdir(buildRoot, { recursive: true });
55
- if (rebuild || !(await FileSys.dirExists(`${this.#app.dist.cwdPath}/backend`))) await this.#build();
56
-
57
- const buildVersion = `${platformVersion}-${buildNum}`;
58
- const buildPath = `${buildRoot}/${buildVersion}`;
59
- await mkdir(buildPath, { recursive: true });
60
- await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
61
-
62
- await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
63
- await rm(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
64
-
65
- const releaseRoot = this.#app.workspace.workspaceRoot;
66
- // Pass a path relative to cwd so tar never sees a Windows drive letter (e.g. "C:\...")
67
- // which GNU tar would interpret as a remote "host:file" spec.
68
- const releaseArchivePath = path
69
- .relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`)
70
- .split(path.sep)
71
- .join("/");
72
- await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
73
- await this.#writeCsrZipIfPresent();
74
- return { platformVersion };
75
- }
76
-
77
- async #writeCsrZipIfPresent(): Promise<void> {
78
- if (!(await FileSys.dirExists(`${this.#app.dist.cwdPath}/csr`))) return;
79
- await cp(`${this.#app.dist.cwdPath}/csr`, "./csr", { recursive: true });
80
- await this.#app.workspace.spawn("zip", [
81
- "-r",
82
- `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-appBuild.zip`,
83
- "./csr",
84
- ]);
85
- await rm("./csr", { recursive: true, force: true });
86
- }
87
-
88
- async #writeSourceArchive({ readme }: { readme: string }): Promise<void> {
89
- const sourceRoot = `${this.#app.workspace.workspaceRoot}/releases/sources/${this.#app.name}`;
90
- await this.#resetSourceRoot(sourceRoot);
91
- await cp(this.#app.dist.cwdPath, `${sourceRoot}/apps/${this.#app.name}`, { recursive: true });
92
-
93
- const libDeps = ["social", "shared", "platform", "util"];
94
- await Promise.all(
95
- libDeps.map((lib) =>
96
- cp(`${this.#app.workspace.cwdPath}/libs/${lib}`, `${sourceRoot}/libs/${lib}`, { recursive: true }),
97
- ),
98
- );
99
- await Promise.all(
100
- [".next", "ios", "android", "public/libs"].map(async (path) => {
101
- const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path}`;
102
- if (await FileSys.dirExists(targetPath)) await rm(targetPath, { recursive: true, force: true });
103
- }),
104
- );
105
-
106
- const syncPaths = [".husky", ".gitignore", "package.json"];
107
- await Promise.all(
108
- syncPaths.map((path) =>
109
- cp(`${this.#app.workspace.cwdPath}/${path}`, `${sourceRoot}/${path}`, { recursive: true }),
110
- ),
111
- );
112
- await this.#writeSourceTsconfig(sourceRoot, libDeps);
113
- await Bun.write(`${sourceRoot}/README.md`, readme);
114
- const sourceCwd = this.#app.workspace.cwdPath;
115
- // Pass a path relative to cwd so tar never sees a Windows drive letter (e.g. "C:\...")
116
- // which GNU tar would interpret as a remote "host:file" spec.
117
- const sourceArchivePath = path
118
- .relative(sourceCwd, `${sourceCwd}/releases/sources/${this.#app.name}-source.tar.gz`)
119
- .split(path.sep)
120
- .join("/");
121
- await this.#app.workspace.spawn("tar", ["-zcf", sourceArchivePath, "-C", sourceRoot, "./"], { cwd: sourceCwd });
122
- }
123
-
124
- async #resetSourceRoot(sourceRoot: string): Promise<void> {
125
- if (await FileSys.dirExists(sourceRoot)) {
126
- const maxRetry = 3;
127
- for (let i = 0; i < maxRetry; i++) {
128
- try {
129
- await rm(sourceRoot, { recursive: true, force: true });
130
- } catch {
131
- //
132
- }
133
- }
134
- }
135
- await mkdir(sourceRoot, { recursive: true });
136
- }
137
-
138
- async #writeSourceTsconfig(sourceRoot: string, libDeps: string[]): Promise<void> {
139
- const tsconfig = (await this.#app.workspace.readJson("tsconfig.json")) as {
140
- compilerOptions: { paths: Record<string, string[]> };
141
- };
142
- const pathEntries: [string, string[]][] = [[`@${this.#app.name}/*`, [`apps/${this.#app.name}/*`]]];
143
- for (const lib of libDeps) {
144
- pathEntries.push([`@${lib}`, [`libs/${lib}/index.ts`]], [`@${lib}/*`, [`libs/${lib}/*`]]);
145
- }
146
- tsconfig.compilerOptions.paths = Object.fromEntries(pathEntries) as Record<string, string[]>;
147
- await Bun.write(`${sourceRoot}/tsconfig.json`, JSON.stringify(tsconfig, null, 2));
148
- }
149
-
150
- #confidentialReadme(): string {
151
- return `# ${this.#app.name}
152
- 본 프로젝트의 소스코드 및 관련자료는 모두 비밀정보로 관리됩니다.
153
-
154
- ## Get Started
155
- Run the code below.
156
- \`\`\`
157
- bun install -g bun
158
- bun i
159
-
160
- cat <<EOF >> .env
161
- # ENV For Server => debug | debug.local | develop | develop.local | main | main.local
162
- SERVER_ENV=debug.local
163
- # Run Mode For Server => federation | batch | all
164
- SERVER_MODE=federation
165
- # ENV For Client => debug | debug.local | develop | develop.local | main | main.local
166
- AKAN_PUBLIC_CLIENT_ENV=debug.local
167
- ANALYZE=false
168
- EOF
169
-
170
- akn start-backend ${this.#app.name}
171
- # or akn start-frontend ${this.#app.name}, etc
172
- \`\`\`
173
-
174
- ## Build
175
- Run the code below.
176
- \`\`\`
177
- akn build-backend ${this.#app.name}
178
- # or akn build-frontend ${this.#app.name}, etc
179
- \`\`\`
180
- `;
181
- }
182
-
183
- #publicReadme(): string {
184
- return `# ${this.#app.name}
185
- ## Get Started
186
- Run the code below.
187
- \`\`\`
188
- cat <<EOF >> .env
189
- # ENV For Server => debug | debug.local | develop | develop.local | main | main.local
190
- SERVER_ENV=debug.local
191
- # Run Mode For Server => federation | batch | all
192
- SERVER_MODE=federation
193
- # ENV For Client => debug | debug.local | develop | develop.local | main | main.local
194
- AKAN_PUBLIC_CLIENT_ENV=debug.local
195
- ANALYZE=false
196
- EOF
197
-
198
- akan start ${this.#app.name}
199
- \`\`\`
200
-
201
- ## Build
202
- Run the code below.
203
- \`\`\`
204
- akan build ${this.#app.name}
205
- # or akn build-frontend ${this.#app.name}, etc
206
- \`\`\`
207
- `;
208
- }
209
- }
@@ -1,372 +0,0 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import type { AkanMobileTargetConfig } from "./akanConfig";
6
- import {
7
- ANDROID_MIN_SDK_VERSION,
8
- assertJsonSerializable,
9
- buildIosNativeRunCommand,
10
- classifyIosRunFailure,
11
- clearRootCapacitorConfigs,
12
- formatAndroidReleaseSigningError,
13
- getAdbDeviceStateIssues,
14
- getAndroidLocalServerHost,
15
- getMissingAndroidReleaseSigningKeys,
16
- isPlaceholderAppId,
17
- materializeCapacitorConfig,
18
- parseDevicectlDevices,
19
- parseIosRuntimeMajor,
20
- parseSimctlDevices,
21
- raiseGradleMinSdkVersion,
22
- rootCapacitorConfigFilenames,
23
- SWIFTUICORE_MIN_IOS_MAJOR,
24
- sanitizeIosNativeRunEnv,
25
- selectLocalDevHost,
26
- sortIosRunTargets,
27
- writeRootCapacitorConfig,
28
- } from "./capacitorApp";
29
-
30
- const tempRoots: string[] = [];
31
-
32
- const makeTempRoot = async () => {
33
- const root = await mkdtemp(path.join(os.tmpdir(), "akan-capacitor-app-"));
34
- tempRoots.push(root);
35
- return root;
36
- };
37
-
38
- const baseTarget: AkanMobileTargetConfig = {
39
- name: "default",
40
- appName: "Minimal",
41
- appId: "com.minimal.app",
42
- version: "1.2.3",
43
- buildNum: 7,
44
- basePath: "admin",
45
- indexPath: "/dashboard",
46
- assets: { icon: "mobile/icon.png" },
47
- permissions: ["camera"],
48
- deepLinks: { schemes: ["minimal"], domains: ["minimal.app"] },
49
- files: { android: { "app/google-services.json": "private/google-services.json" } },
50
- };
51
-
52
- afterEach(async () => {
53
- await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
54
- });
55
-
56
- describe("materializeCapacitorConfig", () => {
57
- test("writes only Capacitor fields for release config", () => {
58
- const config = materializeCapacitorConfig(
59
- {
60
- ...baseTarget,
61
- plugins: { CapacitorHttp: { enabled: true } },
62
- android: { flavor: "qa" },
63
- ios: { scheme: "App QA" },
64
- cordova: { preferences: { ScrollEnabled: "false" } },
65
- experimental: { ios: { spm: { swiftToolsVersion: "5.9" } } },
66
- },
67
- { operation: "release" },
68
- );
69
-
70
- expect(config).toMatchObject({
71
- appId: "com.minimal.app",
72
- appName: "Minimal",
73
- webDir: ".akan/mobile/default/www",
74
- plugins: {
75
- CapacitorCookies: { enabled: true },
76
- CapacitorHttp: { enabled: true },
77
- Keyboard: { resize: "none" },
78
- },
79
- android: { flavor: "qa", path: "android" },
80
- ios: { scheme: "App QA", path: "ios" },
81
- cordova: { preferences: { ScrollEnabled: "false" } },
82
- experimental: { ios: { spm: { swiftToolsVersion: "5.9" } } },
83
- });
84
- expect(config).not.toHaveProperty("name");
85
- expect(config).not.toHaveProperty("basePath");
86
- expect(config).not.toHaveProperty("indexPath");
87
- expect(config).not.toHaveProperty("version");
88
- expect(config).not.toHaveProperty("buildNum");
89
- expect(config).not.toHaveProperty("assets");
90
- expect(config).not.toHaveProperty("permissions");
91
- expect(config).not.toHaveProperty("deepLinks");
92
- expect(config).not.toHaveProperty("files");
93
- expect(config).not.toHaveProperty("server");
94
- });
95
-
96
- test("adds local server config without requiring env target switching", () => {
97
- const config = materializeCapacitorConfig(
98
- {
99
- ...baseTarget,
100
- server: {
101
- hostname: "localhost",
102
- allowNavigation: ["api.example.com"],
103
- },
104
- },
105
- {
106
- operation: "local",
107
- localIp: "192.168.0.5",
108
- localServerUrl: "http://192.168.0.5:8282/en/admin?csr=true&akanMobileTarget=default",
109
- },
110
- );
111
-
112
- expect(config.server).toEqual({
113
- hostname: "localhost",
114
- androidScheme: "http",
115
- url: "http://192.168.0.5:8282/en/admin?csr=true&akanMobileTarget=default",
116
- cleartext: true,
117
- allowNavigation: ["api.example.com", "192.168.0.5", "localhost"],
118
- });
119
- });
120
-
121
- test("allows mobile targets to override the default keyboard resize mode", () => {
122
- const config = materializeCapacitorConfig(
123
- {
124
- ...baseTarget,
125
- plugins: { Keyboard: { resize: "native" } },
126
- },
127
- { operation: "release" },
128
- );
129
-
130
- expect(config.plugins).toMatchObject({
131
- Keyboard: { resize: "native" },
132
- });
133
- });
134
-
135
- test("rejects non-json config values", () => {
136
- expect(() => assertJsonSerializable({ plugins: { Custom: () => null } })).toThrow("must be JSON serializable");
137
- });
138
- });
139
-
140
- describe("root capacitor config helpers", () => {
141
- test("clears root configs before writing the temporary json config", async () => {
142
- const root = await makeTempRoot();
143
- await Promise.all(rootCapacitorConfigFilenames.map((file) => writeFile(path.join(root, file), `old ${file}\n`)));
144
-
145
- await writeRootCapacitorConfig(root, '{\n "appId": "com.minimal.app"\n}\n');
146
-
147
- expect(await Bun.file(path.join(root, "capacitor.config.ts")).exists()).toBe(false);
148
- expect(await Bun.file(path.join(root, "capacitor.config.js")).exists()).toBe(false);
149
- expect(await Bun.file(path.join(root, "capacitor.config.json")).text()).toBe(
150
- '{\n "appId": "com.minimal.app"\n}\n',
151
- );
152
-
153
- await clearRootCapacitorConfigs(root);
154
-
155
- expect(await Bun.file(path.join(root, "capacitor.config.json")).exists()).toBe(false);
156
- });
157
- });
158
-
159
- describe("iOS native run helpers", () => {
160
- test("parses xcrun device and simulator outputs", () => {
161
- expect(
162
- parseDevicectlDevices(
163
- JSON.stringify({
164
- result: {
165
- devices: [
166
- {
167
- identifier: "1BCB6563-02FF-5D49-9655-1BFF02A638D3",
168
- connectionProperties: { pairingState: "paired", transportType: "wired", tunnelState: "available" },
169
- deviceProperties: { name: "Seok iPhone" },
170
- hardwareProperties: { udid: "00008130-000200113EC1001C" },
171
- },
172
- ],
173
- },
174
- }),
175
- ),
176
- ).toEqual([
177
- {
178
- id: "00008130-000200113EC1001C",
179
- name: "Seok iPhone",
180
- kind: "device",
181
- state: "available wired paired",
182
- devicectlId: "1BCB6563-02FF-5D49-9655-1BFF02A638D3",
183
- xcodebuildId: "00008130-000200113EC1001C",
184
- },
185
- ]);
186
-
187
- expect(
188
- parseSimctlDevices(
189
- JSON.stringify({
190
- devices: {
191
- "com.apple.CoreSimulator.SimRuntime.iOS-18-2": [
192
- { udid: "11111111-2222-3333-4444-555555555555", name: "iPhone 16", state: "Shutdown", isAvailable: true },
193
- ],
194
- },
195
- }),
196
- ),
197
- ).toEqual([
198
- {
199
- id: "11111111-2222-3333-4444-555555555555",
200
- name: "iPhone 16",
201
- kind: "simulator",
202
- state: "Shutdown",
203
- runtime: "iOS 18.2",
204
- },
205
- ]);
206
- });
207
-
208
- test("sorts run targets so booted / connected / newest-runtime targets come first", () => {
209
- const ios17 = { id: "a", name: "iPhone 15", kind: "simulator" as const, state: "Shutdown", runtime: "iOS 17.5" };
210
- const ios18 = { id: "b", name: "iPhone 16", kind: "simulator" as const, state: "Shutdown", runtime: "iOS 18.2" };
211
- const booted17 = { id: "c", name: "iPhone SE", kind: "simulator" as const, state: "Booted", runtime: "iOS 17.0" };
212
- const device = { id: "d", name: "Seok iPhone", kind: "device" as const, state: "available wired paired" };
213
- // Input is in the old ascending-runtime order that used to default to iOS 17.
214
- const sorted = sortIosRunTargets([ios17, ios18, booted17, device]);
215
- expect(sorted.map((t) => t.id)).toEqual(["d", "c", "b", "a"]);
216
- // Newest available simulator beats the older one, so the interactive default is no longer iOS 17.
217
- expect(sortIosRunTargets([ios17, ios18]).map((t) => t.id)).toEqual(["b", "a"]);
218
- });
219
-
220
- test("parses the major iOS runtime version", () => {
221
- expect(parseIosRuntimeMajor("iOS 18.2")).toBe(18);
222
- expect(parseIosRuntimeMajor("iOS 17.5")).toBe(17);
223
- expect(parseIosRuntimeMajor(undefined)).toBeUndefined();
224
- expect(SWIFTUICORE_MIN_IOS_MAJOR).toBe(18);
225
- });
226
-
227
- test("builds xcodebuild destinations and output paths by target kind", () => {
228
- const deviceCommand = buildIosNativeRunCommand({
229
- appRoot: "/repo/apps/minimal",
230
- device: { id: "device-1", name: "iPhone", kind: "device" },
231
- scheme: "App QA",
232
- configuration: "Debug",
233
- });
234
- expect(deviceCommand.xcodebuildArgs).toContain("id=device-1");
235
- expect(deviceCommand.xcodebuildArgs).toContain("App QA");
236
- expect(deviceCommand.appPath).toBe(
237
- "/repo/apps/minimal/ios/DerivedData/device-1/Build/Products/Debug-iphoneos/App.app",
238
- );
239
-
240
- const simulatorCommand = buildIosNativeRunCommand({
241
- appRoot: "/repo/apps/minimal",
242
- device: { id: "sim-1", name: "Simulator", kind: "simulator" },
243
- });
244
- expect(simulatorCommand.xcodebuildArgs).toContain("platform=iOS Simulator,id=sim-1");
245
- expect(simulatorCommand.appPath).toBe(
246
- "/repo/apps/minimal/ios/DerivedData/sim-1/Build/Products/Debug-iphonesimulator/App.app",
247
- );
248
- });
249
-
250
- test("classifies iOS signing and device-state failures", () => {
251
- expect(classifyIosRunFailure("There are no accounts registered with Xcode").kind).toBe("apple-account");
252
- expect(
253
- classifyIosRunFailure(
254
- 'Failed Registering Bundle Identifier: The app identifier "com.minimal.app" cannot be registered to your development team',
255
- ).kind,
256
- ).toBe("bundle-identifier");
257
- expect(classifyIosRunFailure("No profiles for 'com.minimal.app' were found").kind).toBe("provisioning-profile");
258
- expect(classifyIosRunFailure("Developer Mode is disabled on this device").kind).toBe("device-state");
259
- expect(classifyIosRunFailure("arm64-apple-darwin20.0: error: unknown argument: '-index-store-path'").kind).toBe(
260
- "compiler-toolchain",
261
- );
262
- expect(
263
- classifyIosRunFailure(
264
- "dyld[1234]: Library not loaded: /System/Library/Frameworks/SwiftUICore.framework/SwiftUICore",
265
- ).kind,
266
- ).toBe("simulator-runtime");
267
- });
268
-
269
- test("removes shell compiler variables from iOS native run env", () => {
270
- expect(
271
- sanitizeIosNativeRunEnv({
272
- AKAN_PUBLIC_APP_NAME: "minimal",
273
- CC: "arm64-apple-darwin20.0.0-clang",
274
- CXX: "arm64-apple-darwin20.0.0-clang++",
275
- SDKROOT: "/wrong/sdk",
276
- }),
277
- ).toEqual({ AKAN_PUBLIC_APP_NAME: "minimal" });
278
- });
279
-
280
- test("flags placeholder bundle identifiers that Apple's portal already claims", () => {
281
- for (const appId of ["com.myapp.app", "com.myorg.myapp", "com.example.foo", "com.changeme.app", ""]) {
282
- expect(isPlaceholderAppId(appId)).toBe(true);
283
- }
284
- for (const appId of ["com.minimal.app", "com.nearthlab.leadingflight", "io.akanjs.demo"]) {
285
- expect(isPlaceholderAppId(appId)).toBe(false);
286
- }
287
- });
288
- });
289
-
290
- describe("selectLocalDevHost", () => {
291
- const build = (map: Record<string, { family: string; address: string; internal?: boolean }[]>) =>
292
- map as unknown as Parameters<typeof selectLocalDevHost>[0];
293
-
294
- test("prefers a routable LAN NIC over an inactive bridge enumerated first", () => {
295
- const resolution = selectLocalDevHost(
296
- build({
297
- bridge0: [{ family: "IPv4", address: "192.168.2.1" }],
298
- en0: [{ family: "IPv4", address: "172.25.140.140" }],
299
- bridge100: [{ family: "IPv4", address: "192.168.3.1" }],
300
- en8: [{ family: "IPv4", address: "172.25.40.227" }],
301
- }),
302
- );
303
- expect(resolution.host).toBe("172.25.140.140");
304
- expect(resolution.source).toBe("detected");
305
- });
306
-
307
- test("an explicit override always wins and is trimmed", () => {
308
- const resolution = selectLocalDevHost(build({ en0: [{ family: "IPv4", address: "172.25.140.140" }] }), {
309
- override: " 10.0.0.9 ",
310
- });
311
- expect(resolution).toMatchObject({ host: "10.0.0.9", source: "override" });
312
- });
313
-
314
- test("never selects a link-local address when a real LAN address exists", () => {
315
- const resolution = selectLocalDevHost(
316
- build({
317
- en0: [{ family: "IPv4", address: "169.254.10.10" }],
318
- en1: [{ family: "IPv4", address: "192.168.1.5" }],
319
- }),
320
- );
321
- expect(resolution.host).toBe("192.168.1.5");
322
- });
323
-
324
- test("falls back to loopback when nothing routable is present", () => {
325
- const resolution = selectLocalDevHost(build({ lo0: [{ family: "IPv4", address: "127.0.0.1", internal: true }] }));
326
- expect(resolution).toMatchObject({ host: "127.0.0.1", source: "loopback" });
327
- });
328
- });
329
-
330
- describe("Android signing diagnostics", () => {
331
- test("reports missing release signing keys and adb authorization issues", () => {
332
- expect(
333
- getMissingAndroidReleaseSigningKeys({
334
- env: { MYAPP_RELEASE_STORE_FILE: "release.jks" } as NodeJS.ProcessEnv,
335
- gradleProperties: "MYAPP_RELEASE_STORE_PASSWORD=secret\n",
336
- }),
337
- ).toEqual(["MYAPP_RELEASE_KEY_ALIAS", "MYAPP_RELEASE_KEY_PASSWORD"]);
338
- expect(formatAndroidReleaseSigningError(["MYAPP_RELEASE_KEY_ALIAS"])).toContain("MYAPP_RELEASE_KEY_ALIAS");
339
- expect(getAdbDeviceStateIssues("List of devices attached\nabc123 unauthorized\nxyz offline\n")).toEqual([
340
- "Android device abc123 is unauthorized. Confirm USB debugging authorization on the device.",
341
- "Android device xyz is offline. Reconnect the device or restart adb.",
342
- ]);
343
- expect(getAndroidLocalServerHost("List of devices attached\nemulator-5554 device\n", "192.168.0.5")).toBe(
344
- "10.0.2.2",
345
- );
346
- expect(getAndroidLocalServerHost("List of devices attached\nabc123 device\n", "192.168.0.5")).toBe("192.168.0.5");
347
- expect(
348
- getAndroidLocalServerHost("List of devices attached\nemulator-5554 device\nabc123 device\n", "192.168.0.5"),
349
- ).toBe("192.168.0.5");
350
- });
351
- });
352
-
353
- describe("raiseGradleMinSdkVersion", () => {
354
- test("raises the scaffold minSdkVersion to the bundled-plugin floor, preserving formatting", () => {
355
- const scaffold = "ext {\n minSdkVersion = 24\n compileSdkVersion = 35\n}\n";
356
- expect(raiseGradleMinSdkVersion(scaffold)).toBe(
357
- `ext {\n minSdkVersion = ${ANDROID_MIN_SDK_VERSION}\n compileSdkVersion = 35\n}\n`,
358
- );
359
- });
360
-
361
- test("returns null when nothing needs changing", () => {
362
- // Already at the floor, already above it, and no assignment present.
363
- expect(raiseGradleMinSdkVersion(`ext {\n minSdkVersion = ${ANDROID_MIN_SDK_VERSION}\n}\n`)).toBeNull();
364
- expect(raiseGradleMinSdkVersion("ext {\n minSdkVersion = 34\n}\n")).toBeNull();
365
- expect(raiseGradleMinSdkVersion("ext {\n compileSdkVersion = 35\n}\n")).toBeNull();
366
- });
367
-
368
- test("honors an explicit floor and leaves higher user values untouched", () => {
369
- expect(raiseGradleMinSdkVersion("minSdkVersion = 21", 23)).toBe("minSdkVersion = 23");
370
- expect(raiseGradleMinSdkVersion("minSdkVersion = 28", 26)).toBeNull();
371
- });
372
- });