@akanjs/devkit 3.0.0-alpha.1 → 3.0.0-alpha.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/DEV_RUNTIME_KNOBS.md +34 -0
  3. package/README.ko.md +1 -1
  4. package/README.md +1 -1
  5. package/agentsIndex.test.ts +10 -0
  6. package/agentsIndex.ts +47 -1
  7. package/aiEditor.ts +1 -1
  8. package/akanApp/BackendImportGraph.test.ts +120 -0
  9. package/akanApp/BackendImportGraph.ts +167 -0
  10. package/akanApp/akanApp.host.test.ts +16 -678
  11. package/akanApp/akanApp.host.ts +113 -578
  12. package/akanApp/devHostPolicy.test.ts +542 -0
  13. package/akanApp/devHostPolicy.ts +442 -0
  14. package/akanApp/index.ts +2 -0
  15. package/akanConfig/akanConfig.test.ts +182 -14
  16. package/akanConfig/akanConfig.ts +143 -47
  17. package/akanConfig/types.ts +9 -0
  18. package/akanContext.ts +70 -42
  19. package/akanMcpContract.ts +25 -4
  20. package/appSelectionMemory.ts +28 -0
  21. package/applicationBuildRunner.test.ts +37 -2
  22. package/applicationBuildRunner.ts +53 -24
  23. package/artifact/implicitRootLayout.test.ts +67 -0
  24. package/artifact/implicitRootLayout.ts +27 -7
  25. package/artifact/routeSeedIndex.test.ts +1 -0
  26. package/biome.base.json +340 -0
  27. package/biomeBase.ts +9 -0
  28. package/capacitorApp.test.ts +8 -0
  29. package/capacitorApp.ts +122 -18
  30. package/cloud/globalConfig.ts +9 -2
  31. package/codegenLock.test.ts +137 -0
  32. package/codegenLock.ts +136 -0
  33. package/commandDecorators/argMeta.ts +9 -1
  34. package/commandDecorators/command.ts +94 -7
  35. package/commandDecorators/commandBuilder.ts +20 -5
  36. package/commandDecorators/commandDecorators.test.ts +115 -3
  37. package/commandDecorators/helpFormatter.ts +4 -0
  38. package/dependencyScanner.test.ts +99 -0
  39. package/dependencyScanner.ts +27 -24
  40. package/devkitUtils.test.ts +0 -104
  41. package/executors.test.ts +137 -5
  42. package/executors.ts +116 -26
  43. package/fileEditor.ts +19 -19
  44. package/fileSys.ts +19 -1
  45. package/formSetterScanner.test.ts +80 -0
  46. package/formSetterScanner.ts +92 -0
  47. package/frontendBuild/allRoutesBuilder.ts +27 -8
  48. package/frontendBuild/autoImportSync.test.ts +58 -0
  49. package/frontendBuild/autoImportSync.ts +7 -2
  50. package/frontendBuild/buildRouteClient.test.ts +47 -24
  51. package/frontendBuild/clientBuildTypes.ts +4 -0
  52. package/frontendBuild/clientEntriesBundler.ts +4 -1
  53. package/frontendBuild/clientEntryDiscovery.ts +2 -2
  54. package/frontendBuild/csrArtifactBuilder.ts +116 -84
  55. package/frontendBuild/cssCandidateCache.test.ts +38 -0
  56. package/frontendBuild/cssCandidateCache.ts +15 -3
  57. package/frontendBuild/cssCompiler.ts +123 -12
  58. package/frontendBuild/cssImportResolver.ts +8 -7
  59. package/frontendBuild/devGeneratedIndexSync.ts +3 -2
  60. package/frontendBuild/fontOptimizer.ts +37 -18
  61. package/frontendBuild/fontPruner.test.ts +220 -0
  62. package/frontendBuild/fontPruner.ts +206 -0
  63. package/frontendBuild/frontendBuild.test.ts +178 -10
  64. package/frontendBuild/hmrWatcher.ts +1 -1
  65. package/frontendBuild/index.ts +1 -1
  66. package/frontendBuild/pagesBundleBuilder.ts +3 -3
  67. package/frontendBuild/pagesEntrySourceGenerator.ts +11 -88
  68. package/frontendBuild/precompressArtifacts.ts +35 -7
  69. package/frontendBuild/routeClientBuilder.ts +26 -7
  70. package/frontendBuild/sourceMtimeIndex.ts +1 -1
  71. package/frontendBuild/ssrBaseArtifactBuilder.ts +21 -4
  72. package/frontendBuild/styleContract.ts +14 -20
  73. package/frontendBuild/themeValidator.ts +22 -17
  74. package/frontendBuild/vendorSpecifiers.ts +1 -0
  75. package/frontendBuild/watchRootResolver.test.ts +129 -0
  76. package/frontendBuild/watchRootResolver.ts +66 -1
  77. package/incrementalBuilder/devWatchBatch.test.ts +18 -20
  78. package/incrementalBuilder/devWatchBatch.ts +6 -2
  79. package/incrementalBuilder/incrementalBuilder.host.ts +51 -7
  80. package/incrementalBuilder/incrementalBuilder.proc.ts +17 -6
  81. package/index.ts +0 -7
  82. package/integration/devStabilityHarness.ts +2 -10
  83. package/libSource.test.ts +112 -0
  84. package/libSource.ts +126 -0
  85. package/lint/__fixtures__/README.md +40 -0
  86. package/lint/__fixtures__/no-arbitrary-color/bad.tsx +3 -0
  87. package/lint/__fixtures__/no-arbitrary-color/good.tsx +4 -0
  88. package/lint/__fixtures__/no-async-component-in-ui/bad.tsx +4 -0
  89. package/lint/__fixtures__/no-async-component-in-ui/good.tsx +4 -0
  90. package/lint/__fixtures__/no-bang-comment-in-client/bad.tsx +4 -0
  91. package/lint/__fixtures__/no-bang-comment-in-client/fixture.json +1 -0
  92. package/lint/__fixtures__/no-bang-comment-in-client/good.tsx +3 -0
  93. package/lint/__fixtures__/no-daisyui-legacy-class/bad.tsx +7 -0
  94. package/lint/__fixtures__/no-daisyui-legacy-class/good.tsx +5 -0
  95. package/lint/__fixtures__/no-deep-internal-import/bad.tsx +3 -0
  96. package/lint/__fixtures__/no-deep-internal-import/fixture.json +1 -0
  97. package/lint/__fixtures__/no-deep-internal-import/good.tsx +3 -0
  98. package/lint/__fixtures__/no-deprecated-log-level/bad.tsx +4 -0
  99. package/lint/__fixtures__/no-deprecated-log-level/good.tsx +4 -0
  100. package/lint/__fixtures__/no-import-client-functions/bad.tsx +2 -0
  101. package/lint/__fixtures__/no-import-client-functions/good.tsx +4 -0
  102. package/lint/__fixtures__/no-import-client-in-server/bad.tsx +6 -0
  103. package/lint/__fixtures__/no-import-client-in-server/good.tsx +5 -0
  104. package/lint/__fixtures__/no-import-external-library/bad.tsx +4 -0
  105. package/lint/__fixtures__/no-import-external-library/good.tsx +5 -0
  106. package/lint/__fixtures__/no-import-server-in-client/bad.tsx +6 -0
  107. package/lint/__fixtures__/no-import-server-in-client/good.tsx +4 -0
  108. package/lint/__fixtures__/no-init-fetch-in-client/store-file/bad.ts +1 -0
  109. package/lint/__fixtures__/no-init-fetch-in-client/store-file/fixture.json +1 -0
  110. package/lint/__fixtures__/no-init-fetch-in-client/store-file/good.ts +1 -0
  111. package/lint/__fixtures__/no-init-fetch-in-client/use-client/bad.tsx +4 -0
  112. package/lint/__fixtures__/no-init-fetch-in-client/use-client/good.tsx +5 -0
  113. package/lint/__fixtures__/no-inline-color/bad.tsx +3 -0
  114. package/lint/__fixtures__/no-inline-color/good.tsx +3 -0
  115. package/lint/__fixtures__/no-interpolated-arbitrary-class/bad.tsx +3 -0
  116. package/lint/__fixtures__/no-interpolated-arbitrary-class/good.tsx +3 -0
  117. package/lint/__fixtures__/no-js-private-class-method/bad.tsx +3 -0
  118. package/lint/__fixtures__/no-js-private-class-method/good.tsx +3 -0
  119. package/lint/__fixtures__/no-model-type-in-util-zone/bad.tsx +3 -0
  120. package/lint/__fixtures__/no-model-type-in-util-zone/good.tsx +6 -0
  121. package/lint/__fixtures__/no-raw-palette-class/bad.tsx +6 -0
  122. package/lint/__fixtures__/no-raw-palette-class/good.tsx +8 -0
  123. package/lint/__fixtures__/no-redeclare-predefined-endpoint/bad.ts +10 -0
  124. package/lint/__fixtures__/no-redeclare-predefined-endpoint/fixture.json +1 -0
  125. package/lint/__fixtures__/no-redeclare-predefined-endpoint/good.ts +6 -0
  126. package/lint/__fixtures__/no-return-in-store-action/bad.tsx +2 -0
  127. package/lint/__fixtures__/no-return-in-store-action/good.tsx +6 -0
  128. package/lint/__fixtures__/no-throw-raw-error/bad.tsx +4 -0
  129. package/lint/__fixtures__/no-throw-raw-error/good.tsx +4 -0
  130. package/lint/__fixtures__/no-unpublished-form-setter/bad.tsx +3 -0
  131. package/lint/__fixtures__/no-unpublished-form-setter/good.tsx +5 -0
  132. package/lint/__fixtures__/no-use-client-in-server/bad.tsx +3 -0
  133. package/lint/__fixtures__/no-use-client-in-server/good.tsx +2 -0
  134. package/lint/__fixtures__/non-scalar-props-restricted/bad.tsx +5 -0
  135. package/lint/__fixtures__/non-scalar-props-restricted/good.tsx +4 -0
  136. package/lint/gritRules.test.ts +178 -0
  137. package/lint/no-arbitrary-color.grit +1 -1
  138. package/lint/no-async-component-in-ui.grit +35 -0
  139. package/lint/no-bang-comment-in-client.grit +33 -0
  140. package/lint/no-daisyui-legacy-class.grit +26 -9
  141. package/lint/no-deprecated-log-level.grit +17 -0
  142. package/lint/no-import-client-in-server.grit +48 -0
  143. package/lint/no-import-server-in-client.grit +45 -0
  144. package/lint/no-init-fetch-in-client.grit +47 -0
  145. package/lint/no-inline-color.grit +10 -8
  146. package/lint/no-interpolated-arbitrary-class.grit +3 -3
  147. package/lint/no-model-type-in-util-zone.grit +58 -0
  148. package/lint/no-raw-palette-class.grit +3 -3
  149. package/lint/no-return-in-store-action.grit +35 -0
  150. package/lint/no-unpublished-form-setter.grit +41 -0
  151. package/lint/non-scalar-props-restricted.grit +16 -7
  152. package/linter.test.ts +80 -0
  153. package/linter.ts +99 -25
  154. package/package.json +6 -8
  155. package/prompter.ts +9 -4
  156. package/qualityScanner.test.ts +130 -0
  157. package/qualityScanner.ts +91 -62
  158. package/recipeScanner.ts +4 -1
  159. package/repoIdentity.ts +42 -0
  160. package/routeSourceValidator.test.ts +41 -0
  161. package/routeSourceValidator.ts +2 -44
  162. package/scanInfo.ts +32 -67
  163. package/semver.test.ts +26 -0
  164. package/semver.ts +31 -0
  165. package/slicePlanner.test.ts +182 -0
  166. package/slicePlanner.ts +235 -0
  167. package/ssrScanner.test.ts +301 -0
  168. package/subspace.test.ts +418 -0
  169. package/subspace.ts +723 -0
  170. package/subspaceConfig.ts +76 -0
  171. package/transforms/asyncDefaultExportDetector.ts +103 -0
  172. package/transforms/barrelImportsPlugin.ts +1 -1
  173. package/transforms/externalizeFrameworkPlugin.ts +0 -1
  174. package/transforms/transforms.test.ts +5 -5
  175. package/transforms/tsconfigPackageResolver.test.ts +230 -0
  176. package/tsconfig.json +6 -2
  177. package/typeChecker.ts +1 -1
  178. package/types.ts +1 -0
  179. package/uploadRelease.ts +2 -2
  180. package/workflow/executor.test.ts +146 -0
  181. package/workspaceLayout.test.ts +78 -0
  182. package/workspaceLayout.ts +106 -0
  183. package/builder.ts +0 -164
  184. package/extractDeps.ts +0 -86
  185. package/frontendBuild/styleGuard.test.ts +0 -165
  186. package/frontendBuild/styleGuard.ts +0 -322
  187. package/getCredentials.ts +0 -19
  188. package/getModelFileData.ts +0 -62
  189. package/src/capacitorApp.ts +0 -282
  190. package/streamAi.ts +0 -45
  191. package/ui/MultiScrollList.tsx +0 -242
  192. package/ui/ScrollList.tsx +0 -107
  193. package/ui/index.ts +0 -2
  194. package/useStdoutDimensions.ts +0 -20
@@ -0,0 +1,340 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.5.12/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": false
7
+ },
8
+ "formatter": {
9
+ "enabled": true,
10
+ "indentStyle": "space",
11
+ "indentWidth": 2,
12
+ "lineWidth": 120
13
+ },
14
+ "linter": {
15
+ "enabled": true,
16
+ "rules": {
17
+ "preset": "recommended",
18
+ "suspicious": {
19
+ "noConsole": {
20
+ "level": "error",
21
+ "options": {
22
+ "allow": ["assert", "error", "info", "warn"]
23
+ }
24
+ },
25
+ "noArrayIndexKey": "off",
26
+ "noShadowRestrictedNames": "off",
27
+ "noUnnecessaryConditions": {
28
+ "level": "warn"
29
+ }
30
+ },
31
+ "correctness": {
32
+ "noUnusedFunctionParameters": "off",
33
+ "noUnusedImports": {
34
+ "level": "error",
35
+ "fix": "safe"
36
+ },
37
+ "useParseIntRadix": "off",
38
+ "useExhaustiveDependencies": "off",
39
+ "useHookAtTopLevel": "off"
40
+ },
41
+ "nursery": {
42
+ "useSortedClasses": {
43
+ "level": "error",
44
+ "fix": "safe",
45
+ "options": {
46
+ "attributes": ["classList"],
47
+ "functions": ["cn"]
48
+ }
49
+ }
50
+ },
51
+ "a11y": "off",
52
+ "complexity": {
53
+ "noStaticOnlyClass": "off"
54
+ },
55
+ "style": {
56
+ "useTemplate": {
57
+ "level": "warn",
58
+ "fix": "safe"
59
+ }
60
+ }
61
+ },
62
+ "domains": {
63
+ "project": "recommended",
64
+ "react": "recommended",
65
+ "test": "recommended",
66
+ "types": "all"
67
+ }
68
+ },
69
+ "javascript": {
70
+ "parser": {
71
+ "unsafeParameterDecoratorsEnabled": true
72
+ },
73
+ "formatter": {
74
+ "quoteStyle": "double"
75
+ }
76
+ },
77
+ "assist": {
78
+ "enabled": true,
79
+ "actions": {
80
+ "source": {
81
+ "organizeImports": "on"
82
+ }
83
+ }
84
+ },
85
+ "overrides": [
86
+ {
87
+ "includes": [
88
+ "**/env.client.local.ts",
89
+ "**/env.client.debug.ts",
90
+ "**/env.client.develop.ts",
91
+ "**/env.client.main.ts",
92
+ "**/env.client.testing.ts",
93
+ "**/env.server.local.ts",
94
+ "**/env.server.develop.ts",
95
+ "**/env.server.main.ts",
96
+ "**/env.server.testing.ts",
97
+ "**/env.server.type.ts",
98
+ "apps/*/lib/cnst.ts",
99
+ "apps/*/lib/dict.ts",
100
+ "apps/*/lib/db.ts",
101
+ "apps/*/lib/srv.ts",
102
+ "apps/*/lib/st.ts",
103
+ "apps/*/lib/sig.ts",
104
+ "apps/*/lib/useClient.ts",
105
+ "apps/*/lib/useServer.ts",
106
+ "apps/*/client.ts",
107
+ "apps/*/server.ts",
108
+ "apps/*/lib/*/index.tsx",
109
+ "libs/*/lib/cnst.ts",
110
+ "libs/*/lib/dict.ts",
111
+ "libs/*/lib/db.ts",
112
+ "libs/*/lib/srv.ts",
113
+ "libs/*/lib/st.ts",
114
+ "libs/*/lib/sig.ts",
115
+ "libs/*/lib/useClient.ts",
116
+ "libs/*/lib/useServer.ts",
117
+ "libs/*/client.ts",
118
+ "libs/*/server.ts",
119
+ "libs/*/index.ts",
120
+ "libs/*/lib/*/index.tsx",
121
+ "apps/*/lib/__lib/**",
122
+ "libs/*/lib/__lib/**"
123
+ ],
124
+ "linter": { "enabled": false },
125
+ "formatter": { "enabled": false },
126
+ "assist": { "enabled": false }
127
+ },
128
+ {
129
+ "includes": [
130
+ "apps/**/*.ts",
131
+ "apps/**/*.tsx",
132
+ "libs/**/*.ts",
133
+ "libs/**/*.tsx",
134
+ "!**/*.test.ts",
135
+ "!**/*.test.tsx",
136
+ "!**/*.spec.ts",
137
+ "!**/*.spec.tsx",
138
+ "!**/*.constant.ts",
139
+ "!**/common/**",
140
+ "!**/env/**"
141
+ ],
142
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-throw-raw-error.grit"]
143
+ },
144
+ {
145
+ "includes": [
146
+ "{apps,libs}/**/page/**/*.ts",
147
+ "{apps,libs}/**/page/**/*.tsx",
148
+ "{apps,libs}/**/*.Unit.tsx",
149
+ "{apps,libs}/**/*.View.tsx"
150
+ ],
151
+ "plugins": [
152
+ "./node_modules/@akanjs/devkit/lint/no-import-client-functions.grit",
153
+ "./node_modules/@akanjs/devkit/lint/no-use-client-in-server.grit"
154
+ ]
155
+ },
156
+ {
157
+ "includes": ["{apps,libs}/**/page/**/*.ts", "{apps,libs}/**/page/**/*.tsx"],
158
+ "plugins": ["./node_modules/@akanjs/devkit/lint/non-scalar-props-restricted.grit"]
159
+ },
160
+ {
161
+ "includes": [
162
+ "{apps,libs}/**/*.constant.ts",
163
+ "{apps,libs}/**/*.document.ts",
164
+ "{apps,libs}/**/*.service.ts",
165
+ "{apps,libs}/**/*.store.ts"
166
+ ],
167
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-js-private-class-method.grit"]
168
+ },
169
+ {
170
+ "includes": ["{apps,libs}/**/*.store.ts"],
171
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-return-in-store-action.grit"]
172
+ },
173
+ {
174
+ "includes": ["{apps,libs}/**/*.ts", "{apps,libs}/**/*.tsx"],
175
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-deprecated-log-level.grit"]
176
+ },
177
+ {
178
+ "includes": ["{apps,libs}/**/*.signal.ts"],
179
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-redeclare-predefined-endpoint.grit"]
180
+ },
181
+ {
182
+ "includes": ["{apps,libs}/**/*.Util.tsx", "{apps,libs}/**/*.Zone.tsx"],
183
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-model-type-in-util-zone.grit"]
184
+ },
185
+ {
186
+ "includes": ["{apps,libs}/**/ui/**/*.tsx", "!**/*.test.tsx", "!**/*.spec.tsx"],
187
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-async-component-in-ui.grit"]
188
+ },
189
+ {
190
+ "includes": ["{apps,libs}/**/*.tsx", "!**/*.test.tsx", "!**/*.spec.tsx"],
191
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-unpublished-form-setter.grit"]
192
+ },
193
+ {
194
+ "includes": [
195
+ "{apps,libs}/**/*.ts",
196
+ "{apps,libs}/**/*.tsx",
197
+ "!**/*.test.ts",
198
+ "!**/*.test.tsx",
199
+ "!**/*.spec.ts",
200
+ "!**/*.spec.tsx"
201
+ ],
202
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-init-fetch-in-client.grit"]
203
+ },
204
+ {
205
+ "includes": [
206
+ "**/ui/**/*.ts",
207
+ "**/ui/**/*.tsx",
208
+ "**/webkit/**/*.ts",
209
+ "**/webkit/**/*.tsx",
210
+ "**/common/**/*.ts",
211
+ "**/page/**/*.tsx",
212
+ "**/*.constant.ts",
213
+ "**/*.store.ts",
214
+ "**/*.Template.tsx",
215
+ "**/*.Unit.tsx",
216
+ "**/*.Util.tsx",
217
+ "**/*.View.tsx",
218
+ "**/*.Zone.tsx",
219
+ "!**/*.test.ts",
220
+ "!**/*.test.tsx",
221
+ "!**/*.spec.ts",
222
+ "!**/*.spec.tsx"
223
+ ],
224
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-bang-comment-in-client.grit"]
225
+ },
226
+ {
227
+ "includes": [
228
+ "{apps,libs}/**/*.constant.ts",
229
+ "{apps,libs}/**/*.dictionary.ts",
230
+ "{apps,libs}/**/*.document.ts",
231
+ "{apps,libs}/**/*.service.ts",
232
+ "{apps,libs}/**/*.signal.ts",
233
+ "{apps,libs}/**/*.store.ts",
234
+ "{apps,libs}/**/*.Template.tsx",
235
+ "{apps,libs}/**/*.Unit.tsx",
236
+ "{apps,libs}/**/*.Util.tsx",
237
+ "{apps,libs}/**/*.View.tsx",
238
+ "{apps,libs}/**/*.Zone.tsx"
239
+ ],
240
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-deep-internal-import.grit"]
241
+ },
242
+ {
243
+ "includes": [
244
+ "{apps,libs}/**/page/**/*.ts",
245
+ "{apps,libs}/**/page/**/*.tsx",
246
+ "{apps,libs}/**/index.ts",
247
+ "{apps,libs}/**/index.tsx",
248
+ "{apps,libs}/**/cnst.ts",
249
+ "{apps,libs}/**/db.ts",
250
+ "{apps,libs}/**/dict.ts",
251
+ "{apps,libs}/**/option.ts",
252
+ "{apps,libs}/**/sig.ts",
253
+ "{apps,libs}/**/srv.ts",
254
+ "{apps,libs}/**/st.ts",
255
+ "{apps,libs}/**/*.constant.ts",
256
+ "{apps,libs}/**/*.dictionary.ts",
257
+ "{apps,libs}/**/*.document.ts",
258
+ "{apps,libs}/**/*.service.ts",
259
+ "{apps,libs}/**/*.signal.ts",
260
+ "{apps,libs}/**/*.signal.test.ts",
261
+ "{apps,libs}/**/*.service.test.ts",
262
+ "{apps,libs}/**/*.store.ts",
263
+ "{apps,libs}/**/*.Template.tsx",
264
+ "{apps,libs}/**/*.Unit.tsx",
265
+ "{apps,libs}/**/*.Util.tsx",
266
+ "{apps,libs}/**/*.View.tsx",
267
+ "{apps,libs}/**/*.Zone.tsx"
268
+ ],
269
+ "plugins": [
270
+ "./node_modules/@akanjs/devkit/lint/no-import-external-library.grit",
271
+ "./node_modules/@akanjs/devkit/lint/no-deep-internal-import.grit"
272
+ ]
273
+ },
274
+ {
275
+ "includes": [
276
+ "apps/**/*.tsx",
277
+ "libs/**/*.tsx",
278
+ "apps/**/ui/**/*.ts",
279
+ "libs/**/ui/**/*.ts",
280
+ "apps/**/webkit/**/*.ts",
281
+ "libs/**/webkit/**/*.ts",
282
+ "apps/**/page/**/*.ts",
283
+ "libs/**/page/**/*.ts",
284
+ "apps/**/common/**/*.ts",
285
+ "libs/**/common/**/*.ts",
286
+ "apps/**/*.store.ts",
287
+ "libs/**/*.store.ts",
288
+ "apps/**/*.constant.ts",
289
+ "libs/**/*.constant.ts",
290
+ "!**/*.test.ts",
291
+ "!**/*.test.tsx",
292
+ "!**/*.spec.ts",
293
+ "!**/*.spec.tsx"
294
+ ],
295
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-import-server-in-client.grit"]
296
+ },
297
+ {
298
+ "includes": [
299
+ "apps/**/*.document.ts",
300
+ "libs/**/*.document.ts",
301
+ "apps/**/*.dictionary.ts",
302
+ "libs/**/*.dictionary.ts",
303
+ "apps/**/*.service.ts",
304
+ "libs/**/*.service.ts",
305
+ "apps/**/*.signal.ts",
306
+ "libs/**/*.signal.ts",
307
+ "apps/**/srvkit/**/*.ts",
308
+ "libs/**/srvkit/**/*.ts",
309
+ "apps/**/common/**/*.ts",
310
+ "libs/**/common/**/*.ts",
311
+ "apps/**/*.constant.ts",
312
+ "libs/**/*.constant.ts",
313
+ "!**/*.test.ts",
314
+ "!**/*.test.tsx",
315
+ "!**/*.spec.ts",
316
+ "!**/*.spec.tsx"
317
+ ],
318
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-import-client-in-server.grit"]
319
+ },
320
+ {
321
+ "includes": [
322
+ "apps/**/*.ts",
323
+ "apps/**/*.tsx",
324
+ "libs/**/*.ts",
325
+ "libs/**/*.tsx",
326
+ "!**/*.test.ts",
327
+ "!**/*.test.tsx",
328
+ "!**/*.spec.ts",
329
+ "!**/*.spec.tsx"
330
+ ],
331
+ "plugins": [
332
+ "./node_modules/@akanjs/devkit/lint/no-raw-palette-class.grit",
333
+ "./node_modules/@akanjs/devkit/lint/no-arbitrary-color.grit",
334
+ "./node_modules/@akanjs/devkit/lint/no-daisyui-legacy-class.grit",
335
+ "./node_modules/@akanjs/devkit/lint/no-inline-color.grit",
336
+ "./node_modules/@akanjs/devkit/lint/no-interpolated-arbitrary-class.grit"
337
+ ]
338
+ }
339
+ ]
340
+ }
package/biomeBase.ts ADDED
@@ -0,0 +1,9 @@
1
+ /** `extends` target for a workspace `biome.json`; Biome resolves it through node_modules. */
2
+ export const biomeBaseConfig = "@akanjs/devkit/biome.base.json";
3
+
4
+ // Biome moves rules between groups across minors — `noUnnecessaryConditions` sat in `nursery` at 2.4 and moved to
5
+ // `suspicious` at 2.5 — and the stale position is a hard "unknown key" error, not a warning. A workspace whose
6
+ // Biome disagrees with the shipped base config therefore fails to load it at all, which is why the version is
7
+ // pinned here instead of resolved to latest at create time. Bump this and `biome.base.json` in one commit, and run
8
+ // `biome migrate --write` in the workspace root and in `pkgs/@akanjs/devkit` so both configs move together.
9
+ export const biomeVersion = "2.5.12";
@@ -11,6 +11,7 @@ import {
11
11
  clearRootCapacitorConfigs,
12
12
  formatAndroidReleaseSigningError,
13
13
  getAdbDeviceStateIssues,
14
+ getAndroidLocalServerHost,
14
15
  getMissingAndroidReleaseSigningKeys,
15
16
  isPlaceholderAppId,
16
17
  materializeCapacitorConfig,
@@ -339,6 +340,13 @@ describe("Android signing diagnostics", () => {
339
340
  "Android device abc123 is unauthorized. Confirm USB debugging authorization on the device.",
340
341
  "Android device xyz is offline. Reconnect the device or restart adb.",
341
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");
342
350
  });
343
351
  });
344
352
 
package/capacitorApp.ts CHANGED
@@ -24,7 +24,9 @@ interface RunIosConfig extends RunConfig {
24
24
  iosDeviceId?: string;
25
25
  }
26
26
 
27
- interface PrepareConfig extends RunConfig {}
27
+ interface PrepareConfig extends RunConfig {
28
+ iosRunTargetKind?: IosRunTargetKind;
29
+ }
28
30
 
29
31
  type MobileCommandEnv = Record<string, string | undefined>;
30
32
 
@@ -119,10 +121,21 @@ interface MaterializeCapacitorConfigOptions {
119
121
  localServerUrl?: string;
120
122
  localIp?: string;
121
123
  }
124
+ type MobilePlatform = "ios" | "android";
125
+
126
+ /**
127
+ * `AppExecutor.spawn`'s own options plus the two the Capacitor config is written from before the spawn:
128
+ * the platform the command targets, and — for iOS — whether it targets a device or a simulator, which
129
+ * decide the `capacitor.config.json` that command reads.
130
+ */
131
+ type SpawnMobileOptions = Parameters<AppExecutor["spawn"]>[2] & {
132
+ platform?: MobilePlatform;
133
+ iosRunTargetKind?: IosRunTargetKind;
134
+ };
122
135
 
123
136
  export interface LocalDevHostResolution {
124
137
  host: string;
125
- source: "override" | "detected" | "loopback";
138
+ source: "override" | "detected" | "loopback" | "platform";
126
139
  candidates: { name: string; address: string }[];
127
140
  }
128
141
 
@@ -565,6 +578,16 @@ export function getAdbDeviceStateIssues(output: string) {
565
578
  });
566
579
  }
567
580
 
581
+ export function getAndroidLocalServerHost(adbDevicesOutput: string | undefined, fallbackHost: string) {
582
+ const onlineDeviceIds =
583
+ adbDevicesOutput
584
+ ?.split(/\r?\n/)
585
+ .map((line) => line.trim().split(/\s+/))
586
+ .filter(([id, state]) => id && state === "device")
587
+ .map(([id]) => id) ?? [];
588
+ return onlineDeviceIds.length === 1 && onlineDeviceIds[0]?.startsWith("emulator-") ? "10.0.2.2" : fallbackHost;
589
+ }
590
+
568
591
  export const ANDROID_MIN_SDK_VERSION = 26;
569
592
  export function raiseGradleMinSdkVersion(content: string, floor: number = ANDROID_MIN_SDK_VERSION): string | null {
570
593
  const match = content.match(/minSdkVersion\s*=\s*(\d+)/);
@@ -748,7 +771,7 @@ export class CapacitorApp {
748
771
  async save() {
749
772
  await this.project.commit();
750
773
  }
751
- async #prepareIos({ operation, env, regenerate = false }: PrepareConfig) {
774
+ async #prepareIos({ operation, env, regenerate = false, iosRunTargetKind }: PrepareConfig) {
752
775
  await this.init({ platform: "ios", operation, env, regenerate });
753
776
  await this.#prepareTargetAssets();
754
777
  await this.#prepareExternalFiles("ios");
@@ -757,9 +780,10 @@ export class CapacitorApp {
757
780
  await this.#applyDeepLinks("ios", { operation, env });
758
781
  await this.#flushIosEntitlements();
759
782
  await this.project.commit();
783
+ await this.#setCodeSignEntitlementsInIosIfExists("App/App.entitlements");
760
784
  await this.#generateAssets({ operation, env });
761
785
  this.app.verbose(`syncing iOS`);
762
- await this.#spawnMobile("npx", ["cap", "sync", "ios"], { operation, env });
786
+ await this.#spawnMobile("npx", ["cap", "sync", "ios"], { operation, env }, { iosRunTargetKind });
763
787
  this.app.verbose(`sync completed.`);
764
788
  }
765
789
  async buildIos({ env = "debug", regenerate = false }: { env?: RunConfig["env"]; regenerate?: boolean } = {}) {
@@ -777,14 +801,14 @@ export class CapacitorApp {
777
801
  }
778
802
  async runIos({ operation, env, regenerate = false, noAllowProvisioningUpdates = false, iosDeviceId }: RunIosConfig) {
779
803
  if (operation === "release") await this.prepareWww();
780
- await this.#prepareIos({ operation, env, regenerate });
781
804
  const runTarget = await this.#selectIosRunTarget(iosDeviceId);
805
+ await this.#prepareIos({ operation, env, regenerate, iosRunTargetKind: runTarget.kind });
782
806
  if (runTarget.kind === "simulator") {
783
807
  await this.#spawnMobile(
784
808
  "npx",
785
- ["cap", "run", "ios", "--target", runTarget.id],
809
+ ["cap", "run", "ios", "--target", runTarget.id, "--no-sync"],
786
810
  { operation, env },
787
- { stdio: "inherit" },
811
+ { stdio: "inherit", platform: "ios", iosRunTargetKind: runTarget.kind },
788
812
  );
789
813
  return;
790
814
  }
@@ -878,7 +902,10 @@ export class CapacitorApp {
878
902
  noAllowProvisioningUpdates: boolean;
879
903
  }) {
880
904
  const mobileEnv = sanitizeIosNativeRunEnv(await this.#commandEnv(operation, env));
881
- const configContent = await this.#writeCapacitorConfig({ operation }, mobileEnv);
905
+ const configContent = await this.#writeCapacitorConfig(
906
+ { operation, platform: "ios", iosRunTargetKind: runTarget.kind },
907
+ mobileEnv,
908
+ );
882
909
  await this.#writeRootCapacitorConfig(configContent);
883
910
  const scheme = this.#iosScheme();
884
911
  const command = buildIosNativeRunCommand({
@@ -943,6 +970,7 @@ export class CapacitorApp {
943
970
  await this.#applyAndroidMinSdkVersion();
944
971
  await this.#applyPermissions({ operation, env });
945
972
  await this.#applyDeepLinks("android", { operation, env });
973
+ await this.#disableNativeKeyboardResizeInAndroid();
946
974
  await this.project.commit();
947
975
  await this.#generateAssets({ operation, env });
948
976
  await this.#ensureAndroidAssetsDir();
@@ -1010,6 +1038,24 @@ export class CapacitorApp {
1010
1038
  async openAndroid() {
1011
1039
  await this.#spawnMobile("npx", ["cap", "open", "android"], { operation: "local", env: "local" });
1012
1040
  }
1041
+ async #disableNativeKeyboardResizeInAndroid() {
1042
+ const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
1043
+ let manifest = await readFile(manifestPath, "utf8");
1044
+ let changed = false;
1045
+ manifest = manifest.replace(/<activity\b[^>]*android:name="\.MainActivity"[^>]*>/, (activityTag) => {
1046
+ if (activityTag.includes("android:windowSoftInputMode=")) {
1047
+ const nextTag = activityTag.replace(
1048
+ /android:windowSoftInputMode="[^"]*"/,
1049
+ 'android:windowSoftInputMode="adjustNothing"',
1050
+ );
1051
+ changed ||= nextTag !== activityTag;
1052
+ return nextTag;
1053
+ }
1054
+ changed = true;
1055
+ return activityTag.replace(/>$/, '\n android:windowSoftInputMode="adjustNothing">');
1056
+ });
1057
+ if (changed) await writeFile(manifestPath, manifest);
1058
+ }
1013
1059
  async #ensureAndroidAssetsDir() {
1014
1060
  await mkdir(path.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
1015
1061
  }
@@ -1100,12 +1146,19 @@ export class CapacitorApp {
1100
1146
  if (html.includes("window.__AKAN_MOBILE_TARGET__")) return html;
1101
1147
  return html.replace(/<\/head\s*>/i, `${script}\n</head>`);
1102
1148
  }
1103
- async #writeCapacitorConfig({ operation }: Pick<RunConfig, "operation">, commandEnv: MobileCommandEnv) {
1149
+ async #writeCapacitorConfig(
1150
+ {
1151
+ operation,
1152
+ platform,
1153
+ iosRunTargetKind,
1154
+ }: Pick<RunConfig, "operation"> & { platform?: MobilePlatform; iosRunTargetKind?: IosRunTargetKind },
1155
+ commandEnv: MobileCommandEnv,
1156
+ ) {
1104
1157
  await mkdir(this.targetRoot, { recursive: true });
1105
1158
  let localIp: string | undefined;
1106
1159
  if (operation === "local") {
1107
1160
  const override = commandEnv.AKAN_PUBLIC_CLIENT_HOST ?? process.env.AKAN_PUBLIC_CLIENT_HOST;
1108
- const resolution = selectLocalDevHost(os.networkInterfaces(), { override });
1161
+ const resolution = await this.#resolveLocalDevHost({ override, platform, iosRunTargetKind });
1109
1162
  localIp = resolution.host;
1110
1163
  this.#logDevHostResolution(resolution, commandEnv);
1111
1164
  }
@@ -1118,11 +1171,36 @@ export class CapacitorApp {
1118
1171
  await Bun.write(path.join(this.targetRoot, "capacitor.config.json"), content);
1119
1172
  return content;
1120
1173
  }
1174
+ // An explicit override always wins; an emulator/simulator run reaches the host machine through a
1175
+ // loopback alias (10.0.2.2 / localhost), so LAN detection only applies to physical-device runs.
1176
+ async #resolveLocalDevHost({
1177
+ override,
1178
+ platform,
1179
+ iosRunTargetKind,
1180
+ }: {
1181
+ override?: string;
1182
+ platform?: MobilePlatform;
1183
+ iosRunTargetKind?: IosRunTargetKind;
1184
+ }): Promise<LocalDevHostResolution> {
1185
+ const resolution = selectLocalDevHost(os.networkInterfaces(), { override });
1186
+ if (resolution.source === "override") return resolution;
1187
+ if (platform === "ios" && iosRunTargetKind === "simulator")
1188
+ return { ...resolution, host: "localhost", source: "platform" };
1189
+ if (platform === "android") {
1190
+ try {
1191
+ const host = getAndroidLocalServerHost(await this.#spawn("adb", ["devices"]), resolution.host);
1192
+ return host === resolution.host ? resolution : { ...resolution, host, source: "platform" };
1193
+ } catch {
1194
+ return resolution;
1195
+ }
1196
+ }
1197
+ return resolution;
1198
+ }
1121
1199
  // Surface the live-reload URL a physical device must reach, and warn when auto-detection landed on
1122
1200
  // a likely-unreachable host so a blank WebView is not mistaken for an app bug.
1123
1201
  #logDevHostResolution(resolution: LocalDevHostResolution, commandEnv: MobileCommandEnv) {
1124
1202
  this.app.log(`Mobile live-reload server: ${this.#localCsrUrl(resolution.host, commandEnv)}`);
1125
- if (resolution.source === "override") return;
1203
+ if (resolution.source === "override" || resolution.source === "platform") return;
1126
1204
  const suspicious = resolution.host === "127.0.0.1" || resolution.host.startsWith("169.254.");
1127
1205
  const alternatives = resolution.candidates.filter((candidate) => candidate.address !== resolution.host);
1128
1206
  if (!suspicious && alternatives.length === 0) return;
@@ -1332,20 +1410,29 @@ export class CapacitorApp {
1332
1410
  command: string,
1333
1411
  args: string[] = [],
1334
1412
  { operation, env }: Pick<RunConfig, "operation" | "env">,
1335
- options: Parameters<AppExecutor["spawn"]>[2] = {},
1413
+ options: SpawnMobileOptions = {},
1336
1414
  ) {
1415
+ const { iosRunTargetKind, platform, ...spawnOptions } = options;
1337
1416
  const mobileEnv = { ...(await this.#commandEnv(operation, env)), ...options.env };
1338
- const configContent = await this.#writeCapacitorConfig({ operation }, mobileEnv);
1417
+ const configContent = await this.#writeCapacitorConfig(
1418
+ { operation, platform: platform ?? this.#inferMobilePlatform(args), iosRunTargetKind },
1419
+ mobileEnv,
1420
+ );
1339
1421
  await this.#writeRootCapacitorConfig(configContent);
1340
1422
  try {
1341
1423
  return await this.#spawn(command, args, {
1342
- ...options,
1424
+ ...spawnOptions,
1343
1425
  env: mobileEnv,
1344
1426
  });
1345
1427
  } finally {
1346
1428
  await this.#clearRootCapacitorConfigs();
1347
1429
  }
1348
1430
  }
1431
+ #inferMobilePlatform(args: string[]): MobilePlatform | undefined {
1432
+ if (args.includes("android")) return "android";
1433
+ if (args.includes("ios")) return "ios";
1434
+ return undefined;
1435
+ }
1349
1436
  async addCamera() {
1350
1437
  await this.#setPermissionInIos({
1351
1438
  cameraUsageDescription: "$(PRODUCT_NAME) requires access to the camera to take photos.",
@@ -1446,14 +1533,31 @@ export class CapacitorApp {
1446
1533
  let end = index;
1447
1534
  while (end < lines.length && !/^\s*\};\s*$/.test(lines[end] ?? "")) end++;
1448
1535
  const settings = lines.slice(start, end + 1);
1449
- if (settings.some((setting) => setting.includes("CODE_SIGN_ENTITLEMENTS"))) continue;
1536
+ const configName = lines.slice(end + 1, end + 8).find((setting) => setting.includes("name = ")) ?? "";
1450
1537
  const indent = line.match(/^\s*/)?.[0] ?? "";
1451
- lines.splice(index, 0, `${indent}CODE_SIGN_ENTITLEMENTS = ${entitlementsRelPath};`);
1452
- index++;
1453
- changed = true;
1538
+ const insertSettings = [];
1539
+ if (!settings.some((setting) => setting.includes("CODE_SIGN_ENTITLEMENTS"))) {
1540
+ insertSettings.push(`${indent}CODE_SIGN_ENTITLEMENTS = ${entitlementsRelPath};`);
1541
+ }
1542
+ if (
1543
+ configName.includes("name = Debug;") &&
1544
+ !settings.some((setting) => setting.includes("CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION"))
1545
+ ) {
1546
+ insertSettings.push(`${indent}CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;`);
1547
+ }
1548
+ if (insertSettings.length > 0) {
1549
+ lines.splice(index, 0, ...insertSettings);
1550
+ index += insertSettings.length;
1551
+ changed = true;
1552
+ }
1454
1553
  }
1455
1554
  if (changed) await writeFile(pbxprojPath, lines.join("\n"));
1456
1555
  }
1556
+ async #setCodeSignEntitlementsInIosIfExists(entitlementsRelPath: string) {
1557
+ const entitlementsPath = path.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
1558
+ if (!(await Bun.file(entitlementsPath).exists())) return;
1559
+ await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
1560
+ }
1457
1561
  async #setUrlSchemesInAndroid(schemes: string[]) {
1458
1562
  const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
1459
1563
  let manifest = await readFile(manifestPath, "utf8");
@@ -1,4 +1,4 @@
1
- import { mkdir } from "node:fs/promises";
1
+ import { chmod, mkdir } from "node:fs/promises";
2
2
  import dayjs from "dayjs";
3
3
  import { FileSys } from "../fileSys";
4
4
  import {
@@ -29,9 +29,16 @@ export class GlobalConfig {
29
29
  remoteEnvServers: akanConfig.remoteEnvServers ?? defaultAkanGlobalConfig.remoteEnvServers,
30
30
  };
31
31
  }
32
+ /**
33
+ * This file holds the cloud jwt, a refresh token that does not expire, and the LLM api key, so it is
34
+ * written owner-only — the same `0600` the runtime gives its control socket. `Bun.write` takes no mode
35
+ * and lands on `0666 & ~umask` (0644 on a default shell), so the mode is applied after the write; an
36
+ * existing world-readable file is tightened by the next write rather than left as it was found.
37
+ */
32
38
  static async #setAkanGlobalConfig(akanConfig: AkanGlobalConfig) {
33
- await mkdir(basePath, { recursive: true });
39
+ await mkdir(basePath, { recursive: true, mode: 0o700 });
34
40
  await Bun.write(configPath, JSON.stringify(akanConfig, null, 2));
41
+ await chmod(configPath, 0o600);
35
42
  }
36
43
  static async getHostConfig(host = GlobalConfig.akanCloudHost): Promise<HostConfig> {
37
44
  const akanConfig = await GlobalConfig.#getAkanGlobalConfig();