akanjs 2.0.0-rc.6 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/base/primitiveRegistry.ts +28 -2
  2. package/cli/application/application.command.ts +11 -3
  3. package/cli/application/application.runner.ts +17 -1
  4. package/cli/guidelines/databaseModule/databaseModule.instruction.md +1 -1
  5. package/cli/guidelines/modelConstant/modelConstant.instruction.md +5 -5
  6. package/cli/guidelines/modelDocument/modelDocument.instruction.md +34 -61
  7. package/cli/guidelines/modelService/modelService.instruction.md +1 -1
  8. package/cli/index.js +9321 -19222
  9. package/cli/library/library.runner.ts +14 -13
  10. package/cli/package/package.runner.ts +31 -6
  11. package/cli/package/package.script.ts +2 -2
  12. package/cli/templates/app/page/_index.tsx +200 -79
  13. package/cli/templates/app/page/_layout.tsx +0 -1
  14. package/cli/templates/app/public/favicon.ico.template +0 -0
  15. package/cli/templates/app/public/logo.png.template +0 -0
  16. package/cli/templates/module/__Model__.Zone.tsx +1 -1
  17. package/cli/templates/module/__model__.document.ts +1 -1
  18. package/cli/templates/workspaceRoot/.gitignore.template +1 -11
  19. package/cli/templates/workspaceRoot/biome.json.template +16 -0
  20. package/cli/workspace/workspace.command.ts +7 -9
  21. package/cli/workspace/workspace.runner.ts +3 -13
  22. package/cli/workspace/workspace.script.ts +24 -9
  23. package/client/csrTypes.ts +1 -1
  24. package/constant/fieldInfo.ts +1 -1
  25. package/constant/serialize.ts +7 -1
  26. package/devkit/capacitor.base.config.ts +1 -1
  27. package/devkit/capacitorApp.ts +5 -1
  28. package/devkit/commandDecorators/argMeta.ts +28 -14
  29. package/devkit/commandDecorators/command.ts +41 -15
  30. package/devkit/commandDecorators/commandBuilder.ts +78 -42
  31. package/devkit/commandDecorators/helpFormatter.ts +7 -4
  32. package/devkit/dependencyScanner.ts +121 -15
  33. package/devkit/executors.ts +35 -23
  34. package/devkit/frontendBuild/cssCompiler.ts +9 -3
  35. package/devkit/incrementalBuilder/incrementalBuilder.proc.ts +2 -1
  36. package/devkit/lint/no-deep-internal-import.grit +25 -0
  37. package/devkit/lint/no-import-external-library.grit +1 -0
  38. package/devkit/mobile/mobileTarget.ts +48 -8
  39. package/devkit/scanInfo.ts +4 -1
  40. package/devkit/src/capacitorApp.ts +277 -0
  41. package/devkit/transforms/barrelImportsPlugin.ts +6 -0
  42. package/fetch/client/fetchClient.ts +1 -0
  43. package/fetch/client/httpClient.ts +13 -1
  44. package/package.json +37 -31
  45. package/server/akanServer.ts +21 -7
  46. package/server/hmr/clientScript.ts +8 -5
  47. package/server/resolver/resolver.contract.fixture.ts +1 -1
  48. package/test/index.ts +14 -0
  49. package/test/signalTest.preload.ts +10 -0
  50. package/test/signalTestRuntime.ts +126 -0
  51. package/test/testServer.ts +130 -25
  52. package/ui/Constant/Doc.tsx +696 -0
  53. package/ui/Constant/Mermaid.tsx +149 -0
  54. package/ui/Constant/index.ts +6 -0
  55. package/ui/Constant/schemaDoc.ts +324 -0
  56. package/ui/Field.tsx +0 -1
  57. package/ui/Portal.tsx +2 -0
  58. package/ui/System/CSR.tsx +6 -5
  59. package/ui/System/SSR.tsx +1 -1
  60. package/ui/System/SelectLanguage.tsx +1 -1
  61. package/ui/index.ts +1 -0
  62. package/ui/styles.css +0 -2
  63. package/webkit/bootCsr.tsx +8 -5
  64. package/base/test-globals.d.ts +0 -4
  65. package/cli/templates/app/common/commonLogic.ts +0 -12
  66. package/cli/templates/app/common/index.ts +0 -10
  67. package/cli/templates/app/public/favicon.ico +0 -0
  68. package/cli/templates/app/public/icons/icon-128x128.png +0 -0
  69. package/cli/templates/app/public/icons/icon-144x144.png +0 -0
  70. package/cli/templates/app/public/icons/icon-152x152.png +0 -0
  71. package/cli/templates/app/public/icons/icon-192x192.png +0 -0
  72. package/cli/templates/app/public/icons/icon-256x256.png +0 -0
  73. package/cli/templates/app/public/icons/icon-384x384.png +0 -0
  74. package/cli/templates/app/public/icons/icon-48x48.png +0 -0
  75. package/cli/templates/app/public/icons/icon-512x512.png +0 -0
  76. package/cli/templates/app/public/icons/icon-72x72.png +0 -0
  77. package/cli/templates/app/public/icons/icon-96x96.png +0 -0
  78. package/cli/templates/app/public/logo.svg +0 -70
  79. package/cli/templates/app/public/manifest.json.template +0 -67
  80. package/cli/templates/app/srvkit/backendLogic.ts +0 -12
  81. package/cli/templates/app/srvkit/index.ts +0 -10
  82. package/cli/templates/app/ui/UiComponent.ts +0 -16
  83. package/cli/templates/app/ui/index.ts +0 -10
  84. package/cli/templates/app/webkit/frontendLogic.ts +0 -12
  85. package/cli/templates/app/webkit/index.ts +0 -10
  86. package/cli/templates/module/index.tsx +0 -44
@@ -1,12 +1,11 @@
1
1
  import path from "node:path";
2
2
  import { type Exec, type PackageJson, runner, type Workspace, WorkspaceExecutor } from "akanjs/devkit";
3
- import latestVersion from "latest-version";
4
3
 
5
4
  export class WorkspaceRunner extends runner("workspace") {
6
5
  async createWorkspace(
7
6
  repoName: string,
8
7
  appName: string,
9
- { dirname = ".", tag = "latest", init = true }: { dirname?: string; tag?: string; init?: boolean },
8
+ { dirname = ".", init = true, akanVersion }: { dirname?: string; init?: boolean; akanVersion: string },
10
9
  ) {
11
10
  const cwdPath = process.cwd();
12
11
  const workspaceRoot = path.join(cwdPath, dirname, repoName);
@@ -21,18 +20,17 @@ export class WorkspaceRunner extends runner("workspace") {
21
20
  templateSpinner.succeed(`Workspace files created in ${dirname}/${repoName}`);
22
21
 
23
22
  const rootPackageJson = await workspace.getPackageJson();
24
- const latestPublishedVersion = await latestVersion("akanjs", { version: tag });
25
23
  const packageJson: PackageJson = {
26
24
  ...rootPackageJson,
27
25
  dependencies: {
28
26
  ...rootPackageJson.dependencies,
29
- akanjs: latestPublishedVersion,
27
+ akanjs: akanVersion,
30
28
  },
31
29
  devDependencies: {
32
30
  ...rootPackageJson.devDependencies,
33
31
  },
34
32
  };
35
- workspace.setPackageJson(packageJson);
33
+ await workspace.setPackageJson(packageJson);
36
34
 
37
35
  if (init) {
38
36
  const installSpinner = workspace.spinning("Installing dependencies with bun...");
@@ -40,14 +38,6 @@ export class WorkspaceRunner extends runner("workspace") {
40
38
  installSpinner.succeed("Dependencies installed with bun");
41
39
  }
42
40
 
43
- const gitSpinner = workspace.spinning("Initializing git repository and commit...");
44
- try {
45
- await workspace.commit("Initial commit", { init: true });
46
- gitSpinner.succeed("Git repository initialized and committed");
47
- } catch (_) {
48
- gitSpinner.fail("Git repository initialization failed. It's not fatal, you can commit manually");
49
- }
50
-
51
41
  return workspace;
52
42
  }
53
43
  async lint(exec: Exec, workspace: Workspace, { fix = true }: { fix?: boolean } = {}) {
@@ -1,29 +1,44 @@
1
+ import path from "node:path";
1
2
  import { Logger } from "akanjs/common";
2
3
  import { AppExecutor, type Exec, LibExecutor, PkgExecutor, script, type Workspace } from "akanjs/devkit";
3
4
 
4
5
  import { ApplicationScript } from "../application/application.script";
5
6
  import { LibraryScript } from "../library/library.script";
7
+ import { PackageScript } from "../package/package.script";
6
8
  import { WorkspaceRunner } from "./workspace.runner";
7
9
 
8
- export class WorkspaceScript extends script("workspace", [WorkspaceRunner, ApplicationScript, LibraryScript]) {
10
+ export class WorkspaceScript extends script("workspace", [
11
+ WorkspaceRunner,
12
+ ApplicationScript,
13
+ LibraryScript,
14
+ PackageScript,
15
+ ]) {
9
16
  async createWorkspace(
10
17
  repoName: string,
11
18
  appName: string,
12
- {
13
- dirname = ".",
14
- installLibs = false,
15
- tag = "latest",
16
- init = true,
17
- }: { dirname?: string; installLibs?: boolean; tag?: string; init?: boolean },
19
+ { dirname = ".", installLibs = false, init = true }: { dirname?: string; installLibs?: boolean; init?: boolean },
18
20
  ) {
19
- const workspace = await this.workspaceRunner.createWorkspace(repoName, appName, { dirname, tag, init });
21
+ const akanVersion = await this.packageScript.version({ log: false });
22
+ const workspace = await this.workspaceRunner.createWorkspace(repoName, appName, {
23
+ dirname,
24
+ init,
25
+ akanVersion,
26
+ });
20
27
  if (installLibs) {
21
28
  await this.libraryScript.installLibrary(workspace, "util");
22
29
  await this.libraryScript.installLibrary(workspace, "shared");
23
30
  }
24
31
  await this.applicationScript.createApplication(appName, workspace, { libs: installLibs ? ["util", "shared"] : [] });
32
+ const gitSpinner = workspace.spinning("Initializing git repository and commit...");
33
+ try {
34
+ await workspace.commit("Initial commit", { init: true });
35
+ gitSpinner.succeed("Git repository initialized and committed");
36
+ } catch (_) {
37
+ gitSpinner.fail("Git repository initialization failed. It's not fatal, you can commit manually");
38
+ }
39
+ const workspacePath = path.join(dirname, repoName);
25
40
  Logger.rawLog(`\n🎉 Welcome aboard! Workspace created in ${dirname}/${repoName}`);
26
- Logger.rawLog(`🚀 Run \`cd ${repoName} && akan start ${appName}\` to start the development server.`);
41
+ Logger.rawLog(`🚀 Run \`cd ${workspacePath} && akan start ${appName}\` to start the development server.`);
27
42
 
28
43
  Logger.rawLog(`\n👋 Happy coding!`);
29
44
  }
@@ -111,7 +111,7 @@ export interface LayoutModule {
111
111
  }
112
112
  export type RouteModule = PageModule | LayoutModule;
113
113
  export interface Route {
114
- csrConfig?: PageConfig;
114
+ PageConfig?: PageConfig;
115
115
  path: string;
116
116
  renderPage?: RouteRender;
117
117
  renderLayout?: RouteRender;
@@ -190,7 +190,7 @@ export class ConstantField<
190
190
  }
191
191
  static getBaseInsightField(): FieldObject {
192
192
  return {
193
- count: field(Int, { default: 0, accumulate: { $sum: 1 } }).toField(),
193
+ count: field(Int, { default: 0, accumulate: {} }).toField(),
194
194
  };
195
195
  }
196
196
  readonly nullable: Nullable;
@@ -54,7 +54,7 @@ const serializeInput = <Input = unknown>(
54
54
  Object.entries(modelRef[FIELD_META]).map(([key, field]) => [
55
55
  key,
56
56
  field.isClass && !field.isScalar
57
- ? serialize(ID, field.arrDepth, (value as Record<string, unknown>)?.[key], serializeType, {
57
+ ? serialize(ID, field.arrDepth, getRelationId((value as Record<string, unknown>)?.[key]), serializeType, {
58
58
  nullable: field.nullable,
59
59
  key,
60
60
  })
@@ -67,6 +67,12 @@ const serializeInput = <Input = unknown>(
67
67
  }
68
68
  };
69
69
 
70
+ const getRelationId = (value: unknown): unknown => {
71
+ if (Array.isArray(value)) return value.map((item) => getRelationId(item));
72
+ if (value && typeof value === "object" && "id" in value) return (value as { id: unknown }).id;
73
+ return value;
74
+ };
75
+
70
76
  export const serialize = (
71
77
  argRef: ConstantCls | PrimitiveScalar,
72
78
  arrDepth: number,
@@ -53,7 +53,7 @@ export const withBase = (
53
53
  androidScheme: "http",
54
54
  url: localCsrUrl(ip, target),
55
55
  cleartext: true,
56
- allowNavigation: [`http://${ip}:8282/*`],
56
+ allowNavigation: [ip, "localhost"],
57
57
  }
58
58
  : {
59
59
  allowNavigation: ["*"],
@@ -201,7 +201,11 @@ export class CapacitorApp {
201
201
  .relative(this.targetRoot, path.join(this.app.cwdPath, "akan.app.json"))
202
202
  .split(path.sep)
203
203
  .join("/");
204
- const content = `import { withBase } from "akanjs/devkit/capacitor.base.config";
204
+ const baseConfigPath = path
205
+ .relative(this.targetRoot, path.join(this.app.workspace.cwdPath, "pkgs/akanjs/devkit/capacitor.base.config"))
206
+ .split(path.sep)
207
+ .join("/");
208
+ const content = `import { withBase } from "${baseConfigPath.startsWith(".") ? baseConfigPath : `./${baseConfigPath}`}";
205
209
  import appInfo from "${appInfoPath}";
206
210
 
207
211
  export default withBase((config) => config, appInfo, "${this.target.name}");
@@ -18,19 +18,33 @@ export type InternalArgType = (typeof internalArgTypes)[number];
18
18
  export type PrimitiveArgType = StringConstructor | NumberConstructor | BooleanConstructor;
19
19
  export type NormalizedPrimitiveArgType = "string" | "number" | "boolean";
20
20
 
21
- export interface ArgsOption {
21
+ export type CommandContext = {
22
+ values: Record<string, unknown>;
23
+ app?: AppExecutor;
24
+ lib?: LibExecutor;
25
+ sys?: SysExecutor;
26
+ pkg?: PkgExecutor;
27
+ module?: ModuleExecutor;
28
+ exec?: Executor;
29
+ };
30
+
31
+ export type EnumChoice = string | number | { label: string; value: string | number | boolean };
32
+ export type EnumChoices = readonly EnumChoice[];
33
+ export type DynamicEnum<Context> = (context: Context) => EnumChoices | Promise<EnumChoices>;
34
+
35
+ export interface ArgsOption<Context = CommandContext> {
22
36
  type?: "string" | "number" | "boolean";
23
37
  flag?: string;
24
38
  desc?: string;
25
39
  default?: string | number | boolean;
26
40
  nullable?: boolean;
27
41
  example?: string | number | boolean;
28
- enum?: (string | number)[] | readonly (string | number)[] | { label: string; value: string | number | boolean }[];
42
+ enum?: EnumChoices | DynamicEnum<Context>;
29
43
  ask?: string;
30
44
  }
31
- export interface ArgMeta {
45
+ export interface ArgMeta<Context = CommandContext> {
32
46
  name: string;
33
- argsOption: ArgsOption;
47
+ argsOption: ArgsOption<Context>;
34
48
  key: string;
35
49
  idx: number;
36
50
  type: ArgType;
@@ -52,12 +66,12 @@ export const getArgMetas = (
52
66
  return [allArgMetas, argMetas, internalArgMetas];
53
67
  };
54
68
 
55
- export interface InternalArgToken<T = unknown> {
56
- type: InternalArgType;
69
+ export interface InternalArgToken<T = unknown, Type extends InternalArgType = InternalArgType> {
70
+ type: Type;
57
71
  _value: T;
58
72
  }
59
73
 
60
- const createInternalArgToken = <T>(type: InternalArgType) => ({ type }) as InternalArgToken<T>;
74
+ const createInternalArgToken = <T, Type extends InternalArgType>(type: Type) => ({ type }) as InternalArgToken<T, Type>;
61
75
 
62
76
  export const normalizePrimitiveArgType = (type: PrimitiveArgType): NormalizedPrimitiveArgType => {
63
77
  if (type === String) return "string";
@@ -66,23 +80,23 @@ export const normalizePrimitiveArgType = (type: PrimitiveArgType): NormalizedPri
66
80
  throw new Error(`Invalid primitive argument type: ${type}`);
67
81
  };
68
82
 
69
- export const App = createInternalArgToken<AppExecutor>("App");
83
+ export const App = createInternalArgToken<AppExecutor, "App">("App");
70
84
  export type App = AppExecutor;
71
85
 
72
- export const Lib = createInternalArgToken<LibExecutor>("Lib");
86
+ export const Lib = createInternalArgToken<LibExecutor, "Lib">("Lib");
73
87
  export type Lib = LibExecutor;
74
88
 
75
- export const Sys = createInternalArgToken<SysExecutor>("Sys");
89
+ export const Sys = createInternalArgToken<SysExecutor, "Sys">("Sys");
76
90
  export type Sys = SysExecutor;
77
91
 
78
- export const Exec = createInternalArgToken<Executor>("Exec");
92
+ export const Exec = createInternalArgToken<Executor, "Exec">("Exec");
79
93
  export type Exec = Executor;
80
94
 
81
- export const Pkg = createInternalArgToken<PkgExecutor>("Pkg");
95
+ export const Pkg = createInternalArgToken<PkgExecutor, "Pkg">("Pkg");
82
96
  export type Pkg = PkgExecutor;
83
97
 
84
- export const Module = createInternalArgToken<ModuleExecutor>("Module");
98
+ export const Module = createInternalArgToken<ModuleExecutor, "Module">("Module");
85
99
  export type Module = ModuleExecutor;
86
100
 
87
- export const Workspace = createInternalArgToken<WorkspaceExecutor>("Workspace");
101
+ export const Workspace = createInternalArgToken<WorkspaceExecutor, "Workspace">("Workspace");
88
102
  export type Workspace = WorkspaceExecutor;
@@ -5,7 +5,14 @@ import { type Command, program } from "commander";
5
5
 
6
6
  import { FileSys, getDirname, type PackageJson } from "..";
7
7
  import { AppExecutor, Executor, LibExecutor, ModuleExecutor, PkgExecutor, WorkspaceExecutor } from "../executors";
8
- import { type ArgMeta, getArgMetas, type InternalArgMeta } from "./argMeta";
8
+ import {
9
+ type ArgMeta,
10
+ type CommandContext,
11
+ type EnumChoice,
12
+ type EnumChoices,
13
+ getArgMetas,
14
+ type InternalArgMeta,
15
+ } from "./argMeta";
9
16
  import { CommandContainer } from "./dependencyBuilder";
10
17
  import { formatCommandHelp, formatHelp } from "./helpFormatter";
11
18
  import { type CommandCls, getTargetMetas } from "./targetMeta";
@@ -22,11 +29,7 @@ const handleOption = (programCommand: Command, argMeta: ArgMeta) => {
22
29
  ask,
23
30
  } = argMeta.argsOption;
24
31
  const kebabName = camelToKebabCase(argMeta.name);
25
- const choices = enumChoices?.map((choice: string | number | { label: string; value: string | number | boolean }) =>
26
- typeof choice === "object"
27
- ? { value: choice.value, name: choice.label }
28
- : { value: choice, name: choice.toString() },
29
- );
32
+ const choices = enumChoices && typeof enumChoices !== "function" ? normalizeEnumChoices(enumChoices) : null;
30
33
  programCommand.option(
31
34
  `-${flag}, --${kebabName}${type === "boolean" ? " [boolean]" : ` <${kebabName}>`}`,
32
35
  `${desc}${ask ? ` (${ask})` : ""}${example ? ` (example: ${example})` : ""}${choices ? ` (choices: ${choices.map((choice) => choice.name).join(", ")})` : ""}`,
@@ -48,24 +51,34 @@ const convertArgValue = (value: string | boolean, type: "string" | "number" | "b
48
51
  else return value === true || value === "true";
49
52
  };
50
53
 
51
- const getOptionValue = async (argMeta: ArgMeta, opt: Record<string, unknown>) => {
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) => {
52
69
  const {
53
70
  name,
54
71
  argsOption: { enum: enumChoices, default: defaultValue, type, desc, nullable, example, ask },
55
72
  } = argMeta;
56
73
  if (opt[argMeta.name] !== undefined) return convertArgValue(opt[argMeta.name] as string, type ?? "string");
57
74
  else if (defaultValue !== undefined) return defaultValue;
58
- else if (nullable) return null;
59
75
 
60
76
  if (enumChoices) {
61
- const choices = enumChoices.map((choice: string | number | { label: string; value: string | number | boolean }) =>
62
- typeof choice === "object"
63
- ? { value: choice.value, name: choice.label }
64
- : { value: choice, name: choice.toString() },
65
- );
77
+ const choices = normalizeEnumChoices((await resolveEnumChoices(argMeta, context)) ?? []);
66
78
  const choice = await select({ message: ask ?? desc ?? `Select the ${name} value`, choices });
67
79
  return choice;
68
- } else if (type === "boolean") {
80
+ } else if (nullable) return null;
81
+ else if (type === "boolean") {
69
82
  const message = ask ?? desc ?? `Do you want to set ${name}? ${desc ? ` (${desc})` : ""}: `;
70
83
  return await confirm({ message });
71
84
  } else {
@@ -96,6 +109,16 @@ const getArgumentValue = async (argMeta: ArgMeta, value: string | undefined) =>
96
109
  return convertArgValue(await input({ message }), type ?? "string");
97
110
  };
98
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
+
99
122
  const assertCurrentDirectoryIsWorkspaceRoot = async () => {
100
123
  const cwd = process.cwd();
101
124
  const [hasPackageJson, hasTsConfig, hasEnv] = await Promise.all([
@@ -281,8 +304,10 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`,
281
304
  const commandArgs = [] as unknown[];
282
305
  if (targetMeta.targetOption.runsOnWorkspaceRoot) await assertCurrentDirectoryIsWorkspaceRoot();
283
306
  const workspace = WorkspaceExecutor.fromRoot();
307
+ const commandContext: CommandContext = { values: {} };
284
308
  for (const argMeta of allArgMetas) {
285
- if (argMeta.type === "Option") commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt);
309
+ if (argMeta.type === "Option")
310
+ commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt, commandContext);
286
311
  else if (argMeta.type === "Argument")
287
312
  commandArgs[argMeta.idx] = await getArgumentValue(argMeta, cmdArgs[argMeta.idx] as string);
288
313
  else
@@ -294,6 +319,7 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`,
294
319
 
295
320
  if (commandArgs[argMeta.idx] instanceof AppExecutor)
296
321
  process.env.AKAN_PUBLIC_APP_NAME = (commandArgs[argMeta.idx] as AppExecutor).name;
322
+ assignCommandContext(commandContext, argMeta, commandArgs[argMeta.idx]);
297
323
  if ((opt as { verbose?: boolean }).verbose) Executor.setVerbose(true);
298
324
  }
299
325
  const cmd = CommandContainer.get(command);
@@ -1,4 +1,17 @@
1
- import type { ArgMeta, ArgsOption, InternalArgMeta, InternalArgToken, PrimitiveArgType } from "./argMeta";
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";
2
15
  import { normalizePrimitiveArgType } from "./argMeta";
3
16
  import { assertUniqueDependencies, type DependencyInstanceMap, injectDependencies } from "./dependencyBuilder";
4
17
  import { COMMAND_META, type CommandCls, type TargetMeta, type TargetOption } from "./targetMeta";
@@ -9,9 +22,8 @@ type PrimitiveValue<T extends PrimitiveArgType> = T extends StringConstructor
9
22
  : T extends NumberConstructor
10
23
  ? number
11
24
  : boolean;
12
- type ArgsOptionInput = Record<string, never> | ArgsOption;
13
- type MaybeNullable<Value, Option extends ArgsOptionInput> = Option extends { nullable: true } ? Value | null : Value;
14
- type AddArg<Params extends unknown[], Type extends PrimitiveArgType, Option extends ArgsOptionInput> = [
25
+ type MaybeNullable<Value, Option> = Option extends { nullable: true } ? Value | null : Value;
26
+ type AddArg<Params extends unknown[], Type extends PrimitiveArgType, Option> = [
15
27
  ...Params,
16
28
  MaybeNullable<PrimitiveValue<Type>, Option>,
17
29
  ];
@@ -22,12 +34,31 @@ type AddInternalArgs<Params extends unknown[], Tokens extends readonly InternalA
22
34
  ]
23
35
  ? AddInternalArgs<AddInternalArg<Params, Head>, Rest>
24
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;
25
56
  type CommandHandler<Deps extends readonly DependencyCls[], Params extends unknown[]> = (
26
57
  this: DependencyInstanceMap<Deps>,
27
58
  ...args: Params
28
59
  ) => unknown | Promise<unknown>;
29
60
 
30
- class TargetBuilder<Deps extends readonly DependencyCls[], Params extends unknown[] = []> {
61
+ class TargetBuilder<Deps extends readonly DependencyCls[], Params extends unknown[] = [], Context = object> {
31
62
  readonly #args: (ArgMeta | InternalArgMeta)[];
32
63
 
33
64
  constructor(
@@ -37,12 +68,12 @@ class TargetBuilder<Deps extends readonly DependencyCls[], Params extends unknow
37
68
  this.#args = args;
38
69
  }
39
70
 
40
- arg<Type extends PrimitiveArgType, Option extends ArgsOptionInput = Record<string, never>>(
71
+ arg<Type extends PrimitiveArgType, Option extends ArgsOption<Context> = ArgsOption<Context>>(
41
72
  name: string,
42
73
  type: Type,
43
74
  argsOption: Option = {} as Option,
44
- ): TargetBuilder<Deps, AddArg<Params, Type, Option>> {
45
- return new TargetBuilder(this.targetOption, [
75
+ ): TargetBuilder<Deps, AddArg<Params, Type, Option>, Context> {
76
+ return new TargetBuilder<Deps, AddArg<Params, Type, Option>, Context>(this.targetOption, [
46
77
  ...this.#args,
47
78
  {
48
79
  name,
@@ -50,16 +81,16 @@ class TargetBuilder<Deps extends readonly DependencyCls[], Params extends unknow
50
81
  key: "",
51
82
  idx: this.#args.length,
52
83
  type: "Argument",
53
- },
84
+ } as ArgMeta<CommandContext>,
54
85
  ]);
55
86
  }
56
87
 
57
- option<Type extends PrimitiveArgType, Option extends ArgsOptionInput = Record<string, never>>(
88
+ option<Type extends PrimitiveArgType, Option extends ArgsOption<Context> = ArgsOption<Context>>(
58
89
  name: string,
59
90
  type: Type,
60
91
  argsOption: Option = {} as Option,
61
- ): TargetBuilder<Deps, AddArg<Params, Type, Option>> {
62
- return new TargetBuilder(this.targetOption, [
92
+ ): TargetBuilder<Deps, AddArg<Params, Type, Option>, Context> {
93
+ return new TargetBuilder<Deps, AddArg<Params, Type, Option>, Context>(this.targetOption, [
63
94
  ...this.#args,
64
95
  {
65
96
  name,
@@ -67,24 +98,27 @@ class TargetBuilder<Deps extends readonly DependencyCls[], Params extends unknow
67
98
  key: "",
68
99
  idx: this.#args.length,
69
100
  type: "Option",
70
- },
101
+ } as ArgMeta<CommandContext>,
71
102
  ]);
72
103
  }
73
104
 
74
- with<Tokens extends readonly InternalArgToken[]>(
105
+ with<const Tokens extends readonly InternalArgToken[]>(
75
106
  ...tokens: Tokens
76
- ): TargetBuilder<Deps, AddInternalArgs<Params, Tokens>> {
77
- return new TargetBuilder(this.targetOption, [
78
- ...this.#args,
79
- ...tokens.map(
80
- (token, offset) =>
81
- ({
82
- key: "",
83
- idx: this.#args.length + offset,
84
- type: token.type,
85
- }) satisfies InternalArgMeta,
86
- ),
87
- ]);
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
+ );
88
122
  }
89
123
 
90
124
  exec(handler: CommandHandler<Deps, Params>) {
@@ -101,12 +135,12 @@ type CommandBuilderContext<Deps extends readonly DependencyCls[]> = {
101
135
  public: (targetOption?: Omit<TargetOption, "type">) => TargetBuilder<Deps>;
102
136
  cloud: (targetOption?: Omit<TargetOption, "type">) => TargetBuilder<Deps>;
103
137
  dev: (targetOption?: Omit<TargetOption, "type">) => TargetBuilder<Deps>;
104
- arg: <Type extends PrimitiveArgType, Option extends ArgsOptionInput = Record<string, never>>(
138
+ arg: <Type extends PrimitiveArgType, Option extends ArgsOption<CommandContext> = ArgsOption<CommandContext>>(
105
139
  name: string,
106
140
  type: Type,
107
141
  argsOption?: Option,
108
142
  ) => ArgMeta;
109
- option: <Type extends PrimitiveArgType, Option extends ArgsOptionInput = Record<string, never>>(
143
+ option: <Type extends PrimitiveArgType, Option extends ArgsOption<CommandContext> = ArgsOption<CommandContext>>(
110
144
  name: string,
111
145
  type: Type,
112
146
  argsOption?: Option,
@@ -125,20 +159,22 @@ const createContext = <Deps extends readonly DependencyCls[]>(): CommandBuilderC
125
159
  public: createTarget<Deps>("public"),
126
160
  cloud: createTarget<Deps>("cloud"),
127
161
  dev: createTarget<Deps>("dev"),
128
- arg: (name, type, argsOption) => ({
129
- name,
130
- argsOption: { ...(argsOption ?? {}), type: normalizePrimitiveArgType(type) },
131
- key: "",
132
- idx: -1,
133
- type: "Argument",
134
- }),
135
- option: (name, type, argsOption) => ({
136
- name,
137
- argsOption: { ...(argsOption ?? {}), type: normalizePrimitiveArgType(type) },
138
- key: "",
139
- idx: -1,
140
- type: "Option",
141
- }),
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>,
142
178
  });
143
179
 
144
180
  const buildCommandMeta = (definitions: Record<string, TargetDefinition>) => {
@@ -1,11 +1,10 @@
1
1
  import chalk from "chalk";
2
2
 
3
- import { getArgMetas } from "./argMeta";
3
+ import { type EnumChoice, getArgMetas } from "./argMeta";
4
4
  import { type CommandCls, getTargetMetas } from "./targetMeta";
5
5
 
6
6
  const camelToKebabCase = (str: string) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
7
- const formatChoice = (choice: string | number | { label: string; value: string | number | boolean }) =>
8
- typeof choice === "object" ? choice.label : choice.toString();
7
+ const formatChoice = (choice: EnumChoice) => (typeof choice === "object" ? choice.label : choice.toString());
9
8
 
10
9
  const groupCommands = (commands: CommandCls[]) => {
11
10
  const groups = new Map<string, { name: string; commands: { key: string; args: string[]; desc?: string }[] }>();
@@ -207,7 +206,11 @@ export const formatCommandHelp = (command: CommandCls, key: string) => {
207
206
  const optName = `${flag}--${kebabName}`;
208
207
  const optDesc = opt.desc ?? "";
209
208
  const defaultVal = opt.default !== undefined ? chalk.gray(` [default: ${String(opt.default)}]`) : "";
210
- const choices = opt.enum ? chalk.gray(` (${opt.enum.map(formatChoice).join(", ")})`) : "";
209
+ const choices = opt.enum
210
+ ? chalk.gray(
211
+ typeof opt.enum === "function" ? " ([dynamic choices])" : ` (${opt.enum.map(formatChoice).join(", ")})`,
212
+ )
213
+ : "";
211
214
  lines.push(` ${chalk.green(optName)} ${chalk.gray(optDesc)}${defaultVal}${choices}`);
212
215
  }
213
216
  lines.push("");