@akanjs/devkit 3.0.0-alpha.11 → 3.0.0-alpha.110

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 (187) 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 +21 -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 +30 -14
  19. package/akanMcpContract.ts +25 -4
  20. package/appSelectionMemory.ts +28 -0
  21. package/applicationBuildRunner.test.ts +37 -2
  22. package/applicationBuildRunner.ts +52 -23
  23. package/artifact/implicitRootLayout.test.ts +70 -1
  24. package/artifact/implicitRootLayout.ts +41 -14
  25. package/artifact/routeSeedIndex.test.ts +1 -0
  26. package/biome.base.json +28 -1
  27. package/biomeBase.ts +1 -1
  28. package/capacitorApp.ts +10 -0
  29. package/cloud/globalConfig.ts +9 -2
  30. package/codegenLock.test.ts +137 -0
  31. package/codegenLock.ts +136 -0
  32. package/commandDecorators/argMeta.ts +9 -1
  33. package/commandDecorators/command.ts +94 -7
  34. package/commandDecorators/commandBuilder.ts +20 -5
  35. package/commandDecorators/commandDecorators.test.ts +115 -3
  36. package/commandDecorators/helpFormatter.ts +4 -0
  37. package/dependencyScanner.test.ts +99 -0
  38. package/dependencyScanner.ts +27 -24
  39. package/devkitUtils.test.ts +0 -104
  40. package/executors.test.ts +135 -7
  41. package/executors.ts +115 -25
  42. package/fileEditor.ts +19 -19
  43. package/fileSys.ts +19 -1
  44. package/formSetterScanner.test.ts +80 -0
  45. package/formSetterScanner.ts +92 -0
  46. package/frontendBuild/autoImportSync.test.ts +58 -0
  47. package/frontendBuild/autoImportSync.ts +7 -2
  48. package/frontendBuild/buildRouteClient.test.ts +47 -24
  49. package/frontendBuild/clientBuildTypes.ts +4 -0
  50. package/frontendBuild/clientEntriesBundler.ts +4 -1
  51. package/frontendBuild/clientEntryDiscovery.ts +2 -2
  52. package/frontendBuild/csrArtifactBuilder.ts +116 -84
  53. package/frontendBuild/cssCompiler.ts +63 -10
  54. package/frontendBuild/devGeneratedIndexSync.ts +3 -2
  55. package/frontendBuild/fontOptimizer.test.ts +75 -2
  56. package/frontendBuild/fontOptimizer.ts +98 -27
  57. package/frontendBuild/fontPruner.test.ts +220 -0
  58. package/frontendBuild/fontPruner.ts +206 -0
  59. package/frontendBuild/frontendBuild.test.ts +124 -10
  60. package/frontendBuild/hmrWatcher.ts +1 -1
  61. package/frontendBuild/index.ts +1 -1
  62. package/frontendBuild/pagesBundleBuilder.ts +3 -3
  63. package/frontendBuild/pagesEntrySourceGenerator.ts +11 -88
  64. package/frontendBuild/routeClientBuilder.ts +12 -5
  65. package/frontendBuild/sourceMtimeIndex.ts +1 -1
  66. package/frontendBuild/ssrBaseArtifactBuilder.ts +21 -4
  67. package/frontendBuild/styleContract.ts +14 -20
  68. package/frontendBuild/themeValidator.ts +22 -17
  69. package/frontendBuild/vendorSpecifiers.ts +1 -0
  70. package/frontendBuild/watchRootResolver.test.ts +129 -0
  71. package/frontendBuild/watchRootResolver.ts +66 -1
  72. package/incrementalBuilder/devWatchBatch.test.ts +18 -20
  73. package/incrementalBuilder/devWatchBatch.ts +6 -2
  74. package/incrementalBuilder/incrementalBuilder.host.ts +51 -7
  75. package/incrementalBuilder/incrementalBuilder.proc.ts +17 -6
  76. package/index.ts +0 -7
  77. package/integration/devStabilityHarness.ts +13 -19
  78. package/libSource.test.ts +112 -0
  79. package/libSource.ts +126 -0
  80. package/lint/__fixtures__/README.md +40 -0
  81. package/lint/__fixtures__/no-arbitrary-color/bad.tsx +3 -0
  82. package/lint/__fixtures__/no-arbitrary-color/good.tsx +4 -0
  83. package/lint/__fixtures__/no-async-component-in-ui/bad.tsx +4 -0
  84. package/lint/__fixtures__/no-async-component-in-ui/good.tsx +4 -0
  85. package/lint/__fixtures__/no-bang-comment-in-client/bad.tsx +4 -0
  86. package/lint/__fixtures__/no-bang-comment-in-client/fixture.json +1 -0
  87. package/lint/__fixtures__/no-bang-comment-in-client/good.tsx +3 -0
  88. package/lint/__fixtures__/no-daisyui-legacy-class/bad.tsx +7 -0
  89. package/lint/__fixtures__/no-daisyui-legacy-class/good.tsx +5 -0
  90. package/lint/__fixtures__/no-deep-internal-import/bad.tsx +3 -0
  91. package/lint/__fixtures__/no-deep-internal-import/fixture.json +1 -0
  92. package/lint/__fixtures__/no-deep-internal-import/good.tsx +3 -0
  93. package/lint/__fixtures__/no-deprecated-log-level/bad.tsx +4 -0
  94. package/lint/__fixtures__/no-deprecated-log-level/good.tsx +4 -0
  95. package/lint/__fixtures__/no-import-client-functions/bad.tsx +2 -0
  96. package/lint/__fixtures__/no-import-client-functions/good.tsx +4 -0
  97. package/lint/__fixtures__/no-import-client-in-server/bad.tsx +6 -0
  98. package/lint/__fixtures__/no-import-client-in-server/good.tsx +5 -0
  99. package/lint/__fixtures__/no-import-external-library/bad.tsx +4 -0
  100. package/lint/__fixtures__/no-import-external-library/good.tsx +5 -0
  101. package/lint/__fixtures__/no-import-server-in-client/bad.tsx +6 -0
  102. package/lint/__fixtures__/no-import-server-in-client/good.tsx +4 -0
  103. package/lint/__fixtures__/no-init-fetch-in-client/store-file/bad.ts +1 -0
  104. package/lint/__fixtures__/no-init-fetch-in-client/store-file/fixture.json +1 -0
  105. package/lint/__fixtures__/no-init-fetch-in-client/store-file/good.ts +1 -0
  106. package/lint/__fixtures__/no-init-fetch-in-client/use-client/bad.tsx +4 -0
  107. package/lint/__fixtures__/no-init-fetch-in-client/use-client/good.tsx +5 -0
  108. package/lint/__fixtures__/no-inline-color/bad.tsx +3 -0
  109. package/lint/__fixtures__/no-inline-color/good.tsx +3 -0
  110. package/lint/__fixtures__/no-interpolated-arbitrary-class/bad.tsx +3 -0
  111. package/lint/__fixtures__/no-interpolated-arbitrary-class/good.tsx +3 -0
  112. package/lint/__fixtures__/no-js-private-class-method/bad.tsx +3 -0
  113. package/lint/__fixtures__/no-js-private-class-method/good.tsx +3 -0
  114. package/lint/__fixtures__/no-model-type-in-util-zone/bad.tsx +3 -0
  115. package/lint/__fixtures__/no-model-type-in-util-zone/good.tsx +6 -0
  116. package/lint/__fixtures__/no-raw-palette-class/bad.tsx +6 -0
  117. package/lint/__fixtures__/no-raw-palette-class/good.tsx +8 -0
  118. package/lint/__fixtures__/no-redeclare-predefined-endpoint/bad.ts +10 -0
  119. package/lint/__fixtures__/no-redeclare-predefined-endpoint/fixture.json +1 -0
  120. package/lint/__fixtures__/no-redeclare-predefined-endpoint/good.ts +6 -0
  121. package/lint/__fixtures__/no-return-in-store-action/bad.tsx +2 -0
  122. package/lint/__fixtures__/no-return-in-store-action/good.tsx +6 -0
  123. package/lint/__fixtures__/no-throw-raw-error/bad.tsx +4 -0
  124. package/lint/__fixtures__/no-throw-raw-error/good.tsx +4 -0
  125. package/lint/__fixtures__/no-unpublished-form-setter/bad.tsx +3 -0
  126. package/lint/__fixtures__/no-unpublished-form-setter/good.tsx +5 -0
  127. package/lint/__fixtures__/no-use-client-in-server/bad.tsx +3 -0
  128. package/lint/__fixtures__/no-use-client-in-server/good.tsx +2 -0
  129. package/lint/__fixtures__/non-scalar-props-restricted/bad.tsx +5 -0
  130. package/lint/__fixtures__/non-scalar-props-restricted/good.tsx +4 -0
  131. package/lint/gritRules.test.ts +178 -0
  132. package/lint/no-arbitrary-color.grit +1 -1
  133. package/lint/no-async-component-in-ui.grit +35 -0
  134. package/lint/no-bang-comment-in-client.grit +23 -10
  135. package/lint/no-daisyui-legacy-class.grit +26 -9
  136. package/lint/no-deprecated-log-level.grit +17 -0
  137. package/lint/no-init-fetch-in-client.grit +47 -0
  138. package/lint/no-inline-color.grit +10 -8
  139. package/lint/no-interpolated-arbitrary-class.grit +3 -3
  140. package/lint/no-model-type-in-util-zone.grit +58 -0
  141. package/lint/no-raw-palette-class.grit +3 -3
  142. package/lint/no-unpublished-form-setter.grit +41 -0
  143. package/lint/non-scalar-props-restricted.grit +16 -7
  144. package/linter.test.ts +80 -0
  145. package/linter.ts +82 -13
  146. package/package.json +5 -8
  147. package/prompter.ts +9 -4
  148. package/qualityScanner.test.ts +80 -142
  149. package/qualityScanner.ts +89 -31
  150. package/recipeScanner.ts +4 -1
  151. package/routeSourceValidator.test.ts +63 -0
  152. package/routeSourceValidator.ts +123 -16
  153. package/scanInfo.ts +32 -26
  154. package/semver.test.ts +26 -0
  155. package/semver.ts +31 -0
  156. package/slicePlanner.test.ts +182 -0
  157. package/slicePlanner.ts +235 -0
  158. package/ssrScanner.test.ts +301 -0
  159. package/subspace.test.ts +418 -0
  160. package/subspace.ts +723 -0
  161. package/subspaceConfig.ts +76 -0
  162. package/transforms/asyncDefaultExportDetector.ts +105 -0
  163. package/transforms/barrelImportsPlugin.ts +1 -1
  164. package/transforms/externalizeFrameworkPlugin.ts +0 -1
  165. package/transforms/transforms.test.ts +5 -5
  166. package/transforms/tsconfigPackageResolver.test.ts +230 -0
  167. package/tsconfig.json +6 -2
  168. package/typeChecker.ts +1 -1
  169. package/types.ts +1 -0
  170. package/uploadRelease.ts +2 -2
  171. package/workflow/executor.test.ts +146 -0
  172. package/workspaceLayout.test.ts +56 -4
  173. package/workspaceLayout.ts +48 -2
  174. package/builder.ts +0 -164
  175. package/extractDeps.ts +0 -86
  176. package/frontendBuild/styleGuard.test.ts +0 -165
  177. package/frontendBuild/styleGuard.ts +0 -322
  178. package/getCredentials.ts +0 -19
  179. package/getModelFileData.ts +0 -62
  180. package/mcpScanner.ts +0 -217
  181. package/src/capacitorApp.ts +0 -282
  182. package/storeScanner.ts +0 -173
  183. package/streamAi.ts +0 -45
  184. package/ui/MultiScrollList.tsx +0 -242
  185. package/ui/ScrollList.tsx +0 -107
  186. package/ui/index.ts +0 -2
  187. package/useStdoutDimensions.ts +0 -20
@@ -1,282 +0,0 @@
1
- import type { AppExecutor } from "@akanjs/devkit/executors";
2
- import { FileSys } from "@akanjs/devkit/fileSys";
3
- import type { CapacitorConfig } from "@capacitor/cli";
4
- import { MobileProject } from "@trapezedev/project";
5
- import type { AndroidProject } from "@trapezedev/project/dist/android/project";
6
- import type { IosProject } from "@trapezedev/project/dist/ios/project";
7
- import { capitalize } from "akanjs/common";
8
-
9
- import { FileEditor } from "./fileEditor";
10
-
11
- interface RunConfig extends CapacitorConfig {
12
- operation: "local" | "release";
13
- version: string;
14
- buildNum: number;
15
- appId?: string;
16
- host?: "local" | "debug" | "develop" | "main";
17
- }
18
-
19
- export class CapacitorApp {
20
- project: MobileProject & { ios: IosProject; android: AndroidProject };
21
- iosTargetName = "App";
22
- constructor(private readonly app: AppExecutor) {
23
- this.project = new MobileProject(this.app.cwdPath, {
24
- android: { path: "android" },
25
- ios: { path: "ios/App" },
26
- }) as MobileProject & { ios: IosProject; android: AndroidProject };
27
- }
28
- async init() {
29
- const project = this.project as MobileProject;
30
- await this.project.load();
31
- const hasAndroid = await FileSys.fileExists(`${this.app.cwdPath}/android/app/build.gradle`);
32
- const hasIos = await FileSys.fileExists(`${this.app.cwdPath}/ios/App/App.xcodeproj/project.pbxproj`);
33
- if (!project.android && !hasAndroid) {
34
- await this.app.spawn("npx", ["cap", "add", "android"]);
35
- await this.project.load();
36
- }
37
- if (!project.ios && !hasIos) {
38
- await this.app.spawn("npx", ["cap", "add", "ios"]);
39
- await this.project.load();
40
- }
41
- return this;
42
- }
43
- async save() {
44
- await this.project.commit();
45
- }
46
- async #prepareIos() {
47
- const isAdded = await FileSys.fileExists(`${this.app.cwdPath}/ios/App/App.xcodeproj/project.pbxproj`);
48
- if (!isAdded) {
49
- await this.app.spawn("npx", ["cap", "add", "ios"]);
50
- await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
51
- } else this.app.verbose(`iOS already added, skip adding process`);
52
- this.app.verbose(`syncing iOS`);
53
- await this.app.spawn("npx", ["cap", "sync", "ios"]);
54
- this.app.verbose(`sync completed.`);
55
- }
56
- async buildIos() {
57
- await this.#prepareIos();
58
- this.app.verbose(`build completed iOS.`);
59
- return;
60
- }
61
- async syncIos() {
62
- await this.app.spawn("npx", ["cap", "sync", "ios"]);
63
- }
64
- async openIos() {
65
- await this.app.spawn("npx", ["cap", "open", "ios"]);
66
- }
67
- async runIos({ operation, appId, version = "0.0.1", buildNum = 1, host = "local" }: RunConfig) {
68
- const defaultAppId = `com.${this.app.name}.app`;
69
- await this.#prepareIos();
70
- this.project.ios.setBundleId("App", "Debug", appId ?? defaultAppId);
71
- this.project.ios.setBundleId("App", "Release", appId ?? defaultAppId);
72
- await this.project.ios.setVersion("App", "Debug", version);
73
- await this.project.ios.setVersion("App", "Release", version);
74
- await this.project.ios.setBuild("App", "Debug", buildNum);
75
- await this.project.ios.setBuild("App", "Release", buildNum);
76
- await this.project.commit();
77
- await this.app.spawn(
78
- "npx",
79
- ["cross-env", `APP_OPERATION_MODE=${operation}`, `BUN_PUBLIC_ENV=${host}`, "npx", "cap", "run", "ios"],
80
- {
81
- stdio: "inherit",
82
- },
83
- );
84
-
85
- // this.project.ios.incrementBuild("App", "Debug");
86
- // this.project.ios.incrementBuild("App", "Release");
87
- }
88
-
89
- async #prepareAndroid() {
90
- const isAdded = await Bun.file(`${this.app.cwdPath}/android/app/build.gradle`).exists();
91
- if (!isAdded) {
92
- await this.app.spawn("npx", ["cap", "add", "android"]);
93
- } else this.app.verbose(`Android already added, skip adding process`);
94
- await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
95
- await this.app.spawn("npx", ["cap", "sync", "android"]);
96
- }
97
-
98
- async #updateAndroidBuildTypes() {
99
- //keystore 기본 설정 및 debug, release 설정
100
-
101
- const appGradle = await FileEditor.create(`${this.app.cwdPath}/android/app/build.gradle`);
102
- const buildTypesBlock = `
103
- debug {
104
- applicationIdSuffix ".debug"
105
- versionNameSuffix "-DEBUG"
106
- debuggable true
107
- minifyEnabled false
108
- }
109
- `;
110
- const singinConfigBlock = `
111
- signingConfigs {
112
- debug {
113
- storeFile file('debug.keystore')
114
- storePassword 'android'
115
- keyAlias 'androiddebugkey'
116
- keyPassword 'android'
117
- }
118
- release {
119
- if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
120
- storeFile file(MYAPP_RELEASE_STORE_FILE)
121
- storePassword MYAPP_RELEASE_STORE_PASSWORD
122
- keyAlias MYAPP_RELEASE_KEY_ALIAS
123
- keyPassword MYAPP_RELEASE_KEY_PASSWORD
124
- }
125
- }
126
- }
127
- `;
128
- if (appGradle.find("signingConfigs {") === -1) {
129
- appGradle.insertBefore("buildTypes {", singinConfigBlock);
130
- }
131
- if (appGradle.find(`applicationIdSuffix ".debug"`) === -1) {
132
- appGradle.insertAfter("buildTypes {", buildTypesBlock);
133
- }
134
- await appGradle.save();
135
- }
136
- async buildAndroid(assembleType: "apk" | "aab") {
137
- await this.#prepareAndroid();
138
- await this.#updateAndroidBuildTypes();
139
- //윈도우는 gradlew.bat 사용
140
- const isWindows = process.platform === "win32";
141
- const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
142
-
143
- await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
144
- stdio: "inherit",
145
- cwd: `${this.app.cwdPath}/android`,
146
- });
147
- }
148
- async openAndroid() {
149
- await this.app.spawn("npx", ["cap", "open", "android"]);
150
- }
151
- async syncAndroid() {
152
- await this.#prepareAndroid();
153
- this.app.log(`Sync Android Completed.`);
154
- }
155
- async runAndroid({ operation, appName, appId, version = "0.0.1", buildNum = 1, host = "local" }: RunConfig) {
156
- const defaultAppId = `com.${this.app.name}.app`;
157
- const defaultAppName = this.app.name;
158
- await this.project.android.setVersionName(version);
159
- await this.project.android.setPackageName(appId ?? defaultAppId);
160
- await this.project.android.setVersionCode(buildNum);
161
- const versionName = await this.project.android.getVersionName();
162
- const versionCode = await this.project.android.getVersionCode();
163
- await this.project.android.setAppName(appName ?? defaultAppName);
164
- await this.project.commit();
165
- await this.#prepareAndroid();
166
-
167
- this.app.logger.info(`Running Android in ${operation} mode on ${host} host`);
168
- await this.app.spawn(
169
- "npx",
170
- ["cross-env", `BUN_PUBLIC_ENV=${host}`, `APP_OPERATION_MODE=${operation}`, "npx", "cap", "run", "android"],
171
- {
172
- stdio: "inherit",
173
- },
174
- );
175
- }
176
-
177
- //? 릴리즈시 buildNum +1 version 파라미터 받아서 업데이트
178
- async updateAndroidVersion(version: string, buildNum: number) {
179
- //TODO: 테스트 확인 필요
180
- await this.project.android.setVersionName(version);
181
- await this.project.android.setVersionCode(buildNum);
182
- const versionName = await this.project.android.getVersionName();
183
- const versionCode = await this.project.android.getVersionCode();
184
- await this.project.commit();
185
- }
186
- async releaseIos() {
187
- //TODO: 작업 필요
188
- const isAdded = await Bun.file(`${this.app.cwdPath}/ios/App/App.xcodeproj/project.pbxproj`).exists();
189
- if (!isAdded) {
190
- await this.app.spawn("npx cap add ios");
191
- await this.app.spawn("npx @capacitor/assets generate");
192
- } else this.app.log(`iOS already added, skip adding process`);
193
- await this.app.spawn("cross-env", ["APP_OPERATION_MODE=release", "npx", "cap", "sync", "ios"]);
194
- }
195
- async releaseAndroid() {
196
- //TODO: 작업 필요
197
- const isAdded = await Bun.file(`${this.app.cwdPath}/android/app/build.gradle`).exists();
198
- if (!isAdded) {
199
- await this.app.spawn("npx cap add android");
200
- await this.app.spawn("npx @capacitor/assets generate");
201
- } else this.app.log(`android already added, skip adding process`);
202
- await this.app.spawn("cross-env", ["APP_OPERATION_MODE=release", "npx", "cap", "sync", "android"]);
203
- }
204
- async addCamera() {
205
- await this.#setPermissionInIos({
206
- cameraUsageDescription: "$(PRODUCT_NAME) requires access to the camera to take photos.",
207
- photoAddUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
208
- photoUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
209
- });
210
- this.#setPermissionsInAndroid(["READ_MEDIA_IMAGES", "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"]);
211
- }
212
- async addContact() {
213
- await this.#setPermissionInIos({
214
- contactsUsageDescription: "$(PRODUCT_NAME) requires access to the contacts to add new contacts.",
215
- });
216
- this.#setPermissionsInAndroid(["READ_CONTACTS", "WRITE_CONTACTS"]);
217
- }
218
- async addLocation() {
219
- await this.#setPermissionInIos({
220
- locationAlwaysUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
221
- locationWhenInUseUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
222
- });
223
- this.#setPermissionsInAndroid(["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"]);
224
- this.#setFeaturesInAndroid(["android.hardware.location.gps"]);
225
- }
226
- async #setPermissionInIos(permissions: { [key: string]: string }) {
227
- const updateNs = Object.fromEntries(
228
- Object.entries(permissions).map(([key, value]) => [`NS${capitalize(key)}`, value]),
229
- );
230
- await Promise.all([
231
- this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", updateNs),
232
- this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs),
233
- ]);
234
- }
235
- #setFeaturesInAndroid(features: string[]) {
236
- for (const feature of features) {
237
- if (this.#hasFeatureInAndroid(feature)) {
238
- this.app.logger.info(`${feature} already exists in android`);
239
- return this;
240
- }
241
- this.app.logger.info(`Adding ${feature} to android`);
242
- this.project.android
243
- .getAndroidManifest()
244
- .injectFragment("manifest", `<uses-feature android:name="${feature}" />`);
245
- }
246
- return this;
247
- }
248
- #getFeaturesInAndroid() {
249
- const androidManifest = this.project.android.getAndroidManifest();
250
- const element = androidManifest.getDocumentElement();
251
- if (!element) throw new Error("manifest not found");
252
- const usesFeature = element.getElementsByTagName("uses-feature");
253
- return Array.from(usesFeature).map((feature) => feature.getAttribute("android:name"));
254
- }
255
- #hasFeatureInAndroid(feature: string) {
256
- return this.#getFeaturesInAndroid().includes(feature);
257
- }
258
-
259
- #setPermissionsInAndroid(permissions: string[]) {
260
- for (const permission of permissions) {
261
- if (this.#hasPermissionInAndroid(permission)) {
262
- this.app.logger.info(`${permission} already exists in android`);
263
- return this;
264
- }
265
- this.app.logger.info(`Adding ${permission} to android`);
266
- this.project.android
267
- .getAndroidManifest()
268
- .injectFragment("manifest", `<uses-permission android:name="android.permission.${permission}" />`);
269
- }
270
- return this;
271
- }
272
- #getPermissionsInAndroid() {
273
- const androidManifest = this.project.android.getAndroidManifest();
274
- const element = androidManifest.getDocumentElement();
275
- if (!element) throw new Error("manifest not found");
276
- const usesPermission = element.getElementsByTagName("uses-permission");
277
- return Array.from(usesPermission).map((permission) => permission.getAttribute("android:name"));
278
- }
279
- #hasPermissionInAndroid(permission: string) {
280
- return this.#getPermissionsInAndroid().includes(permission);
281
- }
282
- }
package/storeScanner.ts DELETED
@@ -1,173 +0,0 @@
1
- import path from "node:path";
2
- import ts from "typescript";
3
- import type { QualityWarning, SourceFileInfo } from "./qualityScanner";
4
-
5
- interface CustomAction {
6
- name: string;
7
- line: number;
8
- /** The endpoints this action calls as `fetch.<name>`. Empty means it never leaves the client. */
9
- fetched: string[];
10
- }
11
-
12
- /**
13
- * Checks that a store action an agent can reach says what it does, in the one place this codebase lets it.
14
- *
15
- * A store is the surface an in-page agent drives — it reads state through `st.use.*` and acts through `st.do.*` —
16
- * and an action's name and argument types are the only other thing it sees. Unlike a signal, a store has no
17
- * builder metadata and no room for prose: the house rules ban JSDoc, and every string a person reads goes through
18
- * `l()`. So the dictionary's `.store()` stage is the only legal channel for the sentence, and this is the check
19
- * that it exists where it is actually needed.
20
- *
21
- * Three kinds of action are deliberately quiet, because a warning nobody should act on teaches people to ignore
22
- * the rest:
23
- *
24
- * - **Generated actions** (`createX`, `setFieldOnX`, `initXInY`, …) are not in the file at all. Their wording is
25
- * derived from the model's own labels, so there is nothing for an author to write.
26
- * - **An action that calls no `fetch.*`** stays on the client and is not published, so its description would be
27
- * read by nobody.
28
- * - **An action named after the endpoint it calls** already reads as that endpoint's `.desc()`. That is most of
29
- * them, and not by accident — the naming rule is that `st.do.X` reads the same as `fetch.X`.
30
- *
31
- * What is left is the case where inheriting would be *wrong* rather than merely absent: nine `getSummaryListIn*`
32
- * actions that all call one `summaryListInPeriod`, where the difference between them is the whole point of having
33
- * nine; or `logout` over `signoutUser`, where the store name is the verb a user would say and the endpoint name is
34
- * the verb the API has. Those are the ones a person has to write.
35
- *
36
- * It reads source, so an action that reaches its endpoint through anything but a literal `fetch.<name>` — a
37
- * destructured `fetch`, a helper, a computed key — reads as calling none and stays quiet. Right for a warning that
38
- * must not fire on what it merely failed to resolve.
39
- */
40
- export class StoreScanner {
41
- scan(sourceFiles: SourceFileInfo[]): QualityWarning[] {
42
- const dictionaries = new Map(
43
- sourceFiles
44
- .filter((sourceFile) => sourceFile.file.endsWith(".dictionary.ts"))
45
- .map((sourceFile) => [path.dirname(sourceFile.file), sourceFile]),
46
- );
47
- return sourceFiles
48
- .filter((sourceFile) => sourceFile.file.endsWith(".store.ts"))
49
- .flatMap((sourceFile) => this.#scanStore(sourceFile, dictionaries.get(path.dirname(sourceFile.file))));
50
- }
51
-
52
- #scanStore(store: SourceFileInfo, dictionary: SourceFileInfo | undefined): QualityWarning[] {
53
- const actions = StoreScanner.#customActions(store);
54
- if (!actions.length) return [];
55
- const described = dictionary ? StoreScanner.#describedEntries(dictionary) : new Map<string, Set<string>>();
56
- return actions
57
- .filter(({ name, fetched }) => fetched.length && !fetched.includes(name))
58
- .filter(({ name }) => !described.get("store")?.has(name) && !described.get("endpoint")?.has(name))
59
- .map(({ name, line, fetched }) => ({
60
- rule: "akan.agent.missing-store-description",
61
- scope: "agent" as const,
62
- severity: "warning" as const,
63
- file: store.file,
64
- line,
65
- message: `Store action "${name}" calls ${fetched.map((key) => `${key}()`).join(", ")} under a different name and has no dictionary .store() entry, so an agent reading it has the name and nothing else.`,
66
- }));
67
- }
68
-
69
- /** Methods written in the store class body. Generated actions never appear here, which is why they are exempt. */
70
- static #customActions(store: SourceFileInfo): CustomAction[] {
71
- const actions: CustomAction[] = [];
72
- const visit = (node: ts.Node) => {
73
- if (ts.isClassDeclaration(node) && StoreScanner.#extendsStore(node)) {
74
- for (const member of node.members) {
75
- // A getter computes rather than dispatches, and a static helper is not on `st.do` at all.
76
- if (!ts.isMethodDeclaration(member)) continue;
77
- if (member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword)) continue;
78
- const name = StoreScanner.#memberName(member);
79
- if (!name) continue;
80
- const line = store.sourceFile.getLineAndCharacterOfPosition(member.getStart(store.sourceFile)).line + 1;
81
- actions.push({ name, line, fetched: StoreScanner.#fetchedEndpoints(member) });
82
- }
83
- }
84
- ts.forEachChild(node, visit);
85
- };
86
- visit(store.sourceFile);
87
- return actions;
88
- }
89
-
90
- static #extendsStore(node: ts.ClassDeclaration) {
91
- return !!node.heritageClauses?.some((clause) =>
92
- clause.types.some((type) => ts.isCallExpression(type.expression) && StoreScanner.#isStoreCall(type.expression)),
93
- );
94
- }
95
-
96
- static #isStoreCall(expression: ts.CallExpression) {
97
- return ts.isIdentifier(expression.expression) && expression.expression.text === "store";
98
- }
99
-
100
- /** The `fetch.<name>` calls inside one action, which is what says whether it is reachable past the client. */
101
- static #fetchedEndpoints(member: ts.MethodDeclaration): string[] {
102
- const fetched = new Set<string>();
103
- const visit = (node: ts.Node) => {
104
- if (
105
- ts.isCallExpression(node) &&
106
- ts.isPropertyAccessExpression(node.expression) &&
107
- ts.isIdentifier(node.expression.expression) &&
108
- node.expression.expression.text === "fetch"
109
- )
110
- fetched.add(node.expression.name.text);
111
- ts.forEachChild(node, visit);
112
- };
113
- visit(member);
114
- return [...fetched];
115
- }
116
-
117
- /** Entry names that carry a `.desc()`, per dictionary stage. */
118
- static #describedEntries(dictionary: SourceFileInfo): Map<string, Set<string>> {
119
- const described = new Map<string, Set<string>>();
120
- const visit = (node: ts.Node) => {
121
- const stage = StoreScanner.#dictionaryStage(node);
122
- if (stage) {
123
- for (const [name, chain] of StoreScanner.#stageEntries(node as ts.CallExpression)) {
124
- if (!chain.has("desc")) continue;
125
- const names = described.get(stage) ?? new Set<string>();
126
- names.add(name);
127
- described.set(stage, names);
128
- }
129
- }
130
- ts.forEachChild(node, visit);
131
- };
132
- visit(dictionary.sourceFile);
133
- return described;
134
- }
135
-
136
- static #dictionaryStage(node: ts.Node) {
137
- if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) return null;
138
- const stage = node.expression.name.text;
139
- return stage === "store" || stage === "endpoint" ? stage : null;
140
- }
141
-
142
- static #stageEntries(stage: ts.CallExpression): Array<[string, Set<string>]> {
143
- const callback = stage.arguments[0];
144
- if (!callback || !ts.isArrowFunction(callback)) return [];
145
- const body = ts.isParenthesizedExpression(callback.body) ? callback.body.expression : callback.body;
146
- if (!ts.isObjectLiteralExpression(body)) return [];
147
- return body.properties.flatMap((property) => {
148
- if (!ts.isPropertyAssignment(property)) return [];
149
- const name = StoreScanner.#memberName(property);
150
- return name ? [[name, StoreScanner.#chainCalls(property.initializer)] as [string, Set<string>]] : [];
151
- });
152
- }
153
-
154
- /**
155
- * Only the calls on the entry's own chain. A nested `.arg((t) => ({ x: t([…]).desc([…]) }))` describes an
156
- * argument, not the entry, so a subtree walk would read every entry as described.
157
- */
158
- static #chainCalls(expression: ts.Expression): Set<string> {
159
- const calls = new Set<string>();
160
- let current: ts.Node = expression;
161
- while (ts.isCallExpression(current) || ts.isPropertyAccessExpression(current)) {
162
- if (ts.isPropertyAccessExpression(current)) calls.add(current.name.text);
163
- current = current.expression;
164
- }
165
- return calls;
166
- }
167
-
168
- static #memberName(member: ts.MethodDeclaration | ts.PropertyAssignment) {
169
- const { name } = member;
170
- if (!name || (!ts.isIdentifier(name) && !ts.isStringLiteral(name))) return null;
171
- return name.text;
172
- }
173
- }
package/streamAi.ts DELETED
@@ -1,45 +0,0 @@
1
- import { PromptTemplate } from "@langchain/core/prompts";
2
- import { RunnableSequence } from "@langchain/core/runnables";
3
- import { ChatOpenAI } from "@langchain/openai";
4
-
5
- interface StreamResponse {
6
- content: string;
7
- chunk?: string;
8
- }
9
-
10
- export const streamAi = async (
11
- question: string,
12
- callback: (chunk: string) => void = (chunk) => {
13
- process.stdout.write(chunk);
14
- },
15
- ): Promise<StreamResponse> => {
16
- const createStreamingModel = (apiKey = process.env.DEEPSEEK_API_KEY) => {
17
- if (!apiKey) throw new Error(`process.env.DEEPSEEK_API_KEY is not set`);
18
- return new ChatOpenAI({
19
- modelName: "deepseek-reasoner",
20
- temperature: 0.7,
21
- streaming: true, // Enable streaming
22
- configuration: { baseURL: "https://api.deepseek.com/v1", apiKey },
23
- });
24
- };
25
- const createProcessingChain = () => {
26
- return RunnableSequence.from([PromptTemplate.fromTemplate(`Answer concisely: {question}`), createStreamingModel()]);
27
- };
28
- try {
29
- const chain = createProcessingChain();
30
- const stream = await chain.stream({ question });
31
-
32
- let fullResponse = "";
33
- for await (const chunk of stream) {
34
- const content = chunk.content;
35
- if (typeof content === "string") {
36
- fullResponse += content;
37
- callback(content); // Send individual chunks to callback
38
- }
39
- }
40
-
41
- return { content: fullResponse };
42
- } catch (error) {
43
- throw new Error("Failed to stream response");
44
- }
45
- };