@akanjs/devkit 3.0.0-beta.19 → 3.0.0-beta.20

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 (233) hide show
  1. package/abstractDoc.test.ts +6 -15
  2. package/abstractDoc.ts +1 -4
  3. package/agentsIndex.test.ts +4 -7
  4. package/agentsIndex.ts +6 -22
  5. package/akanApp/BackendImportGraph.test.ts +8 -22
  6. package/akanApp/BackendImportGraph.ts +1 -20
  7. package/akanApp/akanApp.host.test.ts +1 -3
  8. package/akanApp/akanApp.host.ts +64 -250
  9. package/akanApp/devHostPolicy.test.ts +10 -34
  10. package/akanApp/devHostPolicy.ts +12 -98
  11. package/akanConfig/akanConfig.test.ts +30 -39
  12. package/akanConfig/akanConfig.ts +40 -136
  13. package/akanContext.ts +106 -159
  14. package/akanMcpContract.ts +28 -73
  15. package/appSelectionMemory.ts +1 -4
  16. package/applicationBuildReporter.ts +37 -5
  17. package/applicationBuildRunner.ts +25 -59
  18. package/applicationReleasePackager.ts +24 -44
  19. package/applicationTestPreload.ts +3 -12
  20. package/artifact/implicitRootLayout.test.ts +3 -19
  21. package/artifact/implicitRootLayout.ts +34 -91
  22. package/artifact/routeSeedIndex.ts +11 -44
  23. package/biomeBase.ts +2 -5
  24. package/capacitorApp.test.ts +4 -14
  25. package/capacitorApp.ts +86 -235
  26. package/cloud/cloudApi.test.ts +202 -0
  27. package/cloud/cloudApi.ts +33 -57
  28. package/cloud/configLock.test.ts +64 -0
  29. package/cloud/configLock.ts +96 -0
  30. package/cloud/constants.ts +3 -2
  31. package/cloud/globalConfig.ts +64 -31
  32. package/codeAgent/agent/AkanCodeServices.ts +15 -57
  33. package/codeAgent/agent/AkanContextFiles.ts +5 -28
  34. package/codeAgent/agent/CodeAgent.ts +33 -108
  35. package/codeAgent/agent/CodeAgentAsks.ts +7 -30
  36. package/codeAgent/agent/CodeAgentEventMapper.ts +9 -52
  37. package/codeAgent/agent/CodeAgentGate.ts +8 -34
  38. package/codeAgent/agent/CodeAgentProxy.ts +4 -13
  39. package/codeAgent/agent/CodeAgentRpcHost.ts +3 -16
  40. package/codeAgent/agent/CodeAgentRpcListener.ts +2 -10
  41. package/codeAgent/agent/CodeAgentStreamPrinter.ts +4 -15
  42. package/codeAgent/agent/CodeAgentSuspended.ts +1 -8
  43. package/codeAgent/agent/CodeAgentUi.ts +2 -7
  44. package/codeAgent/agent/CodeMailbox.ts +7 -34
  45. package/codeAgent/agent/CodeSessionFork.ts +4 -19
  46. package/codeAgent/agent/CodeSessionIndex.ts +5 -31
  47. package/codeAgent/agent/SubagentPool.ts +48 -58
  48. package/codeAgent/agent/akanCodeModel.ts +5 -39
  49. package/codeAgent/agent/akanCodePaths.ts +4 -24
  50. package/codeAgent/agent/akanContextFiles.test.ts +0 -2
  51. package/codeAgent/agent/akanSystemPrompt.ts +1 -9
  52. package/codeAgent/agent/codeAgent.test.ts +178 -0
  53. package/codeAgent/agent/codeAgentContract.test.ts +5 -28
  54. package/codeAgent/agent/codeMailbox.test.ts +0 -4
  55. package/codeAgent/agent/codeSessionFork.test.ts +0 -2
  56. package/codeAgent/agent/codeSessionIndex.test.ts +9 -4
  57. package/codeAgent/agent/consoleToStderr.ts +2 -12
  58. package/codeAgent/agent/jsonLines.ts +29 -0
  59. package/codeAgent/agent/stringArg.ts +5 -0
  60. package/codeAgent/feedback/DevLogFeedback.ts +3 -18
  61. package/codeAgent/feedback/PreviewChrome.ts +5 -29
  62. package/codeAgent/feedback/PreviewFeedback.ts +2 -9
  63. package/codeAgent/feedback/PreviewView.ts +3 -15
  64. package/codeAgent/feedback/TurnFeedback.ts +2 -17
  65. package/codeAgent/feedback/VerifyFeedback.ts +1 -7
  66. package/codeAgent/feedback/previewChrome.test.ts +1 -1
  67. package/codeAgent/index.ts +2 -10
  68. package/codeAgent/skills/akan-code/SKILL.md +12 -4
  69. package/codeAgent/tools/AkanCli.ts +4 -16
  70. package/codeAgent/tools/AkanCodePlugins.ts +21 -43
  71. package/codeAgent/tools/AkanEditScope.ts +2 -12
  72. package/codeAgent/tools/AkanEnvKeys.ts +3 -11
  73. package/codeAgent/tools/AkanToolPack.ts +6 -28
  74. package/codeAgent/tools/AkanVerifier.ts +1 -8
  75. package/codeAgent/tools/AskToolPack.ts +3 -15
  76. package/codeAgent/tools/MailToolPack.ts +0 -9
  77. package/codeAgent/tools/McpClient.ts +3 -24
  78. package/codeAgent/tools/McpOAuth.ts +6 -41
  79. package/codeAgent/tools/McpOAuthLoopback.ts +2 -15
  80. package/codeAgent/tools/McpServerConfig.ts +7 -26
  81. package/codeAgent/tools/McpSignIn.ts +46 -49
  82. package/codeAgent/tools/McpTokenStore.ts +6 -17
  83. package/codeAgent/tools/McpToolPack.ts +107 -35
  84. package/codeAgent/tools/SessionToolPack.ts +1 -11
  85. package/codeAgent/tools/WebToolPack.ts +1 -7
  86. package/codeAgent/tools/akanCodePlugins.test.ts +231 -63
  87. package/codeAgent/tools/askToolPack.test.ts +0 -3
  88. package/codeAgent/tools/mcpOAuth.test.ts +315 -27
  89. package/codeAgent/tools/mcpServerConfig.test.ts +21 -14
  90. package/codegenLock.test.ts +4 -13
  91. package/codegenLock.ts +3 -17
  92. package/commandDecorators/argMeta.ts +1 -5
  93. package/commandDecorators/camelToKebabCase.ts +1 -0
  94. package/commandDecorators/command.ts +44 -102
  95. package/commandDecorators/commandBuilder.ts +30 -61
  96. package/commandDecorators/commandDecorators.test.ts +1 -9
  97. package/commandDecorators/dependencyBuilder.ts +10 -1
  98. package/commandDecorators/helpFormatter.ts +66 -144
  99. package/commandDecorators/index.ts +0 -2
  100. package/commandDecorators/targetMeta.ts +9 -7
  101. package/createTunnel.ts +7 -14
  102. package/dependencyScanner.test.ts +4 -16
  103. package/dependencyScanner.ts +27 -78
  104. package/devkitUtils.test.ts +47 -19
  105. package/executors.test.ts +114 -60
  106. package/executors.ts +165 -484
  107. package/fileEditor.ts +9 -37
  108. package/{getDirname.test.ts → fileSys.test.ts} +1 -1
  109. package/fileSys.ts +6 -8
  110. package/formSetterScanner.ts +3 -16
  111. package/frontendBuild/allRoutesBuilder.ts +7 -21
  112. package/frontendBuild/autoImportSync.test.ts +14 -38
  113. package/frontendBuild/autoImportSync.ts +15 -67
  114. package/frontendBuild/buildRouteClient.test.ts +10 -36
  115. package/frontendBuild/bundleDefine.ts +12 -0
  116. package/frontendBuild/clientBuildTypes.ts +5 -36
  117. package/frontendBuild/clientEntriesBundler.ts +9 -40
  118. package/frontendBuild/clientEntryDiscovery.ts +6 -41
  119. package/frontendBuild/csrArtifactBuilder.ts +6 -25
  120. package/frontendBuild/cssCandidateCache.test.ts +0 -6
  121. package/frontendBuild/cssCandidateCache.ts +6 -41
  122. package/frontendBuild/cssCompiler.ts +29 -78
  123. package/frontendBuild/cssImportResolver.ts +5 -18
  124. package/frontendBuild/devChangePlanner.ts +7 -10
  125. package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
  126. package/frontendBuild/devGeneratedIndexSync.ts +1 -4
  127. package/frontendBuild/fontOptimizer.test.ts +5 -13
  128. package/frontendBuild/fontOptimizer.ts +15 -72
  129. package/frontendBuild/fontPruner.test.ts +5 -10
  130. package/frontendBuild/fontPruner.ts +5 -20
  131. package/frontendBuild/frontendBuild.test.ts +27 -52
  132. package/frontendBuild/hmrChangeClassifier.ts +2 -2
  133. package/frontendBuild/hmrWatcher.test.ts +7 -31
  134. package/frontendBuild/hmrWatcher.ts +14 -71
  135. package/frontendBuild/pagesBundleBuilder.ts +4 -29
  136. package/frontendBuild/precompressArtifacts.ts +5 -18
  137. package/frontendBuild/routeClientBuilder.ts +7 -47
  138. package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
  139. package/frontendBuild/sourceMtimeIndex.test.ts +37 -94
  140. package/frontendBuild/sourceMtimeIndex.ts +19 -130
  141. package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
  142. package/frontendBuild/styleContract.ts +1 -8
  143. package/frontendBuild/themeValidator.ts +4 -26
  144. package/frontendBuild/watchRootResolver.test.ts +23 -42
  145. package/frontendBuild/watchRootResolver.ts +5 -27
  146. package/getRelatedCnsts.ts +12 -92
  147. package/incrementalBuilder/buildBatch.proc.ts +22 -57
  148. package/incrementalBuilder/buildBatchProtocol.ts +8 -36
  149. package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
  150. package/incrementalBuilder/buildBatchRunner.ts +10 -31
  151. package/incrementalBuilder/builderChannel.test.ts +24 -49
  152. package/incrementalBuilder/builderChannel.ts +6 -37
  153. package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
  154. package/incrementalBuilder/builderRequestRouter.ts +5 -24
  155. package/incrementalBuilder/devWatchBatch.test.ts +14 -18
  156. package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
  157. package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
  158. package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
  159. package/incrementalBuilder/incrementalBuilder.proc.ts +48 -161
  160. package/index.ts +3 -15
  161. package/integration/devResourceProbe.ts +50 -56
  162. package/integration/devStability.integration.test.ts +47 -194
  163. package/integration/devStabilityHarness.test.ts +9 -29
  164. package/integration/devStabilityHarness.ts +65 -330
  165. package/integration/ssrMemoryProbe.ts +41 -167
  166. package/libSource.test.ts +4 -15
  167. package/libSource.ts +19 -31
  168. package/lint/gritRules.test.ts +6 -23
  169. package/linter.test.ts +1 -2
  170. package/linter.ts +22 -115
  171. package/mobile/mobileTarget.ts +14 -13
  172. package/package.json +2 -2
  173. package/packageExports.test.ts +3 -18
  174. package/packageExportsMap.ts +3 -19
  175. package/piDependencyPins.test.ts +3 -14
  176. package/platformTest/IdleWatchedProcess.test.ts +5 -13
  177. package/platformTest/IdleWatchedProcess.ts +1 -5
  178. package/platformTest/LinuxDockerTarget.ts +20 -35
  179. package/platformTest/PlatformTestRun.test.ts +5 -9
  180. package/platformTest/PlatformTestTarget.ts +24 -2
  181. package/platformTest/TestSnapshot.test.ts +6 -14
  182. package/platformTest/TestSnapshot.ts +1 -5
  183. package/platformTest/WindowsSshTarget.ts +6 -22
  184. package/prompter.ts +3 -6
  185. package/qualityScanner.test.ts +6 -24
  186. package/qualityScanner.ts +196 -284
  187. package/recipeScanner.test.ts +7 -12
  188. package/recipeScanner.ts +9 -38
  189. package/repoIdentity.ts +2 -9
  190. package/routeSourceValidator.ts +7 -30
  191. package/scanInfo.ts +82 -214
  192. package/selectModel.ts +1 -2
  193. package/semver.ts +3 -5
  194. package/slicePlanner.test.ts +6 -24
  195. package/slicePlanner.ts +11 -32
  196. package/spinner.test.ts +3 -8
  197. package/spinner.ts +4 -16
  198. package/sshTunnel.ts +1 -6
  199. package/ssrScanner.test.ts +1 -6
  200. package/ssrScanner.ts +103 -113
  201. package/subspace.test.ts +6 -25
  202. package/subspace.ts +31 -109
  203. package/subspaceConfig.ts +3 -11
  204. package/testHelpers.ts +49 -34
  205. package/transforms/asyncDefaultExportDetector.ts +3 -11
  206. package/transforms/barrelAnalyzer.ts +43 -135
  207. package/transforms/barrelImportsPlugin.ts +38 -175
  208. package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
  209. package/transforms/externalizeFrameworkPlugin.ts +13 -87
  210. package/transforms/moduleSyntax.ts +32 -0
  211. package/transforms/rscUseClientTransform.ts +35 -20
  212. package/transforms/transforms.test.ts +153 -44
  213. package/transforms/tsconfigPackageResolver.test.ts +6 -24
  214. package/transforms/useClientBundlePlugin.ts +2 -22
  215. package/typeChecker.ts +7 -63
  216. package/uploadRelease.ts +10 -25
  217. package/workflow/artifacts.ts +18 -0
  218. package/workflow/executor.test.ts +1 -12
  219. package/workflow/executor.ts +85 -146
  220. package/workflow/index.ts +0 -1
  221. package/workflow/moduleIndex.test.ts +4 -15
  222. package/workflow/moduleIndex.ts +9 -49
  223. package/workflow/plan.ts +18 -25
  224. package/workflow/render.ts +80 -96
  225. package/workflow/source.ts +33 -56
  226. package/workflow/sourceAst.ts +50 -0
  227. package/workspaceLayout.ts +4 -19
  228. package/commandDecorators/commandMeta.ts +0 -7
  229. package/commandDecorators/types.ts +0 -10
  230. package/getDirname.ts +0 -4
  231. package/local/optimize-resource/ipcprobe/child.ts +0 -1
  232. package/local/optimize-resource/ipcprobe/parent.ts +0 -11
  233. package/workflow/primitive.ts +0 -59
@@ -10,11 +10,8 @@ import chalk from "chalk";
10
10
  import { type Command, program } from "commander";
11
11
  import { AppSelectionMemory } from "../appSelectionMemory";
12
12
  import { AppExecutor, Executor, LibExecutor, ModuleExecutor, PkgExecutor, WorkspaceExecutor } from "../executors";
13
- // Import the owning modules directly, never the root barrel: `..` re-exports all 41 devkit modules,
14
- // so a barrel import here drags ink, @trapezedev/project, ssh2 and the cloud stack into
15
- // every process that registers a command (measured: 236MB vs 3MB).
16
- import { FileSys } from "../fileSys";
17
- import { getDirname } from "../getDirname";
13
+ // Never the root barrel: it drags ink, @trapezedev/project, ssh2 and the cloud stack into every command process.
14
+ import { FileSys, getDirname } from "../fileSys";
18
15
  import type { PackageJson } from "../types";
19
16
  import {
20
17
  type ArgMeta,
@@ -24,11 +21,11 @@ import {
24
21
  getArgMetas,
25
22
  type InternalArgMeta,
26
23
  } from "./argMeta";
24
+ import { camelToKebabCase } from "./camelToKebabCase";
27
25
  import { CommandContainer } from "./dependencyBuilder";
28
26
  import { formatCommandHelp, formatHelp } from "./helpFormatter";
29
27
  import { type CommandCls, getTargetCommandNames, getTargetMetas } from "./targetMeta";
30
28
 
31
- const camelToKebabCase = (str: string) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
32
29
  const loggedCliErrorObjects = new WeakSet<object>();
33
30
  const loggedCliErrorMessages = new Set<string>();
34
31
 
@@ -97,30 +94,16 @@ const normalizeEnumChoices = (enumChoices: EnumChoices) =>
97
94
  : { value: choice, name: choice.toString() },
98
95
  );
99
96
 
100
- /**
101
- * The interactive prompt stack, loaded on the first prompt instead of at import.
102
- *
103
- * `runCommands` lives in this module, so a static import put `@inquirer/prompts` (~24MB) in the CLI
104
- * entry's chunk closure — and `akan start` holds that process for the whole dev session while never
105
- * asking a question, because every argument it needs is already on the command line.
106
- *
107
- * The wrappers are typed from the real prompts so no call site changes, and `import type` leaves no
108
- * runtime edge for the bundler to follow (`entryModuleGraph.test.ts` asserts that).
109
- */
97
+ // Loaded on the first prompt: a static import keeps @inquirer/prompts (~24MB) resident for a whole `akan start`.
98
+ // The types come through `import type`, which leaves no runtime edge (entryModuleGraph.test.ts asserts that).
110
99
  const prompts = async () => await import("@inquirer/prompts");
111
100
  const select = ((config, context) => prompts().then((m) => m.select(config, context))) as typeof inquirerSelect;
112
101
  const confirm = ((config, context) => prompts().then((m) => m.confirm(config, context))) as typeof inquirerConfirm;
113
102
  const input = ((config, context) => prompts().then((m) => m.input(config, context))) as typeof inquirerInput;
114
103
  const checkbox = ((config, context) => prompts().then((m) => m.checkbox(config, context))) as typeof inquirerCheckbox;
115
104
 
116
- /**
117
- * Rejects a value that is not one of the declared choices, in commander's own wording.
118
- *
119
- * Only a static choice list can be checked: a `DynamicEnum` resolves against the command context, which
120
- * is not populated until the internal args are resolved, and it is the interactive `select` that consumes
121
- * it. Comparison is stringly on purpose — the value still carries commander's raw string here, while a
122
- * numeric choice list holds numbers.
123
- */
105
+ // A DynamicEnum is not checked: it resolves against a context the internal args have not populated yet.
106
+ // Compared as strings: the value is still commander's raw string while a numeric choice list holds numbers.
124
107
  const assertEnumChoice = (argMeta: ArgMeta, value: unknown) => {
125
108
  const enumChoices = argMeta.argsOption.enum;
126
109
  if (!enumChoices || typeof enumChoices === "function") return;
@@ -133,17 +116,13 @@ const assertEnumChoice = (argMeta: ArgMeta, value: unknown) => {
133
116
  );
134
117
  };
135
118
 
136
- const resolveEnumChoices = async (argMeta: ArgMeta, context: CommandContext) => {
137
- const enumChoices = argMeta.argsOption.enum;
138
- if (!enumChoices) return null;
139
- if (typeof enumChoices === "function") return await enumChoices(context);
140
- return enumChoices;
141
- };
119
+ const inputMessageOf = ({ name, argsOption: { desc, example, ask } }: ArgMeta) =>
120
+ ask ? `${ask}: ` : desc ? `${desc}: ` : `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
142
121
 
143
122
  export const getOptionValue = async (argMeta: ArgMeta, opt: Record<string, unknown>, context: CommandContext) => {
144
123
  const {
145
124
  name,
146
- argsOption: { enum: enumChoices, default: defaultValue, type, desc, nullable, example, ask },
125
+ argsOption: { enum: enumChoices, default: defaultValue, type, desc, nullable, ask },
147
126
  } = argMeta;
148
127
  if (opt[argMeta.name] !== undefined) {
149
128
  assertEnumChoice(argMeta, opt[argMeta.name]);
@@ -151,42 +130,26 @@ export const getOptionValue = async (argMeta: ArgMeta, opt: Record<string, unkno
151
130
  } else if (defaultValue !== undefined) return defaultValue;
152
131
 
153
132
  if (enumChoices) {
154
- const choices = normalizeEnumChoices((await resolveEnumChoices(argMeta, context)) ?? []);
133
+ const choices = normalizeEnumChoices(
134
+ (typeof enumChoices === "function" ? await enumChoices(context) : enumChoices) ?? [],
135
+ );
155
136
  if (choices.length === 1) return choices[0]?.value;
156
- const choice = await select({ message: ask ?? desc ?? `Select the ${name} value`, choices });
157
- return choice;
137
+ return await select({ message: ask ?? desc ?? `Select the ${name} value`, choices });
158
138
  } else if (nullable) return null;
159
139
  else if (type === "boolean") {
160
140
  const message = ask ?? desc ?? `Do you want to set ${name}? ${desc ? ` (${desc})` : ""}: `;
161
141
  return await confirm({ message });
162
- } else {
163
- const message = ask
164
- ? `${ask}: `
165
- : desc
166
- ? `${desc}: `
167
- : `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
168
- if (argMeta.argsOption.nullable) return await input({ message });
169
- else return convertArgValue(await input({ message }), type ?? "string");
170
- }
142
+ } else return convertArgValue(await input({ message: inputMessageOf(argMeta) }), type ?? "string");
171
143
  };
172
144
 
173
145
  export const getArgumentValue = async (argMeta: ArgMeta, value: string | undefined) => {
174
- const {
175
- name,
176
- argsOption: { default: defaultValue, type, desc, nullable, example, ask },
177
- } = argMeta;
146
+ const { default: defaultValue, type, nullable } = argMeta.argsOption;
178
147
  if (value !== undefined) {
179
148
  assertEnumChoice(argMeta, value);
180
149
  return convertArgValue(value, type ?? "string");
181
150
  } else if (defaultValue !== undefined) return defaultValue;
182
151
  else if (nullable) return null;
183
-
184
- const message = ask
185
- ? `${ask}: `
186
- : desc
187
- ? `${desc}: `
188
- : `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
189
- return convertArgValue(await input({ message }), type ?? "string");
152
+ return convertArgValue(await input({ message: inputMessageOf(argMeta) }), type ?? "string");
190
153
  };
191
154
 
192
155
  const assignCommandContext = (context: CommandContext, argMeta: ArgMeta | InternalArgMeta, value: unknown) => {
@@ -217,18 +180,13 @@ const assertCurrentDirectoryIsWorkspaceRoot = async () => {
217
180
  );
218
181
  };
219
182
 
220
- /** A variadic positional arrives as an array, and each entry may itself be a comma-separated list. */
221
183
  const parseAppNameList = (value: string | string[] | undefined): string[] =>
222
184
  (Array.isArray(value) ? value : value === undefined ? [] : [value])
223
185
  .flatMap((entry) => entry.split(","))
224
186
  .map((entry) => entry.trim())
225
187
  .filter(Boolean);
226
188
 
227
- /**
228
- * The `Apps` token: named apps, `all`, or a checkbox when the command line names none. Kept out of
229
- * `getInternalArgumentValue` because it is the only internal arg whose positional is variadic, so it is
230
- * the only one whose raw value is an array.
231
- */
189
+ // Kept out of getInternalArgumentValue: Apps is the only internal arg whose positional is variadic (an array).
232
190
  export const getAppsArgumentValue = async (
233
191
  value: string | string[] | undefined,
234
192
  workspace: WorkspaceExecutor,
@@ -265,32 +223,23 @@ export const getInternalArgumentValue = async (
265
223
  if (argMeta.type === "Workspace") return workspace;
266
224
  const sysType = argMeta.type.toLowerCase();
267
225
  const [appNames, libNames, pkgNames] = await workspace.getExecs();
268
- if (sysType === "sys") {
269
- if (value && appNames.includes(value)) return AppExecutor.from(workspace, value);
270
- else if (value && libNames.includes(value)) return LibExecutor.from(workspace, value);
271
- else {
272
- const sysName = await select<string>({
273
- message: `Select the App or Lib name`,
274
- choices: [...appNames, ...libNames],
275
- });
276
- if (appNames.includes(sysName)) return AppExecutor.from(workspace, sysName);
277
- else if (libNames.includes(sysName)) return LibExecutor.from(workspace, sysName);
278
- else throw new Error(`Invalid system name: ${sysName}`);
279
- }
280
- } else if (sysType === "exec") {
281
- if (value && appNames.includes(value)) return AppExecutor.from(workspace, value);
282
- else if (value && libNames.includes(value)) return LibExecutor.from(workspace, value);
283
- else if (value && pkgNames.includes(value)) return PkgExecutor.from(workspace, value);
284
- else {
285
- const execName = await select<string>({
286
- message: `Select the App or Lib or Pkg name`,
287
- choices: [...appNames, ...libNames, ...pkgNames],
288
- });
289
- if (appNames.includes(execName)) return AppExecutor.from(workspace, execName);
290
- else if (libNames.includes(execName)) return LibExecutor.from(workspace, execName);
291
- else if (pkgNames.includes(execName)) return PkgExecutor.from(workspace, execName);
292
- else throw new Error(`Invalid system name: ${execName}`);
293
- }
226
+ if (sysType === "sys" || sysType === "exec") {
227
+ const pkgChoices = sysType === "exec" ? pkgNames : [];
228
+ const execOf = (name: string) => {
229
+ if (appNames.includes(name)) return AppExecutor.from(workspace, name);
230
+ if (libNames.includes(name)) return LibExecutor.from(workspace, name);
231
+ if (pkgChoices.includes(name)) return PkgExecutor.from(workspace, name);
232
+ return null;
233
+ };
234
+ const found = value ? execOf(value) : null;
235
+ if (found) return found;
236
+ const name = await select<string>({
237
+ message: sysType === "exec" ? `Select the App or Lib or Pkg name` : `Select the App or Lib name`,
238
+ choices: [...appNames, ...libNames, ...pkgChoices],
239
+ });
240
+ const picked = execOf(name);
241
+ if (!picked) throw new Error(`Invalid system name: ${name}`);
242
+ return picked;
294
243
  } else if (sysType === "app") {
295
244
  if (value && appNames.includes(value)) return AppExecutor.from(workspace, value);
296
245
  if (!value && appNames.length === 1 && appNames[0]) return AppExecutor.from(workspace, appNames[0]);
@@ -355,17 +304,10 @@ export const runCommands = async (...commands: CommandCls[]) => {
355
304
  }
356
305
  process.env.AKAN_VERSION = cliPackageJson?.version ?? "0.0.1";
357
306
 
358
- // Custom help handling
359
307
  const hasHelpFlag = process.argv.includes("--help") || process.argv.includes("-h");
360
- const hasCommand = process.argv.length > 2 && !process.argv[2]?.startsWith("-");
361
-
362
- // Show help if: 1) explicit --help flag, or 2) no command provided (just "akan")
363
- if (hasHelpFlag || !hasCommand) {
364
- if (process.argv.length === 2 || (process.argv.length === 3 && hasHelpFlag)) {
365
- // Global help (no specific command)
366
- Logger.rawLog(formatHelp(commands, process.env.AKAN_VERSION));
367
- process.exit(0);
368
- }
308
+ if (process.argv.length === 2 || (process.argv.length === 3 && hasHelpFlag)) {
309
+ Logger.rawLog(formatHelp(commands, process.env.AKAN_VERSION));
310
+ process.exit(0);
369
311
  }
370
312
 
371
313
  program.version(process.env.AKAN_VERSION).description("Akan CLI").configureHelp({
@@ -416,8 +358,6 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`,
416
358
  }
417
359
  }
418
360
  programCommand = programCommand.option(`-v, --verbose [boolean]`, `verbose output`);
419
-
420
- // Override help completely for each command
421
361
  programCommand.helpInformation = () => {
422
362
  return formatCommandHelp(command, targetMeta.key);
423
363
  };
@@ -446,11 +386,9 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`,
446
386
  cmdArgs[argMeta.idx] as string,
447
387
  workspace,
448
388
  );
449
- // set app name to env
450
389
  if (commandArgs[argMeta.idx] instanceof AppExecutor)
451
390
  process.env.AKAN_PUBLIC_APP_NAME = (commandArgs[argMeta.idx] as AppExecutor).name;
452
- //? Only when exactly one app resolved. Publishing a name while several are in play would make
453
- //? every env-derived answer in this process belong to whichever app happened to be last.
391
+ //? Only for a single app: with several, every env-derived answer would belong to whichever came last.
454
392
  else if (Array.isArray(commandArgs[argMeta.idx])) {
455
393
  const apps = commandArgs[argMeta.idx] as AppExecutor[];
456
394
  if (apps.length === 1 && apps[0]) process.env.AKAN_PUBLIC_APP_NAME = apps[0].name;
@@ -471,5 +409,9 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`,
471
409
  }
472
410
  }
473
411
  }
474
- await program.parseAsync(process.argv);
412
+ // Handled here, not by the handler above: a rejection reaching the entry's top-level await prints Bun's own trace.
413
+ await program.parseAsync(process.argv).catch((error: unknown) => {
414
+ printCliError(error);
415
+ process.exit(1);
416
+ });
475
417
  };
@@ -2,6 +2,7 @@ import type {
2
2
  App,
3
3
  ArgMeta,
4
4
  ArgsOption,
5
+ ArgType,
5
6
  CommandContext,
6
7
  Exec,
7
8
  InternalArgMeta,
@@ -13,9 +14,14 @@ import type {
13
14
  Sys,
14
15
  } from "./argMeta";
15
16
  import { normalizePrimitiveArgType } from "./argMeta";
16
- import { assertUniqueDependencies, type DependencyInstanceMap, injectDependencies } from "./dependencyBuilder";
17
+ import {
18
+ assertUniqueDependencies,
19
+ type DependencyCls,
20
+ type DependencyInstanceMap,
21
+ type DependencyKey,
22
+ injectDependencies,
23
+ } from "./dependencyBuilder";
17
24
  import { COMMAND_META, type CommandCls, type TargetMeta, type TargetOption } from "./targetMeta";
18
- import type { DependencyCls, DependencyKey } from "./types";
19
25
 
20
26
  type PrimitiveValue<T extends PrimitiveArgType> = T extends StringConstructor
21
27
  ? string
@@ -23,11 +29,7 @@ type PrimitiveValue<T extends PrimitiveArgType> = T extends StringConstructor
23
29
  ? number
24
30
  : boolean;
25
31
  type MaybeNullable<Value, Option> = Option extends { nullable: true } ? Value | null : Value;
26
- /**
27
- * The literal union a static `enum` declares, or `never` for a `DynamicEnum` — a function does not extend
28
- * a readonly array, so a runtime-resolved choice list falls back to the primitive type. `{ label, value }`
29
- * choices contribute their `value`.
30
- */
32
+ // A DynamicEnum yields never (a function is no readonly array), so ArgValue falls back to the primitive type.
31
33
  type EnumValue<Option> = Option extends { enum: infer Choices }
32
34
  ? Choices extends readonly (infer Choice)[]
33
35
  ? Choice extends { value: infer Value }
@@ -73,6 +75,14 @@ type CommandHandler<Deps extends readonly DependencyCls[], Params extends unknow
73
75
  ...args: Params
74
76
  ) => unknown | Promise<unknown>;
75
77
 
78
+ const argMetaOf = (
79
+ type: ArgType,
80
+ name: string,
81
+ primitive: PrimitiveArgType,
82
+ argsOption: object | undefined,
83
+ idx: number,
84
+ ): ArgMeta => ({ name, argsOption: { ...argsOption, type: normalizePrimitiveArgType(primitive) }, key: "", idx, type });
85
+
76
86
  class TargetBuilder<Deps extends readonly DependencyCls[], Params extends unknown[] = [], Context = object> {
77
87
  readonly #args: (ArgMeta | InternalArgMeta)[];
78
88
 
@@ -83,56 +93,29 @@ class TargetBuilder<Deps extends readonly DependencyCls[], Params extends unknow
83
93
  this.#args = args;
84
94
  }
85
95
 
96
+ #add<NextParams extends unknown[], NextContext = Context>(...argMetas: (ArgMeta | InternalArgMeta)[]) {
97
+ return new TargetBuilder<Deps, NextParams, NextContext>(this.targetOption, [...this.#args, ...argMetas]);
98
+ }
99
+
86
100
  arg<Type extends PrimitiveArgType, const Option extends ArgsOption<Context> = ArgsOption<Context>>(
87
101
  name: string,
88
102
  type: Type,
89
103
  argsOption: Option = {} as Option,
90
- ): TargetBuilder<Deps, AddArg<Params, Type, Option>, Context> {
91
- return new TargetBuilder<Deps, AddArg<Params, Type, Option>, Context>(this.targetOption, [
92
- ...this.#args,
93
- {
94
- name,
95
- argsOption: { ...argsOption, type: normalizePrimitiveArgType(type) },
96
- key: "",
97
- idx: this.#args.length,
98
- type: "Argument",
99
- } as ArgMeta<CommandContext>,
100
- ]);
104
+ ) {
105
+ return this.#add<AddArg<Params, Type, Option>>(argMetaOf("Argument", name, type, argsOption, this.#args.length));
101
106
  }
102
107
 
103
108
  option<Type extends PrimitiveArgType, const Option extends ArgsOption<Context> = ArgsOption<Context>>(
104
109
  name: string,
105
110
  type: Type,
106
111
  argsOption: Option = {} as Option,
107
- ): TargetBuilder<Deps, AddArg<Params, Type, Option>, Context> {
108
- return new TargetBuilder<Deps, AddArg<Params, Type, Option>, Context>(this.targetOption, [
109
- ...this.#args,
110
- {
111
- name,
112
- argsOption: { ...argsOption, type: normalizePrimitiveArgType(type) },
113
- key: "",
114
- idx: this.#args.length,
115
- type: "Option",
116
- } as ArgMeta<CommandContext>,
117
- ]);
112
+ ) {
113
+ return this.#add<AddArg<Params, Type, Option>>(argMetaOf("Option", name, type, argsOption, this.#args.length));
118
114
  }
119
115
 
120
- with<const Tokens extends readonly InternalArgToken[]>(
121
- ...tokens: Tokens
122
- ): TargetBuilder<Deps, AddInternalArgs<Params, Tokens>, AddInternalContext<Context, Tokens>> {
123
- return new TargetBuilder<Deps, AddInternalArgs<Params, Tokens>, AddInternalContext<Context, Tokens>>(
124
- this.targetOption,
125
- [
126
- ...this.#args,
127
- ...tokens.map(
128
- (token, offset) =>
129
- ({
130
- key: "",
131
- idx: this.#args.length + offset,
132
- type: token.type,
133
- }) satisfies InternalArgMeta,
134
- ),
135
- ],
116
+ with<const Tokens extends readonly InternalArgToken[]>(...tokens: Tokens) {
117
+ return this.#add<AddInternalArgs<Params, Tokens>, AddInternalContext<Context, Tokens>>(
118
+ ...tokens.map((token, offset) => ({ key: "", idx: this.#args.length + offset, type: token.type })),
136
119
  );
137
120
  }
138
121
 
@@ -174,22 +157,8 @@ const createContext = <Deps extends readonly DependencyCls[]>(): CommandBuilderC
174
157
  public: createTarget<Deps>("public"),
175
158
  cloud: createTarget<Deps>("cloud"),
176
159
  dev: createTarget<Deps>("dev"),
177
- arg: (name, type, argsOption) =>
178
- ({
179
- name,
180
- argsOption: { ...(argsOption ?? {}), type: normalizePrimitiveArgType(type) },
181
- key: "",
182
- idx: -1,
183
- type: "Argument",
184
- }) as ArgMeta<CommandContext>,
185
- option: (name, type, argsOption) =>
186
- ({
187
- name,
188
- argsOption: { ...(argsOption ?? {}), type: normalizePrimitiveArgType(type) },
189
- key: "",
190
- idx: -1,
191
- type: "Option",
192
- }) as ArgMeta<CommandContext>,
160
+ arg: (name, type, argsOption) => argMetaOf("Argument", name, type, argsOption, -1),
161
+ option: (name, type, argsOption) => argMetaOf("Option", name, type, argsOption, -1),
193
162
  });
194
163
 
195
164
  const buildCommandMeta = (definitions: Record<string, TargetDefinition>) => {
@@ -243,8 +243,6 @@ describe("enum arg choices", () => {
243
243
  expect(await getOptionValue(optionMeta(["text", "json"]), { format: "json" }, context)).toBe("json");
244
244
  });
245
245
 
246
- // Before this check `akan quality --format yaml` reached the script as "yaml" while the declaration
247
- // claimed the parameter was "text" | "json".
248
246
  test("rejects an undeclared value in commander's wording", async () => {
249
247
  await expect(getOptionValue(optionMeta(["text", "json"]), { format: "yaml" }, context)).rejects.toThrow(
250
248
  "option '--format' argument 'yaml' is invalid. Allowed choices are text, json.",
@@ -269,8 +267,6 @@ describe("enum arg choices", () => {
269
267
  await expect(getOptionValue(labelled, { format: "JSON output" }, context)).rejects.toThrow("is invalid");
270
268
  });
271
269
 
272
- // A DynamicEnum resolves against a context that is not populated yet, and the interactive select is its
273
- // only consumer, so an explicit value passes through unchecked rather than being rejected wrongly.
274
270
  test("leaves a dynamic choice list unchecked", async () => {
275
271
  expect(
276
272
  await getOptionValue(
@@ -291,11 +287,7 @@ describe("enum arg type inference", () => {
291
287
  CommandContainer.clear();
292
288
  });
293
289
 
294
- /**
295
- * Compile-time only. Nothing here can fail at runtime: if `enum` stops narrowing, the parameter widens
296
- * to the primitive and these calls stop typechecking, so the regression surfaces in `akan typecheck`
297
- * rather than in fifteen `as "a" | "b"` casts creeping back into the command files.
298
- */
290
+ //? Compile-time only: if `enum` stops narrowing, the parameter widens and these calls stop typechecking.
299
291
  const expectLiteral = <Expected>(_value: Expected) => undefined;
300
292
 
301
293
  test("a static enum narrows the exec parameter to its declared choices", () => {
@@ -1,4 +1,13 @@
1
- import type { DependencyCls, DependencyKey, DependencyKind } from "./types";
1
+ export type Cls<T = unknown> = new (...args: unknown[]) => T;
2
+
3
+ export type DependencyKind = "command" | "script" | "runner";
4
+ export type DependencyKey<RefName extends string, Kind extends DependencyKind> = `${RefName}${Capitalize<Kind>}`;
5
+
6
+ export type DependencyCls<T = unknown, Key extends string = string> = (new () => T) & {
7
+ readonly dependencyKey: Key;
8
+ readonly dependencyKind: DependencyKind;
9
+ readonly refName: string;
10
+ };
2
11
 
3
12
  type UnionToIntersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (
4
13
  value: infer Intersection,