@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
@@ -1,191 +1,119 @@
1
1
  import chalk from "chalk";
2
2
 
3
3
  import { type EnumChoice, getArgMetas } from "./argMeta";
4
+ import { camelToKebabCase } from "./camelToKebabCase";
4
5
  import { type CommandCls, getTargetMetas } from "./targetMeta";
5
6
 
6
- const camelToKebabCase = (str: string) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
7
7
  const formatChoice = (choice: EnumChoice) => (typeof choice === "object" ? choice.label : choice.toString());
8
8
 
9
9
  const groupCommands = (commands: CommandCls[]) => {
10
- const groups = new Map<string, { name: string; commands: { key: string; args: string[]; desc?: string }[] }>();
11
-
10
+ const groups = new Map<string, { key: string; args: string[]; desc?: string }[]>();
12
11
  for (const command of commands) {
13
- const className = command.name.replace("Command", "");
14
- const groupName = className;
15
-
16
- if (!groups.has(groupName)) {
17
- groups.set(groupName, { name: groupName, commands: [] });
18
- }
19
-
20
- const targetMetas = getTargetMetas(command);
21
- for (const targetMeta of targetMetas) {
22
- // Skip devOnly commands in help
12
+ const group = groups.getOrInsert(command.name.replace("Command", ""), []);
13
+ for (const targetMeta of getTargetMetas(command)) {
23
14
  if (targetMeta.targetOption.devOnly) continue;
24
-
25
15
  const [allArgMetas] = getArgMetas(command, targetMeta.key);
26
16
  const args = allArgMetas
27
17
  .filter((arg) => arg.type !== "Option")
28
18
  .map((arg) => {
29
19
  if (arg.type === "Workspace") return "";
30
20
  if (arg.type === "Module") return "[sys:module]";
31
- if (arg.type === "Argument") {
32
- return `[${arg.name}]`;
33
- }
21
+ if (arg.type === "Argument") return `[${arg.name}]`;
34
22
  return `[${arg.type.toLowerCase()}]`;
35
23
  })
36
24
  .filter(Boolean);
37
-
38
- const group = groups.get(groupName);
39
- if (group) {
40
- group.commands.push({
41
- key: camelToKebabCase(targetMeta.key),
42
- args,
43
- desc: targetMeta.targetOption.desc,
44
- });
45
- }
25
+ group.push({ key: camelToKebabCase(targetMeta.key), args, desc: targetMeta.targetOption.desc });
46
26
  }
47
27
  }
48
-
49
28
  return groups;
50
29
  };
51
30
 
52
31
  export const formatHelp = (commands: CommandCls[], version: string) => {
53
- const groups = groupCommands(commands);
54
- const lines: string[] = [];
55
-
56
- // Header
57
- lines.push("");
58
- lines.push(chalk.bold.cyan(" ╔═══════════════════════════════════════════════════╗"));
59
- lines.push(
32
+ const lines = [
33
+ "",
34
+ chalk.bold.cyan(" ╔═══════════════════════════════════════════════════╗"),
60
35
  chalk.bold.cyan(" ║") +
61
36
  chalk.bold.white(" Akan.js Framework CLI ") +
62
37
  chalk.bold.cyan(" ║"),
63
- );
64
- lines.push(chalk.bold.cyan(" ╚═══════════════════════════════════════════════════╝"));
65
- lines.push("");
66
- lines.push(chalk.gray(` Version: ${version}`));
67
- lines.push("");
68
-
69
- // Usage
70
- lines.push(chalk.bold.yellow(" USAGE"));
71
- lines.push("");
72
- lines.push(chalk.gray(" $ ") + chalk.white("akan") + chalk.gray(" <command> [options]"));
73
- lines.push("");
74
-
75
- // Commands by category
76
- lines.push(chalk.bold.yellow(" COMMANDS"));
77
- lines.push("");
78
-
79
- for (const [groupName, group] of groups) {
80
- // Skip empty groups (all commands are devOnly)
81
- if (group.commands.length === 0) continue;
82
-
83
- lines.push(chalk.bold.magenta(` ${groupName}`));
84
- lines.push("");
85
-
86
- for (const cmd of group.commands) {
87
- const cmdName = chalk.green(cmd.key);
88
- const cmdArgs = cmd.args.length > 0 ? chalk.gray(` ${cmd.args.join(" ")}`) : "";
89
-
90
- // Format description: wrap if too long
91
- if (cmd.desc) {
92
- const maxLineLength = 70;
93
- const cmdPrefix = ` ${cmdName}${cmdArgs}`;
94
- const indent = " ";
95
-
96
- if (cmdPrefix.length + cmd.desc.length + 3 < maxLineLength) {
97
- // Single line
98
- lines.push(`${cmdPrefix} ${chalk.gray(cmd.desc)}`);
99
- } else {
100
- // Multi-line
101
- lines.push(cmdPrefix);
102
- lines.push(`${indent}${chalk.gray(cmd.desc)}`);
103
- }
104
- } else {
105
- lines.push(` ${cmdName}${cmdArgs}`);
106
- }
38
+ chalk.bold.cyan(" ╚═══════════════════════════════════════════════════╝"),
39
+ "",
40
+ chalk.gray(` Version: ${version}`),
41
+ "",
42
+ chalk.bold.yellow(" USAGE"),
43
+ "",
44
+ chalk.gray(" $ ") + chalk.white("akan") + chalk.gray(" <command> [options]"),
45
+ "",
46
+ chalk.bold.yellow(" COMMANDS"),
47
+ "",
48
+ ];
49
+ for (const [groupName, cmds] of groupCommands(commands)) {
50
+ if (cmds.length === 0) continue;
51
+ lines.push(chalk.bold.magenta(` ${groupName}`), "");
52
+ for (const cmd of cmds) {
53
+ const cmdPrefix = ` ${chalk.green(cmd.key)}${cmd.args.length > 0 ? chalk.gray(` ${cmd.args.join(" ")}`) : ""}`;
54
+ if (!cmd.desc) lines.push(cmdPrefix);
55
+ else if (cmdPrefix.length + cmd.desc.length + 3 < 70) lines.push(`${cmdPrefix} ${chalk.gray(cmd.desc)}`);
56
+ else lines.push(cmdPrefix, ` ${chalk.gray(cmd.desc)}`);
107
57
  }
108
58
  lines.push("");
109
59
  }
110
-
111
- // Global Options
112
- lines.push(chalk.bold.yellow(" OPTIONS"));
113
- lines.push("");
114
- lines.push(` ${chalk.green("-v, --verbose")} ${chalk.gray("Enable verbose output")}`);
115
- lines.push(` ${chalk.green("-h, --help")} ${chalk.gray("Display this help message")}`);
116
- lines.push(` ${chalk.green("-V, --version")} ${chalk.gray("Output version number")}`);
117
- lines.push("");
118
-
119
- // Examples
120
- lines.push(chalk.bold.yellow(" EXAMPLES"));
121
- lines.push("");
122
- lines.push(chalk.gray(" # Create a new workspace"));
123
- lines.push(chalk.white(" $ akan create-workspace myproject"));
124
- lines.push("");
125
- lines.push(chalk.gray(" # Start development server"));
126
- lines.push(chalk.white(" $ akan start myapp"));
127
- lines.push("");
128
- lines.push(chalk.gray(" # Create a new module"));
129
- lines.push(chalk.white(" $ akan create-module userProfile"));
130
- lines.push("");
131
-
132
- // Footer
133
- lines.push(chalk.gray(" Documentation: ") + chalk.cyan("https://akanjs.com/docs"));
134
- lines.push(chalk.gray(" Report issues: ") + chalk.cyan("https://github.com/akan-team/akanjs/issues"));
135
- lines.push("");
136
-
60
+ lines.push(
61
+ chalk.bold.yellow(" OPTIONS"),
62
+ "",
63
+ ` ${chalk.green("-v, --verbose")} ${chalk.gray("Enable verbose output")}`,
64
+ ` ${chalk.green("-h, --help")} ${chalk.gray("Display this help message")}`,
65
+ ` ${chalk.green("-V, --version")} ${chalk.gray("Output version number")}`,
66
+ "",
67
+ chalk.bold.yellow(" EXAMPLES"),
68
+ "",
69
+ chalk.gray(" # Create a new workspace"),
70
+ chalk.white(" $ akan create-workspace myproject"),
71
+ "",
72
+ chalk.gray(" # Start development server"),
73
+ chalk.white(" $ akan start myapp"),
74
+ "",
75
+ chalk.gray(" # Create a new module"),
76
+ chalk.white(" $ akan create-module userProfile"),
77
+ "",
78
+ chalk.gray(" Documentation: ") + chalk.cyan("https://akanjs.com/docs"),
79
+ chalk.gray(" Report issues: ") + chalk.cyan("https://github.com/akan-team/akanjs/issues"),
80
+ "",
81
+ );
137
82
  return lines.join("\n");
138
83
  };
139
84
 
140
85
  export const formatCommandHelp = (command: CommandCls, key: string) => {
141
- const [allArgMetas, argMetas] = getArgMetas(command, key);
86
+ const [allArgMetas, optionArgs] = getArgMetas(command, key);
142
87
  const kebabKey = camelToKebabCase(key);
143
- const lines: string[] = [];
144
-
145
- const targetMetas = getTargetMetas(command);
146
- const targetMeta = targetMetas.find((t) => t.key === key);
147
- const commandDesc = targetMeta?.targetOption.desc;
148
-
149
- lines.push("");
150
- lines.push(chalk.bold.cyan(` Command: ${kebabKey}`));
151
- if (commandDesc) {
152
- lines.push(chalk.gray(` ${commandDesc}`));
153
- }
154
- lines.push("");
155
-
156
- // Usage
157
- const args = allArgMetas
158
- .filter((arg) => arg.type !== "Option")
88
+ const commandDesc = getTargetMetas(command).find((t) => t.key === key)?.targetOption.desc;
89
+ const nonOptionArgs = allArgMetas.filter((arg) => arg.type !== "Option");
90
+ const args = nonOptionArgs
159
91
  .map((arg) => {
160
92
  if (arg.type === "Workspace") return "";
161
93
  if (arg.type === "Module") return "[sys:module]";
162
94
  if (arg.type === "Apps") return "[apps...]";
163
- if (arg.type === "Argument") {
164
- return `[${camelToKebabCase(arg.name)}]`;
165
- }
95
+ if (arg.type === "Argument") return `[${camelToKebabCase(arg.name)}]`;
166
96
  return `[${arg.type.toLowerCase()}]`;
167
97
  })
168
98
  .filter(Boolean)
169
99
  .join(" ");
170
100
 
171
- lines.push(chalk.bold.yellow(" USAGE"));
172
- lines.push("");
173
- lines.push(chalk.gray(" $ ") + chalk.white(`akan ${kebabKey}`) + (args ? chalk.gray(` ${args}`) : ""));
174
- lines.push("");
175
-
176
- // Arguments
177
- const nonOptionArgs = allArgMetas.filter((arg) => arg.type !== "Option");
101
+ const lines = ["", chalk.bold.cyan(` Command: ${kebabKey}`)];
102
+ if (commandDesc) lines.push(chalk.gray(` ${commandDesc}`));
103
+ lines.push(
104
+ "",
105
+ chalk.bold.yellow(" USAGE"),
106
+ "",
107
+ chalk.gray(" $ ") + chalk.white(`akan ${kebabKey}`) + (args ? chalk.gray(` ${args}`) : ""),
108
+ "",
109
+ );
178
110
  if (nonOptionArgs.length > 0) {
179
- lines.push(chalk.bold.yellow(" ARGUMENTS"));
180
- lines.push("");
181
-
111
+ lines.push(chalk.bold.yellow(" ARGUMENTS"), "");
182
112
  for (const arg of nonOptionArgs) {
183
113
  if (arg.type === "Workspace") continue;
184
-
185
114
  let argName: string;
186
115
  let argDesc: string;
187
116
  let example = "";
188
-
189
117
  if (arg.type === "Argument") {
190
118
  argName = camelToKebabCase(arg.name);
191
119
  argDesc = arg.argsOption.desc ?? "";
@@ -200,19 +128,13 @@ export const formatCommandHelp = (command: CommandCls, key: string) => {
200
128
  argName = arg.type.toLowerCase();
201
129
  argDesc = `${arg.type} name in this workspace`;
202
130
  }
203
-
204
131
  lines.push(` ${chalk.green(argName)} ${chalk.gray(argDesc)}${example}`);
205
132
  }
206
-
207
133
  lines.push("");
208
134
  }
209
135
 
210
- // Options
211
- const optionArgs = argMetas.filter((a) => a.type === "Option");
212
136
  if (optionArgs.length > 0) {
213
- lines.push(chalk.bold.yellow(" OPTIONS"));
214
- lines.push("");
215
-
137
+ lines.push(chalk.bold.yellow(" OPTIONS"), "");
216
138
  for (const arg of optionArgs) {
217
139
  const opt = arg.argsOption;
218
140
  const flag = opt.flag ? `-${opt.flag}, ` : "";
@@ -1,8 +1,6 @@
1
1
  export * from "./argMeta";
2
2
  export * from "./command";
3
3
  export * from "./commandBuilder";
4
- export * from "./commandMeta";
5
4
  export * from "./dependencyBuilder";
6
5
  export * from "./helpFormatter";
7
6
  export * from "./targetMeta";
8
- export * from "./types";
@@ -1,5 +1,6 @@
1
1
  import type { ArgMeta, InternalArgMeta } from "./argMeta";
2
- import type { DependencyCls } from "./types";
2
+ import { camelToKebabCase } from "./camelToKebabCase";
3
+ import type { Cls, DependencyCls } from "./dependencyBuilder";
3
4
 
4
5
  export const COMMAND_META: unique symbol = Symbol("akan.command.meta");
5
6
 
@@ -22,12 +23,7 @@ export const getTargetMetas = (command: CommandCls): TargetMeta[] => {
22
23
  return [...targetMetaMap.values()];
23
24
  };
24
25
 
25
- const camelToKebabCase = (str: string) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
26
-
27
- /**
28
- * CLI names a target answers to. Shared with the command-manifest generator so a lazily-loaded CLI
29
- * resolves `argv[2]` to the same module that `runCommands` would have registered it under.
30
- */
26
+ /** Shared with the command-manifest generator, so a lazily loaded CLI maps argv[2] to the module runCommands would. */
31
27
  export const getTargetCommandNames = (targetMeta: TargetMeta): string[] => {
32
28
  const kebabKey = camelToKebabCase(targetMeta.key);
33
29
  if (targetMeta.targetOption.short !== true) return [kebabKey];
@@ -48,3 +44,9 @@ export interface TargetOption {
48
44
  runsOnWorkspaceRoot?: boolean;
49
45
  stdio?: boolean;
50
46
  }
47
+
48
+ export const Commands = () => {
49
+ return (target: Cls) => {
50
+ // not implemented yet
51
+ };
52
+ };
package/createTunnel.ts CHANGED
@@ -1,18 +1,6 @@
1
- import type { SshOptions } from "akanjs/base";
2
-
3
1
  import { type AppExecutor, WorkspaceExecutor } from "./executors";
4
2
  import { createSshTunnel } from "./sshTunnel";
5
3
 
6
- const getSshTunnelOptions = (app: AppExecutor, environment: string): SshOptions => {
7
- const { serveDomain, repoName } = WorkspaceExecutor.getBaseDevEnv();
8
- return {
9
- host: `${app.name}-${environment}.${serveDomain}`,
10
- port: process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767,
11
- username: process.env.SSH_TUNNEL_USERNAME ?? "root",
12
- password: process.env.SSH_TUNNEL_PASSWORD ?? repoName,
13
- };
14
- };
15
-
16
4
  interface TunnelOption {
17
5
  app: AppExecutor;
18
6
  environment: string;
@@ -22,7 +10,7 @@ export const createTunnel = async (
22
10
  service: "redis" | "postgres",
23
11
  { app, environment, port = service === "postgres" ? 5432 : 6379 }: TunnelOption,
24
12
  ) => {
25
- const sshOptions: SshOptions = getSshTunnelOptions(app, environment);
13
+ const { serveDomain, repoName } = WorkspaceExecutor.getBaseDevEnv();
26
14
  await createSshTunnel({
27
15
  localHost: "0.0.0.0",
28
16
  localPort: port,
@@ -30,7 +18,12 @@ export const createTunnel = async (
30
18
  srcPort: port,
31
19
  dstHost: `${service}-0.${service}-svc.${app.name}-${environment}.svc.cluster.local`,
32
20
  dstPort: service === "postgres" ? 5432 : 6379,
33
- sshOptions,
21
+ sshOptions: {
22
+ host: `${app.name}-${environment}.${serveDomain}`,
23
+ port: process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767,
24
+ username: process.env.SSH_TUNNEL_USERNAME ?? "root",
25
+ password: process.env.SSH_TUNNEL_PASSWORD ?? repoName,
26
+ },
34
27
  });
35
28
  return `localhost:${port}`;
36
29
  };
@@ -1,22 +1,14 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
- import os from "node:os";
1
+ import { describe, expect, test } from "bun:test";
4
2
  import path from "node:path";
5
3
  import { TypeScriptDependencyScanner } from "./dependencyScanner";
4
+ import { tempDirs, writeText } from "./testHelpers";
6
5
  import type { PackageJson } from "./types";
7
6
 
8
- const tempRoots: string[] = [];
9
-
10
- const makeTempRoot = async () => {
11
- const root = await mkdtemp(path.join(os.tmpdir(), "akan-devkit-depscan-"));
12
- tempRoots.push(root);
13
- return root;
14
- };
7
+ const makeTempRoot = tempDirs("akan-devkit-depscan-");
15
8
 
16
9
  const write = async (root: string, relPath: string, content: string) => {
17
10
  const filePath = path.join(root, relPath);
18
- await mkdir(path.dirname(filePath), { recursive: true });
19
- await writeFile(filePath, content);
11
+ await writeText(filePath, content);
20
12
  return filePath;
21
13
  };
22
14
 
@@ -27,10 +19,6 @@ const scannerFor = async (root: string, dependencies: Record<string, string> = {
27
19
  rootPackageJson: { dependencies } as unknown as PackageJson,
28
20
  });
29
21
 
30
- afterEach(async () => {
31
- await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
32
- });
33
-
34
22
  describe("TypeScriptDependencyScanner — getPackageBuildDependencies", () => {
35
23
  test("collects imports from ordinary source files", async () => {
36
24
  const root = await makeTempRoot();
@@ -7,8 +7,7 @@ import { FileSys } from "./fileSys";
7
7
  import type { PackageJson, TsConfigJson } from "./types";
8
8
 
9
9
  const testFileRegex = /\.(?:test|spec)\.[cm]?[tj]sx?$/;
10
- // `__fixtures__` holds deliberately invalid sample sources for the lint-rule suites; tsconfig `exclude` and
11
- // biome's `!` override already skip them, and their imports are violations rather than real dependencies.
10
+ // `__fixtures__` holds deliberately invalid lint-rule samples, whose imports are violations, not dependencies.
12
11
  const skipDirs = ["node_modules", "dist", "build", ".git", ".next", "public", "ios", "android", "__fixtures__"];
13
12
  const isSkippedPath = (filePath: string) => {
14
13
  const posixPath = filePath.split(path.sep).join("/");
@@ -75,11 +74,8 @@ export class TypeScriptDependencyScanner {
75
74
  ): Promise<{ npmDeps: string[]; npmDevDeps: string[]; missingDeps: string[] }> {
76
75
  const runtimeDeps = new Set<string>();
77
76
  const devDeps = new Set<string>();
78
- const sourceFiles = await this.#findTypeScriptFiles(this.#directory, {
79
- excludeBuildFiles: true,
80
- excludeTestFiles: true,
81
- });
82
- const cssFiles = await this.#findCssFiles(this.#directory);
77
+ const sourceFiles = await this.#findFiles("**/*.{ts,tsx}", { excludeBuildFiles: true, excludeTestFiles: true });
78
+ const cssFiles = await this.#findFiles("**/*.css");
83
79
 
84
80
  for (const filePath of sourceFiles) {
85
81
  const fileContent = await FileSys.readText(filePath);
@@ -135,25 +131,19 @@ export class TypeScriptDependencyScanner {
135
131
  depSets: DepSets,
136
132
  fileDependencies: Map<string, string[]>,
137
133
  ): DepSets {
138
- const importedDepSets = new Array<Set<string>>(depSets.length);
139
- for (let i = 0; i < depSets.length; i++) importedDepSets[i] = new Set<string>();
140
- fileDependencies.forEach((imps) => {
141
- imps.forEach((imp) => {
142
- if (imp.startsWith(".")) return;
143
- const moduleName = imp;
144
- const moduleNameParts = moduleName.split("/");
145
- const subModuleLength = moduleNameParts.length;
146
- for (let i = 0; i < subModuleLength; i++) {
147
- const importName = moduleNameParts.slice(0, i + 1).join("/");
148
- for (let j = 0; j < depSets.length; j++) {
149
- if (depSets[j]?.has(importName)) {
150
- importedDepSets[j]?.add(importName);
151
- return;
152
- }
153
- }
134
+ const importedDepSets = depSets.map(() => new Set<string>());
135
+ for (const imps of fileDependencies.values())
136
+ for (const imp of imps) {
137
+ if (imp.startsWith(".")) continue;
138
+ const parts = imp.split("/");
139
+ for (let length = 1; length <= parts.length; length++) {
140
+ const importName = parts.slice(0, length).join("/");
141
+ const setIndex = depSets.findIndex((depSet) => depSet.has(importName));
142
+ if (setIndex === -1) continue;
143
+ importedDepSets[setIndex]?.add(importName);
144
+ break;
154
145
  }
155
- });
156
- });
146
+ }
157
147
  return importedDepSets as DepSets;
158
148
  }
159
149
 
@@ -163,48 +153,24 @@ export class TypeScriptDependencyScanner {
163
153
  this.#fileTypeDependencies.clear();
164
154
  this.#visitedFiles.clear();
165
155
 
166
- const files = await this.#findTypeScriptFiles(this.#directory);
167
-
168
- for (const file of files) await this.#analyzeFile(file, this.#directory);
169
-
156
+ for (const file of await this.#findFiles("**/*.{ts,tsx}")) await this.#analyzeFile(file, this.#directory);
170
157
  return this.#fileDependencies;
171
158
  }
172
159
 
173
- async #findTypeScriptFiles(
174
- directory: string,
160
+ async #findFiles(
161
+ pattern: string,
175
162
  {
176
163
  excludeBuildFiles = false,
177
164
  excludeTestFiles = false,
178
165
  }: { excludeBuildFiles?: boolean; excludeTestFiles?: boolean } = {},
179
166
  ): Promise<string[]> {
180
167
  const files: string[] = [];
181
-
182
- const glob = new Bun.Glob("**/*.{ts,tsx}");
183
- for await (const filePath of glob.scan({ cwd: directory, onlyFiles: true })) {
168
+ for await (const filePath of new Bun.Glob(pattern).scan({ cwd: this.#directory, onlyFiles: true })) {
184
169
  if (isSkippedPath(filePath)) continue;
185
170
  if (excludeBuildFiles && filePath === "build.ts") continue;
186
171
  if (excludeTestFiles && testFileRegex.test(filePath)) continue;
187
-
188
- const fullPath = path.join(directory, filePath);
189
- const relativePath = path.relative(this.#workspaceRoot, fullPath);
190
- if (this.#ig.ignores(relativePath)) continue;
191
-
192
- files.push(fullPath);
193
- }
194
- return files;
195
- }
196
-
197
- async #findCssFiles(directory: string): Promise<string[]> {
198
- const files: string[] = [];
199
- const glob = new Bun.Glob("**/*.css");
200
- for await (const filePath of glob.scan({ cwd: directory, onlyFiles: true })) {
201
- if (isSkippedPath(filePath)) continue;
202
-
203
- const fullPath = path.join(directory, filePath);
204
- const relativePath = path.relative(this.#workspaceRoot, fullPath);
205
- if (this.#ig.ignores(relativePath)) continue;
206
-
207
- files.push(fullPath);
172
+ const fullPath = path.join(this.#directory, filePath);
173
+ if (!this.#ig.ignores(path.relative(this.#workspaceRoot, fullPath))) files.push(fullPath);
208
174
  }
209
175
  return files;
210
176
  }
@@ -217,12 +183,8 @@ export class TypeScriptDependencyScanner {
217
183
  try {
218
184
  const fileContent = await FileSys.readText(filePath);
219
185
  const { imports, typeImports } = this.#extractImports(fileContent, filePath);
220
-
221
- // Convert imports to absolute or relative paths
222
186
  const resolvedImports = await this.#resolveImports(imports, filePath, baseDir);
223
187
  const resolvedTypeImports = await this.#resolveImports(typeImports, filePath, baseDir);
224
-
225
- // Store the dependencies
226
188
  const relativePath = path.relative(baseDir, filePath);
227
189
  this.#fileDependencies.set(relativePath, [...new Set([...resolvedImports, ...resolvedTypeImports])]);
228
190
  this.#fileRuntimeDependencies.set(relativePath, resolvedImports);
@@ -235,12 +197,8 @@ export class TypeScriptDependencyScanner {
235
197
  async #resolveImports(imports: string[], filePath: string, baseDir: string): Promise<string[]> {
236
198
  return await Promise.all(
237
199
  imports.map(async (importPath) => {
238
- if (importPath.startsWith(".")) {
239
- // Handle relative imports
240
- const resolvedPath = `./${path.join(path.relative(baseDir, filePath), importPath)}`;
241
- return await this.#ensureExtension(resolvedPath);
242
- }
243
- return importPath; // Keep package imports as is
200
+ if (!importPath.startsWith(".")) return importPath;
201
+ return await this.#ensureExtension(`./${path.join(path.relative(baseDir, filePath), importPath)}`);
244
202
  }),
245
203
  );
246
204
  }
@@ -326,19 +284,10 @@ export class TypeScriptDependencyScanner {
326
284
  const projectImports = imports.filter((i) => !i.startsWith("react") && !i.startsWith("@"));
327
285
  const externalImports = imports.filter((i) => i.startsWith("react") || i.startsWith("@"));
328
286
 
329
- if (projectImports.length > 0) {
330
- graph += " Project dependencies:\n";
331
- projectImports.forEach((imp) => {
332
- graph += ` → ${imp}\n`;
333
- });
334
- }
335
-
336
- if (externalImports.length > 0) {
337
- graph += " External dependencies:\n";
338
- externalImports.forEach((imp) => {
339
- graph += ` → ${imp}\n`;
340
- });
341
- }
287
+ if (projectImports.length > 0)
288
+ graph += ` Project dependencies:\n${projectImports.map((imp) => ` → ${imp}\n`).join("")}`;
289
+ if (externalImports.length > 0)
290
+ graph += ` External dependencies:\n${externalImports.map((imp) => ` → ${imp}\n`).join("")}`;
342
291
 
343
292
  graph += "\n";
344
293
  }
@@ -1,29 +1,14 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
- import os from "node:os";
1
+ import { describe, expect, test } from "bun:test";
2
+ import { realpath } from "node:fs/promises";
4
3
  import path from "node:path";
5
4
  import { ApplicationBuildReporter } from "./applicationBuildReporter";
6
5
  import { resolveSignalTestPreloadPath } from "./applicationTestPreload";
7
6
  import { TypeScriptDependencyScanner } from "./dependencyScanner";
8
7
  import { AppExecutor, WorkspaceExecutor } from "./executors";
8
+ import { tempDirs, writeText as write } from "./testHelpers";
9
9
  import type { PackageJson, TsConfigJson } from "./types";
10
10
 
11
- const tempRoots: string[] = [];
12
-
13
- const makeTempRoot = async () => {
14
- const root = await mkdtemp(path.join(os.tmpdir(), "akan-devkit-utils-"));
15
- tempRoots.push(root);
16
- return root;
17
- };
18
-
19
- const write = async (filePath: string, content: string) => {
20
- await mkdir(path.dirname(filePath), { recursive: true });
21
- await writeFile(filePath, content);
22
- };
23
-
24
- afterEach(async () => {
25
- await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
26
- });
11
+ const makeTempRoot = tempDirs("akan-devkit-utils-");
27
12
 
28
13
  describe("resolveSignalTestPreloadPath", () => {
29
14
  test("resolves the preload file from an installed akanjs package", async () => {
@@ -213,4 +198,47 @@ describe("ApplicationBuildReporter", () => {
213
198
  ["failed", " first", " second", " third"].join("\n"),
214
199
  );
215
200
  });
201
+
202
+ test("says where the bundler placed each reason, relative to the workspace root", async () => {
203
+ const root = await makeTempRoot();
204
+ const refuseWith = (message: string) => ({
205
+ name: "refuse",
206
+ setup: (build: Bun.PluginBuilder) => {
207
+ build.onLoad({ filter: /plugged\.ts$/ }, () => {
208
+ throw new Error(message);
209
+ });
210
+ },
211
+ });
212
+ const failureOf = (entry: string, plugins: Bun.BunPlugin[] = []) =>
213
+ Bun.build({ entrypoints: [path.join(root, entry)], plugins }).then(
214
+ () => null,
215
+ (error: unknown) => error,
216
+ );
217
+ await write(
218
+ path.join(root, "src/entry.ts"),
219
+ 'export const a = 1;\nimport { gone } from "./not-there";\nexport const b = gone;\n',
220
+ );
221
+ await write(path.join(root, "src/plugged.ts"), "export const b = 1;\n");
222
+
223
+ const unresolved = await failureOf("src/entry.ts");
224
+ expect(ApplicationBuildReporter.formatError(unresolved, root)).toBe(
225
+ ["Bundle failed", ' Could not resolve: "./not-there" (src/entry.ts:2:22)'].join("\n"),
226
+ );
227
+ const outside = path.join(await realpath(root), "src/entry.ts");
228
+ expect(ApplicationBuildReporter.formatError(unresolved, path.join(root, "elsewhere"))).toBe(
229
+ ["Bundle failed", ` Could not resolve: "./not-there" (${outside}:2:22)`].join("\n"),
230
+ );
231
+ expect(
232
+ ApplicationBuildReporter.formatError(
233
+ await failureOf("src/plugged.ts", [refuseWith("refused\nsecond line")]),
234
+ root,
235
+ ),
236
+ ).toBe(["Bundle failed", " refused (src/plugged.ts)", " second line"].join("\n"));
237
+ expect(
238
+ ApplicationBuildReporter.formatError(
239
+ await failureOf("src/plugged.ts", [refuseWith("src/plugged.ts is refused")]),
240
+ root,
241
+ ),
242
+ ).toBe(["Bundle failed", " src/plugged.ts is refused"].join("\n"));
243
+ });
216
244
  });