@akanjs/devkit 1.0.20 → 2.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/aiEditor.ts +304 -0
  2. package/akanApp/akanApp.host.ts +393 -0
  3. package/akanApp/index.ts +1 -0
  4. package/akanConfig/akanConfig.test.ts +236 -0
  5. package/akanConfig/akanConfig.ts +384 -0
  6. package/akanConfig/index.ts +2 -0
  7. package/akanConfig/types.ts +23 -0
  8. package/applicationBuildReporter.ts +69 -0
  9. package/applicationBuildRunner.ts +302 -0
  10. package/applicationReleasePackager.ts +206 -0
  11. package/artifact/implicitRootLayout.ts +155 -0
  12. package/artifact/index.ts +1 -0
  13. package/artifact/routeSeedIndex.test.ts +98 -0
  14. package/artifact/routeSeedIndex.ts +130 -0
  15. package/auth.ts +41 -0
  16. package/builder.ts +164 -0
  17. package/capacitor.base.config.ts +88 -0
  18. package/capacitorApp.ts +440 -0
  19. package/commandDecorators/argMeta.ts +102 -0
  20. package/commandDecorators/command.ts +343 -0
  21. package/commandDecorators/commandBuilder.ts +224 -0
  22. package/commandDecorators/commandDecorators.test.ts +212 -0
  23. package/commandDecorators/commandMeta.ts +7 -0
  24. package/commandDecorators/dependencyBuilder.ts +100 -0
  25. package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
  26. package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
  27. package/commandDecorators/targetMeta.ts +31 -0
  28. package/commandDecorators/types.ts +10 -0
  29. package/constants.ts +25 -0
  30. package/createTunnel.ts +36 -0
  31. package/dependencyScanner.ts +357 -0
  32. package/devkitUtils.test.ts +259 -0
  33. package/executors.test.ts +315 -0
  34. package/executors.ts +1390 -0
  35. package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
  36. package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
  37. package/fileSys.ts +39 -0
  38. package/frontendBuild/allRoutesBuilder.ts +103 -0
  39. package/frontendBuild/buildRouteClient.test.ts +190 -0
  40. package/frontendBuild/clientBuildTypes.ts +114 -0
  41. package/frontendBuild/clientEntriesBundler.ts +303 -0
  42. package/frontendBuild/clientEntryDiscovery.ts +199 -0
  43. package/frontendBuild/csrArtifactBuilder.ts +237 -0
  44. package/frontendBuild/cssCompiler.ts +286 -0
  45. package/frontendBuild/cssImportResolver.ts +116 -0
  46. package/frontendBuild/fontOptimizer.ts +427 -0
  47. package/frontendBuild/frontendBuild.test.ts +204 -0
  48. package/frontendBuild/hmrChangeClassifier.ts +28 -0
  49. package/frontendBuild/hmrWatcher.ts +102 -0
  50. package/frontendBuild/index.ts +18 -0
  51. package/frontendBuild/pagesBundleBuilder.ts +137 -0
  52. package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
  53. package/frontendBuild/precompressArtifacts.ts +59 -0
  54. package/frontendBuild/routeClientBuilder.ts +290 -0
  55. package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
  56. package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
  57. package/frontendBuild/vendorSpecifiers.ts +16 -0
  58. package/frontendBuild/watchRootResolver.ts +28 -0
  59. package/getCredentials.ts +19 -0
  60. package/getDirname.ts +3 -0
  61. package/getModelFileData.ts +59 -0
  62. package/getRelatedCnsts.ts +313 -0
  63. package/guideline.ts +19 -0
  64. package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
  65. package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
  66. package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
  67. package/incrementalBuilder/index.ts +1 -0
  68. package/{esm/src/index.js → index.ts} +28 -15
  69. package/lint/no-deep-internal-import.grit +25 -0
  70. package/lint/no-import-client-functions.grit +32 -0
  71. package/lint/no-import-external-library.grit +21 -0
  72. package/lint/no-js-private-class-method.grit +42 -0
  73. package/lint/no-use-client-in-server.grit +7 -0
  74. package/lint/non-scalar-props-restricted.grit +13 -0
  75. package/linter.ts +271 -0
  76. package/mobile/index.ts +1 -0
  77. package/mobile/mobileTarget.test.ts +53 -0
  78. package/mobile/mobileTarget.ts +88 -0
  79. package/package.json +48 -31
  80. package/prompter.ts +72 -0
  81. package/scanInfo.ts +606 -0
  82. package/selectModel.ts +11 -0
  83. package/{esm/src/spinner.js → spinner.ts} +22 -28
  84. package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
  85. package/sshTunnel.ts +152 -0
  86. package/{esm/src/streamAi.js → streamAi.ts} +18 -12
  87. package/transforms/barrelAnalyzer.ts +278 -0
  88. package/transforms/barrelImportsPlugin.ts +504 -0
  89. package/transforms/externalizeFrameworkPlugin.ts +185 -0
  90. package/transforms/index.ts +5 -0
  91. package/transforms/rscUseClientTransform.ts +59 -0
  92. package/transforms/transforms.test.ts +208 -0
  93. package/transforms/useClientBundlePlugin.ts +47 -0
  94. package/tsconfig.json +37 -0
  95. package/typeChecker.ts +264 -0
  96. package/types.ts +44 -0
  97. package/ui/MultiScrollList.tsx +242 -0
  98. package/ui/ScrollList.tsx +107 -0
  99. package/ui/index.ts +2 -0
  100. package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
  101. package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
  102. package/README.md +0 -7
  103. package/cjs/index.js +0 -21
  104. package/cjs/src/aiEditor.js +0 -311
  105. package/cjs/src/auth.js +0 -72
  106. package/cjs/src/builder.js +0 -114
  107. package/cjs/src/capacitorApp.js +0 -313
  108. package/cjs/src/commandDecorators/argMeta.js +0 -88
  109. package/cjs/src/commandDecorators/command.js +0 -324
  110. package/cjs/src/commandDecorators/commandMeta.js +0 -30
  111. package/cjs/src/commandDecorators/helpFormatter.js +0 -211
  112. package/cjs/src/commandDecorators/index.js +0 -31
  113. package/cjs/src/commandDecorators/targetMeta.js +0 -57
  114. package/cjs/src/commandDecorators/types.js +0 -15
  115. package/cjs/src/constants.js +0 -46
  116. package/cjs/src/createTunnel.js +0 -49
  117. package/cjs/src/dependencyScanner.js +0 -220
  118. package/cjs/src/executors.js +0 -964
  119. package/cjs/src/extractDeps.js +0 -103
  120. package/cjs/src/fileEditor.js +0 -120
  121. package/cjs/src/getCredentials.js +0 -44
  122. package/cjs/src/getDirname.js +0 -38
  123. package/cjs/src/getModelFileData.js +0 -66
  124. package/cjs/src/getRelatedCnsts.js +0 -260
  125. package/cjs/src/guideline.js +0 -15
  126. package/cjs/src/index.js +0 -65
  127. package/cjs/src/linter.js +0 -238
  128. package/cjs/src/prompter.js +0 -85
  129. package/cjs/src/scanInfo.js +0 -491
  130. package/cjs/src/selectModel.js +0 -46
  131. package/cjs/src/spinner.js +0 -93
  132. package/cjs/src/streamAi.js +0 -62
  133. package/cjs/src/typeChecker.js +0 -207
  134. package/cjs/src/types.js +0 -15
  135. package/cjs/src/uploadRelease.js +0 -112
  136. package/cjs/src/useStdoutDimensions.js +0 -43
  137. package/esm/index.js +0 -1
  138. package/esm/src/aiEditor.js +0 -282
  139. package/esm/src/auth.js +0 -42
  140. package/esm/src/builder.js +0 -81
  141. package/esm/src/commandDecorators/argMeta.js +0 -54
  142. package/esm/src/commandDecorators/command.js +0 -290
  143. package/esm/src/commandDecorators/commandMeta.js +0 -7
  144. package/esm/src/commandDecorators/targetMeta.js +0 -33
  145. package/esm/src/commandDecorators/types.js +0 -0
  146. package/esm/src/constants.js +0 -17
  147. package/esm/src/createTunnel.js +0 -26
  148. package/esm/src/dependencyScanner.js +0 -187
  149. package/esm/src/executors.js +0 -928
  150. package/esm/src/getCredentials.js +0 -11
  151. package/esm/src/getDirname.js +0 -5
  152. package/esm/src/getModelFileData.js +0 -33
  153. package/esm/src/getRelatedCnsts.js +0 -221
  154. package/esm/src/guideline.js +0 -0
  155. package/esm/src/linter.js +0 -205
  156. package/esm/src/prompter.js +0 -51
  157. package/esm/src/scanInfo.js +0 -455
  158. package/esm/src/selectModel.js +0 -13
  159. package/esm/src/typeChecker.js +0 -174
  160. package/esm/src/types.js +0 -0
  161. package/index.d.ts +0 -1
  162. package/src/aiEditor.d.ts +0 -50
  163. package/src/auth.d.ts +0 -9
  164. package/src/builder.d.ts +0 -18
  165. package/src/capacitorApp.d.ts +0 -39
  166. package/src/commandDecorators/argMeta.d.ts +0 -67
  167. package/src/commandDecorators/command.d.ts +0 -2
  168. package/src/commandDecorators/commandMeta.d.ts +0 -2
  169. package/src/commandDecorators/helpFormatter.d.ts +0 -3
  170. package/src/commandDecorators/index.d.ts +0 -6
  171. package/src/commandDecorators/targetMeta.d.ts +0 -19
  172. package/src/commandDecorators/types.d.ts +0 -1
  173. package/src/constants.d.ts +0 -26
  174. package/src/createTunnel.d.ts +0 -8
  175. package/src/dependencyScanner.d.ts +0 -23
  176. package/src/executors.d.ts +0 -296
  177. package/src/extractDeps.d.ts +0 -7
  178. package/src/fileEditor.d.ts +0 -16
  179. package/src/getCredentials.d.ts +0 -12
  180. package/src/getDirname.d.ts +0 -1
  181. package/src/getModelFileData.d.ts +0 -16
  182. package/src/getRelatedCnsts.d.ts +0 -53
  183. package/src/guideline.d.ts +0 -19
  184. package/src/index.d.ts +0 -23
  185. package/src/linter.d.ts +0 -109
  186. package/src/prompter.d.ts +0 -14
  187. package/src/scanInfo.d.ts +0 -82
  188. package/src/selectModel.d.ts +0 -1
  189. package/src/spinner.d.ts +0 -20
  190. package/src/streamAi.d.ts +0 -6
  191. package/src/typeChecker.d.ts +0 -52
  192. package/src/types.d.ts +0 -31
  193. package/src/uploadRelease.d.ts +0 -10
  194. package/src/useStdoutDimensions.d.ts +0 -1
package/scanInfo.ts ADDED
@@ -0,0 +1,606 @@
1
+ import path from "node:path";
2
+ import type {
3
+ AppConfigResult,
4
+ AppScanResult,
5
+ FileConventionScanResult,
6
+ LibConfigResult,
7
+ LibScanResult,
8
+ PkgScanResult,
9
+ ScanResult,
10
+ } from "./akanConfig";
11
+
12
+ import { TypeScriptDependencyScanner } from "./dependencyScanner";
13
+ import { AppExecutor, LibExecutor, PkgExecutor, WorkspaceExecutor } from "./executors";
14
+
15
+ const scalarFileTypes = ["constant", "dictionary", "document", "template", "unit", "util", "view", "zone"] as const;
16
+ type ScalarFileType = (typeof scalarFileTypes)[number];
17
+ const serviceFileTypes = [
18
+ "dictionary",
19
+ "service",
20
+ "signal",
21
+ "store",
22
+ "template",
23
+ "unit",
24
+ "util",
25
+ "view",
26
+ "zone",
27
+ ] as const;
28
+ type ServiceFileType = (typeof serviceFileTypes)[number];
29
+ const databaseFileTypes = [
30
+ "constant",
31
+ "dictionary",
32
+ "document",
33
+ "service",
34
+ "signal",
35
+ "store",
36
+ "template",
37
+ "unit",
38
+ "util",
39
+ "view",
40
+ "zone",
41
+ ] as const;
42
+ type DatabaseFileType = (typeof databaseFileTypes)[number];
43
+
44
+ type ModuleKind = "database" | "service" | "scalar";
45
+
46
+ const appRootAllowedFiles = new Set([
47
+ "akan.app.json",
48
+ "akan.config.ts",
49
+ "capacitor.config.ts",
50
+ "client.ts",
51
+ "main.ts",
52
+ "package.json",
53
+ "server.ts",
54
+ "tsconfig.json",
55
+ ]);
56
+ const appRootAllowedDirs = new Set([
57
+ ".akan",
58
+ "android",
59
+ "env",
60
+ "ios",
61
+ "lib",
62
+ "mobile",
63
+ "page",
64
+ "private",
65
+ "public",
66
+ "script",
67
+ "ui",
68
+ "srvkit",
69
+ "webkit",
70
+ "common",
71
+ ]);
72
+ const libRootAllowedFiles = new Set([
73
+ "cnst.ts",
74
+ "db.ts",
75
+ "dict.ts",
76
+ "option.ts",
77
+ "sig.ts",
78
+ "srv.ts",
79
+ "st.ts",
80
+ "useClient.ts",
81
+ "useServer.ts",
82
+ ]);
83
+ const internalLibDirs = new Set(["__lib", "__scalar"]);
84
+ const moduleNonUiFileTypes = {
85
+ database: new Set(["constant", "dictionary", "document", "service", "signal", "store"]),
86
+ service: new Set(["dictionary", "service", "signal", "store"]),
87
+ scalar: new Set(["constant", "dictionary", "document"]),
88
+ } satisfies Record<ModuleKind, Set<string>>;
89
+ const moduleUiFileTypes = {
90
+ database: new Set(["Template", "Unit", "Util", "View", "Zone"]),
91
+ service: new Set(["Util", "Zone"]),
92
+ scalar: new Set(["Template", "Unit"]),
93
+ } satisfies Record<ModuleKind, Set<string>>;
94
+ const testFilePattern = /\.(test|spec)\.(ts|tsx)$/;
95
+ const rootSignalTestFilePattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(ts|tsx)$/;
96
+
97
+ const isAllowedTestFile = (filename: string) => testFilePattern.test(filename);
98
+ const isAllowedLibRootFile = (filename: string) =>
99
+ libRootAllowedFiles.has(filename) || rootSignalTestFilePattern.test(filename);
100
+ const getScanPath = (exec: AppExecutor | LibExecutor, relativePath: string) =>
101
+ path.posix.join(`${exec.type}s`, exec.name, relativePath.split(path.sep).join("/"));
102
+
103
+ async function assertScanConvention(exec: AppExecutor | LibExecutor, libRoot: { files: string[]; dirs: string[] }) {
104
+ const violations: string[] = [];
105
+ const addViolation = (relativePath: string, reason: string) => {
106
+ violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
107
+ };
108
+
109
+ if (exec.type === "app") {
110
+ const { files, dirs } = await exec.getFilesAndDirs(".");
111
+ files
112
+ .filter((filename) => !appRootAllowedFiles.has(filename))
113
+ .forEach((filename) => {
114
+ addViolation(filename, "unsupported app root file");
115
+ });
116
+ dirs
117
+ .filter((dirname) => !appRootAllowedDirs.has(dirname))
118
+ .forEach((dirname) => {
119
+ addViolation(dirname, "unsupported app root folder");
120
+ });
121
+ }
122
+
123
+ libRoot.files
124
+ .filter((filename) => !isAllowedLibRootFile(filename))
125
+ .forEach((filename) => {
126
+ addViolation(path.join("lib", filename), "unsupported lib root file");
127
+ });
128
+
129
+ libRoot.dirs
130
+ .filter((dirname) => dirname.startsWith("__") && !internalLibDirs.has(dirname))
131
+ .forEach((dirname) => {
132
+ addViolation(path.join("lib", dirname), "unsupported internal lib folder");
133
+ });
134
+
135
+ const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
136
+ const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
137
+ const scalarDirs = await exec.readdir("lib/__scalar");
138
+ await Promise.all([
139
+ ...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path.join("lib", dirname))),
140
+ ...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path.join("lib", dirname))),
141
+ ...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path.join("lib/__scalar", dirname))),
142
+ ]);
143
+
144
+ if (violations.length > 0) {
145
+ throw new Error(
146
+ `[scan-convention]\n${violations
147
+ .sort()
148
+ .map((violation) => `- ${violation}`)
149
+ .join("\n")}`,
150
+ );
151
+ }
152
+ }
153
+
154
+ async function validateModuleFiles(
155
+ exec: AppExecutor | LibExecutor,
156
+ violations: string[],
157
+ kind: ModuleKind,
158
+ modulePath: string,
159
+ ) {
160
+ const { files, dirs } = await exec.getFilesAndDirs(modulePath);
161
+ dirs.forEach((dirname) => {
162
+ violations.push(`${getScanPath(exec, path.join(modulePath, dirname))}: unsupported module folder`);
163
+ });
164
+
165
+ files.forEach((filename) => {
166
+ const filePath = path.join(modulePath, filename);
167
+ if (filename === "index.ts" || filename === "index.tsx" || isAllowedTestFile(filename)) return;
168
+
169
+ const uiMatch = filename.match(/\.([A-Z][A-Za-z0-9]*)\.tsx$/);
170
+ if (uiMatch) {
171
+ const fileType = uiMatch[1];
172
+ if (!moduleUiFileTypes[kind].has(fileType)) {
173
+ violations.push(`${getScanPath(exec, filePath)}: unsupported ${kind} UI file`);
174
+ }
175
+ return;
176
+ }
177
+
178
+ const nonUiMatch = filename.match(/\.([a-z][A-Za-z0-9]*)\.ts$/);
179
+ if (nonUiMatch) {
180
+ const fileType = nonUiMatch[1];
181
+ if (!moduleNonUiFileTypes[kind].has(fileType)) {
182
+ violations.push(`${getScanPath(exec, filePath)}: unsupported ${kind} file`);
183
+ }
184
+ return;
185
+ }
186
+
187
+ violations.push(`${getScanPath(exec, filePath)}: unsupported module file`);
188
+ });
189
+ }
190
+
191
+ class ScanInfo {
192
+ protected scanResult: ScanResult;
193
+
194
+ readonly name: string;
195
+ readonly scalar = new Map<string, Set<ScalarFileType>>();
196
+ readonly service = new Map<string, Set<ServiceFileType>>();
197
+ readonly database = new Map<string, Set<DatabaseFileType>>();
198
+ readonly file = Object.fromEntries(
199
+ databaseFileTypes.map((type) => [
200
+ type,
201
+ { all: new Set(), databases: new Set(), services: new Set(), scalars: new Set() },
202
+ ]),
203
+ ) as {
204
+ [key in DatabaseFileType]: {
205
+ all: Set<string>;
206
+ databases: Set<string>;
207
+ services: Set<string>;
208
+ scalars: Set<string>;
209
+ };
210
+ };
211
+
212
+ static async getScanResult(exec: AppExecutor | LibExecutor) {
213
+ const [akanConfig, scanner, pkgs, libs] = await Promise.all([
214
+ exec.getConfig(),
215
+ TypeScriptDependencyScanner.from(exec),
216
+ exec.workspace.getPkgs(),
217
+ exec.workspace.getLibs(),
218
+ ]);
219
+ const { pkgDeps, libDeps, npmDeps, npmDevDeps } = await scanner.getMonorepoDependencies(exec.name, { pkgs, libs });
220
+ const files: FileConventionScanResult = {
221
+ constant: { databases: [], scalars: [] },
222
+ dictionary: { databases: [], services: [], scalars: [] },
223
+ document: { databases: [], scalars: [] },
224
+ service: { databases: [], services: [] },
225
+ signal: { databases: [], services: [] },
226
+ store: { databases: [], services: [] },
227
+ template: { databases: [], services: [], scalars: [] },
228
+ unit: { databases: [], services: [], scalars: [] },
229
+ util: { databases: [], services: [], scalars: [] },
230
+ view: { databases: [], services: [], scalars: [] },
231
+ zone: { databases: [], services: [], scalars: [] },
232
+ };
233
+ const [libRoot, scalarDirs] = await Promise.all([exec.getFilesAndDirs("lib"), exec.readdir("lib/__scalar")]);
234
+ await assertScanConvention(exec, libRoot);
235
+ const { dirs: dirnames } = libRoot;
236
+ const databaseDirs: string[] = [];
237
+ const serviceDirs: string[] = [];
238
+ dirnames.forEach((name) => {
239
+ if (name.startsWith("_")) {
240
+ if (name.startsWith("__")) return;
241
+ else serviceDirs.push(name);
242
+ } else databaseDirs.push(name);
243
+ });
244
+
245
+ await Promise.all([
246
+ ...databaseDirs.map(async (name) => {
247
+ const filenames = await exec.readdir(path.join("lib", name));
248
+ filenames.forEach((filename) => {
249
+ if (filename.endsWith(".constant.ts")) files.constant.databases.push(name);
250
+ else if (filename.endsWith(".dictionary.ts")) files.dictionary.databases.push(name);
251
+ else if (filename.endsWith(".document.ts")) files.document.databases.push(name);
252
+ else if (filename.endsWith(".service.ts")) files.service.databases.push(name);
253
+ else if (filename.endsWith(".signal.ts")) files.signal.databases.push(name);
254
+ else if (filename.endsWith(".store.ts")) files.store.databases.push(name);
255
+ else if (filename.endsWith(".Template.tsx")) files.template.databases.push(name);
256
+ else if (filename.endsWith(".Unit.tsx")) files.unit.databases.push(name);
257
+ else if (filename.endsWith(".Util.tsx")) files.util.databases.push(name);
258
+ else if (filename.endsWith(".View.tsx")) files.view.databases.push(name);
259
+ else if (filename.endsWith(".Zone.tsx")) files.zone.databases.push(name);
260
+ });
261
+ }),
262
+ ...serviceDirs.map(async (dirname) => {
263
+ const name = dirname.slice(1);
264
+ const filenames = await exec.readdir(path.join("lib", dirname));
265
+ filenames.forEach((filename) => {
266
+ if (filename.endsWith(".dictionary.ts")) files.dictionary.services.push(name);
267
+ else if (filename.endsWith(".service.ts")) files.service.services.push(name);
268
+ else if (filename.endsWith(".signal.ts")) files.signal.services.push(name);
269
+ else if (filename.endsWith(".store.ts")) files.store.services.push(name);
270
+ else if (filename.endsWith(".Template.tsx")) files.template.services.push(name);
271
+ else if (filename.endsWith(".Unit.tsx")) files.unit.services.push(name);
272
+ else if (filename.endsWith(".Util.tsx")) files.util.services.push(name);
273
+ else if (filename.endsWith(".View.tsx")) files.view.services.push(name);
274
+ else if (filename.endsWith(".Zone.tsx")) files.zone.services.push(name);
275
+ });
276
+ }),
277
+ ...scalarDirs.map(async (name) => {
278
+ const filenames = await exec.readdir(path.join("lib/__scalar", name));
279
+ filenames.forEach((filename) => {
280
+ if (filename.endsWith(".constant.ts")) files.constant.scalars.push(name);
281
+ else if (filename.endsWith(".dictionary.ts")) files.dictionary.scalars.push(name);
282
+ else if (filename.endsWith(".document.ts")) files.document.scalars.push(name);
283
+ else if (filename.endsWith(".Template.tsx")) files.template.scalars.push(name);
284
+ else if (filename.endsWith(".Unit.tsx")) files.unit.scalars.push(name);
285
+ else if (filename.endsWith(".Util.tsx")) files.util.scalars.push(name);
286
+ else if (filename.endsWith(".View.tsx")) files.view.scalars.push(name);
287
+ else if (filename.endsWith(".Zone.tsx")) files.zone.scalars.push(name);
288
+ });
289
+ }),
290
+ ]);
291
+ const routes = exec.type === "lib" ? [] : await (exec as AppExecutor).getPageKeys();
292
+ const scanResult: AppScanResult | LibScanResult = {
293
+ name: exec.name,
294
+ type: exec.type,
295
+ repoName: exec.workspace.repoName,
296
+ serveDomain: WorkspaceExecutor.getBaseDevEnv(path.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
297
+ akanConfig,
298
+ files,
299
+ libDeps,
300
+ pkgDeps,
301
+ dependencies: npmDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
302
+ devDependencies: npmDevDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
303
+ routes,
304
+ };
305
+ return scanResult;
306
+ }
307
+
308
+ constructor(scanResult: ScanResult) {
309
+ this.name = scanResult.name;
310
+ this.scanResult = scanResult;
311
+ Object.entries(scanResult.files).forEach(([_key, value]) => {
312
+ const key = _key as DatabaseFileType;
313
+ const { databases, services, scalars } = value as {
314
+ databases: string[];
315
+ services?: string[];
316
+ scalars?: string[];
317
+ };
318
+ databases.forEach((modelName) => {
319
+ const model = this.database.get(modelName) ?? new Set<DatabaseFileType>();
320
+ model.add(key);
321
+ this.database.set(modelName, model);
322
+ this.file[key].all.add(modelName);
323
+ this.file[key].databases.add(modelName);
324
+ });
325
+ services?.forEach((serviceName) => {
326
+ const service = this.service.get(serviceName) ?? new Set<ServiceFileType>();
327
+ service.add(key as ServiceFileType);
328
+ this.service.set(serviceName, service);
329
+ this.file[key].all.add(serviceName);
330
+ this.file[key].services.add(serviceName);
331
+ });
332
+ scalars?.forEach((scalarName) => {
333
+ const scalar = this.scalar.get(scalarName) ?? new Set<ScalarFileType>();
334
+ scalar.add(key as ScalarFileType);
335
+ this.scalar.set(scalarName, scalar);
336
+ this.file[key].all.add(scalarName);
337
+ this.file[key].scalars.add(scalarName);
338
+ });
339
+ });
340
+ }
341
+ getScanResult() {
342
+ return this.scanResult;
343
+ }
344
+ getDatabaseModules() {
345
+ return [...this.database.keys()];
346
+ }
347
+ getServiceModules() {
348
+ return [...this.service.keys()];
349
+ }
350
+ getScalarModules() {
351
+ return [...this.scalar.keys()];
352
+ }
353
+ }
354
+
355
+ const isAkanFrameworkDependency = (dep: string) => dep === "akanjs" || dep.startsWith("akanjs/");
356
+ export class AppInfo extends ScanInfo {
357
+ readonly type = "app";
358
+ readonly exec: AppExecutor;
359
+ readonly akanConfig: AppConfigResult;
360
+ readonly libDeps: string[];
361
+
362
+ static appInfos = new Map<string, AppInfo>();
363
+ static async fromExecutor(exec: AppExecutor, options: { refresh?: boolean } = {}) {
364
+ // cache check
365
+ const existingAppInfo = AppInfo.appInfos.get(exec.name);
366
+ if (existingAppInfo && !options.refresh) return existingAppInfo;
367
+ const scanResult = await ScanInfo.getScanResult(exec);
368
+
369
+ await Promise.all(
370
+ scanResult.libDeps.map(async (libName) => {
371
+ LibInfo.loadedLibs.add(libName);
372
+ const libExecutor = LibExecutor.from(exec, libName);
373
+ LibInfo.libInfos.set(libName, await LibInfo.fromExecutor(libExecutor));
374
+ }),
375
+ );
376
+ const libDeps = await AppInfo.#getAllLibDeps(exec, scanResult.libDeps);
377
+ const appInfo = new AppInfo(exec, scanResult as AppScanResult, libDeps);
378
+ AppInfo.appInfos.set(exec.name, appInfo);
379
+ return appInfo;
380
+ }
381
+
382
+ constructor(exec: AppExecutor, scanResult: AppScanResult, libDeps: string[]) {
383
+ super(scanResult);
384
+ this.exec = exec;
385
+ this.akanConfig = scanResult.akanConfig;
386
+ this.libDeps = libDeps;
387
+ }
388
+ override getScanResult(): AppScanResult {
389
+ return this.scanResult as AppScanResult;
390
+ }
391
+
392
+ static async #getAllLibDeps(exec: AppExecutor, libDeps: string[], libSet = new Set<string>()) {
393
+ await Promise.all(
394
+ libDeps.map(async (libName) => {
395
+ if (libSet.has(libName)) return;
396
+ libSet.add(libName);
397
+ const libExecutor = LibExecutor.from(exec, libName);
398
+ const libInfo = await LibInfo.fromExecutor(libExecutor);
399
+ const libScanResult = libInfo.getScanResult();
400
+ if (libScanResult.libDeps.length > 0) await AppInfo.#getAllLibDeps(exec, libScanResult.libDeps, libSet);
401
+ }),
402
+ );
403
+ return [...libSet];
404
+ }
405
+
406
+ #sortedLibs: string[] | null = null;
407
+ #getSortedLibs() {
408
+ if (this.#sortedLibs) return this.#sortedLibs;
409
+ const libIndices = LibInfo.getSortedLibIndices();
410
+ this.#sortedLibs = this.libDeps.sort((libNameA, libNameB) => {
411
+ const indexA = libIndices.get(libNameA);
412
+ const indexB = libIndices.get(libNameB);
413
+ if (indexA === undefined || indexB === undefined)
414
+ throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
415
+ return indexA - indexB;
416
+ });
417
+ return this.#sortedLibs;
418
+ }
419
+ getLibs() {
420
+ return this.#getSortedLibs();
421
+ }
422
+ getLibInfos() {
423
+ return new Map(
424
+ this.#getSortedLibs().map((libName) => {
425
+ const libInfo = LibInfo.libInfos.get(libName);
426
+ if (!libInfo) throw new Error(`LibInfo not found: ${libName}`);
427
+ return [libName, libInfo];
428
+ }),
429
+ );
430
+ }
431
+ }
432
+ export class LibInfo extends ScanInfo {
433
+ readonly type = "lib";
434
+ readonly exec: LibExecutor;
435
+ readonly akanConfig: LibConfigResult;
436
+
437
+ static loadedLibs = new Set<string>();
438
+ static readonly libInfos = new Map<string, LibInfo>();
439
+ static #sortedLibIndices: Map<string, number> | null = null;
440
+
441
+ static getSortedLibIndices() {
442
+ if (LibInfo.#sortedLibIndices) return LibInfo.#sortedLibIndices;
443
+ LibInfo.#sortedLibIndices = new Map(
444
+ [...LibInfo.libInfos.entries()]
445
+ .sort(([_, libInfoA], [__, libInfoB]) => (libInfoA.getScanResult().libDeps.includes(libInfoB.name) ? 1 : -1))
446
+ .map(([libName], index) => [libName, index]),
447
+ );
448
+ return LibInfo.#sortedLibIndices;
449
+ }
450
+
451
+ static async fromExecutor(exec: LibExecutor, { refresh }: { refresh?: boolean } = {}) {
452
+ const existingLibInfo = LibInfo.libInfos.get(exec.name);
453
+ if (existingLibInfo && !refresh) return existingLibInfo;
454
+
455
+ const scanResult = await ScanInfo.getScanResult(exec);
456
+ await Promise.all(
457
+ scanResult.libDeps
458
+ .filter((libName) => !LibInfo.loadedLibs.has(libName))
459
+ .map(async (libName) => {
460
+ LibInfo.loadedLibs.add(libName);
461
+ const libExecutor = LibExecutor.from(exec, libName);
462
+ LibInfo.libInfos.set(libName, await LibInfo.fromExecutor(libExecutor));
463
+ }),
464
+ );
465
+ const libInfo = new LibInfo(exec, scanResult);
466
+ LibInfo.libInfos.set(exec.name, libInfo);
467
+ LibInfo.#sortedLibIndices = null;
468
+ return libInfo;
469
+ }
470
+
471
+ constructor(exec: LibExecutor, scanResult: LibScanResult) {
472
+ super(scanResult);
473
+ this.exec = exec;
474
+ this.akanConfig = scanResult.akanConfig;
475
+ }
476
+ override getScanResult(): LibScanResult {
477
+ return this.scanResult as LibScanResult;
478
+ }
479
+
480
+ #sortedLibs: string[] | null = null;
481
+ #getSortedLibs() {
482
+ if (this.#sortedLibs) return this.#sortedLibs;
483
+ const libs = LibInfo.getSortedLibIndices();
484
+ this.#sortedLibs = this.scanResult.libDeps.sort((libNameA, libNameB) => {
485
+ const indexA = libs.get(libNameA);
486
+ const indexB = libs.get(libNameB);
487
+ if (indexA === undefined || indexB === undefined)
488
+ throw new Error(`LibInfo not found: ${libNameA} or ${libNameB}`);
489
+ return indexA - indexB;
490
+ });
491
+ return this.#sortedLibs;
492
+ }
493
+ getLibs() {
494
+ return this.#getSortedLibs();
495
+ }
496
+ getLibInfo(libName: string) {
497
+ if (!this.getScanResult().libDeps.includes(libName)) return undefined;
498
+ const libSet = new Set(this.#getSortedLibs());
499
+ if (!libSet.has(libName)) throw new Error(`LibInfo is invalid: ${libName}`);
500
+ return LibInfo.libInfos.get(libName);
501
+ }
502
+ getLibInfos() {
503
+ return new Map(
504
+ this.#getSortedLibs().map((libName) => {
505
+ const libInfo = LibInfo.libInfos.get(libName);
506
+ if (!libInfo) throw new Error(`LibInfo not found: ${libName}`);
507
+ return [libName, libInfo];
508
+ }),
509
+ );
510
+ }
511
+ }
512
+
513
+ export class PkgInfo {
514
+ readonly exec: PkgExecutor;
515
+ readonly name: string;
516
+ private scanResult: PkgScanResult;
517
+
518
+ static async getScanResult(exec: PkgExecutor) {
519
+ const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
520
+ const scanner = await TypeScriptDependencyScanner.from(exec);
521
+ const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
522
+ const pkgPathSet = new Set(
523
+ Object.keys(tsconfig.compilerOptions.paths ?? {})
524
+ .filter((path) => tsconfig.compilerOptions.paths?.[path]?.some((resolve) => resolve.startsWith("pkgs/")))
525
+ .map((path) => path.replace("/*", "")),
526
+ );
527
+ const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
528
+ const pkgDeps = [...pkgPathDepSet]
529
+ .map((path) => {
530
+ const pathSplitLength = path.split("/").length;
531
+ return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*")
532
+ .split("/")
533
+ .slice(1, 1 + pathSplitLength)
534
+ .join("/");
535
+ })
536
+ .filter((pkg) => pkg !== exec.name);
537
+ const pkgScanResult = {
538
+ name: exec.name,
539
+ pkgDeps,
540
+ dependencies: [...npmDepSet],
541
+ };
542
+ return pkgScanResult;
543
+ }
544
+
545
+ static #pkgInfos = new Map<string, PkgInfo>();
546
+ static async fromExecutor(exec: PkgExecutor, options: { refresh?: boolean } = {}) {
547
+ const existingPkgInfo = PkgInfo.#pkgInfos.get(exec.name);
548
+ if (existingPkgInfo && !options.refresh) return existingPkgInfo;
549
+
550
+ const scanResult = await PkgInfo.getScanResult(exec);
551
+ const pkgInfo = new PkgInfo(exec, scanResult);
552
+ PkgInfo.#pkgInfos.set(exec.name, pkgInfo);
553
+ return pkgInfo;
554
+ }
555
+ constructor(exec: PkgExecutor, scanResult: PkgScanResult) {
556
+ this.exec = exec;
557
+ this.name = exec.name;
558
+ this.scanResult = scanResult;
559
+ }
560
+ getScanResult() {
561
+ return this.scanResult;
562
+ }
563
+ }
564
+
565
+ export class WorkspaceInfo {
566
+ constructor(
567
+ public readonly appInfos: Map<string, AppInfo> = new Map(),
568
+ public readonly libInfos: Map<string, LibInfo> = new Map(),
569
+ public readonly pkgInfos: Map<string, PkgInfo> = new Map(),
570
+ ) {}
571
+
572
+ static #workspaceInfos = new Map<string, WorkspaceInfo>();
573
+ static async fromExecutor(exec: WorkspaceExecutor, options: { refresh?: boolean } = {}) {
574
+ const existingWorkspaceInfo = WorkspaceInfo.#workspaceInfos.get(exec.name);
575
+ if (existingWorkspaceInfo && !options.refresh) return existingWorkspaceInfo;
576
+
577
+ const [appNames, libNames, pkgNames] = await Promise.all([exec.getApps(), exec.getLibs(), exec.getPkgs()]);
578
+ // TODO: prevent duplicate scan by resolving the dependency graph
579
+ const [appInfos, libInfos, pkgInfos] = await Promise.all([
580
+ Promise.all(
581
+ appNames.map(async (appName) => {
582
+ const app = AppExecutor.from(exec, appName);
583
+ return await app.scan();
584
+ }),
585
+ ),
586
+ Promise.all(
587
+ libNames.map(async (libName) => {
588
+ const lib = LibExecutor.from(exec, libName);
589
+ return await lib.scan();
590
+ }),
591
+ ),
592
+ Promise.all(
593
+ pkgNames.map(async (pkgName) => {
594
+ return await PkgExecutor.from(exec, pkgName).scan();
595
+ }),
596
+ ),
597
+ ]);
598
+ const workspaceInfo = new WorkspaceInfo(
599
+ new Map(appInfos.map((app) => [app.exec.name, app as AppInfo])),
600
+ new Map(libInfos.map((lib) => [lib.exec.name, lib as LibInfo])),
601
+ new Map(pkgInfos.map((pkg: PkgInfo) => [pkg.exec.name, pkg])),
602
+ );
603
+ WorkspaceInfo.#workspaceInfos.set(exec.name, workspaceInfo);
604
+ return workspaceInfo;
605
+ }
606
+ }
package/selectModel.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { select } from "@inquirer/prompts";
3
+
4
+ export const selectModel = async (modulePath: string) => {
5
+ const modelNames = readdirSync(`${modulePath}/lib`).filter((dir) => !dir.includes(".") && !dir.startsWith("_"));
6
+ const modelName = await select({
7
+ message: "Select the model to create the unit for",
8
+ choices: modelNames.map((name) => ({ name, value: name })),
9
+ });
10
+ return modelName;
11
+ };
@@ -1,13 +1,14 @@
1
- import ora from "ora";
2
- class Spinner {
1
+ import ora, { type Ora } from "ora";
2
+
3
+ export class Spinner {
3
4
  static padding = 12;
4
- spinner;
5
- stopWatch = null;
6
- startAt = /* @__PURE__ */ new Date();
7
- prefix;
8
- message;
9
- enableSpin;
10
- constructor(message, { prefix = "", indent = 0, enableSpin = true } = {}) {
5
+ spinner: Ora;
6
+ stopWatch: NodeJS.Timeout | null = null;
7
+ startAt: Date = new Date();
8
+ prefix: string;
9
+ message: string;
10
+ enableSpin: boolean;
11
+ constructor(message: string, { prefix = "", indent = 0, enableSpin = true } = {}) {
11
12
  Spinner.padding = Math.max(Spinner.padding, prefix.length);
12
13
  this.prefix = prefix;
13
14
  this.message = message;
@@ -17,22 +18,21 @@ class Spinner {
17
18
  this.enableSpin = enableSpin;
18
19
  }
19
20
  start() {
20
- this.startAt = /* @__PURE__ */ new Date();
21
+ this.startAt = new Date();
21
22
  if (this.enableSpin) {
22
23
  this.spinner.start();
23
24
  this.stopWatch = setInterval(() => {
24
25
  this.spinner.prefixText = this.prefix.padStart(Spinner.padding, " ");
25
- this.spinner.text = `${this.message} (${this.#getElapsedTimeStr()})`;
26
- }, 1e3);
27
- } else
28
- this.spinner.info();
26
+ this.spinner.text = `${this.message} (${this.#getElapsedTimeStr({ floor: true })})`;
27
+ }, 1000);
28
+ } else this.spinner.info();
29
29
  return this;
30
30
  }
31
- succeed(message) {
31
+ succeed(message: string) {
32
32
  this.spinner.succeed(`${message} (${this.#getElapsedTimeStr()})`);
33
33
  this.#reset();
34
34
  }
35
- fail(message) {
35
+ fail(message: string) {
36
36
  this.spinner.fail(`${message} (${this.#getElapsedTimeStr()})`);
37
37
  this.#reset();
38
38
  }
@@ -40,21 +40,15 @@ class Spinner {
40
40
  return this.spinner.isSpinning;
41
41
  }
42
42
  #reset() {
43
- if (this.stopWatch)
44
- clearInterval(this.stopWatch);
43
+ if (this.stopWatch) clearInterval(this.stopWatch);
45
44
  this.stopWatch = null;
46
45
  }
47
- #getElapsedTimeStr() {
48
- const ms = (/* @__PURE__ */ new Date()).getTime() - this.startAt.getTime();
49
- if (ms < 1e3)
50
- return `${ms}ms`;
51
- const s = Math.floor(ms / 1e3);
52
- if (s < 60)
53
- return `${s}s`;
46
+ #getElapsedTimeStr({ floor = false }: { floor?: boolean } = {}) {
47
+ const ms = Date.now() - this.startAt.getTime();
48
+ if (ms < 1000) return `${ms}ms`;
49
+ const s = Math.floor(ms / 1000);
50
+ if (s < 60) return `${floor ? Math.floor(ms / 1000) : Math.floor(ms / 100) / 10}s`;
54
51
  const m = Math.floor(s / 60);
55
52
  return `${m}m ${s % 60}s`;
56
53
  }
57
54
  }
58
- export {
59
- Spinner
60
- };