@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,786 @@
1
+ /**
2
+ * CodeMod Tool Implementation for Recipe Step System
3
+ *
4
+ * This tool provides AST-based code transformations and modifications using
5
+ * the TypeScript compiler API for TypeScript/JavaScript files, with fallback
6
+ * support for text-based transformations for other file types.
7
+ */
8
+ import path from "node:path";
9
+ import { ErrorCode, ErrorHandler, HypergenError } from "@hypercli/core";
10
+ import createDebug from "debug";
11
+ import fs from "fs-extra";
12
+ import { glob } from "glob";
13
+ import * as ts from "typescript";
14
+ import { isCodeModStep, } from "#recipe-engine/types";
15
+ import { Tool } from "./base.js";
16
+ const debug = createDebug("hyper:recipe:tool:codemod");
17
+ /**
18
+ * Built-in CodeMod transformations
19
+ */
20
+ class CodeModTransformations {
21
+ /**
22
+ * Add import statement to TypeScript/JavaScript file
23
+ */
24
+ static addImport(sourceFile, parameters) {
25
+ if (!parameters.import || !parameters.from) {
26
+ throw new Error("import and from parameters are required for add-import transformation");
27
+ }
28
+ const factory = ts.factory;
29
+ let importDeclaration;
30
+ switch (parameters.importType || "named") {
31
+ case "default":
32
+ importDeclaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, factory.createIdentifier(parameters.alias || parameters.import), undefined), factory.createStringLiteral(parameters.from));
33
+ break;
34
+ case "named":
35
+ importDeclaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([
36
+ factory.createImportSpecifier(false, parameters.alias ? factory.createIdentifier(parameters.import) : undefined, factory.createIdentifier(parameters.alias || parameters.import)),
37
+ ])), factory.createStringLiteral(parameters.from));
38
+ break;
39
+ case "namespace":
40
+ importDeclaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier(parameters.alias || parameters.import))), factory.createStringLiteral(parameters.from));
41
+ break;
42
+ case "side-effect":
43
+ importDeclaration = factory.createImportDeclaration(undefined, undefined, factory.createStringLiteral(parameters.from));
44
+ break;
45
+ default:
46
+ throw new Error(`Unsupported import type: ${parameters.importType}`);
47
+ }
48
+ // Check if import already exists
49
+ const existingImport = sourceFile.statements.find((statement) => ts.isImportDeclaration(statement) &&
50
+ ts.isStringLiteral(statement.moduleSpecifier) &&
51
+ statement.moduleSpecifier.text === parameters.from);
52
+ if (existingImport) {
53
+ // TODO: Merge with existing import if needed
54
+ debug("Import from %s already exists, skipping", parameters.from);
55
+ return sourceFile;
56
+ }
57
+ // Add import at the top (after other imports)
58
+ let lastImportIndex = -1;
59
+ for (let i = sourceFile.statements.length - 1; i >= 0; i--) {
60
+ if (ts.isImportDeclaration(sourceFile.statements[i])) {
61
+ lastImportIndex = i;
62
+ break;
63
+ }
64
+ }
65
+ const insertIndex = lastImportIndex >= 0 ? lastImportIndex + 1 : 0;
66
+ const newStatements = [...sourceFile.statements];
67
+ newStatements.splice(insertIndex, 0, importDeclaration);
68
+ return factory.updateSourceFile(sourceFile, newStatements);
69
+ }
70
+ /**
71
+ * Add export statement to TypeScript/JavaScript file
72
+ */
73
+ static addExport(sourceFile, parameters) {
74
+ if (!parameters.export) {
75
+ throw new Error("export parameter is required for add-export transformation");
76
+ }
77
+ const factory = ts.factory;
78
+ let exportDeclaration;
79
+ if (parameters.exportType === "default") {
80
+ exportDeclaration = factory.createExportAssignment(undefined, true, factory.createIdentifier(parameters.export));
81
+ }
82
+ else {
83
+ exportDeclaration = factory.createExportDeclaration(undefined, false, factory.createNamedExports([
84
+ factory.createExportSpecifier(false, undefined, factory.createIdentifier(parameters.export)),
85
+ ]));
86
+ }
87
+ // Add export at the end
88
+ const newStatements = [...sourceFile.statements, exportDeclaration];
89
+ return factory.updateSourceFile(sourceFile, newStatements);
90
+ }
91
+ /**
92
+ * Add property to class or object
93
+ */
94
+ static addProperty(sourceFile, parameters) {
95
+ if (!parameters.propertyName || !parameters.propertyValue) {
96
+ throw new Error("propertyName and propertyValue are required for add-property transformation");
97
+ }
98
+ const factory = ts.factory;
99
+ // Transform function to add property
100
+ const transformer = (context) => {
101
+ return (rootNode) => {
102
+ function visit(node) {
103
+ // Add to class
104
+ if (parameters.className &&
105
+ ts.isClassDeclaration(node) &&
106
+ node.name?.text === parameters.className) {
107
+ const newProperty = factory.createPropertyDeclaration(undefined, factory.createIdentifier(parameters.propertyName), undefined, parameters.propertyType
108
+ ? factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
109
+ : undefined, // Simplified
110
+ factory.createStringLiteral(parameters.propertyValue));
111
+ const newMembers = [...(node.members || []), newProperty];
112
+ return factory.updateClassDeclaration(node, node.modifiers, node.name, node.typeParameters, node.heritageClauses, newMembers);
113
+ }
114
+ // Add to object literal
115
+ if (parameters.objectName &&
116
+ ts.isVariableDeclaration(node) &&
117
+ ts.isIdentifier(node.name) &&
118
+ node.name.text === parameters.objectName &&
119
+ node.initializer &&
120
+ ts.isObjectLiteralExpression(node.initializer)) {
121
+ const newProperty = factory.createPropertyAssignment(factory.createIdentifier(parameters.propertyName), factory.createStringLiteral(parameters.propertyValue));
122
+ const newProperties = [...node.initializer.properties, newProperty];
123
+ const newInitializer = factory.updateObjectLiteralExpression(node.initializer, newProperties);
124
+ return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, newInitializer);
125
+ }
126
+ return ts.visitEachChild(node, visit, context);
127
+ }
128
+ return ts.visitNode(rootNode, visit);
129
+ };
130
+ };
131
+ const result = ts.transform(sourceFile, [transformer]);
132
+ const transformedFile = result.transformed[0];
133
+ result.dispose();
134
+ return transformedFile;
135
+ }
136
+ /**
137
+ * Replace text using regex or string matching
138
+ */
139
+ static replaceText(content, parameters) {
140
+ if (parameters.find === undefined || parameters.replace === undefined) {
141
+ throw new Error("find and replace parameters are required for replace-text transformation");
142
+ }
143
+ if (typeof parameters.find === "string") {
144
+ if (parameters.global) {
145
+ return content.split(parameters.find).join(parameters.replace);
146
+ }
147
+ return content.replace(parameters.find, parameters.replace);
148
+ }
149
+ // RegExp
150
+ const flags = parameters.global ? "g" : "";
151
+ const regex = new RegExp(parameters.find.source, flags);
152
+ return content.replace(regex, parameters.replace);
153
+ }
154
+ }
155
+ /**
156
+ * CodeMod Tool for AST transformations and code modifications
157
+ *
158
+ * Features:
159
+ * - TypeScript/JavaScript AST transformations using TypeScript compiler API
160
+ * - Text-based transformations for other file types
161
+ * - Built-in common transformations (add imports, exports, properties, etc.)
162
+ * - Custom transformation functions
163
+ * - Safe modification with backup and rollback
164
+ * - Batch processing with glob patterns
165
+ * - Comprehensive error handling and validation
166
+ */
167
+ export class CodeModTool extends Tool {
168
+ tsConfigCache = new Map();
169
+ transformationCache = new Map();
170
+ backupFiles = new Set();
171
+ constructor(name = "codemod-tool", options = {}) {
172
+ super("codemod", name, options);
173
+ }
174
+ /**
175
+ * Initialize CodeMod tool
176
+ */
177
+ async onInitialize() {
178
+ this.debug("Initializing CodeMod tool");
179
+ try {
180
+ // Register resource for cleanup of temporary files and caches
181
+ this.registerResource({
182
+ id: "tsconfig-cache",
183
+ type: "cache",
184
+ cleanup: () => {
185
+ this.tsConfigCache.clear();
186
+ },
187
+ metadata: { cacheSize: 0 },
188
+ });
189
+ this.registerResource({
190
+ id: "transformation-cache",
191
+ type: "cache",
192
+ cleanup: () => {
193
+ this.transformationCache.clear();
194
+ },
195
+ metadata: { cacheSize: 0 },
196
+ });
197
+ this.registerResource({
198
+ id: "backup-files",
199
+ type: "file",
200
+ cleanup: async () => {
201
+ // Clean up backup files if they exist and cleanup is configured
202
+ if (this.options.cleanupBackups) {
203
+ for (const backupPath of this.backupFiles) {
204
+ try {
205
+ if (await fs.pathExists(backupPath)) {
206
+ await fs.unlink(backupPath);
207
+ this.debug("Cleaned up backup file: %s", backupPath);
208
+ }
209
+ }
210
+ catch (error) {
211
+ this.debug("Failed to cleanup backup file %s: %s", backupPath, error);
212
+ }
213
+ }
214
+ }
215
+ this.backupFiles.clear();
216
+ },
217
+ metadata: { backupCount: 0 },
218
+ });
219
+ this.debug("CodeMod tool initialized successfully");
220
+ }
221
+ catch (error) {
222
+ throw ErrorHandler.createError(ErrorCode.INTERNAL_ERROR, `Failed to initialize CodeMod tool: ${error instanceof Error ? error.message : String(error)}`, { phase: "initialize", cause: error });
223
+ }
224
+ }
225
+ /**
226
+ * Validate CodeMod step configuration
227
+ */
228
+ async onValidate(step, context) {
229
+ const errors = [];
230
+ const warnings = [];
231
+ const suggestions = [];
232
+ // Validate step is a CodeMod step
233
+ if (!isCodeModStep(step)) {
234
+ errors.push("Step is not a valid CodeModStep");
235
+ return { isValid: false, errors, warnings, suggestions };
236
+ }
237
+ // Validate required fields
238
+ if (!step.codemod) {
239
+ errors.push("CodeMod identifier is required");
240
+ }
241
+ if (!step.files || step.files.length === 0) {
242
+ errors.push("File patterns are required");
243
+ }
244
+ // Validate CodeMod type
245
+ const validCodeModTypes = [
246
+ "add-import",
247
+ "add-export",
248
+ "modify-function",
249
+ "add-property",
250
+ "replace-text",
251
+ "custom",
252
+ ];
253
+ if (step.codemod && !validCodeModTypes.includes(step.codemod)) {
254
+ errors.push(`Invalid CodeMod type: ${step.codemod}. Must be one of: ${validCodeModTypes.join(", ")}`);
255
+ }
256
+ // Validate file patterns resolve to actual files
257
+ if (step.files) {
258
+ for (const pattern of step.files) {
259
+ try {
260
+ const resolvedPattern = path.resolve(context.projectRoot, pattern);
261
+ const matches = await this.globFiles(resolvedPattern);
262
+ if (matches.length === 0) {
263
+ warnings.push(`No files found matching pattern: ${pattern}`);
264
+ }
265
+ else {
266
+ this.debug("Pattern %s matches %d files", pattern, matches.length);
267
+ }
268
+ }
269
+ catch (error) {
270
+ errors.push(`Invalid file pattern: ${pattern}`);
271
+ }
272
+ }
273
+ }
274
+ // Validate parser setting
275
+ if (step.parser && !["typescript", "javascript", "json", "auto"].includes(step.parser)) {
276
+ errors.push(`Invalid parser: ${step.parser}. Must be 'typescript', 'javascript', 'json', or 'auto'`);
277
+ }
278
+ // Validate parameters based on CodeMod type
279
+ if (step.parameters) {
280
+ const params = step.parameters;
281
+ switch (step.codemod) {
282
+ case "add-import":
283
+ if (!params.import || !params.from) {
284
+ errors.push('add-import requires "import" and "from" parameters');
285
+ }
286
+ if (params.importType &&
287
+ !["default", "named", "namespace", "side-effect"].includes(params.importType)) {
288
+ errors.push("importType must be one of: default, named, namespace, side-effect");
289
+ }
290
+ break;
291
+ case "add-export":
292
+ if (!params.export) {
293
+ errors.push('add-export requires "export" parameter');
294
+ }
295
+ if (params.exportType && !["default", "named"].includes(params.exportType)) {
296
+ errors.push('exportType must be "default" or "named"');
297
+ }
298
+ break;
299
+ case "add-property":
300
+ if (!params.propertyName || !params.propertyValue) {
301
+ errors.push('add-property requires "propertyName" and "propertyValue" parameters');
302
+ }
303
+ if (!params.className && !params.objectName) {
304
+ errors.push('add-property requires either "className" or "objectName" parameter');
305
+ }
306
+ break;
307
+ case "replace-text":
308
+ if (params.find === undefined || params.replace === undefined) {
309
+ errors.push('replace-text requires "find" and "replace" parameters');
310
+ }
311
+ break;
312
+ case "custom":
313
+ if (!params.transformFunction) {
314
+ errors.push('custom CodeMod requires "transformFunction" parameter');
315
+ }
316
+ break;
317
+ }
318
+ }
319
+ // Performance and complexity warnings
320
+ if (step.files && step.files.length > 50) {
321
+ warnings.push("Large number of file patterns may impact performance");
322
+ suggestions.push("Consider using more specific patterns or processing files in batches");
323
+ }
324
+ // Backup recommendations
325
+ if (step.backup === false) {
326
+ warnings.push("Backup is disabled - consider enabling for safety");
327
+ suggestions.push("Set backup: true to create backup files before transformation");
328
+ }
329
+ // Estimate execution time
330
+ let estimatedTime = 200; // Base time in ms
331
+ if (step.files) {
332
+ estimatedTime += step.files.length * 100; // Time per file pattern
333
+ }
334
+ if (step.parser === "typescript") {
335
+ estimatedTime += 500; // TypeScript parsing overhead
336
+ }
337
+ const resourceRequirements = {
338
+ memory: 20 * 1024 * 1024, // 20MB for TypeScript compiler
339
+ disk: 1 * 1024 * 1024, // 1MB for backup files
340
+ network: false,
341
+ processes: 1,
342
+ };
343
+ return {
344
+ isValid: errors.length === 0,
345
+ errors,
346
+ warnings,
347
+ suggestions,
348
+ estimatedExecutionTime: estimatedTime,
349
+ resourceRequirements,
350
+ };
351
+ }
352
+ /**
353
+ * Execute the CodeMod tool
354
+ */
355
+ async onExecute(step, context, options) {
356
+ this.debug("Executing CodeMod step: %s -> %s", step.name, step.codemod);
357
+ const startTime = new Date();
358
+ const filesModified = [];
359
+ const transformationResults = [];
360
+ const allErrors = [];
361
+ const allWarnings = [];
362
+ try {
363
+ // Resolve all files matching the patterns
364
+ const filesToProcess = await this.resolveFiles(step.files, context.projectRoot);
365
+ this.debug("Found %d files to process", filesToProcess.length);
366
+ if (filesToProcess.length === 0) {
367
+ this.logger.warn("No files found matching the specified patterns");
368
+ }
369
+ // Process each file
370
+ for (const filePath of filesToProcess) {
371
+ try {
372
+ const result = await this.transformFile(filePath, step, context, options);
373
+ transformationResults.push(result);
374
+ if (result.modified) {
375
+ filesModified.push(filePath);
376
+ }
377
+ allErrors.push(...result.errors);
378
+ allWarnings.push(...result.warnings);
379
+ }
380
+ catch (error) {
381
+ const errorMessage = error instanceof Error ? error.message : String(error);
382
+ this.debug("Failed to transform file %s: %s", filePath, errorMessage);
383
+ allErrors.push(`Failed to transform ${filePath}: ${errorMessage}`);
384
+ }
385
+ }
386
+ const endTime = new Date();
387
+ const duration = endTime.getTime() - startTime.getTime();
388
+ // Update resource metadata
389
+ const backupResource = this.resources.get("backup-files");
390
+ if (backupResource) {
391
+ backupResource.metadata.backupCount = this.backupFiles.size;
392
+ }
393
+ // Create tool-specific result
394
+ const codemodResult = {
395
+ codemodName: step.codemod,
396
+ codemodPath: step.codemod, // Could be enhanced to support external codemods
397
+ filesProcessed: filesToProcess,
398
+ transformationsSummary: {
399
+ totalFiles: filesToProcess.length,
400
+ modifiedFiles: filesModified.length,
401
+ errors: allErrors.length,
402
+ },
403
+ backupFiles: Array.from(this.backupFiles),
404
+ };
405
+ const stepResult = {
406
+ status: allErrors.length > 0 ? "failed" : "completed",
407
+ stepName: step.name,
408
+ toolType: "codemod",
409
+ startTime,
410
+ endTime,
411
+ duration,
412
+ retryCount: 0,
413
+ dependenciesSatisfied: true,
414
+ toolResult: codemodResult,
415
+ filesCreated: [], // CodeMods typically don't create new files
416
+ filesModified,
417
+ filesDeleted: [], // CodeMods typically don't delete files
418
+ output: {
419
+ codemodType: step.codemod,
420
+ totalFiles: filesToProcess.length,
421
+ modifiedFiles: filesModified.length,
422
+ backupsCreated: this.backupFiles.size,
423
+ transformations: transformationResults.map((r) => ({
424
+ file: r.filePath,
425
+ modified: r.modified,
426
+ transformations: r.transformations,
427
+ })),
428
+ },
429
+ metadata: {
430
+ parser: filesToProcess.length > 0
431
+ ? this.determineParser(step.parser, filesToProcess[0])
432
+ : step.parser || "auto",
433
+ backupEnabled: step.backup !== false,
434
+ transformationResults: transformationResults.length > 10
435
+ ? transformationResults.slice(0, 10) // Truncate for large sets
436
+ : transformationResults,
437
+ cacheStats: {
438
+ tsConfigCacheSize: this.tsConfigCache.size,
439
+ transformationCacheSize: this.transformationCache.size,
440
+ },
441
+ },
442
+ };
443
+ // Add errors if any occurred
444
+ if (allErrors.length > 0) {
445
+ stepResult.error = {
446
+ message: `CodeMod transformation failed with ${allErrors.length} errors`,
447
+ code: "CODEMOD_TRANSFORMATION_FAILED",
448
+ };
449
+ }
450
+ return stepResult;
451
+ }
452
+ catch (error) {
453
+ const endTime = new Date();
454
+ const duration = endTime.getTime() - startTime.getTime();
455
+ return {
456
+ status: "failed",
457
+ stepName: step.name,
458
+ toolType: "codemod",
459
+ startTime,
460
+ endTime,
461
+ duration,
462
+ retryCount: 0,
463
+ dependenciesSatisfied: true,
464
+ filesCreated: [],
465
+ filesModified,
466
+ filesDeleted: [],
467
+ error: {
468
+ message: error instanceof Error ? error.message : String(error),
469
+ code: error instanceof HypergenError ? error.code : "CODEMOD_EXECUTION_ERROR",
470
+ stack: error instanceof Error ? error.stack : undefined,
471
+ cause: error,
472
+ },
473
+ metadata: {
474
+ codemodType: step.codemod,
475
+ partialResults: transformationResults,
476
+ },
477
+ };
478
+ }
479
+ }
480
+ /**
481
+ * Tool-specific cleanup logic
482
+ */
483
+ async onCleanup() {
484
+ this.debug("Cleaning up CodeMod tool resources");
485
+ // Cleanup is handled by registered resources
486
+ }
487
+ /**
488
+ * Resolve file patterns to actual file paths
489
+ */
490
+ async resolveFiles(patterns, projectRoot) {
491
+ const allFiles = new Set();
492
+ for (const pattern of patterns) {
493
+ const resolvedPattern = path.resolve(projectRoot, pattern);
494
+ const matches = await this.globFiles(resolvedPattern);
495
+ for (const file of matches) {
496
+ allFiles.add(file);
497
+ }
498
+ }
499
+ return Array.from(allFiles).sort();
500
+ }
501
+ /**
502
+ * Glob files with proper error handling
503
+ */
504
+ async globFiles(pattern) {
505
+ try {
506
+ return await glob(pattern, { absolute: true });
507
+ }
508
+ catch (error) {
509
+ this.debug("Glob pattern failed: %s - %s", pattern, error);
510
+ return [];
511
+ }
512
+ }
513
+ /**
514
+ * Transform a single file
515
+ */
516
+ async transformFile(filePath, step, context, options) {
517
+ this.debug("Transforming file: %s", filePath);
518
+ const originalContent = await fs.readFile(filePath, "utf8");
519
+ const parser = this.determineParser(step.parser, filePath);
520
+ const isDryRun = options?.dryRun || context.dryRun || false;
521
+ let transformedContent = originalContent;
522
+ let modified = false;
523
+ const errors = [];
524
+ const warnings = [];
525
+ const transformations = [];
526
+ try {
527
+ // Create transformation context
528
+ const transformContext = {
529
+ filePath,
530
+ sourceCode: originalContent,
531
+ stepContext: context,
532
+ parameters: (step.parameters || {}),
533
+ dryRun: isDryRun,
534
+ force: step.force || context.force || false,
535
+ };
536
+ // Apply transformation based on parser type and CodeMod type
537
+ if (parser === "typescript" || parser === "javascript") {
538
+ const result = await this.applyASTTransformation(transformContext, step, parser);
539
+ transformedContent = result.content;
540
+ modified = result.modified;
541
+ transformations.push(...result.transformations);
542
+ }
543
+ else {
544
+ const result = await this.applyTextTransformation(transformContext, step);
545
+ transformedContent = result.content;
546
+ modified = result.modified;
547
+ transformations.push(...result.transformations);
548
+ }
549
+ // Create backup if file was modified and backup is enabled
550
+ let backupPath;
551
+ if (modified && !isDryRun && step.backup !== false) {
552
+ backupPath = await this.createBackup(filePath, originalContent);
553
+ }
554
+ // Write transformed content if not dry run
555
+ if (modified && !isDryRun) {
556
+ await fs.writeFile(filePath, transformedContent, "utf8");
557
+ this.debug("File transformed successfully: %s", filePath);
558
+ }
559
+ else if (modified && isDryRun) {
560
+ this.debug("Dry run: would transform file %s", filePath);
561
+ }
562
+ return {
563
+ filePath,
564
+ modified,
565
+ originalContent,
566
+ transformedContent,
567
+ backupPath,
568
+ errors,
569
+ warnings,
570
+ transformations,
571
+ };
572
+ }
573
+ catch (error) {
574
+ const errorMessage = error instanceof Error ? error.message : String(error);
575
+ errors.push(`Transformation failed: ${errorMessage}`);
576
+ return {
577
+ filePath,
578
+ modified: false,
579
+ originalContent,
580
+ transformedContent: originalContent,
581
+ errors,
582
+ warnings,
583
+ transformations,
584
+ };
585
+ }
586
+ }
587
+ /**
588
+ * Apply AST-based transformation for TypeScript/JavaScript files
589
+ */
590
+ async applyASTTransformation(context, step, parser) {
591
+ const transformations = [];
592
+ try {
593
+ // Parse source file
594
+ const sourceFile = ts.createSourceFile(context.filePath, context.sourceCode, parser === "typescript" ? ts.ScriptTarget.Latest : ts.ScriptTarget.ES2015, true, parser === "typescript" ? ts.ScriptKind.TS : ts.ScriptKind.JS);
595
+ // Check for basic syntax errors
596
+ if (step.codemodConfig?.validation?.validateSyntax !== false) {
597
+ // Simple check for obvious syntax errors like unmatched braces
598
+ const openBraces = (context.sourceCode.match(/\{/g) || []).length;
599
+ const closeBraces = (context.sourceCode.match(/\}/g) || []).length;
600
+ if (openBraces !== closeBraces) {
601
+ throw new Error("Unmatched braces detected in source code");
602
+ }
603
+ // Check for basic TypeScript/JavaScript syntax issues
604
+ if (context.sourceCode.includes("{{{") || context.sourceCode.includes("}}}")) {
605
+ throw new Error("Invalid brace syntax detected");
606
+ }
607
+ }
608
+ context.sourceFile = sourceFile;
609
+ let transformedFile = sourceFile;
610
+ // Apply built-in transformations
611
+ switch (step.codemod) {
612
+ case "add-import":
613
+ transformedFile = CodeModTransformations.addImport(transformedFile, context.parameters);
614
+ transformations.push({
615
+ type: "add-import",
616
+ description: `Added import ${context.parameters.import} from ${context.parameters.from}`,
617
+ line: 1,
618
+ });
619
+ break;
620
+ case "add-export":
621
+ transformedFile = CodeModTransformations.addExport(transformedFile, context.parameters);
622
+ transformations.push({
623
+ type: "add-export",
624
+ description: `Added export ${context.parameters.export}`,
625
+ line: transformedFile.statements.length,
626
+ });
627
+ break;
628
+ case "add-property":
629
+ transformedFile = CodeModTransformations.addProperty(transformedFile, context.parameters);
630
+ transformations.push({
631
+ type: "add-property",
632
+ description: `Added property ${context.parameters.propertyName} to ${context.parameters.className || context.parameters.objectName}`,
633
+ });
634
+ break;
635
+ case "custom":
636
+ if (typeof context.parameters.transformFunction === "function") {
637
+ transformedFile = context.parameters.transformFunction(transformedFile, context);
638
+ transformations.push({
639
+ type: "custom",
640
+ description: "Applied custom transformation function",
641
+ });
642
+ }
643
+ else if (typeof context.parameters.transformFunction === "string") {
644
+ // Execute custom transformation function from string (with safety considerations)
645
+ transformations.push({
646
+ type: "custom",
647
+ description: "Custom transformation function not executed (string functions not supported for security)",
648
+ });
649
+ this.logger.warn("String-based custom transformation functions are not supported for security reasons");
650
+ }
651
+ break;
652
+ default:
653
+ throw new Error(`Unsupported AST transformation type: ${step.codemod}`);
654
+ }
655
+ // Generate transformed content
656
+ const printer = ts.createPrinter({
657
+ newLine: ts.NewLineKind.LineFeed,
658
+ removeComments: !step.codemodConfig?.transform?.includeComments,
659
+ });
660
+ const transformedContent = printer.printFile(transformedFile);
661
+ const modified = transformedContent !== context.sourceCode;
662
+ return {
663
+ content: transformedContent,
664
+ modified,
665
+ transformations,
666
+ };
667
+ }
668
+ catch (error) {
669
+ throw ErrorHandler.createError(ErrorCode.CODEMOD_TRANSFORMATION_FAILED, `AST transformation failed: ${error instanceof Error ? error.message : String(error)}`, {
670
+ filePath: context.filePath,
671
+ codemodType: step.codemod,
672
+ parser,
673
+ cause: error,
674
+ });
675
+ }
676
+ }
677
+ /**
678
+ * Apply text-based transformation for non-TypeScript files
679
+ */
680
+ async applyTextTransformation(context, step) {
681
+ const transformations = [];
682
+ let transformedContent = context.sourceCode;
683
+ try {
684
+ switch (step.codemod) {
685
+ case "replace-text": {
686
+ const originalContent = transformedContent;
687
+ transformedContent = CodeModTransformations.replaceText(transformedContent, context.parameters);
688
+ if (transformedContent !== originalContent) {
689
+ transformations.push({
690
+ type: "replace-text",
691
+ description: `Replaced "${context.parameters.find}" with "${context.parameters.replace}"`,
692
+ });
693
+ }
694
+ break;
695
+ }
696
+ default:
697
+ throw new Error(`Unsupported text transformation type: ${step.codemod}`);
698
+ }
699
+ return {
700
+ content: transformedContent,
701
+ modified: transformedContent !== context.sourceCode,
702
+ transformations,
703
+ };
704
+ }
705
+ catch (error) {
706
+ throw ErrorHandler.createError(ErrorCode.CODEMOD_TRANSFORMATION_FAILED, `Text transformation failed: ${error instanceof Error ? error.message : String(error)}`, {
707
+ filePath: context.filePath,
708
+ codemodType: step.codemod,
709
+ cause: error,
710
+ });
711
+ }
712
+ }
713
+ /**
714
+ * Determine parser to use for file
715
+ */
716
+ determineParser(specifiedParser, filePath) {
717
+ if (specifiedParser && specifiedParser !== "auto") {
718
+ return specifiedParser;
719
+ }
720
+ const ext = path.extname(filePath).toLowerCase();
721
+ switch (ext) {
722
+ case ".ts":
723
+ case ".tsx":
724
+ return "typescript";
725
+ case ".js":
726
+ case ".jsx":
727
+ case ".mjs":
728
+ return "javascript";
729
+ case ".json":
730
+ return "json";
731
+ default:
732
+ return "text";
733
+ }
734
+ }
735
+ /**
736
+ * Create backup of file before transformation
737
+ */
738
+ async createBackup(filePath, content) {
739
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
740
+ const backupPath = `${filePath}.backup.${timestamp}`;
741
+ await fs.writeFile(backupPath, content, "utf8");
742
+ this.backupFiles.add(backupPath);
743
+ this.debug("Created backup: %s", backupPath);
744
+ return backupPath;
745
+ }
746
+ }
747
+ /**
748
+ * CodeMod Tool Factory
749
+ */
750
+ export class CodeModToolFactory {
751
+ create(name = "codemod-tool", options = {}) {
752
+ return new CodeModTool(name, options);
753
+ }
754
+ getToolType() {
755
+ return "codemod";
756
+ }
757
+ validateConfig(config) {
758
+ const errors = [];
759
+ const warnings = [];
760
+ const suggestions = [];
761
+ // Validate backup cleanup setting
762
+ if (config.cleanupBackups !== undefined && typeof config.cleanupBackups !== "boolean") {
763
+ warnings.push("cleanupBackups should be a boolean");
764
+ }
765
+ // Validate cache settings
766
+ if (config.enableCaching !== undefined && typeof config.enableCaching !== "boolean") {
767
+ warnings.push("enableCaching should be a boolean");
768
+ }
769
+ // Performance suggestions
770
+ if (!config.enableCaching) {
771
+ suggestions.push("Consider enabling caching for better performance with repeated transformations");
772
+ }
773
+ if (config.cleanupBackups === undefined) {
774
+ suggestions.push("Consider setting cleanupBackups to control backup file cleanup behavior");
775
+ }
776
+ return {
777
+ isValid: errors.length === 0,
778
+ errors,
779
+ warnings,
780
+ suggestions,
781
+ };
782
+ }
783
+ }
784
+ // Export default instance
785
+ export const codemodToolFactory = new CodeModToolFactory();
786
+ //# sourceMappingURL=codemod-tool.js.map