@akanjs/devkit 1.0.19 → 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.
- package/aiEditor.ts +304 -0
- package/akanApp/akanApp.host.ts +393 -0
- package/akanApp/index.ts +1 -0
- package/akanConfig/akanConfig.test.ts +236 -0
- package/akanConfig/akanConfig.ts +384 -0
- package/akanConfig/index.ts +2 -0
- package/akanConfig/types.ts +23 -0
- package/applicationBuildReporter.ts +69 -0
- package/applicationBuildRunner.ts +302 -0
- package/applicationReleasePackager.ts +206 -0
- package/artifact/implicitRootLayout.ts +155 -0
- package/artifact/index.ts +1 -0
- package/artifact/routeSeedIndex.test.ts +98 -0
- package/artifact/routeSeedIndex.ts +130 -0
- package/auth.ts +41 -0
- package/builder.ts +164 -0
- package/capacitor.base.config.ts +88 -0
- package/capacitorApp.ts +440 -0
- package/commandDecorators/argMeta.ts +102 -0
- package/commandDecorators/command.ts +343 -0
- package/commandDecorators/commandBuilder.ts +224 -0
- package/commandDecorators/commandDecorators.test.ts +212 -0
- package/commandDecorators/commandMeta.ts +7 -0
- package/commandDecorators/dependencyBuilder.ts +100 -0
- package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
- package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
- package/commandDecorators/targetMeta.ts +31 -0
- package/commandDecorators/types.ts +10 -0
- package/constants.ts +25 -0
- package/createTunnel.ts +36 -0
- package/dependencyScanner.ts +357 -0
- package/devkitUtils.test.ts +259 -0
- package/executors.test.ts +315 -0
- package/executors.ts +1390 -0
- package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
- package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
- package/fileSys.ts +39 -0
- package/frontendBuild/allRoutesBuilder.ts +103 -0
- package/frontendBuild/buildRouteClient.test.ts +190 -0
- package/frontendBuild/clientBuildTypes.ts +114 -0
- package/frontendBuild/clientEntriesBundler.ts +303 -0
- package/frontendBuild/clientEntryDiscovery.ts +199 -0
- package/frontendBuild/csrArtifactBuilder.ts +237 -0
- package/frontendBuild/cssCompiler.ts +286 -0
- package/frontendBuild/cssImportResolver.ts +116 -0
- package/frontendBuild/fontOptimizer.ts +427 -0
- package/frontendBuild/frontendBuild.test.ts +204 -0
- package/frontendBuild/hmrChangeClassifier.ts +28 -0
- package/frontendBuild/hmrWatcher.ts +102 -0
- package/frontendBuild/index.ts +18 -0
- package/frontendBuild/pagesBundleBuilder.ts +137 -0
- package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
- package/frontendBuild/precompressArtifacts.ts +59 -0
- package/frontendBuild/routeClientBuilder.ts +290 -0
- package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
- package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
- package/frontendBuild/vendorSpecifiers.ts +16 -0
- package/frontendBuild/watchRootResolver.ts +28 -0
- package/getCredentials.ts +19 -0
- package/getDirname.ts +3 -0
- package/getModelFileData.ts +59 -0
- package/getRelatedCnsts.ts +313 -0
- package/guideline.ts +19 -0
- package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
- package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
- package/incrementalBuilder/index.ts +1 -0
- package/{esm/src/index.js → index.ts} +28 -15
- package/lint/no-deep-internal-import.grit +25 -0
- package/lint/no-import-client-functions.grit +32 -0
- package/lint/no-import-external-library.grit +21 -0
- package/lint/no-js-private-class-method.grit +42 -0
- package/lint/no-use-client-in-server.grit +7 -0
- package/lint/non-scalar-props-restricted.grit +13 -0
- package/linter.ts +271 -0
- package/mobile/index.ts +1 -0
- package/mobile/mobileTarget.test.ts +53 -0
- package/mobile/mobileTarget.ts +88 -0
- package/package.json +48 -31
- package/prompter.ts +72 -0
- package/scanInfo.ts +606 -0
- package/selectModel.ts +11 -0
- package/{esm/src/spinner.js → spinner.ts} +22 -28
- package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
- package/sshTunnel.ts +152 -0
- package/{esm/src/streamAi.js → streamAi.ts} +18 -12
- package/transforms/barrelAnalyzer.ts +278 -0
- package/transforms/barrelImportsPlugin.ts +504 -0
- package/transforms/externalizeFrameworkPlugin.ts +185 -0
- package/transforms/index.ts +5 -0
- package/transforms/rscUseClientTransform.ts +59 -0
- package/transforms/transforms.test.ts +208 -0
- package/transforms/useClientBundlePlugin.ts +47 -0
- package/tsconfig.json +37 -0
- package/typeChecker.ts +264 -0
- package/types.ts +44 -0
- package/ui/MultiScrollList.tsx +242 -0
- package/ui/ScrollList.tsx +107 -0
- package/ui/index.ts +2 -0
- package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
- package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
- package/README.md +0 -1
- package/cjs/index.js +0 -21
- package/cjs/src/aiEditor.js +0 -311
- package/cjs/src/auth.js +0 -72
- package/cjs/src/builder.js +0 -114
- package/cjs/src/capacitorApp.js +0 -313
- package/cjs/src/commandDecorators/argMeta.js +0 -88
- package/cjs/src/commandDecorators/command.js +0 -324
- package/cjs/src/commandDecorators/commandMeta.js +0 -30
- package/cjs/src/commandDecorators/helpFormatter.js +0 -211
- package/cjs/src/commandDecorators/index.js +0 -31
- package/cjs/src/commandDecorators/targetMeta.js +0 -57
- package/cjs/src/commandDecorators/types.js +0 -15
- package/cjs/src/constants.js +0 -46
- package/cjs/src/createTunnel.js +0 -49
- package/cjs/src/dependencyScanner.js +0 -220
- package/cjs/src/executors.js +0 -964
- package/cjs/src/extractDeps.js +0 -103
- package/cjs/src/fileEditor.js +0 -120
- package/cjs/src/getCredentials.js +0 -44
- package/cjs/src/getDirname.js +0 -38
- package/cjs/src/getModelFileData.js +0 -66
- package/cjs/src/getRelatedCnsts.js +0 -260
- package/cjs/src/guideline.js +0 -15
- package/cjs/src/index.js +0 -65
- package/cjs/src/linter.js +0 -238
- package/cjs/src/prompter.js +0 -85
- package/cjs/src/scanInfo.js +0 -491
- package/cjs/src/selectModel.js +0 -46
- package/cjs/src/spinner.js +0 -93
- package/cjs/src/streamAi.js +0 -62
- package/cjs/src/typeChecker.js +0 -207
- package/cjs/src/types.js +0 -15
- package/cjs/src/uploadRelease.js +0 -112
- package/cjs/src/useStdoutDimensions.js +0 -43
- package/esm/index.js +0 -1
- package/esm/src/aiEditor.js +0 -282
- package/esm/src/auth.js +0 -42
- package/esm/src/builder.js +0 -81
- package/esm/src/commandDecorators/argMeta.js +0 -54
- package/esm/src/commandDecorators/command.js +0 -290
- package/esm/src/commandDecorators/commandMeta.js +0 -7
- package/esm/src/commandDecorators/targetMeta.js +0 -33
- package/esm/src/commandDecorators/types.js +0 -0
- package/esm/src/constants.js +0 -17
- package/esm/src/createTunnel.js +0 -26
- package/esm/src/dependencyScanner.js +0 -187
- package/esm/src/executors.js +0 -928
- package/esm/src/getCredentials.js +0 -11
- package/esm/src/getDirname.js +0 -5
- package/esm/src/getModelFileData.js +0 -33
- package/esm/src/getRelatedCnsts.js +0 -221
- package/esm/src/guideline.js +0 -0
- package/esm/src/linter.js +0 -205
- package/esm/src/prompter.js +0 -51
- package/esm/src/scanInfo.js +0 -455
- package/esm/src/selectModel.js +0 -13
- package/esm/src/typeChecker.js +0 -174
- package/esm/src/types.js +0 -0
- package/index.d.ts +0 -1
- package/src/aiEditor.d.ts +0 -50
- package/src/auth.d.ts +0 -9
- package/src/builder.d.ts +0 -18
- package/src/capacitorApp.d.ts +0 -39
- package/src/commandDecorators/argMeta.d.ts +0 -67
- package/src/commandDecorators/command.d.ts +0 -2
- package/src/commandDecorators/commandMeta.d.ts +0 -2
- package/src/commandDecorators/helpFormatter.d.ts +0 -3
- package/src/commandDecorators/index.d.ts +0 -6
- package/src/commandDecorators/targetMeta.d.ts +0 -19
- package/src/commandDecorators/types.d.ts +0 -1
- package/src/constants.d.ts +0 -26
- package/src/createTunnel.d.ts +0 -8
- package/src/dependencyScanner.d.ts +0 -23
- package/src/executors.d.ts +0 -296
- package/src/extractDeps.d.ts +0 -7
- package/src/fileEditor.d.ts +0 -16
- package/src/getCredentials.d.ts +0 -12
- package/src/getDirname.d.ts +0 -1
- package/src/getModelFileData.d.ts +0 -16
- package/src/getRelatedCnsts.d.ts +0 -53
- package/src/guideline.d.ts +0 -19
- package/src/index.d.ts +0 -23
- package/src/linter.d.ts +0 -109
- package/src/prompter.d.ts +0 -14
- package/src/scanInfo.d.ts +0 -82
- package/src/selectModel.d.ts +0 -1
- package/src/spinner.d.ts +0 -20
- package/src/streamAi.d.ts +0 -6
- package/src/typeChecker.d.ts +0 -52
- package/src/types.d.ts +0 -31
- package/src/uploadRelease.d.ts +0 -10
- package/src/useStdoutDimensions.d.ts +0 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
2
|
+
import { Logger } from "akanjs/common";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { type Command, program } from "commander";
|
|
5
|
+
|
|
6
|
+
import { FileSys, getDirname, type PackageJson } from "..";
|
|
7
|
+
import { AppExecutor, Executor, LibExecutor, ModuleExecutor, PkgExecutor, WorkspaceExecutor } from "../executors";
|
|
8
|
+
import {
|
|
9
|
+
type ArgMeta,
|
|
10
|
+
type CommandContext,
|
|
11
|
+
type EnumChoice,
|
|
12
|
+
type EnumChoices,
|
|
13
|
+
getArgMetas,
|
|
14
|
+
type InternalArgMeta,
|
|
15
|
+
} from "./argMeta";
|
|
16
|
+
import { CommandContainer } from "./dependencyBuilder";
|
|
17
|
+
import { formatCommandHelp, formatHelp } from "./helpFormatter";
|
|
18
|
+
import { type CommandCls, getTargetMetas } from "./targetMeta";
|
|
19
|
+
|
|
20
|
+
const camelToKebabCase = (str: string) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
21
|
+
|
|
22
|
+
const handleOption = (programCommand: Command, argMeta: ArgMeta) => {
|
|
23
|
+
const {
|
|
24
|
+
type,
|
|
25
|
+
flag = argMeta.name.slice(0, 1).toLowerCase(),
|
|
26
|
+
desc = argMeta.name,
|
|
27
|
+
example,
|
|
28
|
+
enum: enumChoices,
|
|
29
|
+
ask,
|
|
30
|
+
} = argMeta.argsOption;
|
|
31
|
+
const kebabName = camelToKebabCase(argMeta.name);
|
|
32
|
+
const choices = enumChoices && typeof enumChoices !== "function" ? normalizeEnumChoices(enumChoices) : null;
|
|
33
|
+
programCommand.option(
|
|
34
|
+
`-${flag}, --${kebabName}${type === "boolean" ? " [boolean]" : ` <${kebabName}>`}`,
|
|
35
|
+
`${desc}${ask ? ` (${ask})` : ""}${example ? ` (example: ${example})` : ""}${choices ? ` (choices: ${choices.map((choice) => choice.name).join(", ")})` : ""}`,
|
|
36
|
+
);
|
|
37
|
+
return programCommand;
|
|
38
|
+
};
|
|
39
|
+
const handleArgument = (programCommand: Command, argMeta: ArgMeta) => {
|
|
40
|
+
const kebabName = camelToKebabCase(argMeta.name);
|
|
41
|
+
programCommand.argument(
|
|
42
|
+
`[${kebabName}]`,
|
|
43
|
+
`${argMeta.argsOption.desc}${argMeta.argsOption.example ? ` (example: ${argMeta.argsOption.example})` : ""}`,
|
|
44
|
+
);
|
|
45
|
+
return programCommand;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const convertArgValue = (value: string | boolean, type: "string" | "number" | "boolean") => {
|
|
49
|
+
if (type === "string") return value as string;
|
|
50
|
+
else if (type === "number") return Number(value);
|
|
51
|
+
else return value === true || value === "true";
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const normalizeEnumChoices = (enumChoices: EnumChoices) =>
|
|
55
|
+
enumChoices.map((choice: EnumChoice) =>
|
|
56
|
+
typeof choice === "object"
|
|
57
|
+
? { value: choice.value, name: choice.label }
|
|
58
|
+
: { value: choice, name: choice.toString() },
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const resolveEnumChoices = async (argMeta: ArgMeta, context: CommandContext) => {
|
|
62
|
+
const enumChoices = argMeta.argsOption.enum;
|
|
63
|
+
if (!enumChoices) return null;
|
|
64
|
+
if (typeof enumChoices === "function") return await enumChoices(context);
|
|
65
|
+
return enumChoices;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const getOptionValue = async (argMeta: ArgMeta, opt: Record<string, unknown>, context: CommandContext) => {
|
|
69
|
+
const {
|
|
70
|
+
name,
|
|
71
|
+
argsOption: { enum: enumChoices, default: defaultValue, type, desc, nullable, example, ask },
|
|
72
|
+
} = argMeta;
|
|
73
|
+
if (opt[argMeta.name] !== undefined) return convertArgValue(opt[argMeta.name] as string, type ?? "string");
|
|
74
|
+
else if (defaultValue !== undefined) return defaultValue;
|
|
75
|
+
|
|
76
|
+
if (enumChoices) {
|
|
77
|
+
const choices = normalizeEnumChoices((await resolveEnumChoices(argMeta, context)) ?? []);
|
|
78
|
+
const choice = await select({ message: ask ?? desc ?? `Select the ${name} value`, choices });
|
|
79
|
+
return choice;
|
|
80
|
+
} else if (nullable) return null;
|
|
81
|
+
else if (type === "boolean") {
|
|
82
|
+
const message = ask ?? desc ?? `Do you want to set ${name}? ${desc ? ` (${desc})` : ""}: `;
|
|
83
|
+
return await confirm({ message });
|
|
84
|
+
} else {
|
|
85
|
+
const message = ask
|
|
86
|
+
? `${ask}: `
|
|
87
|
+
: desc
|
|
88
|
+
? `${desc}: `
|
|
89
|
+
: `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
|
|
90
|
+
if (argMeta.argsOption.nullable) return await input({ message });
|
|
91
|
+
else return convertArgValue(await input({ message }), type ?? "string");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const getArgumentValue = async (argMeta: ArgMeta, value: string | undefined) => {
|
|
96
|
+
const {
|
|
97
|
+
name,
|
|
98
|
+
argsOption: { default: defaultValue, type, desc, nullable, example, ask },
|
|
99
|
+
} = argMeta;
|
|
100
|
+
if (value !== undefined) return convertArgValue(value, type ?? "string");
|
|
101
|
+
else if (defaultValue !== undefined) return defaultValue;
|
|
102
|
+
else if (nullable) return null;
|
|
103
|
+
|
|
104
|
+
const message = ask
|
|
105
|
+
? `${ask}: `
|
|
106
|
+
: desc
|
|
107
|
+
? `${desc}: `
|
|
108
|
+
: `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
|
|
109
|
+
return convertArgValue(await input({ message }), type ?? "string");
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const assignCommandContext = (context: CommandContext, argMeta: ArgMeta | InternalArgMeta, value: unknown) => {
|
|
113
|
+
if (value instanceof AppExecutor) context.app = value;
|
|
114
|
+
else if (value instanceof LibExecutor) context.lib = value;
|
|
115
|
+
else if (value instanceof PkgExecutor) context.pkg = value;
|
|
116
|
+
else if (value instanceof ModuleExecutor) context.module = value;
|
|
117
|
+
else if (value instanceof Executor) context.exec = value;
|
|
118
|
+
if (argMeta.type === "Argument" || argMeta.type === "Option") context.values[argMeta.name] = value;
|
|
119
|
+
else context.values[argMeta.type.toLowerCase()] = value;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const assertCurrentDirectoryIsWorkspaceRoot = async () => {
|
|
123
|
+
const cwd = process.cwd();
|
|
124
|
+
const [hasPackageJson, hasTsConfig, hasEnv] = await Promise.all([
|
|
125
|
+
FileSys.fileExists(`${cwd}/package.json`),
|
|
126
|
+
FileSys.fileExists(`${cwd}/tsconfig.json`),
|
|
127
|
+
FileSys.fileExists(`${cwd}/.env`),
|
|
128
|
+
]);
|
|
129
|
+
if (hasPackageJson && hasTsConfig && hasEnv) return;
|
|
130
|
+
|
|
131
|
+
throw new Error(
|
|
132
|
+
[
|
|
133
|
+
"Akan CLI commands must be run from the workspace root.",
|
|
134
|
+
`Current directory: ${cwd}`,
|
|
135
|
+
"Move to the directory that contains package.json, tsconfig.json, and .env, then run the command again.",
|
|
136
|
+
].join("\n"),
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const getInternalArgumentValue = async (
|
|
141
|
+
argMeta: InternalArgMeta,
|
|
142
|
+
value: string | undefined,
|
|
143
|
+
workspace: WorkspaceExecutor,
|
|
144
|
+
) => {
|
|
145
|
+
if (argMeta.type === "Workspace") return workspace;
|
|
146
|
+
const sysType = argMeta.type.toLowerCase();
|
|
147
|
+
const [appNames, libNames, pkgNames] = await workspace.getExecs();
|
|
148
|
+
if (sysType === "sys") {
|
|
149
|
+
if (value && appNames.includes(value)) return AppExecutor.from(workspace, value);
|
|
150
|
+
else if (value && libNames.includes(value)) return LibExecutor.from(workspace, value);
|
|
151
|
+
else {
|
|
152
|
+
const sysName = await select<string>({
|
|
153
|
+
message: `Select the App or Lib name`,
|
|
154
|
+
choices: [...appNames, ...libNames],
|
|
155
|
+
});
|
|
156
|
+
if (appNames.includes(sysName)) return AppExecutor.from(workspace, sysName);
|
|
157
|
+
else if (libNames.includes(sysName)) return LibExecutor.from(workspace, sysName);
|
|
158
|
+
else throw new Error(`Invalid system name: ${sysName}`);
|
|
159
|
+
}
|
|
160
|
+
} else if (sysType === "exec") {
|
|
161
|
+
if (value && appNames.includes(value)) return AppExecutor.from(workspace, value);
|
|
162
|
+
else if (value && libNames.includes(value)) return LibExecutor.from(workspace, value);
|
|
163
|
+
else if (value && pkgNames.includes(value)) return PkgExecutor.from(workspace, value);
|
|
164
|
+
else {
|
|
165
|
+
const execName = await select<string>({
|
|
166
|
+
message: `Select the App or Lib or Pkg name`,
|
|
167
|
+
choices: [...appNames, ...libNames, ...pkgNames],
|
|
168
|
+
});
|
|
169
|
+
if (appNames.includes(execName)) return AppExecutor.from(workspace, execName);
|
|
170
|
+
else if (libNames.includes(execName)) return LibExecutor.from(workspace, execName);
|
|
171
|
+
else if (pkgNames.includes(execName)) return PkgExecutor.from(workspace, execName);
|
|
172
|
+
else throw new Error(`Invalid system name: ${execName}`);
|
|
173
|
+
}
|
|
174
|
+
} else if (sysType === "app") {
|
|
175
|
+
if (value && appNames.includes(value)) return AppExecutor.from(workspace, value);
|
|
176
|
+
const appName = await select<string>({ message: `Select the ${sysType} name`, choices: appNames });
|
|
177
|
+
return AppExecutor.from(workspace, appName);
|
|
178
|
+
} else if (sysType === "lib") {
|
|
179
|
+
if (value && libNames.includes(value)) return LibExecutor.from(workspace, value);
|
|
180
|
+
const libName = await select<string>({ message: `Select the ${sysType} name`, choices: libNames });
|
|
181
|
+
return LibExecutor.from(workspace, libName);
|
|
182
|
+
} else if (sysType === "pkg") {
|
|
183
|
+
const pkgs = await workspace.getPkgs();
|
|
184
|
+
if (value && pkgs.includes(value)) return PkgExecutor.from(workspace, value);
|
|
185
|
+
const pkgName = await select<string>({ message: `Select the ${sysType} name`, choices: pkgs });
|
|
186
|
+
return PkgExecutor.from(workspace, pkgName);
|
|
187
|
+
} else if (sysType === "module") {
|
|
188
|
+
if (value) {
|
|
189
|
+
const [sysName, moduleName] = value.split(":");
|
|
190
|
+
if (!sysName || !moduleName) throw new Error(`Invalid module name: ${value}`);
|
|
191
|
+
if (appNames.includes(sysName)) {
|
|
192
|
+
const app = AppExecutor.from(workspace, sysName);
|
|
193
|
+
const modules = await app.getModules();
|
|
194
|
+
if (modules.includes(moduleName)) return ModuleExecutor.from(app, moduleName);
|
|
195
|
+
else throw new Error(`Invalid module name: ${moduleName}`);
|
|
196
|
+
} else if (libNames.includes(sysName)) {
|
|
197
|
+
const lib = LibExecutor.from(workspace, sysName);
|
|
198
|
+
const modules = await lib.getModules();
|
|
199
|
+
if (modules.includes(moduleName)) return ModuleExecutor.from(lib, moduleName);
|
|
200
|
+
} else throw new Error(`Invalid system name: ${sysName}`);
|
|
201
|
+
}
|
|
202
|
+
const { type, name } = await select<{ type: "app" | "lib"; name: string }>({
|
|
203
|
+
message: `select the App or Lib name`,
|
|
204
|
+
choices: [
|
|
205
|
+
...appNames.map((name) => ({ name, value: { type: "app" as const, name } })),
|
|
206
|
+
...libNames.map((name) => ({ name, value: { type: "lib" as const, name } })),
|
|
207
|
+
],
|
|
208
|
+
});
|
|
209
|
+
const executor = type === "app" ? AppExecutor.from(workspace, name) : LibExecutor.from(workspace, name);
|
|
210
|
+
const modules = await executor.getModules();
|
|
211
|
+
const moduleName = await select<string>({
|
|
212
|
+
message: `Select the module name`,
|
|
213
|
+
choices: modules.map((name) => ({ name: `${executor.name}:${name}`, value: name })),
|
|
214
|
+
});
|
|
215
|
+
return ModuleExecutor.from(executor, moduleName);
|
|
216
|
+
} else throw new Error(`Invalid system type: ${argMeta.type}`);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const runCommands = async (...commands: CommandCls[]) => {
|
|
220
|
+
process.on("unhandledRejection", (error) => {
|
|
221
|
+
process.exit(1);
|
|
222
|
+
});
|
|
223
|
+
const __dirname = getDirname(import.meta.url);
|
|
224
|
+
const hasPackageJson = await FileSys.fileExists(`${__dirname}/../package.json`);
|
|
225
|
+
process.env.AKAN_VERSION = hasPackageJson
|
|
226
|
+
? (await FileSys.readJson<PackageJson>(`${__dirname}/../package.json`)).version
|
|
227
|
+
: "0.0.1";
|
|
228
|
+
|
|
229
|
+
// Custom help handling
|
|
230
|
+
const hasHelpFlag = process.argv.includes("--help") || process.argv.includes("-h");
|
|
231
|
+
const hasCommand = process.argv.length > 2 && !process.argv[2]?.startsWith("-");
|
|
232
|
+
|
|
233
|
+
// Show help if: 1) explicit --help flag, or 2) no command provided (just "akan")
|
|
234
|
+
if (hasHelpFlag || !hasCommand) {
|
|
235
|
+
if (process.argv.length === 2 || (process.argv.length === 3 && hasHelpFlag)) {
|
|
236
|
+
// Global help (no specific command)
|
|
237
|
+
Logger.rawLog(formatHelp(commands, process.env.AKAN_VERSION));
|
|
238
|
+
process.exit(0);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
program.version(process.env.AKAN_VERSION).description("Akan CLI").configureHelp({
|
|
243
|
+
helpWidth: 100,
|
|
244
|
+
});
|
|
245
|
+
const installedAkanPackageJson = (await FileSys.fileExists("./node_modules/akanjs/package.json"))
|
|
246
|
+
? await FileSys.readJson<PackageJson>("./node_modules/akanjs/package.json")
|
|
247
|
+
: null;
|
|
248
|
+
if (installedAkanPackageJson && installedAkanPackageJson.version !== process.env.AKAN_VERSION) {
|
|
249
|
+
Logger.rawLog(
|
|
250
|
+
chalk.yellow(
|
|
251
|
+
`
|
|
252
|
+
Akan CLI version is mismatch with installed package. ${process.env.AKAN_VERSION} (global) vs ${installedAkanPackageJson.version} (akanjs)
|
|
253
|
+
It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`,
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const command of commands) {
|
|
259
|
+
const targetMetas = getTargetMetas(command);
|
|
260
|
+
for (const targetMeta of targetMetas) {
|
|
261
|
+
const kebabKey = camelToKebabCase(targetMeta.key);
|
|
262
|
+
const commandNames =
|
|
263
|
+
targetMeta.targetOption.short === true
|
|
264
|
+
? [
|
|
265
|
+
kebabKey,
|
|
266
|
+
typeof targetMeta.targetOption.short === "string"
|
|
267
|
+
? targetMeta.targetOption.short
|
|
268
|
+
: kebabKey
|
|
269
|
+
.split("-")
|
|
270
|
+
.map((s) => s.slice(0, 1))
|
|
271
|
+
.join(""),
|
|
272
|
+
]
|
|
273
|
+
: [kebabKey];
|
|
274
|
+
for (const commandName of commandNames) {
|
|
275
|
+
let programCommand = program.command(commandName, {
|
|
276
|
+
hidden: targetMeta.targetOption.devOnly,
|
|
277
|
+
});
|
|
278
|
+
const [allArgMetas] = getArgMetas(command, targetMeta.key);
|
|
279
|
+
for (const argMeta of allArgMetas) {
|
|
280
|
+
if (argMeta.type === "Option") programCommand = handleOption(programCommand, argMeta);
|
|
281
|
+
else if (argMeta.type === "Argument") programCommand = handleArgument(programCommand, argMeta);
|
|
282
|
+
else if (argMeta.type === "Workspace") continue;
|
|
283
|
+
else if (argMeta.type === "Module") {
|
|
284
|
+
programCommand = programCommand.argument(
|
|
285
|
+
`[sys-name:module-name]`,
|
|
286
|
+
`${argMeta.type} in this workspace (apps|libs)/<sys-name>/lib/<module-name>`,
|
|
287
|
+
);
|
|
288
|
+
} else {
|
|
289
|
+
const sysType = argMeta.type.toLowerCase();
|
|
290
|
+
programCommand = programCommand.argument(
|
|
291
|
+
`[${sysType}]`,
|
|
292
|
+
`${sysType} in this workspace ${sysType}s/<${sysType}Name>`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
programCommand = programCommand.option(`-v, --verbose [boolean]`, `verbose output`);
|
|
297
|
+
|
|
298
|
+
// Override help completely for each command
|
|
299
|
+
programCommand.helpInformation = () => {
|
|
300
|
+
return formatCommandHelp(command, targetMeta.key);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
programCommand.action(async (...args: unknown[]) => {
|
|
304
|
+
Logger.rawLog();
|
|
305
|
+
const cmdArgs = args.slice(0, args.length - 2);
|
|
306
|
+
const opt = args[args.length - 2] as Record<string, unknown>;
|
|
307
|
+
const commandArgs = [] as unknown[];
|
|
308
|
+
if (targetMeta.targetOption.runsOnWorkspaceRoot) await assertCurrentDirectoryIsWorkspaceRoot();
|
|
309
|
+
const workspace = WorkspaceExecutor.fromRoot();
|
|
310
|
+
const commandContext: CommandContext = { values: {} };
|
|
311
|
+
for (const argMeta of allArgMetas) {
|
|
312
|
+
if (argMeta.type === "Option")
|
|
313
|
+
commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt, commandContext);
|
|
314
|
+
else if (argMeta.type === "Argument")
|
|
315
|
+
commandArgs[argMeta.idx] = await getArgumentValue(argMeta, cmdArgs[argMeta.idx] as string);
|
|
316
|
+
else
|
|
317
|
+
commandArgs[argMeta.idx] = await getInternalArgumentValue(
|
|
318
|
+
argMeta as InternalArgMeta,
|
|
319
|
+
cmdArgs[argMeta.idx] as string,
|
|
320
|
+
workspace,
|
|
321
|
+
);
|
|
322
|
+
// set app name to env
|
|
323
|
+
if (commandArgs[argMeta.idx] instanceof AppExecutor)
|
|
324
|
+
process.env.AKAN_PUBLIC_APP_NAME = (commandArgs[argMeta.idx] as AppExecutor).name;
|
|
325
|
+
assignCommandContext(commandContext, argMeta, commandArgs[argMeta.idx]);
|
|
326
|
+
if ((opt as { verbose?: boolean }).verbose) Executor.setVerbose(true);
|
|
327
|
+
}
|
|
328
|
+
const cmd = CommandContainer.get(command);
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
await targetMeta.handler.call(cmd, ...commandArgs);
|
|
332
|
+
Logger.rawLog();
|
|
333
|
+
} catch (e) {
|
|
334
|
+
const errMsg = e instanceof Error ? e.message : typeof e === "string" ? e : JSON.stringify(e);
|
|
335
|
+
Logger.rawLog(`\n${chalk.red(errMsg)}`);
|
|
336
|
+
throw e;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
await program.parseAsync(process.argv);
|
|
343
|
+
};
|
|
@@ -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
|
+
}
|