@hypercli/gen 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/dist/actions/communication.d.ts +201 -0
  4. package/dist/actions/communication.d.ts.map +1 -0
  5. package/dist/actions/communication.js +515 -0
  6. package/dist/actions/communication.js.map +1 -0
  7. package/dist/actions/decorator.d.ts +22 -0
  8. package/dist/actions/decorator.d.ts.map +1 -0
  9. package/dist/actions/decorator.js +110 -0
  10. package/dist/actions/decorator.js.map +1 -0
  11. package/dist/actions/executor.d.ts +85 -0
  12. package/dist/actions/executor.d.ts.map +1 -0
  13. package/dist/actions/executor.js +289 -0
  14. package/dist/actions/executor.js.map +1 -0
  15. package/dist/actions/index.d.ts +14 -0
  16. package/dist/actions/index.d.ts.map +1 -0
  17. package/dist/actions/index.js +15 -0
  18. package/dist/actions/index.js.map +1 -0
  19. package/dist/actions/parameter-resolver.d.ts +54 -0
  20. package/dist/actions/parameter-resolver.d.ts.map +1 -0
  21. package/dist/actions/parameter-resolver.js +300 -0
  22. package/dist/actions/parameter-resolver.js.map +1 -0
  23. package/dist/actions/registry.d.ts +78 -0
  24. package/dist/actions/registry.d.ts.map +1 -0
  25. package/dist/actions/registry.js +221 -0
  26. package/dist/actions/registry.js.map +1 -0
  27. package/dist/actions/types.d.ts +109 -0
  28. package/dist/actions/types.d.ts.map +1 -0
  29. package/dist/actions/types.js +31 -0
  30. package/dist/actions/types.js.map +1 -0
  31. package/dist/actions/utils.d.ts +42 -0
  32. package/dist/actions/utils.d.ts.map +1 -0
  33. package/dist/actions/utils.js +144 -0
  34. package/dist/actions/utils.js.map +1 -0
  35. package/dist/ai/ai-collector.d.ts +52 -0
  36. package/dist/ai/ai-collector.d.ts.map +1 -0
  37. package/dist/ai/ai-collector.js +64 -0
  38. package/dist/ai/ai-collector.js.map +1 -0
  39. package/dist/ai/ai-config.d.ts +230 -0
  40. package/dist/ai/ai-config.d.ts.map +1 -0
  41. package/dist/ai/ai-config.js +8 -0
  42. package/dist/ai/ai-config.js.map +1 -0
  43. package/dist/ai/ai-service.d.ts +66 -0
  44. package/dist/ai/ai-service.d.ts.map +1 -0
  45. package/dist/ai/ai-service.js +198 -0
  46. package/dist/ai/ai-service.js.map +1 -0
  47. package/dist/ai/ai-variable-resolver.d.ts +59 -0
  48. package/dist/ai/ai-variable-resolver.d.ts.map +1 -0
  49. package/dist/ai/ai-variable-resolver.js +219 -0
  50. package/dist/ai/ai-variable-resolver.js.map +1 -0
  51. package/dist/ai/context-collector.d.ts +30 -0
  52. package/dist/ai/context-collector.d.ts.map +1 -0
  53. package/dist/ai/context-collector.js +158 -0
  54. package/dist/ai/context-collector.js.map +1 -0
  55. package/dist/ai/cost-tracker.d.ts +41 -0
  56. package/dist/ai/cost-tracker.d.ts.map +1 -0
  57. package/dist/ai/cost-tracker.js +131 -0
  58. package/dist/ai/cost-tracker.js.map +1 -0
  59. package/dist/ai/env.d.ts +36 -0
  60. package/dist/ai/env.d.ts.map +1 -0
  61. package/dist/ai/env.js +100 -0
  62. package/dist/ai/env.js.map +1 -0
  63. package/dist/ai/index.d.ts +17 -0
  64. package/dist/ai/index.d.ts.map +1 -0
  65. package/dist/ai/index.js +25 -0
  66. package/dist/ai/index.js.map +1 -0
  67. package/dist/ai/model-router.d.ts +32 -0
  68. package/dist/ai/model-router.d.ts.map +1 -0
  69. package/dist/ai/model-router.js +113 -0
  70. package/dist/ai/model-router.js.map +1 -0
  71. package/dist/ai/output-validator.d.ts +24 -0
  72. package/dist/ai/output-validator.d.ts.map +1 -0
  73. package/dist/ai/output-validator.js +279 -0
  74. package/dist/ai/output-validator.js.map +1 -0
  75. package/dist/ai/prompt-assembler.d.ts +30 -0
  76. package/dist/ai/prompt-assembler.d.ts.map +1 -0
  77. package/dist/ai/prompt-assembler.js +93 -0
  78. package/dist/ai/prompt-assembler.js.map +1 -0
  79. package/dist/ai/prompt-pipeline.d.ts +63 -0
  80. package/dist/ai/prompt-pipeline.d.ts.map +1 -0
  81. package/dist/ai/prompt-pipeline.js +119 -0
  82. package/dist/ai/prompt-pipeline.js.map +1 -0
  83. package/dist/ai/prompt-template.jig +88 -0
  84. package/dist/ai/transports/api-transport.d.ts +12 -0
  85. package/dist/ai/transports/api-transport.d.ts.map +1 -0
  86. package/dist/ai/transports/api-transport.js +86 -0
  87. package/dist/ai/transports/api-transport.js.map +1 -0
  88. package/dist/ai/transports/command-transport.d.ts +20 -0
  89. package/dist/ai/transports/command-transport.d.ts.map +1 -0
  90. package/dist/ai/transports/command-transport.js +203 -0
  91. package/dist/ai/transports/command-transport.js.map +1 -0
  92. package/dist/ai/transports/index.d.ts +11 -0
  93. package/dist/ai/transports/index.d.ts.map +1 -0
  94. package/dist/ai/transports/index.js +10 -0
  95. package/dist/ai/transports/index.js.map +1 -0
  96. package/dist/ai/transports/resolve-transport.d.ts +15 -0
  97. package/dist/ai/transports/resolve-transport.d.ts.map +1 -0
  98. package/dist/ai/transports/resolve-transport.js +96 -0
  99. package/dist/ai/transports/resolve-transport.js.map +1 -0
  100. package/dist/ai/transports/stdout-transport.d.ts +14 -0
  101. package/dist/ai/transports/stdout-transport.d.ts.map +1 -0
  102. package/dist/ai/transports/stdout-transport.js +27 -0
  103. package/dist/ai/transports/stdout-transport.js.map +1 -0
  104. package/dist/ai/transports/types.d.ts +77 -0
  105. package/dist/ai/transports/types.d.ts.map +1 -0
  106. package/dist/ai/transports/types.js +8 -0
  107. package/dist/ai/transports/types.js.map +1 -0
  108. package/dist/commands/cookbook/info.d.ts +22 -0
  109. package/dist/commands/cookbook/info.d.ts.map +1 -0
  110. package/dist/commands/cookbook/info.js +217 -0
  111. package/dist/commands/cookbook/info.js.map +1 -0
  112. package/dist/commands/cookbook/list.d.ts +20 -0
  113. package/dist/commands/cookbook/list.d.ts.map +1 -0
  114. package/dist/commands/cookbook/list.js +133 -0
  115. package/dist/commands/cookbook/list.js.map +1 -0
  116. package/dist/commands/gen.d.ts +65 -0
  117. package/dist/commands/gen.d.ts.map +1 -0
  118. package/dist/commands/gen.js +478 -0
  119. package/dist/commands/gen.js.map +1 -0
  120. package/dist/commands/recipe/info.d.ts +18 -0
  121. package/dist/commands/recipe/info.d.ts.map +1 -0
  122. package/dist/commands/recipe/info.js +89 -0
  123. package/dist/commands/recipe/info.js.map +1 -0
  124. package/dist/commands/recipe/list.d.ts +29 -0
  125. package/dist/commands/recipe/list.d.ts.map +1 -0
  126. package/dist/commands/recipe/list.js +215 -0
  127. package/dist/commands/recipe/list.js.map +1 -0
  128. package/dist/commands/recipe/run.d.ts +44 -0
  129. package/dist/commands/recipe/run.d.ts.map +1 -0
  130. package/dist/commands/recipe/run.js +239 -0
  131. package/dist/commands/recipe/run.js.map +1 -0
  132. package/dist/commands/recipe/validate.d.ts +19 -0
  133. package/dist/commands/recipe/validate.d.ts.map +1 -0
  134. package/dist/commands/recipe/validate.js +66 -0
  135. package/dist/commands/recipe/validate.js.map +1 -0
  136. package/dist/discovery/generator-discovery.d.ts +130 -0
  137. package/dist/discovery/generator-discovery.d.ts.map +1 -0
  138. package/dist/discovery/generator-discovery.js +674 -0
  139. package/dist/discovery/generator-discovery.js.map +1 -0
  140. package/dist/discovery/index.d.ts +8 -0
  141. package/dist/discovery/index.d.ts.map +1 -0
  142. package/dist/discovery/index.js +7 -0
  143. package/dist/discovery/index.js.map +1 -0
  144. package/dist/hooks/command-not-found.d.ts +18 -0
  145. package/dist/hooks/command-not-found.d.ts.map +1 -0
  146. package/dist/hooks/command-not-found.js +182 -0
  147. package/dist/hooks/command-not-found.js.map +1 -0
  148. package/dist/hooks/suggest.d.ts +13 -0
  149. package/dist/hooks/suggest.d.ts.map +1 -0
  150. package/dist/hooks/suggest.js +28 -0
  151. package/dist/hooks/suggest.js.map +1 -0
  152. package/dist/index.d.ts +2 -0
  153. package/dist/index.d.ts.map +1 -0
  154. package/dist/index.js +3 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/lib/base-command.d.ts +26 -0
  157. package/dist/lib/base-command.d.ts.map +1 -0
  158. package/dist/lib/base-command.js +24 -0
  159. package/dist/lib/base-command.js.map +1 -0
  160. package/dist/lib/flags.d.ts +33 -0
  161. package/dist/lib/flags.d.ts.map +1 -0
  162. package/dist/lib/flags.js +64 -0
  163. package/dist/lib/flags.js.map +1 -0
  164. package/dist/ops/add.d.ts +4 -0
  165. package/dist/ops/add.d.ts.map +1 -0
  166. package/dist/ops/add.js +85 -0
  167. package/dist/ops/add.js.map +1 -0
  168. package/dist/ops/inject.d.ts +4 -0
  169. package/dist/ops/inject.d.ts.map +1 -0
  170. package/dist/ops/inject.js +28 -0
  171. package/dist/ops/inject.js.map +1 -0
  172. package/dist/ops/injector.d.ts +4 -0
  173. package/dist/ops/injector.d.ts.map +1 -0
  174. package/dist/ops/injector.js +68 -0
  175. package/dist/ops/injector.js.map +1 -0
  176. package/dist/ops/result.d.ts +3 -0
  177. package/dist/ops/result.d.ts.map +1 -0
  178. package/dist/ops/result.js +8 -0
  179. package/dist/ops/result.js.map +1 -0
  180. package/dist/prompts/interactive-prompts.d.ts +152 -0
  181. package/dist/prompts/interactive-prompts.d.ts.map +1 -0
  182. package/dist/prompts/interactive-prompts.js +574 -0
  183. package/dist/prompts/interactive-prompts.js.map +1 -0
  184. package/dist/recipe-engine/group-executor.d.ts +97 -0
  185. package/dist/recipe-engine/group-executor.d.ts.map +1 -0
  186. package/dist/recipe-engine/group-executor.js +293 -0
  187. package/dist/recipe-engine/group-executor.js.map +1 -0
  188. package/dist/recipe-engine/index.d.ts +112 -0
  189. package/dist/recipe-engine/index.d.ts.map +1 -0
  190. package/dist/recipe-engine/index.js +223 -0
  191. package/dist/recipe-engine/index.js.map +1 -0
  192. package/dist/recipe-engine/output-evaluator.d.ts +28 -0
  193. package/dist/recipe-engine/output-evaluator.d.ts.map +1 -0
  194. package/dist/recipe-engine/output-evaluator.js +78 -0
  195. package/dist/recipe-engine/output-evaluator.js.map +1 -0
  196. package/dist/recipe-engine/recipe-engine.d.ts +227 -0
  197. package/dist/recipe-engine/recipe-engine.d.ts.map +1 -0
  198. package/dist/recipe-engine/recipe-engine.js +1036 -0
  199. package/dist/recipe-engine/recipe-engine.js.map +1 -0
  200. package/dist/recipe-engine/step-executor.d.ts +172 -0
  201. package/dist/recipe-engine/step-executor.d.ts.map +1 -0
  202. package/dist/recipe-engine/step-executor.js +802 -0
  203. package/dist/recipe-engine/step-executor.js.map +1 -0
  204. package/dist/recipe-engine/tools/action-tool.d.ts +103 -0
  205. package/dist/recipe-engine/tools/action-tool.d.ts.map +1 -0
  206. package/dist/recipe-engine/tools/action-tool.js +473 -0
  207. package/dist/recipe-engine/tools/action-tool.js.map +1 -0
  208. package/dist/recipe-engine/tools/ai-tool.d.ts +26 -0
  209. package/dist/recipe-engine/tools/ai-tool.d.ts.map +1 -0
  210. package/dist/recipe-engine/tools/ai-tool.js +233 -0
  211. package/dist/recipe-engine/tools/ai-tool.js.map +1 -0
  212. package/dist/recipe-engine/tools/base.d.ts +214 -0
  213. package/dist/recipe-engine/tools/base.d.ts.map +1 -0
  214. package/dist/recipe-engine/tools/base.js +397 -0
  215. package/dist/recipe-engine/tools/base.js.map +1 -0
  216. package/dist/recipe-engine/tools/codemod-tool.d.ts +130 -0
  217. package/dist/recipe-engine/tools/codemod-tool.d.ts.map +1 -0
  218. package/dist/recipe-engine/tools/codemod-tool.js +786 -0
  219. package/dist/recipe-engine/tools/codemod-tool.js.map +1 -0
  220. package/dist/recipe-engine/tools/ensure-dirs-tool.d.ts +21 -0
  221. package/dist/recipe-engine/tools/ensure-dirs-tool.d.ts.map +1 -0
  222. package/dist/recipe-engine/tools/ensure-dirs-tool.js +130 -0
  223. package/dist/recipe-engine/tools/ensure-dirs-tool.js.map +1 -0
  224. package/dist/recipe-engine/tools/index.d.ts +126 -0
  225. package/dist/recipe-engine/tools/index.d.ts.map +1 -0
  226. package/dist/recipe-engine/tools/index.js +290 -0
  227. package/dist/recipe-engine/tools/index.js.map +1 -0
  228. package/dist/recipe-engine/tools/install-tool.d.ts +20 -0
  229. package/dist/recipe-engine/tools/install-tool.d.ts.map +1 -0
  230. package/dist/recipe-engine/tools/install-tool.js +194 -0
  231. package/dist/recipe-engine/tools/install-tool.js.map +1 -0
  232. package/dist/recipe-engine/tools/parallel-tool.d.ts +21 -0
  233. package/dist/recipe-engine/tools/parallel-tool.d.ts.map +1 -0
  234. package/dist/recipe-engine/tools/parallel-tool.js +134 -0
  235. package/dist/recipe-engine/tools/parallel-tool.js.map +1 -0
  236. package/dist/recipe-engine/tools/patch-tool.d.ts +21 -0
  237. package/dist/recipe-engine/tools/patch-tool.d.ts.map +1 -0
  238. package/dist/recipe-engine/tools/patch-tool.js +248 -0
  239. package/dist/recipe-engine/tools/patch-tool.js.map +1 -0
  240. package/dist/recipe-engine/tools/prompt-tool.d.ts +25 -0
  241. package/dist/recipe-engine/tools/prompt-tool.d.ts.map +1 -0
  242. package/dist/recipe-engine/tools/prompt-tool.js +162 -0
  243. package/dist/recipe-engine/tools/prompt-tool.js.map +1 -0
  244. package/dist/recipe-engine/tools/query-tool.d.ts +21 -0
  245. package/dist/recipe-engine/tools/query-tool.d.ts.map +1 -0
  246. package/dist/recipe-engine/tools/query-tool.js +249 -0
  247. package/dist/recipe-engine/tools/query-tool.js.map +1 -0
  248. package/dist/recipe-engine/tools/recipe-tool.d.ts +103 -0
  249. package/dist/recipe-engine/tools/recipe-tool.d.ts.map +1 -0
  250. package/dist/recipe-engine/tools/recipe-tool.js +617 -0
  251. package/dist/recipe-engine/tools/recipe-tool.js.map +1 -0
  252. package/dist/recipe-engine/tools/registry.d.ts +151 -0
  253. package/dist/recipe-engine/tools/registry.d.ts.map +1 -0
  254. package/dist/recipe-engine/tools/registry.js +244 -0
  255. package/dist/recipe-engine/tools/registry.js.map +1 -0
  256. package/dist/recipe-engine/tools/sequence-tool.d.ts +22 -0
  257. package/dist/recipe-engine/tools/sequence-tool.d.ts.map +1 -0
  258. package/dist/recipe-engine/tools/sequence-tool.js +122 -0
  259. package/dist/recipe-engine/tools/sequence-tool.js.map +1 -0
  260. package/dist/recipe-engine/tools/shell-tool.d.ts +25 -0
  261. package/dist/recipe-engine/tools/shell-tool.d.ts.map +1 -0
  262. package/dist/recipe-engine/tools/shell-tool.js +149 -0
  263. package/dist/recipe-engine/tools/shell-tool.js.map +1 -0
  264. package/dist/recipe-engine/tools/template-tool.d.ts +88 -0
  265. package/dist/recipe-engine/tools/template-tool.d.ts.map +1 -0
  266. package/dist/recipe-engine/tools/template-tool.js +613 -0
  267. package/dist/recipe-engine/tools/template-tool.js.map +1 -0
  268. package/dist/recipe-engine/types.d.ts +963 -0
  269. package/dist/recipe-engine/types.d.ts.map +1 -0
  270. package/dist/recipe-engine/types.js +94 -0
  271. package/dist/recipe-engine/types.js.map +1 -0
  272. package/dist/template-engines/ai-tags.d.ts +26 -0
  273. package/dist/template-engines/ai-tags.d.ts.map +1 -0
  274. package/dist/template-engines/ai-tags.js +233 -0
  275. package/dist/template-engines/ai-tags.js.map +1 -0
  276. package/dist/template-engines/index.d.ts +8 -0
  277. package/dist/template-engines/index.d.ts.map +1 -0
  278. package/dist/template-engines/index.js +8 -0
  279. package/dist/template-engines/index.js.map +1 -0
  280. package/dist/template-engines/jig-engine.d.ts +47 -0
  281. package/dist/template-engines/jig-engine.d.ts.map +1 -0
  282. package/dist/template-engines/jig-engine.js +173 -0
  283. package/dist/template-engines/jig-engine.js.map +1 -0
  284. package/dist/utils/coerce-value.d.ts +7 -0
  285. package/dist/utils/coerce-value.d.ts.map +1 -0
  286. package/dist/utils/coerce-value.js +18 -0
  287. package/dist/utils/coerce-value.js.map +1 -0
  288. package/dist/utils/diff.d.ts +8 -0
  289. package/dist/utils/diff.d.ts.map +1 -0
  290. package/dist/utils/diff.js +10 -0
  291. package/dist/utils/diff.js.map +1 -0
  292. package/dist/utils/global-packages.d.ts +11 -0
  293. package/dist/utils/global-packages.d.ts.map +1 -0
  294. package/dist/utils/global-packages.js +88 -0
  295. package/dist/utils/global-packages.js.map +1 -0
  296. package/dist/utils/pager.d.ts +6 -0
  297. package/dist/utils/pager.d.ts.map +1 -0
  298. package/dist/utils/pager.js +41 -0
  299. package/dist/utils/pager.js.map +1 -0
  300. package/help/cookbook/info.md +35 -0
  301. package/help/cookbook/list.md +37 -0
  302. package/help/gen.md +26 -0
  303. package/help/recipe/run.md +52 -0
  304. package/help/recipe/validate.md +51 -0
  305. package/oclif.manifest.json +580 -0
  306. package/package.json +120 -0
@@ -0,0 +1,674 @@
1
+ /**
2
+ * Generator Auto-Discovery
3
+ *
4
+ * Automatically discovers and registers generators from various sources
5
+ */
6
+ import path from "node:path";
7
+ import { findProjectRoot } from "@hypercli/core";
8
+ import { parseKitFile } from "@hypercli/core";
9
+ import { discoverCookbooksInKit } from "@hypercli/core";
10
+ import createDebug from "debug";
11
+ import fs from "fs-extra";
12
+ import { glob } from "glob";
13
+ import { isActionFunction } from "#actions/decorator";
14
+ import { getGlobalPackages } from "#utils/global-packages";
15
+ const debug = createDebug("hypergen:discovery");
16
+ export class GeneratorDiscovery {
17
+ options;
18
+ discoveredGenerators = new Map();
19
+ projectRoot;
20
+ constructor(options = {}) {
21
+ this.options = options;
22
+ // Find project root with monorepo detection
23
+ const projectInfo = findProjectRoot(this.options.startDir);
24
+ this.projectRoot = projectInfo.workspaceRoot;
25
+ debug("Using project root: %s (isMonorepo: %s)", this.projectRoot, projectInfo.isMonorepo);
26
+ // Always include .hyper/kits in addition to any configured directories
27
+ const defaultDirs = ["recipes", "cookbooks"];
28
+ const configDirs = this.options.directories || [];
29
+ const allDirs = [...new Set([...defaultDirs, ...configDirs, ".hyper/kits"])];
30
+ this.options = {
31
+ patterns: ["**/*.{js,ts,mjs}", "**/template.yml", "**/generator.{js,ts,mjs}"],
32
+ excludePatterns: ["**/node_modules/**", "**/dist/**", "**/*.test.*", "**/*.spec.*"],
33
+ enabledSources: ["local", "workspace", "global"],
34
+ ...this.options,
35
+ directories: allDirs, // Always use merged directories
36
+ };
37
+ }
38
+ /**
39
+ * Discover all generators from enabled sources
40
+ */
41
+ async discoverAll() {
42
+ debug("Starting generator discovery with sources: %o", this.options.enabledSources);
43
+ const discoveries = [];
44
+ if (this.options.enabledSources?.includes("local")) {
45
+ const localGenerators = await this.discoverLocal();
46
+ discoveries.push(...localGenerators);
47
+ }
48
+ if (this.options.enabledSources?.includes("workspace")) {
49
+ const workspaceGenerators = await this.discoverWorkspace();
50
+ discoveries.push(...workspaceGenerators);
51
+ }
52
+ if (this.options.enabledSources?.includes("npm")) {
53
+ const npmGenerators = await this.discoverNpm();
54
+ discoveries.push(...npmGenerators);
55
+ }
56
+ if (this.options.enabledSources?.includes("git")) {
57
+ const gitGenerators = await this.discoverGit();
58
+ discoveries.push(...gitGenerators);
59
+ }
60
+ if (this.options.enabledSources?.includes("global")) {
61
+ const globalGenerators = await this.discoverGlobal();
62
+ discoveries.push(...globalGenerators);
63
+ }
64
+ // Create virtual "workspace" kit for standalone cookbooks/recipes
65
+ const workspaceKit = await this.createWorkspaceKit(discoveries);
66
+ if (workspaceKit) {
67
+ discoveries.push(workspaceKit);
68
+ }
69
+ // Enrich all discovered generators with kit metadata
70
+ await Promise.all(discoveries.map((gen) => this.enrichWithKitMetadata(gen)));
71
+ // Store discovered generators
72
+ for (const generator of discoveries) {
73
+ this.discoveredGenerators.set(generator.name, generator);
74
+ }
75
+ debug("Discovery complete: found %d generators", discoveries.length);
76
+ return discoveries;
77
+ }
78
+ /**
79
+ * Discover local generators in template directories
80
+ */
81
+ async discoverLocal() {
82
+ debug("Discovering local generators in directories: %o", this.options.directories);
83
+ const generators = [];
84
+ const discoveredKitPaths = new Set();
85
+ for (const dir of this.options.directories || []) {
86
+ const fullPath = path.resolve(this.projectRoot, dir);
87
+ if (!(await fs.pathExists(fullPath))) {
88
+ continue;
89
+ }
90
+ debug("Scanning directory: %s", fullPath);
91
+ // Look for kit.yml files first (recipe-based kits)
92
+ const kitYmlFiles = await this.findKitYmlFiles(fullPath);
93
+ for (const kitYmlPath of kitYmlFiles) {
94
+ const kitPath = path.dirname(kitYmlPath);
95
+ const kitName = path.basename(kitPath);
96
+ // Avoid duplicates
97
+ if (discoveredKitPaths.has(kitPath)) {
98
+ continue;
99
+ }
100
+ discoveredKitPaths.add(kitPath);
101
+ try {
102
+ const parsedKit = await parseKitFile(kitYmlPath);
103
+ if (parsedKit.isValid) {
104
+ generators.push({
105
+ name: kitName,
106
+ source: "local",
107
+ path: kitPath,
108
+ actions: [],
109
+ metadata: {
110
+ description: parsedKit.config.description,
111
+ version: parsedKit.config.version,
112
+ author: parsedKit.config.author,
113
+ license: parsedKit.config.license,
114
+ keywords: parsedKit.config.keywords,
115
+ tags: parsedKit.config.tags,
116
+ },
117
+ });
118
+ debug("Discovered kit from kit.yml: %s at %s", kitName, kitPath);
119
+ }
120
+ }
121
+ catch (error) {
122
+ debug("Failed to parse kit.yml at %s: %s", kitYmlPath, error);
123
+ }
124
+ }
125
+ // Look for action files (traditional action-based generators)
126
+ const actionFiles = await this.findActionFiles(fullPath);
127
+ // Look for template.yml files
128
+ const templateFiles = await this.findTemplateFiles(fullPath);
129
+ // Group by generator name (typically directory name)
130
+ const generatorGroups = this.groupFilesByGenerator(actionFiles, templateFiles, fullPath);
131
+ for (const [generatorName, files] of generatorGroups) {
132
+ const generatorPath = path.join(fullPath, generatorName);
133
+ // Skip if already discovered as a kit
134
+ if (discoveredKitPaths.has(generatorPath)) {
135
+ continue;
136
+ }
137
+ const actions = await this.extractActionsFromFiles(files.actions);
138
+ generators.push({
139
+ name: generatorName,
140
+ source: "local",
141
+ path: generatorPath,
142
+ actions,
143
+ metadata: await this.extractGeneratorMetadata(files.templates[0]),
144
+ });
145
+ }
146
+ }
147
+ debug("Found %d local generators", generators.length);
148
+ return generators;
149
+ }
150
+ /**
151
+ * Find kit.yml files in a directory
152
+ */
153
+ async findKitYmlFiles(directory) {
154
+ const matches = await glob("**/kit.yml", {
155
+ cwd: directory,
156
+ ignore: this.options.excludePatterns,
157
+ });
158
+ return matches.map((f) => path.resolve(directory, f));
159
+ }
160
+ /**
161
+ * Discover workspace generators (monorepo packages)
162
+ */
163
+ async discoverWorkspace() {
164
+ debug("Discovering workspace generators");
165
+ const generators = [];
166
+ const cwd = process.cwd();
167
+ // Look for workspace packages that might contain generators
168
+ const workspacePatterns = [
169
+ "packages/*/generators/**",
170
+ "apps/*/generators/**",
171
+ "tools/generators/**",
172
+ ];
173
+ for (const pattern of workspacePatterns) {
174
+ const matches = await glob(pattern, {
175
+ cwd,
176
+ });
177
+ for (const match of matches) {
178
+ const fullPath = path.resolve(cwd, match);
179
+ const packageName = this.extractPackageNameFromPath(match);
180
+ const actionFiles = await this.findActionFiles(fullPath);
181
+ const actions = await this.extractActionsFromFiles(actionFiles);
182
+ if (actions.length > 0) {
183
+ generators.push({
184
+ name: packageName,
185
+ source: "workspace",
186
+ path: fullPath,
187
+ actions,
188
+ metadata: {
189
+ description: `Workspace generator from ${match}`,
190
+ },
191
+ });
192
+ }
193
+ }
194
+ }
195
+ debug("Found %d workspace generators", generators.length);
196
+ return generators;
197
+ }
198
+ /**
199
+ * Discover npm-installed generators
200
+ */
201
+ async discoverNpm() {
202
+ debug("Discovering npm generators");
203
+ const generators = [];
204
+ const nodeModulesPath = path.resolve(process.cwd(), "node_modules");
205
+ if (!(await fs.pathExists(nodeModulesPath))) {
206
+ return generators;
207
+ }
208
+ // Look for packages that follow hypergen generator conventions
209
+ const packageDirs = await fs.readdir(nodeModulesPath);
210
+ for (const packageDir of packageDirs) {
211
+ if (packageDir.startsWith("."))
212
+ continue;
213
+ const packagePath = path.join(nodeModulesPath, packageDir);
214
+ const packageJsonPath = path.join(packagePath, "package.json");
215
+ if (!(await fs.pathExists(packageJsonPath)))
216
+ continue;
217
+ try {
218
+ const packageJson = await fs.readJson(packageJsonPath);
219
+ // Check if package is a hypergen generator
220
+ if (this.isHypergenPackage(packageJson)) {
221
+ const generatorPath = path.join(packagePath, "generators");
222
+ if (await fs.pathExists(generatorPath)) {
223
+ const actionFiles = await this.findActionFiles(generatorPath);
224
+ const actions = await this.extractActionsFromFiles(actionFiles);
225
+ generators.push({
226
+ name: packageJson.name,
227
+ source: "npm",
228
+ path: generatorPath,
229
+ actions,
230
+ metadata: {
231
+ description: packageJson.description,
232
+ version: packageJson.version,
233
+ author: packageJson.author,
234
+ },
235
+ });
236
+ }
237
+ }
238
+ }
239
+ catch (error) {
240
+ debug("Failed to read package.json for %s: %s", packageDir, error);
241
+ }
242
+ }
243
+ debug("Found %d npm generators", generators.length);
244
+ return generators;
245
+ }
246
+ /**
247
+ * Discover git-based generators (placeholder for future implementation)
248
+ */
249
+ async discoverGit() {
250
+ debug("Git discovery not yet implemented");
251
+ return [];
252
+ }
253
+ /**
254
+ * Discover global packages (npm/bun global installs)
255
+ */
256
+ async discoverGlobal() {
257
+ debug("Discovering global generators via engine");
258
+ const generators = [];
259
+ try {
260
+ const globalPackages = await getGlobalPackages();
261
+ debug("Found %d global packages installed: %o", globalPackages.length, globalPackages.map((p) => p.name));
262
+ for (const pkg of globalPackages) {
263
+ // Filter by naming convention
264
+ const isHyperKit = pkg.name.endsWith("-hyper-kit") || pkg.name.startsWith("@kit/");
265
+ if (isHyperKit) {
266
+ debug("Checking potential kit: %s at %s", pkg.name, pkg.path);
267
+ await this.checkAndAddGlobalPackage(pkg.path, generators);
268
+ }
269
+ }
270
+ }
271
+ catch (error) {
272
+ debug("Global discovery failed: %s", error);
273
+ }
274
+ debug("Found %d global generators", generators.length);
275
+ return generators;
276
+ }
277
+ async checkAndAddGlobalPackage(packagePath, generators) {
278
+ try {
279
+ const packageJsonPath = path.join(packagePath, "package.json");
280
+ if (!(await fs.pathExists(packageJsonPath)))
281
+ return;
282
+ const packageJson = await fs.readJson(packageJsonPath);
283
+ const packageName = packageJson.name;
284
+ // Check name requirement: ends with -hyper-kit or starts with @kit/
285
+ const isHyperKit = packageName.endsWith("-hyper-kit") || packageName.startsWith("@kit/");
286
+ if (!isHyperKit)
287
+ return;
288
+ // It's a match! Check for generator content (actions/templates)
289
+ // Usually in a 'generators' dir or root?
290
+ // The `discoverNpm` logic checks for `generators` dir.
291
+ // The prompt didn't specify structure, but "hypergen kit my-templates" implies it acts like a kit.
292
+ // We'll stick to the convention of looking for a 'generators' folder OR
293
+ // just treat the root as a potential generator if it has actions/templates.
294
+ // Let's try `generators` dir first to match npm logic, and fall back to root?
295
+ // Actually `npm` discovery explicitly looks for `generators` subdir.
296
+ // "Will hypergen be able to find the kit...?"
297
+ // If it's a kit package, it probably follows the kit structure.
298
+ // Let's look for `generators` folder logic first, similar to discoverNpm
299
+ const generatorPath = path.join(packagePath, "generators");
300
+ const hasGeneratorsDir = await fs.pathExists(generatorPath);
301
+ if (!hasGeneratorsDir) {
302
+ // Fallback: maybe the package IS the generator (root) if it has template.yml or actions
303
+ // But `discoverNpm` enforces `generators` subdir.
304
+ // Let's be a bit more flexible for global kits or stick to `npm` convention?
305
+ // existing `discoverNpm` logic:
306
+ // if (await fs.pathExists(generatorPath)) { ... }
307
+ // Let's stick to that for consistency, but maybe allow root if template.yml exists?
308
+ // For now, I'll match `discoverNpm` logic but applying to these filtered packages.
309
+ // Actually, if the package name is explicitly `@kits/kit`, the user command `hypergen kit my-templates`
310
+ // implies we are looking for a generator named `kit`.
311
+ // If the package is `my-hyper-kit`, and inside it has `generators/my-templates`...
312
+ // The user said `hypergen kit my-templates`.
313
+ // If the package IS the kit, maybe the generator name is the package name?
314
+ // Or if the package contains multiple generators?
315
+ // `discoverNpm` uses `packageJson.name` as the generator name!
316
+ // `generators.push({ name: packageJson.name ... })`
317
+ // And it sets path to `path.join(packagePath, 'generators')`.
318
+ // This implies the `generators` folder contains the actions/code.
319
+ // I will follow the same pattern.
320
+ if (!hasGeneratorsDir)
321
+ return;
322
+ }
323
+ if (hasGeneratorsDir) {
324
+ const actionFiles = await this.findActionFiles(generatorPath);
325
+ const actions = await this.extractActionsFromFiles(actionFiles);
326
+ generators.push({
327
+ name: packageName,
328
+ source: "global",
329
+ path: generatorPath,
330
+ actions,
331
+ metadata: {
332
+ description: packageJson.description,
333
+ version: packageJson.version,
334
+ author: packageJson.author,
335
+ },
336
+ });
337
+ }
338
+ }
339
+ catch (e) {
340
+ debug("Error checking global package %s: %s", packagePath, e);
341
+ }
342
+ }
343
+ /**
344
+ * Get a discovered generator by name
345
+ */
346
+ getGenerator(name) {
347
+ return this.discoveredGenerators.get(name);
348
+ }
349
+ /**
350
+ * Get all discovered generators
351
+ */
352
+ getGenerators() {
353
+ return Array.from(this.discoveredGenerators.values());
354
+ }
355
+ /**
356
+ * Get generators by source
357
+ */
358
+ getGeneratorsBySource(source) {
359
+ return this.getGenerators().filter((g) => g.source === source);
360
+ }
361
+ /**
362
+ * Find action files in a directory
363
+ */
364
+ async findActionFiles(directory) {
365
+ debug("Finding action files in directory: %s", directory);
366
+ // Filter patterns that match JavaScript/TypeScript files
367
+ const patterns = this.options.patterns?.filter((p) => p.includes(".js") || p.includes(".ts") || p.includes(".mjs") || p.includes("{js,ts,mjs}")) || ["**/*.{js,ts,mjs}"];
368
+ debug("Using patterns: %o", patterns);
369
+ debug("Exclude patterns: %o", this.options.excludePatterns);
370
+ const files = [];
371
+ for (const pattern of patterns) {
372
+ debug("Searching with pattern: %s in directory: %s", pattern, directory);
373
+ const matches = await glob(pattern, {
374
+ cwd: directory,
375
+ ignore: this.options.excludePatterns,
376
+ });
377
+ debug("Pattern %s found %d matches: %o", pattern, matches.length, matches);
378
+ files.push(...matches.map((f) => path.resolve(directory, f)));
379
+ }
380
+ debug("Total action files found: %d", files.length);
381
+ return files;
382
+ }
383
+ /**
384
+ * Find template.yml files in a directory
385
+ */
386
+ async findTemplateFiles(directory) {
387
+ const matches = await glob("**/template.yml", {
388
+ cwd: directory,
389
+ ignore: this.options.excludePatterns,
390
+ });
391
+ return matches.map((f) => path.resolve(directory, f));
392
+ }
393
+ /**
394
+ * Group files by generator name
395
+ */
396
+ groupFilesByGenerator(actionFiles, templateFiles, baseDir) {
397
+ const groups = new Map();
398
+ // Group action files
399
+ for (const file of actionFiles) {
400
+ const relativePath = path.relative(baseDir, file);
401
+ const generatorName = relativePath.split(path.sep)[0];
402
+ if (!groups.has(generatorName)) {
403
+ groups.set(generatorName, { actions: [], templates: [] });
404
+ }
405
+ groups.get(generatorName)?.actions.push(file);
406
+ }
407
+ // Group template files
408
+ for (const file of templateFiles) {
409
+ const relativePath = path.relative(baseDir, file);
410
+ const generatorName = relativePath.split(path.sep)[0];
411
+ if (!groups.has(generatorName)) {
412
+ groups.set(generatorName, { actions: [], templates: [] });
413
+ }
414
+ groups.get(generatorName)?.templates.push(file);
415
+ }
416
+ return groups;
417
+ }
418
+ /**
419
+ * Extract action names from files by loading and checking for @action decorators
420
+ */
421
+ async extractActionsFromFiles(files) {
422
+ const actions = [];
423
+ for (const file of files) {
424
+ try {
425
+ // Import the module and check for decorated actions
426
+ // This will also register the actions via their decorators
427
+ const module = await this.importModule(file);
428
+ for (const [exportName, exportValue] of Object.entries(module)) {
429
+ if (typeof exportValue === "function" && isActionFunction(exportValue)) {
430
+ actions.push(exportName);
431
+ }
432
+ }
433
+ }
434
+ catch (error) {
435
+ debug("Failed to load module %s: %s", file, error);
436
+ }
437
+ }
438
+ return actions;
439
+ }
440
+ /**
441
+ * Import module with proper handling for TypeScript files
442
+ */
443
+ async importModule(filePath) {
444
+ try {
445
+ // Convert to absolute path for consistent importing
446
+ const absolutePath = path.resolve(filePath);
447
+ // Use Bun's built-in TypeScript support with file:// protocol
448
+ const fileUrl = `file://${absolutePath}`;
449
+ debug("Importing module: %s", fileUrl);
450
+ const module = await import(fileUrl);
451
+ debug("Successfully imported module with exports: %o", Object.keys(module));
452
+ return module;
453
+ }
454
+ catch (error) {
455
+ debug("Failed to import module %s: %s", filePath, error.message);
456
+ // For debugging, let's still try the old way as fallback
457
+ try {
458
+ return await import(filePath);
459
+ }
460
+ catch (fallbackError) {
461
+ debug("Fallback import also failed: %s", fallbackError.message);
462
+ throw error; // throw original error
463
+ }
464
+ }
465
+ }
466
+ /**
467
+ * Register all discovered actions with the ActionRegistry
468
+ * This should be called after discovery to ensure actions are available
469
+ */
470
+ async registerDiscoveredActions() {
471
+ debug("Registering discovered actions...");
472
+ const generators = this.getGenerators();
473
+ debug("Found %d generators to register", generators.length);
474
+ for (const generator of generators) {
475
+ debug("Looking for action files in generator: %s at path: %s", generator.name, generator.path);
476
+ const actionFiles = await this.findActionFiles(generator.path);
477
+ debug("Found %d action files for generator %s: %o", actionFiles.length, generator.name, actionFiles);
478
+ for (const file of actionFiles) {
479
+ try {
480
+ debug("Attempting to import action file: %s", file);
481
+ // Import the file to trigger decorator registration
482
+ await this.importModule(file);
483
+ debug("Successfully loaded action file: %s", file);
484
+ }
485
+ catch (error) {
486
+ debug("Failed to load action file %s: %s", file, error.message);
487
+ }
488
+ }
489
+ }
490
+ debug("Action registration complete");
491
+ }
492
+ /**
493
+ * Extract generator metadata from template.yml
494
+ */
495
+ async extractGeneratorMetadata(templateFile) {
496
+ if (!templateFile || !(await fs.pathExists(templateFile))) {
497
+ return undefined;
498
+ }
499
+ try {
500
+ // This would integrate with the template.yml parser from Phase 1
501
+ // For now, return basic metadata
502
+ return {
503
+ description: "Local generator",
504
+ };
505
+ }
506
+ catch (error) {
507
+ debug("Failed to parse template metadata from %s: %s", templateFile, error);
508
+ return undefined;
509
+ }
510
+ }
511
+ /**
512
+ * Extract package name from workspace path
513
+ */
514
+ extractPackageNameFromPath(workspacePath) {
515
+ const parts = workspacePath.split(path.sep);
516
+ return parts.slice(0, 2).join("-"); // e.g., "packages-ui" or "apps-web"
517
+ }
518
+ /**
519
+ * Check if package.json indicates a hypergen generator package
520
+ */
521
+ isHypergenPackage(packageJson) {
522
+ return (packageJson.keywords?.includes("hypergen") ||
523
+ packageJson.keywords?.includes("generator") ||
524
+ packageJson.name?.includes("hypergen-") ||
525
+ packageJson.hypergen !== undefined);
526
+ }
527
+ /**
528
+ * Create a virtual "workspace" kit containing standalone cookbooks and recipes
529
+ * that don't belong to any installed kit
530
+ */
531
+ async createWorkspaceKit(discoveries) {
532
+ debug("Creating virtual workspace kit for standalone cookbooks/recipes");
533
+ const workspaceItems = {
534
+ actions: [],
535
+ templates: [],
536
+ };
537
+ // Check default workspace directories (excluding .hyper/kits)
538
+ const workspaceDirs = this.options.directories?.filter((dir) => dir !== ".hyper/kits") || [];
539
+ for (const dir of workspaceDirs) {
540
+ const fullPath = path.resolve(this.projectRoot, dir);
541
+ if (!(await fs.pathExists(fullPath))) {
542
+ continue;
543
+ }
544
+ debug("Scanning for standalone items in: %s", fullPath);
545
+ // Find cookbooks and recipes that are not part of any kit
546
+ const actionFiles = await this.findActionFiles(fullPath);
547
+ const templateFiles = await this.findTemplateFiles(fullPath);
548
+ // Check if these files belong to a discovered kit
549
+ const kitPaths = discoveries.map((d) => d.path);
550
+ const standaloneActions = actionFiles.filter((file) => !this.belongsToKit(file, kitPaths));
551
+ const standaloneTemplates = templateFiles.filter((file) => !this.belongsToKit(file, kitPaths));
552
+ workspaceItems.actions.push(...standaloneActions);
553
+ workspaceItems.templates.push(...standaloneTemplates);
554
+ }
555
+ // If we found standalone items, create the workspace kit
556
+ if (workspaceItems.actions.length > 0 || workspaceItems.templates.length > 0) {
557
+ const actions = await this.extractActionsFromFiles(workspaceItems.actions);
558
+ debug("Created workspace kit with %d actions and %d templates", actions.length, workspaceItems.templates.length);
559
+ return {
560
+ name: "workspace",
561
+ source: "local",
562
+ path: this.projectRoot,
563
+ actions,
564
+ metadata: {
565
+ description: "Standalone cookbooks and recipes not belonging to any kit",
566
+ },
567
+ };
568
+ }
569
+ debug("No standalone workspace items found");
570
+ return null;
571
+ }
572
+ /**
573
+ * Check if a file path belongs to any of the given kit paths
574
+ */
575
+ belongsToKit(filePath, kitPaths) {
576
+ for (const kitPath of kitPaths) {
577
+ if (filePath.startsWith(kitPath)) {
578
+ return true;
579
+ }
580
+ }
581
+ return false;
582
+ }
583
+ /**
584
+ * Enrich a discovered generator with kit metadata
585
+ */
586
+ async enrichWithKitMetadata(generator) {
587
+ // Look for kit.yml in the generator path
588
+ const kitYmlPath = path.join(generator.path, "kit.yml");
589
+ if (await fs.pathExists(kitYmlPath)) {
590
+ try {
591
+ const parsedKit = await parseKitFile(kitYmlPath);
592
+ if (parsedKit.isValid) {
593
+ const { config } = parsedKit;
594
+ // Update metadata
595
+ generator.metadata = {
596
+ ...generator.metadata,
597
+ description: config.description || generator.metadata?.description,
598
+ version: config.version || generator.metadata?.version,
599
+ author: config.author || generator.metadata?.author,
600
+ tags: config.tags || generator.metadata?.tags,
601
+ license: config.license,
602
+ keywords: config.keywords,
603
+ };
604
+ // Discover cookbooks
605
+ if (config.cookbooks && config.cookbooks.length > 0) {
606
+ const cookbooks = await discoverCookbooksInKit(generator.path, config.cookbooks);
607
+ generator.cookbooks = Array.from(cookbooks.values()).map((c) => c.config.name);
608
+ }
609
+ // Discover recipes (direct recipes not in cookbooks)
610
+ if (config.recipes && config.recipes.length > 0) {
611
+ const recipePatterns = config.recipes;
612
+ const recipeFiles = [];
613
+ for (const pattern of recipePatterns) {
614
+ const matches = await glob(pattern, {
615
+ cwd: generator.path,
616
+ ignore: this.options.excludePatterns,
617
+ });
618
+ recipeFiles.push(...matches);
619
+ }
620
+ // Extract recipe names from paths
621
+ const recipeNames = recipeFiles
622
+ .map((f) => {
623
+ const dir = path.dirname(f);
624
+ const parts = dir.split(path.sep);
625
+ return parts[parts.length - 1];
626
+ })
627
+ .filter((name, index, self) => self.indexOf(name) === index); // unique
628
+ generator.recipes = recipeNames;
629
+ }
630
+ // Check for helpers
631
+ if (config.helpers) {
632
+ const helpersPath = path.join(generator.path, config.helpers);
633
+ if (await fs.pathExists(helpersPath)) {
634
+ const stats = await fs.stat(helpersPath);
635
+ if (stats.isDirectory()) {
636
+ // List helper files
637
+ const helperFiles = await fs.readdir(helpersPath);
638
+ generator.helpers = helperFiles
639
+ .filter((f) => f.endsWith(".js") || f.endsWith(".ts") || f.endsWith(".mjs"))
640
+ .map((f) => path.basename(f, path.extname(f)));
641
+ }
642
+ else {
643
+ // Single helper file
644
+ generator.helpers = [path.basename(config.helpers, path.extname(config.helpers))];
645
+ }
646
+ }
647
+ }
648
+ }
649
+ }
650
+ catch (error) {
651
+ debug("Failed to parse kit.yml for %s: %s", generator.name, error);
652
+ }
653
+ }
654
+ // Also check package.json for additional metadata
655
+ const packageJsonPath = path.join(generator.path, "package.json");
656
+ if (await fs.pathExists(packageJsonPath)) {
657
+ try {
658
+ const packageJson = await fs.readJson(packageJsonPath);
659
+ generator.metadata = {
660
+ ...generator.metadata,
661
+ version: generator.metadata?.version || packageJson.version,
662
+ author: generator.metadata?.author || packageJson.author,
663
+ description: generator.metadata?.description || packageJson.description,
664
+ license: generator.metadata?.license || packageJson.license,
665
+ keywords: generator.metadata?.keywords || packageJson.keywords,
666
+ };
667
+ }
668
+ catch (error) {
669
+ debug("Failed to read package.json for %s: %s", generator.name, error);
670
+ }
671
+ }
672
+ }
673
+ }
674
+ //# sourceMappingURL=generator-discovery.js.map