@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
@@ -0,0 +1,351 @@
1
+ import { confirm, input, select } from "@inquirer/prompts";
2
+ import path from "node:path";
3
+ import { Logger } from "akanjs/common";
4
+ import chalk from "chalk";
5
+ import { type Command, program } from "commander";
6
+
7
+ import { FileSys, getDirname, type PackageJson } from "..";
8
+ import { AppExecutor, Executor, LibExecutor, ModuleExecutor, PkgExecutor, WorkspaceExecutor } from "../executors";
9
+ import {
10
+ type ArgMeta,
11
+ type CommandContext,
12
+ type EnumChoice,
13
+ type EnumChoices,
14
+ getArgMetas,
15
+ type InternalArgMeta,
16
+ } from "./argMeta";
17
+ import { CommandContainer } from "./dependencyBuilder";
18
+ import { formatCommandHelp, formatHelp } from "./helpFormatter";
19
+ import { type CommandCls, getTargetMetas } from "./targetMeta";
20
+
21
+ const camelToKebabCase = (str: string) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
22
+
23
+ const handleOption = (programCommand: Command, argMeta: ArgMeta) => {
24
+ const {
25
+ type,
26
+ flag = argMeta.name.slice(0, 1).toLowerCase(),
27
+ desc = argMeta.name,
28
+ example,
29
+ enum: enumChoices,
30
+ ask,
31
+ } = argMeta.argsOption;
32
+ const kebabName = camelToKebabCase(argMeta.name);
33
+ const choices = enumChoices && typeof enumChoices !== "function" ? normalizeEnumChoices(enumChoices) : null;
34
+ programCommand.option(
35
+ `-${flag}, --${kebabName}${type === "boolean" ? " [boolean]" : ` <${kebabName}>`}`,
36
+ `${desc}${ask ? ` (${ask})` : ""}${example ? ` (example: ${example})` : ""}${choices ? ` (choices: ${choices.map((choice) => choice.name).join(", ")})` : ""}`,
37
+ );
38
+ return programCommand;
39
+ };
40
+ const handleArgument = (programCommand: Command, argMeta: ArgMeta) => {
41
+ const kebabName = camelToKebabCase(argMeta.name);
42
+ programCommand.argument(
43
+ `[${kebabName}]`,
44
+ `${argMeta.argsOption.desc}${argMeta.argsOption.example ? ` (example: ${argMeta.argsOption.example})` : ""}`,
45
+ );
46
+ return programCommand;
47
+ };
48
+
49
+ const convertArgValue = (value: string | boolean, type: "string" | "number" | "boolean") => {
50
+ if (type === "string") return value as string;
51
+ else if (type === "number") return Number(value);
52
+ else return value === true || value === "true";
53
+ };
54
+
55
+ const normalizeEnumChoices = (enumChoices: EnumChoices) =>
56
+ enumChoices.map((choice: EnumChoice) =>
57
+ typeof choice === "object"
58
+ ? { value: choice.value, name: choice.label }
59
+ : { value: choice, name: choice.toString() },
60
+ );
61
+
62
+ const resolveEnumChoices = async (argMeta: ArgMeta, context: CommandContext) => {
63
+ const enumChoices = argMeta.argsOption.enum;
64
+ if (!enumChoices) return null;
65
+ if (typeof enumChoices === "function") return await enumChoices(context);
66
+ return enumChoices;
67
+ };
68
+
69
+ const getOptionValue = async (argMeta: ArgMeta, opt: Record<string, unknown>, context: CommandContext) => {
70
+ const {
71
+ name,
72
+ argsOption: { enum: enumChoices, default: defaultValue, type, desc, nullable, example, ask },
73
+ } = argMeta;
74
+ if (opt[argMeta.name] !== undefined) return convertArgValue(opt[argMeta.name] as string, type ?? "string");
75
+ else if (defaultValue !== undefined) return defaultValue;
76
+
77
+ if (enumChoices) {
78
+ const choices = normalizeEnumChoices((await resolveEnumChoices(argMeta, context)) ?? []);
79
+ const choice = await select({ message: ask ?? desc ?? `Select the ${name} value`, choices });
80
+ return choice;
81
+ } else if (nullable) return null;
82
+ else if (type === "boolean") {
83
+ const message = ask ?? desc ?? `Do you want to set ${name}? ${desc ? ` (${desc})` : ""}: `;
84
+ return await confirm({ message });
85
+ } else {
86
+ const message = ask
87
+ ? `${ask}: `
88
+ : desc
89
+ ? `${desc}: `
90
+ : `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
91
+ if (argMeta.argsOption.nullable) return await input({ message });
92
+ else return convertArgValue(await input({ message }), type ?? "string");
93
+ }
94
+ };
95
+
96
+ const getArgumentValue = async (argMeta: ArgMeta, value: string | undefined) => {
97
+ const {
98
+ name,
99
+ argsOption: { default: defaultValue, type, desc, nullable, example, ask },
100
+ } = argMeta;
101
+ if (value !== undefined) return convertArgValue(value, type ?? "string");
102
+ else if (defaultValue !== undefined) return defaultValue;
103
+ else if (nullable) return null;
104
+
105
+ const message = ask
106
+ ? `${ask}: `
107
+ : desc
108
+ ? `${desc}: `
109
+ : `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
110
+ return convertArgValue(await input({ message }), type ?? "string");
111
+ };
112
+
113
+ const assignCommandContext = (context: CommandContext, argMeta: ArgMeta | InternalArgMeta, value: unknown) => {
114
+ if (value instanceof AppExecutor) context.app = value;
115
+ else if (value instanceof LibExecutor) context.lib = value;
116
+ else if (value instanceof PkgExecutor) context.pkg = value;
117
+ else if (value instanceof ModuleExecutor) context.module = value;
118
+ else if (value instanceof Executor) context.exec = value;
119
+ if (argMeta.type === "Argument" || argMeta.type === "Option") context.values[argMeta.name] = value;
120
+ else context.values[argMeta.type.toLowerCase()] = value;
121
+ };
122
+
123
+ const assertCurrentDirectoryIsWorkspaceRoot = async () => {
124
+ const cwd = process.cwd();
125
+ const [hasPackageJson, hasTsConfig, hasEnv] = await Promise.all([
126
+ FileSys.fileExists(`${cwd}/package.json`),
127
+ FileSys.fileExists(`${cwd}/tsconfig.json`),
128
+ FileSys.fileExists(`${cwd}/.env`),
129
+ ]);
130
+ if (hasPackageJson && hasTsConfig && hasEnv) return;
131
+
132
+ throw new Error(
133
+ [
134
+ "Akan CLI commands must be run from the workspace root.",
135
+ `Current directory: ${cwd}`,
136
+ "Move to the directory that contains package.json, tsconfig.json, and .env, then run the command again.",
137
+ ].join("\n"),
138
+ );
139
+ };
140
+
141
+ const getInternalArgumentValue = async (
142
+ argMeta: InternalArgMeta,
143
+ value: string | undefined,
144
+ workspace: WorkspaceExecutor,
145
+ ) => {
146
+ if (argMeta.type === "Workspace") return workspace;
147
+ const sysType = argMeta.type.toLowerCase();
148
+ const [appNames, libNames, pkgNames] = await workspace.getExecs();
149
+ if (sysType === "sys") {
150
+ if (value && appNames.includes(value)) return AppExecutor.from(workspace, value);
151
+ else if (value && libNames.includes(value)) return LibExecutor.from(workspace, value);
152
+ else {
153
+ const sysName = await select<string>({
154
+ message: `Select the App or Lib name`,
155
+ choices: [...appNames, ...libNames],
156
+ });
157
+ if (appNames.includes(sysName)) return AppExecutor.from(workspace, sysName);
158
+ else if (libNames.includes(sysName)) return LibExecutor.from(workspace, sysName);
159
+ else throw new Error(`Invalid system name: ${sysName}`);
160
+ }
161
+ } else if (sysType === "exec") {
162
+ if (value && appNames.includes(value)) return AppExecutor.from(workspace, value);
163
+ else if (value && libNames.includes(value)) return LibExecutor.from(workspace, value);
164
+ else if (value && pkgNames.includes(value)) return PkgExecutor.from(workspace, value);
165
+ else {
166
+ const execName = await select<string>({
167
+ message: `Select the App or Lib or Pkg name`,
168
+ choices: [...appNames, ...libNames, ...pkgNames],
169
+ });
170
+ if (appNames.includes(execName)) return AppExecutor.from(workspace, execName);
171
+ else if (libNames.includes(execName)) return LibExecutor.from(workspace, execName);
172
+ else if (pkgNames.includes(execName)) return PkgExecutor.from(workspace, execName);
173
+ else throw new Error(`Invalid system name: ${execName}`);
174
+ }
175
+ } else if (sysType === "app") {
176
+ if (value && appNames.includes(value)) return AppExecutor.from(workspace, value);
177
+ const appName = await select<string>({ message: `Select the ${sysType} name`, choices: appNames });
178
+ return AppExecutor.from(workspace, appName);
179
+ } else if (sysType === "lib") {
180
+ if (value && libNames.includes(value)) return LibExecutor.from(workspace, value);
181
+ const libName = await select<string>({ message: `Select the ${sysType} name`, choices: libNames });
182
+ return LibExecutor.from(workspace, libName);
183
+ } else if (sysType === "pkg") {
184
+ const pkgs = await workspace.getPkgs();
185
+ if (value && pkgs.includes(value)) return PkgExecutor.from(workspace, value);
186
+ const pkgName = await select<string>({ message: `Select the ${sysType} name`, choices: pkgs });
187
+ return PkgExecutor.from(workspace, pkgName);
188
+ } else if (sysType === "module") {
189
+ if (value) {
190
+ const [sysName, moduleName] = value.split(":");
191
+ if (!sysName || !moduleName) throw new Error(`Invalid module name: ${value}`);
192
+ if (appNames.includes(sysName)) {
193
+ const app = AppExecutor.from(workspace, sysName);
194
+ const modules = await app.getModules();
195
+ if (modules.includes(moduleName)) return ModuleExecutor.from(app, moduleName);
196
+ else throw new Error(`Invalid module name: ${moduleName}`);
197
+ } else if (libNames.includes(sysName)) {
198
+ const lib = LibExecutor.from(workspace, sysName);
199
+ const modules = await lib.getModules();
200
+ if (modules.includes(moduleName)) return ModuleExecutor.from(lib, moduleName);
201
+ } else throw new Error(`Invalid system name: ${sysName}`);
202
+ }
203
+ const { type, name } = await select<{ type: "app" | "lib"; name: string }>({
204
+ message: `select the App or Lib name`,
205
+ choices: [
206
+ ...appNames.map((name) => ({ name, value: { type: "app" as const, name } })),
207
+ ...libNames.map((name) => ({ name, value: { type: "lib" as const, name } })),
208
+ ],
209
+ });
210
+ const executor = type === "app" ? AppExecutor.from(workspace, name) : LibExecutor.from(workspace, name);
211
+ const modules = await executor.getModules();
212
+ const moduleName = await select<string>({
213
+ message: `Select the module name`,
214
+ choices: modules.map((name) => ({ name: `${executor.name}:${name}`, value: name })),
215
+ });
216
+ return ModuleExecutor.from(executor, moduleName);
217
+ } else throw new Error(`Invalid system type: ${argMeta.type}`);
218
+ };
219
+
220
+ export const runCommands = async (...commands: CommandCls[]) => {
221
+ process.on("unhandledRejection", (error) => {
222
+ process.exit(1);
223
+ });
224
+ const __dirname = getDirname(import.meta.url);
225
+ const packageJsonCandidates = [`${path.dirname(Bun.main)}/package.json`, `${__dirname}/../package.json`];
226
+ let cliPackageJson: PackageJson | null = null;
227
+ for (const packageJsonPath of packageJsonCandidates) {
228
+ if (!(await FileSys.fileExists(packageJsonPath))) continue;
229
+ const packageJson = await FileSys.readJson<PackageJson>(packageJsonPath);
230
+ if (packageJson.name === "@akanjs/cli" || packageJson.name === "@akanjs/devkit") {
231
+ cliPackageJson = packageJson;
232
+ break;
233
+ }
234
+ }
235
+ process.env.AKAN_VERSION = cliPackageJson?.version ?? "0.0.1";
236
+
237
+ // Custom help handling
238
+ const hasHelpFlag = process.argv.includes("--help") || process.argv.includes("-h");
239
+ const hasCommand = process.argv.length > 2 && !process.argv[2]?.startsWith("-");
240
+
241
+ // Show help if: 1) explicit --help flag, or 2) no command provided (just "akan")
242
+ if (hasHelpFlag || !hasCommand) {
243
+ if (process.argv.length === 2 || (process.argv.length === 3 && hasHelpFlag)) {
244
+ // Global help (no specific command)
245
+ Logger.rawLog(formatHelp(commands, process.env.AKAN_VERSION));
246
+ process.exit(0);
247
+ }
248
+ }
249
+
250
+ program.version(process.env.AKAN_VERSION).description("Akan CLI").configureHelp({
251
+ helpWidth: 100,
252
+ });
253
+ const installedAkanPackageJson = (await FileSys.fileExists("./node_modules/akanjs/package.json"))
254
+ ? await FileSys.readJson<PackageJson>("./node_modules/akanjs/package.json")
255
+ : null;
256
+ if (installedAkanPackageJson && installedAkanPackageJson.version !== process.env.AKAN_VERSION) {
257
+ Logger.rawLog(
258
+ chalk.yellow(
259
+ `
260
+ Akan CLI version is mismatch with installed package. ${process.env.AKAN_VERSION} (global) vs ${installedAkanPackageJson.version} (akanjs)
261
+ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`,
262
+ ),
263
+ );
264
+ }
265
+
266
+ for (const command of commands) {
267
+ const targetMetas = getTargetMetas(command);
268
+ for (const targetMeta of targetMetas) {
269
+ const kebabKey = camelToKebabCase(targetMeta.key);
270
+ const commandNames =
271
+ targetMeta.targetOption.short === true
272
+ ? [
273
+ kebabKey,
274
+ typeof targetMeta.targetOption.short === "string"
275
+ ? targetMeta.targetOption.short
276
+ : kebabKey
277
+ .split("-")
278
+ .map((s) => s.slice(0, 1))
279
+ .join(""),
280
+ ]
281
+ : [kebabKey];
282
+ for (const commandName of commandNames) {
283
+ let programCommand = program.command(commandName, {
284
+ hidden: targetMeta.targetOption.devOnly,
285
+ });
286
+ const [allArgMetas] = getArgMetas(command, targetMeta.key);
287
+ for (const argMeta of allArgMetas) {
288
+ if (argMeta.type === "Option") programCommand = handleOption(programCommand, argMeta);
289
+ else if (argMeta.type === "Argument") programCommand = handleArgument(programCommand, argMeta);
290
+ else if (argMeta.type === "Workspace") continue;
291
+ else if (argMeta.type === "Module") {
292
+ programCommand = programCommand.argument(
293
+ `[sys-name:module-name]`,
294
+ `${argMeta.type} in this workspace (apps|libs)/<sys-name>/lib/<module-name>`,
295
+ );
296
+ } else {
297
+ const sysType = argMeta.type.toLowerCase();
298
+ programCommand = programCommand.argument(
299
+ `[${sysType}]`,
300
+ `${sysType} in this workspace ${sysType}s/<${sysType}Name>`,
301
+ );
302
+ }
303
+ }
304
+ programCommand = programCommand.option(`-v, --verbose [boolean]`, `verbose output`);
305
+
306
+ // Override help completely for each command
307
+ programCommand.helpInformation = () => {
308
+ return formatCommandHelp(command, targetMeta.key);
309
+ };
310
+
311
+ programCommand.action(async (...args: unknown[]) => {
312
+ Logger.rawLog();
313
+ const cmdArgs = args.slice(0, args.length - 2);
314
+ const opt = args[args.length - 2] as Record<string, unknown>;
315
+ const commandArgs = [] as unknown[];
316
+ if (targetMeta.targetOption.runsOnWorkspaceRoot) await assertCurrentDirectoryIsWorkspaceRoot();
317
+ const workspace = WorkspaceExecutor.fromRoot();
318
+ const commandContext: CommandContext = { values: {} };
319
+ for (const argMeta of allArgMetas) {
320
+ if (argMeta.type === "Option")
321
+ commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt, commandContext);
322
+ else if (argMeta.type === "Argument")
323
+ commandArgs[argMeta.idx] = await getArgumentValue(argMeta, cmdArgs[argMeta.idx] as string);
324
+ else
325
+ commandArgs[argMeta.idx] = await getInternalArgumentValue(
326
+ argMeta as InternalArgMeta,
327
+ cmdArgs[argMeta.idx] as string,
328
+ workspace,
329
+ );
330
+ // set app name to env
331
+ if (commandArgs[argMeta.idx] instanceof AppExecutor)
332
+ process.env.AKAN_PUBLIC_APP_NAME = (commandArgs[argMeta.idx] as AppExecutor).name;
333
+ assignCommandContext(commandContext, argMeta, commandArgs[argMeta.idx]);
334
+ if ((opt as { verbose?: boolean }).verbose) Executor.setVerbose(true);
335
+ }
336
+ const cmd = CommandContainer.get(command);
337
+
338
+ try {
339
+ await targetMeta.handler.call(cmd, ...commandArgs);
340
+ Logger.rawLog();
341
+ } catch (e) {
342
+ const errMsg = e instanceof Error ? e.message : typeof e === "string" ? e : JSON.stringify(e);
343
+ Logger.rawLog(`\n${chalk.red(errMsg)}`);
344
+ throw e;
345
+ }
346
+ });
347
+ }
348
+ }
349
+ }
350
+ await program.parseAsync(process.argv);
351
+ };
@@ -0,0 +1,224 @@
1
+ import type {
2
+ App,
3
+ ArgMeta,
4
+ ArgsOption,
5
+ CommandContext,
6
+ Exec,
7
+ InternalArgMeta,
8
+ InternalArgToken,
9
+ Lib,
10
+ Module,
11
+ Pkg,
12
+ PrimitiveArgType,
13
+ Sys,
14
+ } from "./argMeta";
15
+ import { normalizePrimitiveArgType } from "./argMeta";
16
+ import { assertUniqueDependencies, type DependencyInstanceMap, injectDependencies } from "./dependencyBuilder";
17
+ import { COMMAND_META, type CommandCls, type TargetMeta, type TargetOption } from "./targetMeta";
18
+ import type { DependencyCls, DependencyKey } from "./types";
19
+
20
+ type PrimitiveValue<T extends PrimitiveArgType> = T extends StringConstructor
21
+ ? string
22
+ : T extends NumberConstructor
23
+ ? number
24
+ : boolean;
25
+ type MaybeNullable<Value, Option> = Option extends { nullable: true } ? Value | null : Value;
26
+ type AddArg<Params extends unknown[], Type extends PrimitiveArgType, Option> = [
27
+ ...Params,
28
+ MaybeNullable<PrimitiveValue<Type>, Option>,
29
+ ];
30
+ type AddInternalArg<Params extends unknown[], Token extends InternalArgToken> = [...Params, Token["_value"]];
31
+ type AddInternalArgs<Params extends unknown[], Tokens extends readonly InternalArgToken[]> = Tokens extends readonly [
32
+ infer Head extends InternalArgToken,
33
+ ...infer Rest extends InternalArgToken[],
34
+ ]
35
+ ? AddInternalArgs<AddInternalArg<Params, Head>, Rest>
36
+ : Params;
37
+ type ContextFromToken<Token extends InternalArgToken> = Token["_value"] extends App
38
+ ? { app: App }
39
+ : Token["_value"] extends Lib
40
+ ? { lib: Lib }
41
+ : Token["_value"] extends Sys
42
+ ? { sys: Sys }
43
+ : Token["_value"] extends Pkg
44
+ ? { pkg: Pkg }
45
+ : Token["_value"] extends Module
46
+ ? { module: Module }
47
+ : Token["_value"] extends Exec
48
+ ? { exec: Exec }
49
+ : object;
50
+ type AddInternalContext<Context, Tokens extends readonly InternalArgToken[]> = Tokens extends readonly [
51
+ infer Head extends InternalArgToken,
52
+ ...infer Rest extends InternalArgToken[],
53
+ ]
54
+ ? AddInternalContext<Context & ContextFromToken<Head>, Rest>
55
+ : Context;
56
+ type CommandHandler<Deps extends readonly DependencyCls[], Params extends unknown[]> = (
57
+ this: DependencyInstanceMap<Deps>,
58
+ ...args: Params
59
+ ) => unknown | Promise<unknown>;
60
+
61
+ class TargetBuilder<Deps extends readonly DependencyCls[], Params extends unknown[] = [], Context = object> {
62
+ readonly #args: (ArgMeta | InternalArgMeta)[];
63
+
64
+ constructor(
65
+ private readonly targetOption: TargetOption,
66
+ args: (ArgMeta | InternalArgMeta)[] = [],
67
+ ) {
68
+ this.#args = args;
69
+ }
70
+
71
+ arg<Type extends PrimitiveArgType, Option extends ArgsOption<Context> = ArgsOption<Context>>(
72
+ name: string,
73
+ type: Type,
74
+ argsOption: Option = {} as Option,
75
+ ): TargetBuilder<Deps, AddArg<Params, Type, Option>, Context> {
76
+ return new TargetBuilder<Deps, AddArg<Params, Type, Option>, Context>(this.targetOption, [
77
+ ...this.#args,
78
+ {
79
+ name,
80
+ argsOption: { ...argsOption, type: normalizePrimitiveArgType(type) },
81
+ key: "",
82
+ idx: this.#args.length,
83
+ type: "Argument",
84
+ } as ArgMeta<CommandContext>,
85
+ ]);
86
+ }
87
+
88
+ option<Type extends PrimitiveArgType, Option extends ArgsOption<Context> = ArgsOption<Context>>(
89
+ name: string,
90
+ type: Type,
91
+ argsOption: Option = {} as Option,
92
+ ): TargetBuilder<Deps, AddArg<Params, Type, Option>, Context> {
93
+ return new TargetBuilder<Deps, AddArg<Params, Type, Option>, Context>(this.targetOption, [
94
+ ...this.#args,
95
+ {
96
+ name,
97
+ argsOption: { ...argsOption, type: normalizePrimitiveArgType(type) },
98
+ key: "",
99
+ idx: this.#args.length,
100
+ type: "Option",
101
+ } as ArgMeta<CommandContext>,
102
+ ]);
103
+ }
104
+
105
+ with<const Tokens extends readonly InternalArgToken[]>(
106
+ ...tokens: Tokens
107
+ ): TargetBuilder<Deps, AddInternalArgs<Params, Tokens>, AddInternalContext<Context, Tokens>> {
108
+ return new TargetBuilder<Deps, AddInternalArgs<Params, Tokens>, AddInternalContext<Context, Tokens>>(
109
+ this.targetOption,
110
+ [
111
+ ...this.#args,
112
+ ...tokens.map(
113
+ (token, offset) =>
114
+ ({
115
+ key: "",
116
+ idx: this.#args.length + offset,
117
+ type: token.type,
118
+ }) satisfies InternalArgMeta,
119
+ ),
120
+ ],
121
+ );
122
+ }
123
+
124
+ exec(handler: CommandHandler<Deps, Params>) {
125
+ return {
126
+ args: this.#args,
127
+ handler: handler as TargetMeta["handler"],
128
+ targetOption: this.targetOption,
129
+ };
130
+ }
131
+ }
132
+
133
+ type TargetDefinition = ReturnType<TargetBuilder<readonly DependencyCls[], unknown[]>["exec"]>;
134
+ type CommandBuilderContext<Deps extends readonly DependencyCls[]> = {
135
+ public: (targetOption?: Omit<TargetOption, "type">) => TargetBuilder<Deps>;
136
+ cloud: (targetOption?: Omit<TargetOption, "type">) => TargetBuilder<Deps>;
137
+ dev: (targetOption?: Omit<TargetOption, "type">) => TargetBuilder<Deps>;
138
+ arg: <Type extends PrimitiveArgType, Option extends ArgsOption<CommandContext> = ArgsOption<CommandContext>>(
139
+ name: string,
140
+ type: Type,
141
+ argsOption?: Option,
142
+ ) => ArgMeta;
143
+ option: <Type extends PrimitiveArgType, Option extends ArgsOption<CommandContext> = ArgsOption<CommandContext>>(
144
+ name: string,
145
+ type: Type,
146
+ argsOption?: Option,
147
+ ) => ArgMeta;
148
+ };
149
+ type CommandBuilder<Deps extends readonly DependencyCls[]> = (
150
+ context: CommandBuilderContext<Deps>,
151
+ ) => Record<string, TargetDefinition>;
152
+
153
+ const createTarget =
154
+ <Deps extends readonly DependencyCls[]>(type: TargetOption["type"]) =>
155
+ (targetOption: Omit<TargetOption, "type"> = {}) =>
156
+ new TargetBuilder<Deps>({ runsOnWorkspaceRoot: true, ...targetOption, type });
157
+
158
+ const createContext = <Deps extends readonly DependencyCls[]>(): CommandBuilderContext<Deps> => ({
159
+ public: createTarget<Deps>("public"),
160
+ cloud: createTarget<Deps>("cloud"),
161
+ dev: createTarget<Deps>("dev"),
162
+ arg: (name, type, argsOption) =>
163
+ ({
164
+ name,
165
+ argsOption: { ...(argsOption ?? {}), type: normalizePrimitiveArgType(type) },
166
+ key: "",
167
+ idx: -1,
168
+ type: "Argument",
169
+ }) as ArgMeta<CommandContext>,
170
+ option: (name, type, argsOption) =>
171
+ ({
172
+ name,
173
+ argsOption: { ...(argsOption ?? {}), type: normalizePrimitiveArgType(type) },
174
+ key: "",
175
+ idx: -1,
176
+ type: "Option",
177
+ }) as ArgMeta<CommandContext>,
178
+ });
179
+
180
+ const buildCommandMeta = (definitions: Record<string, TargetDefinition>) => {
181
+ const commandMeta = new Map<string, TargetMeta>();
182
+ for (const [key, definition] of Object.entries(definitions)) {
183
+ commandMeta.set(key, {
184
+ key,
185
+ args: definition.args.map((arg) => ({ ...arg, key })),
186
+ handler: definition.handler,
187
+ targetOption: definition.targetOption,
188
+ });
189
+ }
190
+ return commandMeta;
191
+ };
192
+
193
+ export function command<RefName extends string, Deps extends readonly DependencyCls[]>(
194
+ refName: RefName,
195
+ deps: Deps,
196
+ builder: CommandBuilder<Deps>,
197
+ ): CommandCls<DependencyInstanceMap<Deps>, DependencyKey<RefName, "command">>;
198
+ export function command<RefName extends string>(
199
+ refName: RefName,
200
+ builder: CommandBuilder<[]>,
201
+ ): CommandCls<DependencyInstanceMap<[]>, DependencyKey<RefName, "command">>;
202
+ export function command<RefName extends string, Deps extends readonly DependencyCls[]>(
203
+ refName: RefName,
204
+ depsOrBuilder: Deps | CommandBuilder<[]>,
205
+ builder?: CommandBuilder<Deps>,
206
+ ) {
207
+ const deps = (Array.isArray(depsOrBuilder) ? depsOrBuilder : []) as unknown as Deps;
208
+ const commandBuilder = (Array.isArray(depsOrBuilder) ? builder : depsOrBuilder) as CommandBuilder<Deps>;
209
+ assertUniqueDependencies(deps);
210
+ const commandMeta = buildCommandMeta(commandBuilder(createContext<Deps>()));
211
+
212
+ class CommandBase {
213
+ static readonly refName = refName;
214
+ static readonly dependencyKind = "command";
215
+ static readonly dependencyKey = `${refName}Command` as const;
216
+ static readonly [COMMAND_META] = commandMeta;
217
+
218
+ constructor() {
219
+ injectDependencies(this, deps);
220
+ }
221
+ }
222
+
223
+ return CommandBase as unknown as CommandCls<DependencyInstanceMap<Deps>, DependencyKey<RefName, "command">>;
224
+ }