@akanjs/devkit 1.0.20 → 2.1.0-rc.1

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 (195) hide show
  1. package/README.ko.md +65 -0
  2. package/README.md +62 -6
  3. package/aiEditor.ts +304 -0
  4. package/akanApp/akanApp.host.ts +393 -0
  5. package/akanApp/index.ts +1 -0
  6. package/akanConfig/akanConfig.test.ts +236 -0
  7. package/akanConfig/akanConfig.ts +384 -0
  8. package/akanConfig/index.ts +2 -0
  9. package/akanConfig/types.ts +23 -0
  10. package/applicationBuildReporter.ts +69 -0
  11. package/applicationBuildRunner.ts +302 -0
  12. package/applicationReleasePackager.ts +206 -0
  13. package/artifact/implicitRootLayout.ts +155 -0
  14. package/artifact/index.ts +1 -0
  15. package/artifact/routeSeedIndex.test.ts +98 -0
  16. package/artifact/routeSeedIndex.ts +130 -0
  17. package/auth.ts +41 -0
  18. package/builder.ts +164 -0
  19. package/capacitor.base.config.ts +88 -0
  20. package/capacitorApp.ts +440 -0
  21. package/commandDecorators/argMeta.ts +102 -0
  22. package/commandDecorators/command.ts +351 -0
  23. package/commandDecorators/commandBuilder.ts +224 -0
  24. package/commandDecorators/commandDecorators.test.ts +212 -0
  25. package/commandDecorators/commandMeta.ts +7 -0
  26. package/commandDecorators/dependencyBuilder.ts +100 -0
  27. package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
  28. package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
  29. package/commandDecorators/targetMeta.ts +31 -0
  30. package/commandDecorators/types.ts +10 -0
  31. package/constants.ts +25 -0
  32. package/createTunnel.ts +36 -0
  33. package/dependencyScanner.ts +357 -0
  34. package/devkitUtils.test.ts +259 -0
  35. package/executors.test.ts +315 -0
  36. package/executors.ts +1390 -0
  37. package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
  38. package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
  39. package/fileSys.ts +39 -0
  40. package/frontendBuild/allRoutesBuilder.ts +103 -0
  41. package/frontendBuild/buildRouteClient.test.ts +190 -0
  42. package/frontendBuild/clientBuildTypes.ts +114 -0
  43. package/frontendBuild/clientEntriesBundler.ts +303 -0
  44. package/frontendBuild/clientEntryDiscovery.ts +199 -0
  45. package/frontendBuild/csrArtifactBuilder.ts +237 -0
  46. package/frontendBuild/cssCompiler.ts +286 -0
  47. package/frontendBuild/cssImportResolver.ts +116 -0
  48. package/frontendBuild/fontOptimizer.ts +427 -0
  49. package/frontendBuild/frontendBuild.test.ts +204 -0
  50. package/frontendBuild/hmrChangeClassifier.ts +28 -0
  51. package/frontendBuild/hmrWatcher.ts +102 -0
  52. package/frontendBuild/index.ts +18 -0
  53. package/frontendBuild/pagesBundleBuilder.ts +137 -0
  54. package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
  55. package/frontendBuild/precompressArtifacts.ts +59 -0
  56. package/frontendBuild/routeClientBuilder.ts +290 -0
  57. package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
  58. package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
  59. package/frontendBuild/vendorSpecifiers.ts +16 -0
  60. package/frontendBuild/watchRootResolver.ts +28 -0
  61. package/getCredentials.ts +19 -0
  62. package/getDirname.ts +3 -0
  63. package/getModelFileData.ts +59 -0
  64. package/getRelatedCnsts.ts +313 -0
  65. package/guideline.ts +19 -0
  66. package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
  67. package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
  68. package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
  69. package/incrementalBuilder/index.ts +1 -0
  70. package/{esm/src/index.js → index.ts} +28 -15
  71. package/lint/no-deep-internal-import.grit +25 -0
  72. package/lint/no-import-client-functions.grit +32 -0
  73. package/lint/no-import-external-library.grit +21 -0
  74. package/lint/no-js-private-class-method.grit +42 -0
  75. package/lint/no-use-client-in-server.grit +7 -0
  76. package/lint/non-scalar-props-restricted.grit +13 -0
  77. package/linter.ts +271 -0
  78. package/mobile/index.ts +1 -0
  79. package/mobile/mobileTarget.test.ts +53 -0
  80. package/mobile/mobileTarget.ts +88 -0
  81. package/package.json +48 -31
  82. package/prompter.ts +72 -0
  83. package/scanInfo.ts +606 -0
  84. package/selectModel.ts +11 -0
  85. package/{esm/src/spinner.js → spinner.ts} +22 -28
  86. package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
  87. package/sshTunnel.ts +152 -0
  88. package/{esm/src/streamAi.js → streamAi.ts} +18 -12
  89. package/transforms/barrelAnalyzer.ts +278 -0
  90. package/transforms/barrelImportsPlugin.ts +504 -0
  91. package/transforms/externalizeFrameworkPlugin.ts +185 -0
  92. package/transforms/index.ts +5 -0
  93. package/transforms/rscUseClientTransform.ts +59 -0
  94. package/transforms/transforms.test.ts +208 -0
  95. package/transforms/useClientBundlePlugin.ts +47 -0
  96. package/tsconfig.json +37 -0
  97. package/typeChecker.ts +264 -0
  98. package/types.ts +44 -0
  99. package/ui/MultiScrollList.tsx +242 -0
  100. package/ui/ScrollList.tsx +107 -0
  101. package/ui/index.ts +2 -0
  102. package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
  103. package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
  104. package/cjs/index.js +0 -21
  105. package/cjs/src/aiEditor.js +0 -311
  106. package/cjs/src/auth.js +0 -72
  107. package/cjs/src/builder.js +0 -114
  108. package/cjs/src/capacitorApp.js +0 -313
  109. package/cjs/src/commandDecorators/argMeta.js +0 -88
  110. package/cjs/src/commandDecorators/command.js +0 -324
  111. package/cjs/src/commandDecorators/commandMeta.js +0 -30
  112. package/cjs/src/commandDecorators/helpFormatter.js +0 -211
  113. package/cjs/src/commandDecorators/index.js +0 -31
  114. package/cjs/src/commandDecorators/targetMeta.js +0 -57
  115. package/cjs/src/commandDecorators/types.js +0 -15
  116. package/cjs/src/constants.js +0 -46
  117. package/cjs/src/createTunnel.js +0 -49
  118. package/cjs/src/dependencyScanner.js +0 -220
  119. package/cjs/src/executors.js +0 -964
  120. package/cjs/src/extractDeps.js +0 -103
  121. package/cjs/src/fileEditor.js +0 -120
  122. package/cjs/src/getCredentials.js +0 -44
  123. package/cjs/src/getDirname.js +0 -38
  124. package/cjs/src/getModelFileData.js +0 -66
  125. package/cjs/src/getRelatedCnsts.js +0 -260
  126. package/cjs/src/guideline.js +0 -15
  127. package/cjs/src/index.js +0 -65
  128. package/cjs/src/linter.js +0 -238
  129. package/cjs/src/prompter.js +0 -85
  130. package/cjs/src/scanInfo.js +0 -491
  131. package/cjs/src/selectModel.js +0 -46
  132. package/cjs/src/spinner.js +0 -93
  133. package/cjs/src/streamAi.js +0 -62
  134. package/cjs/src/typeChecker.js +0 -207
  135. package/cjs/src/types.js +0 -15
  136. package/cjs/src/uploadRelease.js +0 -112
  137. package/cjs/src/useStdoutDimensions.js +0 -43
  138. package/esm/index.js +0 -1
  139. package/esm/src/aiEditor.js +0 -282
  140. package/esm/src/auth.js +0 -42
  141. package/esm/src/builder.js +0 -81
  142. package/esm/src/commandDecorators/argMeta.js +0 -54
  143. package/esm/src/commandDecorators/command.js +0 -290
  144. package/esm/src/commandDecorators/commandMeta.js +0 -7
  145. package/esm/src/commandDecorators/targetMeta.js +0 -33
  146. package/esm/src/commandDecorators/types.js +0 -0
  147. package/esm/src/constants.js +0 -17
  148. package/esm/src/createTunnel.js +0 -26
  149. package/esm/src/dependencyScanner.js +0 -187
  150. package/esm/src/executors.js +0 -928
  151. package/esm/src/getCredentials.js +0 -11
  152. package/esm/src/getDirname.js +0 -5
  153. package/esm/src/getModelFileData.js +0 -33
  154. package/esm/src/getRelatedCnsts.js +0 -221
  155. package/esm/src/guideline.js +0 -0
  156. package/esm/src/linter.js +0 -205
  157. package/esm/src/prompter.js +0 -51
  158. package/esm/src/scanInfo.js +0 -455
  159. package/esm/src/selectModel.js +0 -13
  160. package/esm/src/typeChecker.js +0 -174
  161. package/esm/src/types.js +0 -0
  162. package/index.d.ts +0 -1
  163. package/src/aiEditor.d.ts +0 -50
  164. package/src/auth.d.ts +0 -9
  165. package/src/builder.d.ts +0 -18
  166. package/src/capacitorApp.d.ts +0 -39
  167. package/src/commandDecorators/argMeta.d.ts +0 -67
  168. package/src/commandDecorators/command.d.ts +0 -2
  169. package/src/commandDecorators/commandMeta.d.ts +0 -2
  170. package/src/commandDecorators/helpFormatter.d.ts +0 -3
  171. package/src/commandDecorators/index.d.ts +0 -6
  172. package/src/commandDecorators/targetMeta.d.ts +0 -19
  173. package/src/commandDecorators/types.d.ts +0 -1
  174. package/src/constants.d.ts +0 -26
  175. package/src/createTunnel.d.ts +0 -8
  176. package/src/dependencyScanner.d.ts +0 -23
  177. package/src/executors.d.ts +0 -296
  178. package/src/extractDeps.d.ts +0 -7
  179. package/src/fileEditor.d.ts +0 -16
  180. package/src/getCredentials.d.ts +0 -12
  181. package/src/getDirname.d.ts +0 -1
  182. package/src/getModelFileData.d.ts +0 -16
  183. package/src/getRelatedCnsts.d.ts +0 -53
  184. package/src/guideline.d.ts +0 -19
  185. package/src/index.d.ts +0 -23
  186. package/src/linter.d.ts +0 -109
  187. package/src/prompter.d.ts +0 -14
  188. package/src/scanInfo.d.ts +0 -82
  189. package/src/selectModel.d.ts +0 -1
  190. package/src/spinner.d.ts +0 -20
  191. package/src/streamAi.d.ts +0 -6
  192. package/src/typeChecker.d.ts +0 -52
  193. package/src/types.d.ts +0 -31
  194. package/src/uploadRelease.d.ts +0 -10
  195. 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
- };