@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.
- package/LICENSE +21 -0
- package/README.md +24 -0
- package/dist/actions/communication.d.ts +201 -0
- package/dist/actions/communication.d.ts.map +1 -0
- package/dist/actions/communication.js +515 -0
- package/dist/actions/communication.js.map +1 -0
- package/dist/actions/decorator.d.ts +22 -0
- package/dist/actions/decorator.d.ts.map +1 -0
- package/dist/actions/decorator.js +110 -0
- package/dist/actions/decorator.js.map +1 -0
- package/dist/actions/executor.d.ts +85 -0
- package/dist/actions/executor.d.ts.map +1 -0
- package/dist/actions/executor.js +289 -0
- package/dist/actions/executor.js.map +1 -0
- package/dist/actions/index.d.ts +14 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +15 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/parameter-resolver.d.ts +54 -0
- package/dist/actions/parameter-resolver.d.ts.map +1 -0
- package/dist/actions/parameter-resolver.js +300 -0
- package/dist/actions/parameter-resolver.js.map +1 -0
- package/dist/actions/registry.d.ts +78 -0
- package/dist/actions/registry.d.ts.map +1 -0
- package/dist/actions/registry.js +221 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/actions/types.d.ts +109 -0
- package/dist/actions/types.d.ts.map +1 -0
- package/dist/actions/types.js +31 -0
- package/dist/actions/types.js.map +1 -0
- package/dist/actions/utils.d.ts +42 -0
- package/dist/actions/utils.d.ts.map +1 -0
- package/dist/actions/utils.js +144 -0
- package/dist/actions/utils.js.map +1 -0
- package/dist/ai/ai-collector.d.ts +52 -0
- package/dist/ai/ai-collector.d.ts.map +1 -0
- package/dist/ai/ai-collector.js +64 -0
- package/dist/ai/ai-collector.js.map +1 -0
- package/dist/ai/ai-config.d.ts +230 -0
- package/dist/ai/ai-config.d.ts.map +1 -0
- package/dist/ai/ai-config.js +8 -0
- package/dist/ai/ai-config.js.map +1 -0
- package/dist/ai/ai-service.d.ts +66 -0
- package/dist/ai/ai-service.d.ts.map +1 -0
- package/dist/ai/ai-service.js +198 -0
- package/dist/ai/ai-service.js.map +1 -0
- package/dist/ai/ai-variable-resolver.d.ts +59 -0
- package/dist/ai/ai-variable-resolver.d.ts.map +1 -0
- package/dist/ai/ai-variable-resolver.js +219 -0
- package/dist/ai/ai-variable-resolver.js.map +1 -0
- package/dist/ai/context-collector.d.ts +30 -0
- package/dist/ai/context-collector.d.ts.map +1 -0
- package/dist/ai/context-collector.js +158 -0
- package/dist/ai/context-collector.js.map +1 -0
- package/dist/ai/cost-tracker.d.ts +41 -0
- package/dist/ai/cost-tracker.d.ts.map +1 -0
- package/dist/ai/cost-tracker.js +131 -0
- package/dist/ai/cost-tracker.js.map +1 -0
- package/dist/ai/env.d.ts +36 -0
- package/dist/ai/env.d.ts.map +1 -0
- package/dist/ai/env.js +100 -0
- package/dist/ai/env.js.map +1 -0
- package/dist/ai/index.d.ts +17 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +25 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/model-router.d.ts +32 -0
- package/dist/ai/model-router.d.ts.map +1 -0
- package/dist/ai/model-router.js +113 -0
- package/dist/ai/model-router.js.map +1 -0
- package/dist/ai/output-validator.d.ts +24 -0
- package/dist/ai/output-validator.d.ts.map +1 -0
- package/dist/ai/output-validator.js +279 -0
- package/dist/ai/output-validator.js.map +1 -0
- package/dist/ai/prompt-assembler.d.ts +30 -0
- package/dist/ai/prompt-assembler.d.ts.map +1 -0
- package/dist/ai/prompt-assembler.js +93 -0
- package/dist/ai/prompt-assembler.js.map +1 -0
- package/dist/ai/prompt-pipeline.d.ts +63 -0
- package/dist/ai/prompt-pipeline.d.ts.map +1 -0
- package/dist/ai/prompt-pipeline.js +119 -0
- package/dist/ai/prompt-pipeline.js.map +1 -0
- package/dist/ai/prompt-template.jig +88 -0
- package/dist/ai/transports/api-transport.d.ts +12 -0
- package/dist/ai/transports/api-transport.d.ts.map +1 -0
- package/dist/ai/transports/api-transport.js +86 -0
- package/dist/ai/transports/api-transport.js.map +1 -0
- package/dist/ai/transports/command-transport.d.ts +20 -0
- package/dist/ai/transports/command-transport.d.ts.map +1 -0
- package/dist/ai/transports/command-transport.js +203 -0
- package/dist/ai/transports/command-transport.js.map +1 -0
- package/dist/ai/transports/index.d.ts +11 -0
- package/dist/ai/transports/index.d.ts.map +1 -0
- package/dist/ai/transports/index.js +10 -0
- package/dist/ai/transports/index.js.map +1 -0
- package/dist/ai/transports/resolve-transport.d.ts +15 -0
- package/dist/ai/transports/resolve-transport.d.ts.map +1 -0
- package/dist/ai/transports/resolve-transport.js +96 -0
- package/dist/ai/transports/resolve-transport.js.map +1 -0
- package/dist/ai/transports/stdout-transport.d.ts +14 -0
- package/dist/ai/transports/stdout-transport.d.ts.map +1 -0
- package/dist/ai/transports/stdout-transport.js +27 -0
- package/dist/ai/transports/stdout-transport.js.map +1 -0
- package/dist/ai/transports/types.d.ts +77 -0
- package/dist/ai/transports/types.d.ts.map +1 -0
- package/dist/ai/transports/types.js +8 -0
- package/dist/ai/transports/types.js.map +1 -0
- package/dist/commands/cookbook/info.d.ts +22 -0
- package/dist/commands/cookbook/info.d.ts.map +1 -0
- package/dist/commands/cookbook/info.js +217 -0
- package/dist/commands/cookbook/info.js.map +1 -0
- package/dist/commands/cookbook/list.d.ts +20 -0
- package/dist/commands/cookbook/list.d.ts.map +1 -0
- package/dist/commands/cookbook/list.js +133 -0
- package/dist/commands/cookbook/list.js.map +1 -0
- package/dist/commands/gen.d.ts +65 -0
- package/dist/commands/gen.d.ts.map +1 -0
- package/dist/commands/gen.js +478 -0
- package/dist/commands/gen.js.map +1 -0
- package/dist/commands/recipe/info.d.ts +18 -0
- package/dist/commands/recipe/info.d.ts.map +1 -0
- package/dist/commands/recipe/info.js +89 -0
- package/dist/commands/recipe/info.js.map +1 -0
- package/dist/commands/recipe/list.d.ts +29 -0
- package/dist/commands/recipe/list.d.ts.map +1 -0
- package/dist/commands/recipe/list.js +215 -0
- package/dist/commands/recipe/list.js.map +1 -0
- package/dist/commands/recipe/run.d.ts +44 -0
- package/dist/commands/recipe/run.d.ts.map +1 -0
- package/dist/commands/recipe/run.js +239 -0
- package/dist/commands/recipe/run.js.map +1 -0
- package/dist/commands/recipe/validate.d.ts +19 -0
- package/dist/commands/recipe/validate.d.ts.map +1 -0
- package/dist/commands/recipe/validate.js +66 -0
- package/dist/commands/recipe/validate.js.map +1 -0
- package/dist/discovery/generator-discovery.d.ts +130 -0
- package/dist/discovery/generator-discovery.d.ts.map +1 -0
- package/dist/discovery/generator-discovery.js +674 -0
- package/dist/discovery/generator-discovery.js.map +1 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +7 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/hooks/command-not-found.d.ts +18 -0
- package/dist/hooks/command-not-found.d.ts.map +1 -0
- package/dist/hooks/command-not-found.js +182 -0
- package/dist/hooks/command-not-found.js.map +1 -0
- package/dist/hooks/suggest.d.ts +13 -0
- package/dist/hooks/suggest.d.ts.map +1 -0
- package/dist/hooks/suggest.js +28 -0
- package/dist/hooks/suggest.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/base-command.d.ts +26 -0
- package/dist/lib/base-command.d.ts.map +1 -0
- package/dist/lib/base-command.js +24 -0
- package/dist/lib/base-command.js.map +1 -0
- package/dist/lib/flags.d.ts +33 -0
- package/dist/lib/flags.d.ts.map +1 -0
- package/dist/lib/flags.js +64 -0
- package/dist/lib/flags.js.map +1 -0
- package/dist/ops/add.d.ts +4 -0
- package/dist/ops/add.d.ts.map +1 -0
- package/dist/ops/add.js +85 -0
- package/dist/ops/add.js.map +1 -0
- package/dist/ops/inject.d.ts +4 -0
- package/dist/ops/inject.d.ts.map +1 -0
- package/dist/ops/inject.js +28 -0
- package/dist/ops/inject.js.map +1 -0
- package/dist/ops/injector.d.ts +4 -0
- package/dist/ops/injector.d.ts.map +1 -0
- package/dist/ops/injector.js +68 -0
- package/dist/ops/injector.js.map +1 -0
- package/dist/ops/result.d.ts +3 -0
- package/dist/ops/result.d.ts.map +1 -0
- package/dist/ops/result.js +8 -0
- package/dist/ops/result.js.map +1 -0
- package/dist/prompts/interactive-prompts.d.ts +152 -0
- package/dist/prompts/interactive-prompts.d.ts.map +1 -0
- package/dist/prompts/interactive-prompts.js +574 -0
- package/dist/prompts/interactive-prompts.js.map +1 -0
- package/dist/recipe-engine/group-executor.d.ts +97 -0
- package/dist/recipe-engine/group-executor.d.ts.map +1 -0
- package/dist/recipe-engine/group-executor.js +293 -0
- package/dist/recipe-engine/group-executor.js.map +1 -0
- package/dist/recipe-engine/index.d.ts +112 -0
- package/dist/recipe-engine/index.d.ts.map +1 -0
- package/dist/recipe-engine/index.js +223 -0
- package/dist/recipe-engine/index.js.map +1 -0
- package/dist/recipe-engine/output-evaluator.d.ts +28 -0
- package/dist/recipe-engine/output-evaluator.d.ts.map +1 -0
- package/dist/recipe-engine/output-evaluator.js +78 -0
- package/dist/recipe-engine/output-evaluator.js.map +1 -0
- package/dist/recipe-engine/recipe-engine.d.ts +227 -0
- package/dist/recipe-engine/recipe-engine.d.ts.map +1 -0
- package/dist/recipe-engine/recipe-engine.js +1036 -0
- package/dist/recipe-engine/recipe-engine.js.map +1 -0
- package/dist/recipe-engine/step-executor.d.ts +172 -0
- package/dist/recipe-engine/step-executor.d.ts.map +1 -0
- package/dist/recipe-engine/step-executor.js +802 -0
- package/dist/recipe-engine/step-executor.js.map +1 -0
- package/dist/recipe-engine/tools/action-tool.d.ts +103 -0
- package/dist/recipe-engine/tools/action-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/action-tool.js +473 -0
- package/dist/recipe-engine/tools/action-tool.js.map +1 -0
- package/dist/recipe-engine/tools/ai-tool.d.ts +26 -0
- package/dist/recipe-engine/tools/ai-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/ai-tool.js +233 -0
- package/dist/recipe-engine/tools/ai-tool.js.map +1 -0
- package/dist/recipe-engine/tools/base.d.ts +214 -0
- package/dist/recipe-engine/tools/base.d.ts.map +1 -0
- package/dist/recipe-engine/tools/base.js +397 -0
- package/dist/recipe-engine/tools/base.js.map +1 -0
- package/dist/recipe-engine/tools/codemod-tool.d.ts +130 -0
- package/dist/recipe-engine/tools/codemod-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/codemod-tool.js +786 -0
- package/dist/recipe-engine/tools/codemod-tool.js.map +1 -0
- package/dist/recipe-engine/tools/ensure-dirs-tool.d.ts +21 -0
- package/dist/recipe-engine/tools/ensure-dirs-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/ensure-dirs-tool.js +130 -0
- package/dist/recipe-engine/tools/ensure-dirs-tool.js.map +1 -0
- package/dist/recipe-engine/tools/index.d.ts +126 -0
- package/dist/recipe-engine/tools/index.d.ts.map +1 -0
- package/dist/recipe-engine/tools/index.js +290 -0
- package/dist/recipe-engine/tools/index.js.map +1 -0
- package/dist/recipe-engine/tools/install-tool.d.ts +20 -0
- package/dist/recipe-engine/tools/install-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/install-tool.js +194 -0
- package/dist/recipe-engine/tools/install-tool.js.map +1 -0
- package/dist/recipe-engine/tools/parallel-tool.d.ts +21 -0
- package/dist/recipe-engine/tools/parallel-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/parallel-tool.js +134 -0
- package/dist/recipe-engine/tools/parallel-tool.js.map +1 -0
- package/dist/recipe-engine/tools/patch-tool.d.ts +21 -0
- package/dist/recipe-engine/tools/patch-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/patch-tool.js +248 -0
- package/dist/recipe-engine/tools/patch-tool.js.map +1 -0
- package/dist/recipe-engine/tools/prompt-tool.d.ts +25 -0
- package/dist/recipe-engine/tools/prompt-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/prompt-tool.js +162 -0
- package/dist/recipe-engine/tools/prompt-tool.js.map +1 -0
- package/dist/recipe-engine/tools/query-tool.d.ts +21 -0
- package/dist/recipe-engine/tools/query-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/query-tool.js +249 -0
- package/dist/recipe-engine/tools/query-tool.js.map +1 -0
- package/dist/recipe-engine/tools/recipe-tool.d.ts +103 -0
- package/dist/recipe-engine/tools/recipe-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/recipe-tool.js +617 -0
- package/dist/recipe-engine/tools/recipe-tool.js.map +1 -0
- package/dist/recipe-engine/tools/registry.d.ts +151 -0
- package/dist/recipe-engine/tools/registry.d.ts.map +1 -0
- package/dist/recipe-engine/tools/registry.js +244 -0
- package/dist/recipe-engine/tools/registry.js.map +1 -0
- package/dist/recipe-engine/tools/sequence-tool.d.ts +22 -0
- package/dist/recipe-engine/tools/sequence-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/sequence-tool.js +122 -0
- package/dist/recipe-engine/tools/sequence-tool.js.map +1 -0
- package/dist/recipe-engine/tools/shell-tool.d.ts +25 -0
- package/dist/recipe-engine/tools/shell-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/shell-tool.js +149 -0
- package/dist/recipe-engine/tools/shell-tool.js.map +1 -0
- package/dist/recipe-engine/tools/template-tool.d.ts +88 -0
- package/dist/recipe-engine/tools/template-tool.d.ts.map +1 -0
- package/dist/recipe-engine/tools/template-tool.js +613 -0
- package/dist/recipe-engine/tools/template-tool.js.map +1 -0
- package/dist/recipe-engine/types.d.ts +963 -0
- package/dist/recipe-engine/types.d.ts.map +1 -0
- package/dist/recipe-engine/types.js +94 -0
- package/dist/recipe-engine/types.js.map +1 -0
- package/dist/template-engines/ai-tags.d.ts +26 -0
- package/dist/template-engines/ai-tags.d.ts.map +1 -0
- package/dist/template-engines/ai-tags.js +233 -0
- package/dist/template-engines/ai-tags.js.map +1 -0
- package/dist/template-engines/index.d.ts +8 -0
- package/dist/template-engines/index.d.ts.map +1 -0
- package/dist/template-engines/index.js +8 -0
- package/dist/template-engines/index.js.map +1 -0
- package/dist/template-engines/jig-engine.d.ts +47 -0
- package/dist/template-engines/jig-engine.d.ts.map +1 -0
- package/dist/template-engines/jig-engine.js +173 -0
- package/dist/template-engines/jig-engine.js.map +1 -0
- package/dist/utils/coerce-value.d.ts +7 -0
- package/dist/utils/coerce-value.d.ts.map +1 -0
- package/dist/utils/coerce-value.js +18 -0
- package/dist/utils/coerce-value.js.map +1 -0
- package/dist/utils/diff.d.ts +8 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +10 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/global-packages.d.ts +11 -0
- package/dist/utils/global-packages.d.ts.map +1 -0
- package/dist/utils/global-packages.js +88 -0
- package/dist/utils/global-packages.js.map +1 -0
- package/dist/utils/pager.d.ts +6 -0
- package/dist/utils/pager.d.ts.map +1 -0
- package/dist/utils/pager.js +41 -0
- package/dist/utils/pager.js.map +1 -0
- package/help/cookbook/info.md +35 -0
- package/help/cookbook/list.md +37 -0
- package/help/gen.md +26 -0
- package/help/recipe/run.md +52 -0
- package/help/recipe/validate.md +51 -0
- package/oclif.manifest.json +580 -0
- 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
|