@akanjs/devkit 3.0.0-alpha.5 → 3.0.0-alpha.50
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.
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/agentsIndex.test.ts +10 -0
- package/agentsIndex.ts +47 -1
- package/aiEditor.ts +1 -1
- package/akanConfig/akanConfig.test.ts +47 -10
- package/akanConfig/akanConfig.ts +23 -16
- package/akanContext.ts +53 -11
- package/applicationBuildRunner.test.ts +1 -1
- package/applicationBuildRunner.ts +0 -1
- package/biome.base.json +321 -0
- package/biomeBase.ts +9 -0
- package/capacitorApp.test.ts +8 -0
- package/capacitorApp.ts +112 -18
- package/executors.test.ts +29 -5
- package/executors.ts +23 -5
- package/formSetterScanner.test.ts +80 -0
- package/formSetterScanner.ts +92 -0
- package/frontendBuild/buildRouteClient.test.ts +28 -2
- package/frontendBuild/clientBuildTypes.ts +4 -0
- package/frontendBuild/clientEntriesBundler.ts +4 -1
- package/frontendBuild/cssCandidateCache.test.ts +38 -0
- package/frontendBuild/cssCandidateCache.ts +15 -3
- package/frontendBuild/cssCompiler.ts +122 -11
- package/frontendBuild/cssImportResolver.ts +8 -7
- package/frontendBuild/frontendBuild.test.ts +88 -1
- package/frontendBuild/hmrWatcher.ts +1 -1
- package/frontendBuild/routeClientBuilder.ts +12 -5
- package/frontendBuild/ssrBaseArtifactBuilder.ts +20 -4
- package/frontendBuild/styleGuard.test.ts +15 -0
- package/frontendBuild/styleGuard.ts +17 -0
- package/frontendBuild/vendorSpecifiers.ts +1 -0
- package/getCredentials.ts +1 -3
- package/incrementalBuilder/devWatchBatch.test.ts +18 -20
- package/incrementalBuilder/incrementalBuilder.host.ts +1 -1
- package/incrementalBuilder/incrementalBuilder.proc.ts +2 -2
- package/integration/devStabilityHarness.ts +1 -5
- package/lint/no-daisyui-legacy-class.grit +26 -9
- package/lint/no-import-client-in-server.grit +48 -0
- package/lint/no-import-server-in-client.grit +45 -0
- package/lint/no-model-type-in-util-zone.grit +58 -0
- package/lint/no-return-in-store-action.grit +35 -0
- package/lint/no-unpublished-form-setter.grit +41 -0
- package/linter.ts +17 -12
- package/package.json +4 -4
- package/qualityScanner.test.ts +52 -0
- package/qualityScanner.ts +48 -37
- package/repoIdentity.ts +42 -0
- package/routeSourceValidator.test.ts +41 -0
- package/routeSourceValidator.ts +2 -44
- package/scanInfo.ts +21 -20
- package/transforms/externalizeFrameworkPlugin.ts +0 -1
- package/workspaceLayout.test.ts +56 -4
- package/workspaceLayout.ts +49 -4
package/biome.base.json
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.5.8/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}/**/*.signal.ts"],
|
|
175
|
+
"plugins": ["./node_modules/@akanjs/devkit/lint/no-redeclare-predefined-endpoint.grit"]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"includes": ["{apps,libs}/**/*.Util.tsx", "{apps,libs}/**/*.Zone.tsx"],
|
|
179
|
+
"plugins": ["./node_modules/@akanjs/devkit/lint/no-model-type-in-util-zone.grit"]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"includes": ["{apps,libs}/**/*.tsx", "!**/*.test.tsx", "!**/*.spec.tsx"],
|
|
183
|
+
"plugins": ["./node_modules/@akanjs/devkit/lint/no-unpublished-form-setter.grit"]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"includes": [
|
|
187
|
+
"**/ui/**/*.ts",
|
|
188
|
+
"**/ui/**/*.tsx",
|
|
189
|
+
"**/webkit/**/*.ts",
|
|
190
|
+
"**/webkit/**/*.tsx",
|
|
191
|
+
"**/common/**/*.ts",
|
|
192
|
+
"**/page/**/*.tsx",
|
|
193
|
+
"**/*.constant.ts",
|
|
194
|
+
"**/*.store.ts",
|
|
195
|
+
"**/*.Template.tsx",
|
|
196
|
+
"**/*.Unit.tsx",
|
|
197
|
+
"**/*.Util.tsx",
|
|
198
|
+
"**/*.View.tsx",
|
|
199
|
+
"**/*.Zone.tsx",
|
|
200
|
+
"!**/*.test.ts",
|
|
201
|
+
"!**/*.test.tsx",
|
|
202
|
+
"!**/*.spec.ts",
|
|
203
|
+
"!**/*.spec.tsx"
|
|
204
|
+
],
|
|
205
|
+
"plugins": ["./node_modules/@akanjs/devkit/lint/no-bang-comment-in-client.grit"]
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"includes": [
|
|
209
|
+
"{apps,libs}/**/*.constant.ts",
|
|
210
|
+
"{apps,libs}/**/*.dictionary.ts",
|
|
211
|
+
"{apps,libs}/**/*.document.ts",
|
|
212
|
+
"{apps,libs}/**/*.service.ts",
|
|
213
|
+
"{apps,libs}/**/*.signal.ts",
|
|
214
|
+
"{apps,libs}/**/*.store.ts",
|
|
215
|
+
"{apps,libs}/**/*.Template.tsx",
|
|
216
|
+
"{apps,libs}/**/*.Unit.tsx",
|
|
217
|
+
"{apps,libs}/**/*.Util.tsx",
|
|
218
|
+
"{apps,libs}/**/*.View.tsx",
|
|
219
|
+
"{apps,libs}/**/*.Zone.tsx"
|
|
220
|
+
],
|
|
221
|
+
"plugins": ["./node_modules/@akanjs/devkit/lint/no-deep-internal-import.grit"]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"includes": [
|
|
225
|
+
"{apps,libs}/**/page/**/*.ts",
|
|
226
|
+
"{apps,libs}/**/page/**/*.tsx",
|
|
227
|
+
"{apps,libs}/**/index.ts",
|
|
228
|
+
"{apps,libs}/**/index.tsx",
|
|
229
|
+
"{apps,libs}/**/cnst.ts",
|
|
230
|
+
"{apps,libs}/**/db.ts",
|
|
231
|
+
"{apps,libs}/**/dict.ts",
|
|
232
|
+
"{apps,libs}/**/option.ts",
|
|
233
|
+
"{apps,libs}/**/sig.ts",
|
|
234
|
+
"{apps,libs}/**/srv.ts",
|
|
235
|
+
"{apps,libs}/**/st.ts",
|
|
236
|
+
"{apps,libs}/**/*.constant.ts",
|
|
237
|
+
"{apps,libs}/**/*.dictionary.ts",
|
|
238
|
+
"{apps,libs}/**/*.document.ts",
|
|
239
|
+
"{apps,libs}/**/*.service.ts",
|
|
240
|
+
"{apps,libs}/**/*.signal.ts",
|
|
241
|
+
"{apps,libs}/**/*.signal.test.ts",
|
|
242
|
+
"{apps,libs}/**/*.service.test.ts",
|
|
243
|
+
"{apps,libs}/**/*.store.ts",
|
|
244
|
+
"{apps,libs}/**/*.Template.tsx",
|
|
245
|
+
"{apps,libs}/**/*.Unit.tsx",
|
|
246
|
+
"{apps,libs}/**/*.Util.tsx",
|
|
247
|
+
"{apps,libs}/**/*.View.tsx",
|
|
248
|
+
"{apps,libs}/**/*.Zone.tsx"
|
|
249
|
+
],
|
|
250
|
+
"plugins": [
|
|
251
|
+
"./node_modules/@akanjs/devkit/lint/no-import-external-library.grit",
|
|
252
|
+
"./node_modules/@akanjs/devkit/lint/no-deep-internal-import.grit"
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"includes": [
|
|
257
|
+
"apps/**/*.tsx",
|
|
258
|
+
"libs/**/*.tsx",
|
|
259
|
+
"apps/**/ui/**/*.ts",
|
|
260
|
+
"libs/**/ui/**/*.ts",
|
|
261
|
+
"apps/**/webkit/**/*.ts",
|
|
262
|
+
"libs/**/webkit/**/*.ts",
|
|
263
|
+
"apps/**/page/**/*.ts",
|
|
264
|
+
"libs/**/page/**/*.ts",
|
|
265
|
+
"apps/**/common/**/*.ts",
|
|
266
|
+
"libs/**/common/**/*.ts",
|
|
267
|
+
"apps/**/*.store.ts",
|
|
268
|
+
"libs/**/*.store.ts",
|
|
269
|
+
"apps/**/*.constant.ts",
|
|
270
|
+
"libs/**/*.constant.ts",
|
|
271
|
+
"!**/*.test.ts",
|
|
272
|
+
"!**/*.test.tsx",
|
|
273
|
+
"!**/*.spec.ts",
|
|
274
|
+
"!**/*.spec.tsx"
|
|
275
|
+
],
|
|
276
|
+
"plugins": ["./node_modules/@akanjs/devkit/lint/no-import-server-in-client.grit"]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"includes": [
|
|
280
|
+
"apps/**/*.document.ts",
|
|
281
|
+
"libs/**/*.document.ts",
|
|
282
|
+
"apps/**/*.dictionary.ts",
|
|
283
|
+
"libs/**/*.dictionary.ts",
|
|
284
|
+
"apps/**/*.service.ts",
|
|
285
|
+
"libs/**/*.service.ts",
|
|
286
|
+
"apps/**/*.signal.ts",
|
|
287
|
+
"libs/**/*.signal.ts",
|
|
288
|
+
"apps/**/srvkit/**/*.ts",
|
|
289
|
+
"libs/**/srvkit/**/*.ts",
|
|
290
|
+
"apps/**/common/**/*.ts",
|
|
291
|
+
"libs/**/common/**/*.ts",
|
|
292
|
+
"apps/**/*.constant.ts",
|
|
293
|
+
"libs/**/*.constant.ts",
|
|
294
|
+
"!**/*.test.ts",
|
|
295
|
+
"!**/*.test.tsx",
|
|
296
|
+
"!**/*.spec.ts",
|
|
297
|
+
"!**/*.spec.tsx"
|
|
298
|
+
],
|
|
299
|
+
"plugins": ["./node_modules/@akanjs/devkit/lint/no-import-client-in-server.grit"]
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"includes": [
|
|
303
|
+
"apps/**/*.ts",
|
|
304
|
+
"apps/**/*.tsx",
|
|
305
|
+
"libs/**/*.ts",
|
|
306
|
+
"libs/**/*.tsx",
|
|
307
|
+
"!**/*.test.ts",
|
|
308
|
+
"!**/*.test.tsx",
|
|
309
|
+
"!**/*.spec.ts",
|
|
310
|
+
"!**/*.spec.tsx"
|
|
311
|
+
],
|
|
312
|
+
"plugins": [
|
|
313
|
+
"./node_modules/@akanjs/devkit/lint/no-raw-palette-class.grit",
|
|
314
|
+
"./node_modules/@akanjs/devkit/lint/no-arbitrary-color.grit",
|
|
315
|
+
"./node_modules/@akanjs/devkit/lint/no-daisyui-legacy-class.grit",
|
|
316
|
+
"./node_modules/@akanjs/devkit/lint/no-inline-color.grit",
|
|
317
|
+
"./node_modules/@akanjs/devkit/lint/no-interpolated-arbitrary-class.grit"
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
}
|
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.8";
|
package/capacitorApp.test.ts
CHANGED
|
@@ -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,11 @@ interface MaterializeCapacitorConfigOptions {
|
|
|
119
121
|
localServerUrl?: string;
|
|
120
122
|
localIp?: string;
|
|
121
123
|
}
|
|
124
|
+
type MobilePlatform = "ios" | "android";
|
|
122
125
|
|
|
123
126
|
export interface LocalDevHostResolution {
|
|
124
127
|
host: string;
|
|
125
|
-
source: "override" | "detected" | "loopback";
|
|
128
|
+
source: "override" | "detected" | "loopback" | "platform";
|
|
126
129
|
candidates: { name: string; address: string }[];
|
|
127
130
|
}
|
|
128
131
|
|
|
@@ -565,6 +568,16 @@ export function getAdbDeviceStateIssues(output: string) {
|
|
|
565
568
|
});
|
|
566
569
|
}
|
|
567
570
|
|
|
571
|
+
export function getAndroidLocalServerHost(adbDevicesOutput: string | undefined, fallbackHost: string) {
|
|
572
|
+
const onlineDeviceIds =
|
|
573
|
+
adbDevicesOutput
|
|
574
|
+
?.split(/\r?\n/)
|
|
575
|
+
.map((line) => line.trim().split(/\s+/))
|
|
576
|
+
.filter(([id, state]) => id && state === "device")
|
|
577
|
+
.map(([id]) => id) ?? [];
|
|
578
|
+
return onlineDeviceIds.length === 1 && onlineDeviceIds[0]?.startsWith("emulator-") ? "10.0.2.2" : fallbackHost;
|
|
579
|
+
}
|
|
580
|
+
|
|
568
581
|
export const ANDROID_MIN_SDK_VERSION = 26;
|
|
569
582
|
export function raiseGradleMinSdkVersion(content: string, floor: number = ANDROID_MIN_SDK_VERSION): string | null {
|
|
570
583
|
const match = content.match(/minSdkVersion\s*=\s*(\d+)/);
|
|
@@ -748,7 +761,7 @@ export class CapacitorApp {
|
|
|
748
761
|
async save() {
|
|
749
762
|
await this.project.commit();
|
|
750
763
|
}
|
|
751
|
-
async #prepareIos({ operation, env, regenerate = false }: PrepareConfig) {
|
|
764
|
+
async #prepareIos({ operation, env, regenerate = false, iosRunTargetKind }: PrepareConfig) {
|
|
752
765
|
await this.init({ platform: "ios", operation, env, regenerate });
|
|
753
766
|
await this.#prepareTargetAssets();
|
|
754
767
|
await this.#prepareExternalFiles("ios");
|
|
@@ -757,9 +770,10 @@ export class CapacitorApp {
|
|
|
757
770
|
await this.#applyDeepLinks("ios", { operation, env });
|
|
758
771
|
await this.#flushIosEntitlements();
|
|
759
772
|
await this.project.commit();
|
|
773
|
+
await this.#setCodeSignEntitlementsInIosIfExists("App/App.entitlements");
|
|
760
774
|
await this.#generateAssets({ operation, env });
|
|
761
775
|
this.app.verbose(`syncing iOS`);
|
|
762
|
-
await this.#spawnMobile("npx", ["cap", "sync", "ios"], { operation, env });
|
|
776
|
+
await this.#spawnMobile("npx", ["cap", "sync", "ios"], { operation, env }, { iosRunTargetKind });
|
|
763
777
|
this.app.verbose(`sync completed.`);
|
|
764
778
|
}
|
|
765
779
|
async buildIos({ env = "debug", regenerate = false }: { env?: RunConfig["env"]; regenerate?: boolean } = {}) {
|
|
@@ -777,14 +791,14 @@ export class CapacitorApp {
|
|
|
777
791
|
}
|
|
778
792
|
async runIos({ operation, env, regenerate = false, noAllowProvisioningUpdates = false, iosDeviceId }: RunIosConfig) {
|
|
779
793
|
if (operation === "release") await this.prepareWww();
|
|
780
|
-
await this.#prepareIos({ operation, env, regenerate });
|
|
781
794
|
const runTarget = await this.#selectIosRunTarget(iosDeviceId);
|
|
795
|
+
await this.#prepareIos({ operation, env, regenerate, iosRunTargetKind: runTarget.kind });
|
|
782
796
|
if (runTarget.kind === "simulator") {
|
|
783
797
|
await this.#spawnMobile(
|
|
784
798
|
"npx",
|
|
785
|
-
["cap", "run", "ios", "--target", runTarget.id],
|
|
799
|
+
["cap", "run", "ios", "--target", runTarget.id, "--no-sync"],
|
|
786
800
|
{ operation, env },
|
|
787
|
-
{ stdio: "inherit" },
|
|
801
|
+
{ stdio: "inherit", platform: "ios", iosRunTargetKind: runTarget.kind },
|
|
788
802
|
);
|
|
789
803
|
return;
|
|
790
804
|
}
|
|
@@ -878,7 +892,10 @@ export class CapacitorApp {
|
|
|
878
892
|
noAllowProvisioningUpdates: boolean;
|
|
879
893
|
}) {
|
|
880
894
|
const mobileEnv = sanitizeIosNativeRunEnv(await this.#commandEnv(operation, env));
|
|
881
|
-
const configContent = await this.#writeCapacitorConfig(
|
|
895
|
+
const configContent = await this.#writeCapacitorConfig(
|
|
896
|
+
{ operation, platform: "ios", iosRunTargetKind: runTarget.kind },
|
|
897
|
+
mobileEnv,
|
|
898
|
+
);
|
|
882
899
|
await this.#writeRootCapacitorConfig(configContent);
|
|
883
900
|
const scheme = this.#iosScheme();
|
|
884
901
|
const command = buildIosNativeRunCommand({
|
|
@@ -943,6 +960,7 @@ export class CapacitorApp {
|
|
|
943
960
|
await this.#applyAndroidMinSdkVersion();
|
|
944
961
|
await this.#applyPermissions({ operation, env });
|
|
945
962
|
await this.#applyDeepLinks("android", { operation, env });
|
|
963
|
+
await this.#disableNativeKeyboardResizeInAndroid();
|
|
946
964
|
await this.project.commit();
|
|
947
965
|
await this.#generateAssets({ operation, env });
|
|
948
966
|
await this.#ensureAndroidAssetsDir();
|
|
@@ -1010,6 +1028,24 @@ export class CapacitorApp {
|
|
|
1010
1028
|
async openAndroid() {
|
|
1011
1029
|
await this.#spawnMobile("npx", ["cap", "open", "android"], { operation: "local", env: "local" });
|
|
1012
1030
|
}
|
|
1031
|
+
async #disableNativeKeyboardResizeInAndroid() {
|
|
1032
|
+
const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
1033
|
+
let manifest = await readFile(manifestPath, "utf8");
|
|
1034
|
+
let changed = false;
|
|
1035
|
+
manifest = manifest.replace(/<activity\b[^>]*android:name="\.MainActivity"[^>]*>/, (activityTag) => {
|
|
1036
|
+
if (activityTag.includes("android:windowSoftInputMode=")) {
|
|
1037
|
+
const nextTag = activityTag.replace(
|
|
1038
|
+
/android:windowSoftInputMode="[^"]*"/,
|
|
1039
|
+
'android:windowSoftInputMode="adjustNothing"',
|
|
1040
|
+
);
|
|
1041
|
+
changed ||= nextTag !== activityTag;
|
|
1042
|
+
return nextTag;
|
|
1043
|
+
}
|
|
1044
|
+
changed = true;
|
|
1045
|
+
return activityTag.replace(/>$/, '\n android:windowSoftInputMode="adjustNothing">');
|
|
1046
|
+
});
|
|
1047
|
+
if (changed) await writeFile(manifestPath, manifest);
|
|
1048
|
+
}
|
|
1013
1049
|
async #ensureAndroidAssetsDir() {
|
|
1014
1050
|
await mkdir(path.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
|
|
1015
1051
|
}
|
|
@@ -1100,12 +1136,19 @@ export class CapacitorApp {
|
|
|
1100
1136
|
if (html.includes("window.__AKAN_MOBILE_TARGET__")) return html;
|
|
1101
1137
|
return html.replace(/<\/head\s*>/i, `${script}\n</head>`);
|
|
1102
1138
|
}
|
|
1103
|
-
async #writeCapacitorConfig(
|
|
1139
|
+
async #writeCapacitorConfig(
|
|
1140
|
+
{
|
|
1141
|
+
operation,
|
|
1142
|
+
platform,
|
|
1143
|
+
iosRunTargetKind,
|
|
1144
|
+
}: Pick<RunConfig, "operation"> & { platform?: MobilePlatform; iosRunTargetKind?: IosRunTargetKind },
|
|
1145
|
+
commandEnv: MobileCommandEnv,
|
|
1146
|
+
) {
|
|
1104
1147
|
await mkdir(this.targetRoot, { recursive: true });
|
|
1105
1148
|
let localIp: string | undefined;
|
|
1106
1149
|
if (operation === "local") {
|
|
1107
1150
|
const override = commandEnv.AKAN_PUBLIC_CLIENT_HOST ?? process.env.AKAN_PUBLIC_CLIENT_HOST;
|
|
1108
|
-
const resolution =
|
|
1151
|
+
const resolution = await this.#resolveLocalDevHost({ override, platform, iosRunTargetKind });
|
|
1109
1152
|
localIp = resolution.host;
|
|
1110
1153
|
this.#logDevHostResolution(resolution, commandEnv);
|
|
1111
1154
|
}
|
|
@@ -1118,11 +1161,36 @@ export class CapacitorApp {
|
|
|
1118
1161
|
await Bun.write(path.join(this.targetRoot, "capacitor.config.json"), content);
|
|
1119
1162
|
return content;
|
|
1120
1163
|
}
|
|
1164
|
+
// An explicit override always wins; an emulator/simulator run reaches the host machine through a
|
|
1165
|
+
// loopback alias (10.0.2.2 / localhost), so LAN detection only applies to physical-device runs.
|
|
1166
|
+
async #resolveLocalDevHost({
|
|
1167
|
+
override,
|
|
1168
|
+
platform,
|
|
1169
|
+
iosRunTargetKind,
|
|
1170
|
+
}: {
|
|
1171
|
+
override?: string;
|
|
1172
|
+
platform?: MobilePlatform;
|
|
1173
|
+
iosRunTargetKind?: IosRunTargetKind;
|
|
1174
|
+
}): Promise<LocalDevHostResolution> {
|
|
1175
|
+
const resolution = selectLocalDevHost(os.networkInterfaces(), { override });
|
|
1176
|
+
if (resolution.source === "override") return resolution;
|
|
1177
|
+
if (platform === "ios" && iosRunTargetKind === "simulator")
|
|
1178
|
+
return { ...resolution, host: "localhost", source: "platform" };
|
|
1179
|
+
if (platform === "android") {
|
|
1180
|
+
try {
|
|
1181
|
+
const host = getAndroidLocalServerHost(await this.#spawn("adb", ["devices"]), resolution.host);
|
|
1182
|
+
return host === resolution.host ? resolution : { ...resolution, host, source: "platform" };
|
|
1183
|
+
} catch {
|
|
1184
|
+
return resolution;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return resolution;
|
|
1188
|
+
}
|
|
1121
1189
|
// Surface the live-reload URL a physical device must reach, and warn when auto-detection landed on
|
|
1122
1190
|
// a likely-unreachable host so a blank WebView is not mistaken for an app bug.
|
|
1123
1191
|
#logDevHostResolution(resolution: LocalDevHostResolution, commandEnv: MobileCommandEnv) {
|
|
1124
1192
|
this.app.log(`Mobile live-reload server: ${this.#localCsrUrl(resolution.host, commandEnv)}`);
|
|
1125
|
-
if (resolution.source === "override") return;
|
|
1193
|
+
if (resolution.source === "override" || resolution.source === "platform") return;
|
|
1126
1194
|
const suspicious = resolution.host === "127.0.0.1" || resolution.host.startsWith("169.254.");
|
|
1127
1195
|
const alternatives = resolution.candidates.filter((candidate) => candidate.address !== resolution.host);
|
|
1128
1196
|
if (!suspicious && alternatives.length === 0) return;
|
|
@@ -1332,20 +1400,29 @@ export class CapacitorApp {
|
|
|
1332
1400
|
command: string,
|
|
1333
1401
|
args: string[] = [],
|
|
1334
1402
|
{ operation, env }: Pick<RunConfig, "operation" | "env">,
|
|
1335
|
-
options:
|
|
1403
|
+
options: SpawnMobileOptions = {},
|
|
1336
1404
|
) {
|
|
1405
|
+
const { iosRunTargetKind, platform, ...spawnOptions } = options;
|
|
1337
1406
|
const mobileEnv = { ...(await this.#commandEnv(operation, env)), ...options.env };
|
|
1338
|
-
const configContent = await this.#writeCapacitorConfig(
|
|
1407
|
+
const configContent = await this.#writeCapacitorConfig(
|
|
1408
|
+
{ operation, platform: platform ?? this.#inferMobilePlatform(args), iosRunTargetKind },
|
|
1409
|
+
mobileEnv,
|
|
1410
|
+
);
|
|
1339
1411
|
await this.#writeRootCapacitorConfig(configContent);
|
|
1340
1412
|
try {
|
|
1341
1413
|
return await this.#spawn(command, args, {
|
|
1342
|
-
...
|
|
1414
|
+
...spawnOptions,
|
|
1343
1415
|
env: mobileEnv,
|
|
1344
1416
|
});
|
|
1345
1417
|
} finally {
|
|
1346
1418
|
await this.#clearRootCapacitorConfigs();
|
|
1347
1419
|
}
|
|
1348
1420
|
}
|
|
1421
|
+
#inferMobilePlatform(args: string[]): MobilePlatform | undefined {
|
|
1422
|
+
if (args.includes("android")) return "android";
|
|
1423
|
+
if (args.includes("ios")) return "ios";
|
|
1424
|
+
return undefined;
|
|
1425
|
+
}
|
|
1349
1426
|
async addCamera() {
|
|
1350
1427
|
await this.#setPermissionInIos({
|
|
1351
1428
|
cameraUsageDescription: "$(PRODUCT_NAME) requires access to the camera to take photos.",
|
|
@@ -1446,14 +1523,31 @@ export class CapacitorApp {
|
|
|
1446
1523
|
let end = index;
|
|
1447
1524
|
while (end < lines.length && !/^\s*\};\s*$/.test(lines[end] ?? "")) end++;
|
|
1448
1525
|
const settings = lines.slice(start, end + 1);
|
|
1449
|
-
|
|
1526
|
+
const configName = lines.slice(end + 1, end + 8).find((setting) => setting.includes("name = ")) ?? "";
|
|
1450
1527
|
const indent = line.match(/^\s*/)?.[0] ?? "";
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1528
|
+
const insertSettings = [];
|
|
1529
|
+
if (!settings.some((setting) => setting.includes("CODE_SIGN_ENTITLEMENTS"))) {
|
|
1530
|
+
insertSettings.push(`${indent}CODE_SIGN_ENTITLEMENTS = ${entitlementsRelPath};`);
|
|
1531
|
+
}
|
|
1532
|
+
if (
|
|
1533
|
+
configName.includes("name = Debug;") &&
|
|
1534
|
+
!settings.some((setting) => setting.includes("CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION"))
|
|
1535
|
+
) {
|
|
1536
|
+
insertSettings.push(`${indent}CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;`);
|
|
1537
|
+
}
|
|
1538
|
+
if (insertSettings.length > 0) {
|
|
1539
|
+
lines.splice(index, 0, ...insertSettings);
|
|
1540
|
+
index += insertSettings.length;
|
|
1541
|
+
changed = true;
|
|
1542
|
+
}
|
|
1454
1543
|
}
|
|
1455
1544
|
if (changed) await writeFile(pbxprojPath, lines.join("\n"));
|
|
1456
1545
|
}
|
|
1546
|
+
async #setCodeSignEntitlementsInIosIfExists(entitlementsRelPath: string) {
|
|
1547
|
+
const entitlementsPath = path.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
|
|
1548
|
+
if (!(await Bun.file(entitlementsPath).exists())) return;
|
|
1549
|
+
await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
|
|
1550
|
+
}
|
|
1457
1551
|
async #setUrlSchemesInAndroid(schemes: string[]) {
|
|
1458
1552
|
const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
1459
1553
|
let manifest = await readFile(manifestPath, "utf8");
|
package/executors.test.ts
CHANGED
|
@@ -31,7 +31,6 @@ const rootPackageJson = (extra: Partial<PackageJson> = {}): PackageJson => ({
|
|
|
31
31
|
react: "19.0.0",
|
|
32
32
|
"react-dom": "19.0.0",
|
|
33
33
|
"react-server-dom-webpack": "19.0.0",
|
|
34
|
-
sharp: "1.0.0",
|
|
35
34
|
lodash: "4.0.0",
|
|
36
35
|
},
|
|
37
36
|
devDependencies: {
|
|
@@ -149,8 +148,10 @@ describe("Executor filesystem helpers", () => {
|
|
|
149
148
|
"AI Development Guide",
|
|
150
149
|
);
|
|
151
150
|
expect(await readFile(path.join(root, "workspace/docs/GENERATED.md"), "utf8")).toContain("Generated Akan Files");
|
|
151
|
+
// Rules and their plugin registrations live in the package's base config, so a framework release reaches an
|
|
152
|
+
// existing workspace on `bun update` — the workspace file only extends it and scopes its own files.
|
|
152
153
|
expect(await readFile(path.join(root, "workspace/biome.json"), "utf8")).toContain(
|
|
153
|
-
"
|
|
154
|
+
'"extends": ["@akanjs/devkit/biome.base.json"]',
|
|
154
155
|
);
|
|
155
156
|
});
|
|
156
157
|
|
|
@@ -758,6 +759,29 @@ describe("Workspace and app executor environment contracts", () => {
|
|
|
758
759
|
expect(offsetMinimalStart.env.AKAN_PUBLIC_CLIENT_PORT).toBe("8286");
|
|
759
760
|
expect(offsetMinimalStart.env.AKAN_PUBLIC_SERVER_PORT).toBe("8286");
|
|
760
761
|
});
|
|
762
|
+
|
|
763
|
+
test("pins the start command to development so an ambient NODE_ENV cannot pick the production router", async () => {
|
|
764
|
+
const root = await makeTempRoot();
|
|
765
|
+
process.env.AKAN_PUBLIC_REPO_NAME = "repo";
|
|
766
|
+
process.env.AKAN_PUBLIC_SERVE_DOMAIN = "example.com";
|
|
767
|
+
process.env.AKAN_PUBLIC_ENV = "local";
|
|
768
|
+
await writeJson(path.join(root, "package.json"), rootPackageJson());
|
|
769
|
+
await mkdir(path.join(root, "apps/ambient"), { recursive: true });
|
|
770
|
+
await writeFile(path.join(root, "apps/ambient/akan.config.ts"), "export default {};\n");
|
|
771
|
+
|
|
772
|
+
const originalNodeEnv = process.env.NODE_ENV;
|
|
773
|
+
process.env.NODE_ENV = "production";
|
|
774
|
+
try {
|
|
775
|
+
const workspace = new WorkspaceExecutor({ workspaceRoot: root, repoName: "repo" });
|
|
776
|
+
const started = await AppExecutor.from(workspace, "ambient").prepareCommand("start");
|
|
777
|
+
expect(started.env.NODE_ENV).toBe("development");
|
|
778
|
+
// The builder runs in this process and bakes NODE_ENV into the dev bundles it emits.
|
|
779
|
+
expect(process.env.NODE_ENV).toBe("development");
|
|
780
|
+
} finally {
|
|
781
|
+
if (originalNodeEnv === undefined) delete process.env.NODE_ENV;
|
|
782
|
+
else process.env.NODE_ENV = originalNodeEnv;
|
|
783
|
+
}
|
|
784
|
+
});
|
|
761
785
|
});
|
|
762
786
|
|
|
763
787
|
describe("PkgExecutor package generation", () => {
|
|
@@ -771,7 +795,7 @@ describe("PkgExecutor package generation", () => {
|
|
|
771
795
|
exports: { "./extra": { import: "./extra.ts" } },
|
|
772
796
|
peerDependencies: { react: "19.0.0" },
|
|
773
797
|
peerDependenciesMeta: { react: { optional: true } },
|
|
774
|
-
optionalDependencies: {
|
|
798
|
+
optionalDependencies: { "@sample/native": "1.0.0" },
|
|
775
799
|
});
|
|
776
800
|
|
|
777
801
|
const workspace = new WorkspaceExecutor({ workspaceRoot: root, repoName: "repo" });
|
|
@@ -781,12 +805,12 @@ describe("PkgExecutor package generation", () => {
|
|
|
781
805
|
expect(distPackageJson).toMatchObject({
|
|
782
806
|
name: "@sample/tool",
|
|
783
807
|
type: "module",
|
|
784
|
-
engines: { bun: ">=1.
|
|
808
|
+
engines: { bun: ">=1.4.0" },
|
|
785
809
|
dependencies: { lodash: "4.0.0" },
|
|
786
810
|
devDependencies: { typescript: "6.0.0" },
|
|
787
811
|
peerDependencies: { react: "19.0.0" },
|
|
788
812
|
peerDependenciesMeta: { react: { optional: true } },
|
|
789
|
-
optionalDependencies: {
|
|
813
|
+
optionalDependencies: { "@sample/native": "1.0.0" },
|
|
790
814
|
});
|
|
791
815
|
expect(distPackageJson.exports?.["."]).toEqual({
|
|
792
816
|
import: "./index.ts",
|