@hypercli/core 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 +11 -0
- package/dist/config/config-loader.d.ts +117 -0
- package/dist/config/config-loader.d.ts.map +1 -0
- package/dist/config/config-loader.js +287 -0
- package/dist/config/config-loader.js.map +1 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/load-helpers.d.ts +13 -0
- package/dist/config/load-helpers.d.ts.map +1 -0
- package/dist/config/load-helpers.js +67 -0
- package/dist/config/load-helpers.js.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors/hypergen-errors.d.ts +162 -0
- package/dist/errors/hypergen-errors.d.ts.map +1 -0
- package/dist/errors/hypergen-errors.js +944 -0
- package/dist/errors/hypergen-errors.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/helpers.d.ts +25 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +20 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/cli-html-types.d.ts +36 -0
- package/dist/logger/cli-html-types.d.ts.map +1 -0
- package/dist/logger/cli-html-types.js +2 -0
- package/dist/logger/cli-html-types.js.map +1 -0
- package/dist/logger/index.d.ts +3 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +3 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.d.ts +17 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +42 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/logger/types.d.ts +16 -0
- package/dist/logger/types.d.ts.map +1 -0
- package/dist/logger/types.js +2 -0
- package/dist/logger/types.js.map +1 -0
- package/dist/parsers/cookbook-parser.d.ts +30 -0
- package/dist/parsers/cookbook-parser.d.ts.map +1 -0
- package/dist/parsers/cookbook-parser.js +155 -0
- package/dist/parsers/cookbook-parser.js.map +1 -0
- package/dist/parsers/index.d.ts +10 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +16 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/kit-parser.d.ts +44 -0
- package/dist/parsers/kit-parser.d.ts.map +1 -0
- package/dist/parsers/kit-parser.js +195 -0
- package/dist/parsers/kit-parser.js.map +1 -0
- package/dist/parsers/path-resolver.d.ts +72 -0
- package/dist/parsers/path-resolver.d.ts.map +1 -0
- package/dist/parsers/path-resolver.js +281 -0
- package/dist/parsers/path-resolver.js.map +1 -0
- package/dist/parsers/template-parser.d.ts +142 -0
- package/dist/parsers/template-parser.d.ts.map +1 -0
- package/dist/parsers/template-parser.js +896 -0
- package/dist/parsers/template-parser.js.map +1 -0
- package/dist/types/actions.d.ts +109 -0
- package/dist/types/actions.d.ts.map +1 -0
- package/dist/types/actions.js +31 -0
- package/dist/types/actions.js.map +1 -0
- package/dist/types/ai-config.d.ts +170 -0
- package/dist/types/ai-config.d.ts.map +1 -0
- package/dist/types/ai-config.js +10 -0
- package/dist/types/ai-config.js.map +1 -0
- package/dist/types/common.d.ts +24 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +10 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/kit.d.ts +92 -0
- package/dist/types/kit.d.ts.map +1 -0
- package/dist/types/kit.js +17 -0
- package/dist/types/kit.js.map +1 -0
- package/dist/types/recipe.d.ts +953 -0
- package/dist/types/recipe.d.ts.map +1 -0
- package/dist/types/recipe.js +92 -0
- package/dist/types/recipe.js.map +1 -0
- package/dist/types/template.d.ts +49 -0
- package/dist/types/template.d.ts.map +1 -0
- package/dist/types/template.js +7 -0
- package/dist/types/template.js.map +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +4 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/messages.d.ts +16 -0
- package/dist/ui/messages.d.ts.map +1 -0
- package/dist/ui/messages.js +57 -0
- package/dist/ui/messages.js.map +1 -0
- package/dist/ui/styles.d.ts +7 -0
- package/dist/ui/styles.d.ts.map +1 -0
- package/dist/ui/styles.js +8 -0
- package/dist/ui/styles.js.map +1 -0
- package/dist/ui/symbols.d.ts +9 -0
- package/dist/ui/symbols.d.ts.map +1 -0
- package/dist/ui/symbols.js +10 -0
- package/dist/ui/symbols.js.map +1 -0
- package/dist/utils/display-utils.d.ts +65 -0
- package/dist/utils/display-utils.d.ts.map +1 -0
- package/dist/utils/display-utils.js +101 -0
- package/dist/utils/display-utils.js.map +1 -0
- package/dist/utils/find-project-root.d.ts +32 -0
- package/dist/utils/find-project-root.d.ts.map +1 -0
- package/dist/utils/find-project-root.js +133 -0
- package/dist/utils/find-project-root.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/newline.d.ts +3 -0
- package/dist/utils/newline.d.ts.map +1 -0
- package/dist/utils/newline.js +12 -0
- package/dist/utils/newline.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,896 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template.yml Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses and validates template.yml files to extract action definitions
|
|
5
|
+
* and variable configurations for code generation
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import yaml from "js-yaml";
|
|
10
|
+
export class TemplateParser {
|
|
11
|
+
static SUPPORTED_VERSIONS = ["1.0.0"];
|
|
12
|
+
static VALID_VARIABLE_TYPES = [
|
|
13
|
+
"string",
|
|
14
|
+
"number",
|
|
15
|
+
"boolean",
|
|
16
|
+
"enum",
|
|
17
|
+
"array",
|
|
18
|
+
"object",
|
|
19
|
+
"file",
|
|
20
|
+
"directory",
|
|
21
|
+
];
|
|
22
|
+
static VALID_TOOL_TYPES = [
|
|
23
|
+
"template",
|
|
24
|
+
"action",
|
|
25
|
+
"codemod",
|
|
26
|
+
"recipe",
|
|
27
|
+
"shell",
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Parse a template.yml file and return validated configuration
|
|
31
|
+
*/
|
|
32
|
+
static async parseTemplateFile(filePath) {
|
|
33
|
+
const result = {
|
|
34
|
+
config: {},
|
|
35
|
+
filePath,
|
|
36
|
+
isValid: false,
|
|
37
|
+
errors: [],
|
|
38
|
+
warnings: [],
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
// Check if file exists
|
|
42
|
+
if (!fs.existsSync(filePath)) {
|
|
43
|
+
result.errors.push(`Template file not found: ${filePath}`);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
// Read and parse YAML
|
|
47
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
48
|
+
const parsed = yaml.load(content);
|
|
49
|
+
if (!parsed || typeof parsed !== "object") {
|
|
50
|
+
result.errors.push("Invalid YAML format or empty file");
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
// Validate and build config
|
|
54
|
+
result.config = TemplateParser.validateAndBuildConfig(parsed, result.errors, result.warnings);
|
|
55
|
+
result.isValid = result.errors.length === 0;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
result.errors.push(`Failed to parse template file: ${error.message}`);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse all template.yml files in a directory
|
|
65
|
+
*/
|
|
66
|
+
static async parseTemplateDirectory(dirPath) {
|
|
67
|
+
const results = [];
|
|
68
|
+
try {
|
|
69
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
const templatePath = path.join(dirPath, entry.name, "template.yml");
|
|
73
|
+
if (fs.existsSync(templatePath)) {
|
|
74
|
+
const parsed = await TemplateParser.parseTemplateFile(templatePath);
|
|
75
|
+
results.push(parsed);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Return empty array if directory doesn't exist or can't be read
|
|
82
|
+
console.warn(`Warning: Could not read template directory: ${dirPath}`);
|
|
83
|
+
}
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Validate template configuration and build normalized config object
|
|
88
|
+
*/
|
|
89
|
+
static validateAndBuildConfig(parsed, errors, warnings) {
|
|
90
|
+
const config = {
|
|
91
|
+
name: "",
|
|
92
|
+
variables: {},
|
|
93
|
+
};
|
|
94
|
+
// Validate required fields
|
|
95
|
+
if (!parsed.name || typeof parsed.name !== "string") {
|
|
96
|
+
errors.push("Template name is required and must be a string");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
config.name = parsed.name;
|
|
100
|
+
}
|
|
101
|
+
if (!parsed.variables || typeof parsed.variables !== "object") {
|
|
102
|
+
errors.push("Template variables section is required and must be an object");
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
TemplateParser.validateVariables(parsed.variables, config, errors, warnings);
|
|
106
|
+
}
|
|
107
|
+
// Validate optional fields
|
|
108
|
+
if (parsed.description && typeof parsed.description === "string") {
|
|
109
|
+
config.description = parsed.description;
|
|
110
|
+
}
|
|
111
|
+
if (parsed.version) {
|
|
112
|
+
if (typeof parsed.version !== "string") {
|
|
113
|
+
warnings.push("Template version should be a string");
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
config.version = parsed.version;
|
|
117
|
+
if (!TemplateParser.SUPPORTED_VERSIONS.includes(parsed.version)) {
|
|
118
|
+
warnings.push(`Unsupported template version: ${parsed.version}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (parsed.author && typeof parsed.author === "string") {
|
|
123
|
+
config.author = parsed.author;
|
|
124
|
+
}
|
|
125
|
+
if (parsed.category && typeof parsed.category === "string") {
|
|
126
|
+
config.category = parsed.category;
|
|
127
|
+
}
|
|
128
|
+
if (parsed.tags) {
|
|
129
|
+
if (Array.isArray(parsed.tags)) {
|
|
130
|
+
config.tags = parsed.tags.filter((tag) => typeof tag === "string");
|
|
131
|
+
if (config.tags && config.tags.length !== parsed.tags.length) {
|
|
132
|
+
warnings.push("Some tags were ignored (must be strings)");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
warnings.push("Tags should be an array of strings");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (parsed.examples) {
|
|
140
|
+
if (Array.isArray(parsed.examples)) {
|
|
141
|
+
config.examples = TemplateParser.validateExamples(parsed.examples, config.variables, warnings);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
warnings.push("Examples should be an array");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (parsed.outputs) {
|
|
148
|
+
if (Array.isArray(parsed.outputs)) {
|
|
149
|
+
config.outputs = parsed.outputs.filter((output) => typeof output === "string");
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
warnings.push("Outputs should be an array of strings");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Recipe Step System validation
|
|
156
|
+
if (parsed.steps) {
|
|
157
|
+
if (Array.isArray(parsed.steps)) {
|
|
158
|
+
config.steps = TemplateParser.validateSteps(parsed.steps, config.variables, errors, warnings);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
warnings.push("Steps should be an array");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Recipe settings validation
|
|
165
|
+
if (parsed.settings) {
|
|
166
|
+
config.settings = TemplateParser.validateSettings(parsed.settings, warnings);
|
|
167
|
+
}
|
|
168
|
+
return config;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Validate variables section of template configuration
|
|
172
|
+
*/
|
|
173
|
+
static validateVariables(variables, config, errors, warnings) {
|
|
174
|
+
for (const [varName, varConfig] of Object.entries(variables)) {
|
|
175
|
+
if (!varConfig || typeof varConfig !== "object") {
|
|
176
|
+
errors.push(`Variable '${varName}' must be an object`);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const variable = TemplateParser.validateVariable(varName, varConfig, errors, warnings);
|
|
180
|
+
if (variable) {
|
|
181
|
+
config.variables[varName] = variable;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Validate individual variable configuration
|
|
187
|
+
*/
|
|
188
|
+
static validateVariable(varName, varConfig, errors, warnings) {
|
|
189
|
+
const variable = {
|
|
190
|
+
type: "string",
|
|
191
|
+
};
|
|
192
|
+
// Validate type
|
|
193
|
+
if (!varConfig.type) {
|
|
194
|
+
errors.push(`Variable '${varName}' must have a type`);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
if (!TemplateParser.VALID_VARIABLE_TYPES.includes(varConfig.type)) {
|
|
198
|
+
errors.push(`Variable '${varName}' has invalid type: ${varConfig.type}`);
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
variable.type = varConfig.type;
|
|
202
|
+
// Validate required field
|
|
203
|
+
if (varConfig.required !== undefined) {
|
|
204
|
+
if (typeof varConfig.required !== "boolean") {
|
|
205
|
+
warnings.push(`Variable '${varName}' required field should be boolean`);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
variable.required = varConfig.required;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Validate default value
|
|
212
|
+
if (varConfig.default !== undefined) {
|
|
213
|
+
if (variable.required) {
|
|
214
|
+
errors.push(`Variable '${varName}': 'default' and 'required' are mutually exclusive`);
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
if (varConfig.suggestion !== undefined) {
|
|
218
|
+
errors.push(`Variable '${varName}': 'default' and 'suggestion' are mutually exclusive`);
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
variable.default = varConfig.default;
|
|
222
|
+
}
|
|
223
|
+
// Validate suggestion value
|
|
224
|
+
if (varConfig.suggestion !== undefined) {
|
|
225
|
+
variable.suggestion = varConfig.suggestion;
|
|
226
|
+
}
|
|
227
|
+
// Validate description
|
|
228
|
+
if (varConfig.description) {
|
|
229
|
+
if (typeof varConfig.description !== "string") {
|
|
230
|
+
warnings.push(`Variable '${varName}' description should be a string`);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
variable.description = varConfig.description;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Validate pattern (for string types)
|
|
237
|
+
if (varConfig.pattern) {
|
|
238
|
+
if (variable.type !== "string") {
|
|
239
|
+
warnings.push(`Variable '${varName}' pattern only applies to string types`);
|
|
240
|
+
}
|
|
241
|
+
else if (typeof varConfig.pattern !== "string") {
|
|
242
|
+
warnings.push(`Variable '${varName}' pattern should be a string`);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
try {
|
|
246
|
+
new RegExp(varConfig.pattern);
|
|
247
|
+
variable.pattern = varConfig.pattern;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
errors.push(`Variable '${varName}' has invalid regex pattern: ${varConfig.pattern}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Validate enum values
|
|
255
|
+
if (varConfig.values) {
|
|
256
|
+
if (variable.type !== "enum") {
|
|
257
|
+
warnings.push(`Variable '${varName}' values only apply to enum types`);
|
|
258
|
+
}
|
|
259
|
+
else if (!Array.isArray(varConfig.values)) {
|
|
260
|
+
errors.push(`Variable '${varName}' values must be an array`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
variable.values = varConfig.values.filter((val) => typeof val === "string");
|
|
264
|
+
if (variable.values && variable.values.length === 0) {
|
|
265
|
+
errors.push(`Variable '${varName}' enum must have at least one value`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Validate position (for positional CLI args)
|
|
270
|
+
if (varConfig.position !== undefined) {
|
|
271
|
+
if (typeof varConfig.position !== "number" ||
|
|
272
|
+
varConfig.position < 0 ||
|
|
273
|
+
!Number.isInteger(varConfig.position)) {
|
|
274
|
+
warnings.push(`Variable '${varName}' position should be a non-negative integer`);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
variable.position = varConfig.position;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Validate min/max (for number types)
|
|
281
|
+
if (varConfig.min !== undefined) {
|
|
282
|
+
if (variable.type !== "number") {
|
|
283
|
+
warnings.push(`Variable '${varName}' min only applies to number types`);
|
|
284
|
+
}
|
|
285
|
+
else if (typeof varConfig.min !== "number") {
|
|
286
|
+
warnings.push(`Variable '${varName}' min should be a number`);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
variable.min = varConfig.min;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (varConfig.max !== undefined) {
|
|
293
|
+
if (variable.type !== "number") {
|
|
294
|
+
warnings.push(`Variable '${varName}' max only applies to number types`);
|
|
295
|
+
}
|
|
296
|
+
else if (typeof varConfig.max !== "number") {
|
|
297
|
+
warnings.push(`Variable '${varName}' max should be a number`);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
variable.max = varConfig.max;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return variable;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Validate examples section
|
|
307
|
+
*/
|
|
308
|
+
static validateExamples(examples, variables, warnings) {
|
|
309
|
+
const validExamples = [];
|
|
310
|
+
for (const [index, example] of examples.entries()) {
|
|
311
|
+
if (!example || typeof example !== "object") {
|
|
312
|
+
warnings.push(`Example ${index + 1} must be an object`);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (!example.title || typeof example.title !== "string") {
|
|
316
|
+
warnings.push(`Example ${index + 1} must have a title`);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (!example.variables || typeof example.variables !== "object") {
|
|
320
|
+
warnings.push(`Example ${index + 1} must have variables`);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const validExample = {
|
|
324
|
+
title: example.title,
|
|
325
|
+
variables: example.variables,
|
|
326
|
+
};
|
|
327
|
+
if (example.description && typeof example.description === "string") {
|
|
328
|
+
validExample.description = example.description;
|
|
329
|
+
}
|
|
330
|
+
// Validate example variables against template variables
|
|
331
|
+
for (const [varName, _varValue] of Object.entries(example.variables)) {
|
|
332
|
+
if (!variables[varName]) {
|
|
333
|
+
warnings.push(`Example ${index + 1} references undefined variable: ${varName}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
validExamples.push(validExample);
|
|
337
|
+
}
|
|
338
|
+
return validExamples;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Validate variable value against template variable definition
|
|
342
|
+
*/
|
|
343
|
+
static validateVariableValue(varName, value, variable) {
|
|
344
|
+
// Check required
|
|
345
|
+
if (variable.required && (value === undefined || value === null || value === "")) {
|
|
346
|
+
return { isValid: false, error: `Variable '${varName}' is required` };
|
|
347
|
+
}
|
|
348
|
+
// If value is undefined and not required, use default
|
|
349
|
+
if (value === undefined && variable.default !== undefined) {
|
|
350
|
+
return { isValid: true };
|
|
351
|
+
}
|
|
352
|
+
// If value is undefined and no default, skip validation
|
|
353
|
+
if (value === undefined) {
|
|
354
|
+
return { isValid: true };
|
|
355
|
+
}
|
|
356
|
+
// Type validation
|
|
357
|
+
switch (variable.type) {
|
|
358
|
+
case "string":
|
|
359
|
+
if (typeof value !== "string") {
|
|
360
|
+
return {
|
|
361
|
+
isValid: false,
|
|
362
|
+
error: `Variable '${varName}' must be a string`,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (variable.pattern) {
|
|
366
|
+
const regex = new RegExp(variable.pattern);
|
|
367
|
+
if (!regex.test(value)) {
|
|
368
|
+
return {
|
|
369
|
+
isValid: false,
|
|
370
|
+
error: `Variable '${varName}' does not match pattern: ${variable.pattern}`,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
break;
|
|
375
|
+
case "number":
|
|
376
|
+
if (typeof value !== "number") {
|
|
377
|
+
return {
|
|
378
|
+
isValid: false,
|
|
379
|
+
error: `Variable '${varName}' must be a number`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (variable.min !== undefined && value < variable.min) {
|
|
383
|
+
return {
|
|
384
|
+
isValid: false,
|
|
385
|
+
error: `Variable '${varName}' must be >= ${variable.min}`,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
if (variable.max !== undefined && value > variable.max) {
|
|
389
|
+
return {
|
|
390
|
+
isValid: false,
|
|
391
|
+
error: `Variable '${varName}' must be <= ${variable.max}`,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
case "boolean":
|
|
396
|
+
if (typeof value !== "boolean") {
|
|
397
|
+
return {
|
|
398
|
+
isValid: false,
|
|
399
|
+
error: `Variable '${varName}' must be a boolean`,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
case "enum":
|
|
404
|
+
if (variable.multiple && Array.isArray(value)) {
|
|
405
|
+
// Validate each value in array
|
|
406
|
+
const invalid = value.find((v) => !variable.values?.includes(v));
|
|
407
|
+
if (invalid) {
|
|
408
|
+
return {
|
|
409
|
+
isValid: false,
|
|
410
|
+
error: `Value '${invalid}' for variable '${varName}' must be one of: ${variable.values?.join(", ")}`,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else if (!variable.values || !variable.values.includes(value)) {
|
|
415
|
+
return {
|
|
416
|
+
isValid: false,
|
|
417
|
+
error: `Variable '${varName}' must be one of: ${variable.values?.join(", ")}`,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
case "array":
|
|
422
|
+
if (!Array.isArray(value)) {
|
|
423
|
+
return {
|
|
424
|
+
isValid: false,
|
|
425
|
+
error: `Variable '${varName}' must be an array`,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
case "object":
|
|
430
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
431
|
+
return {
|
|
432
|
+
isValid: false,
|
|
433
|
+
error: `Variable '${varName}' must be an object`,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
return { isValid: true };
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get resolved variable value (with default if not provided)
|
|
442
|
+
*/
|
|
443
|
+
static getResolvedValue(value, variable) {
|
|
444
|
+
if (value !== undefined) {
|
|
445
|
+
return value;
|
|
446
|
+
}
|
|
447
|
+
return variable.default;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Validate recipe steps array (Recipe Step System)
|
|
451
|
+
*/
|
|
452
|
+
static validateSteps(steps, variables, errors, warnings) {
|
|
453
|
+
const validSteps = [];
|
|
454
|
+
const stepNames = new Set();
|
|
455
|
+
const stepDependencies = new Map();
|
|
456
|
+
for (const [index, step] of steps.entries()) {
|
|
457
|
+
if (!step || typeof step !== "object") {
|
|
458
|
+
errors.push(`Step ${index + 1} must be an object`);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const validStep = TemplateParser.validateStep(step, index + 1, variables, errors, warnings);
|
|
462
|
+
if (validStep) {
|
|
463
|
+
// Check for duplicate step names
|
|
464
|
+
if (stepNames.has(validStep.name)) {
|
|
465
|
+
errors.push(`Duplicate step name: '${validStep.name}'`);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
stepNames.add(validStep.name);
|
|
469
|
+
}
|
|
470
|
+
validSteps.push(validStep);
|
|
471
|
+
// Track dependencies for circular dependency check
|
|
472
|
+
if (validStep.dependsOn) {
|
|
473
|
+
stepDependencies.set(validStep.name, validStep.dependsOn);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Check for circular dependencies
|
|
478
|
+
TemplateParser.validateStepDependencies(stepDependencies, errors);
|
|
479
|
+
// Validate step dependencies reference existing steps
|
|
480
|
+
TemplateParser.validateStepDependencyReferences(stepDependencies, stepNames, warnings);
|
|
481
|
+
return validSteps;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Validate individual recipe step
|
|
485
|
+
*/
|
|
486
|
+
static validateStep(step, index, _variables, errors, warnings) {
|
|
487
|
+
// Validate required fields
|
|
488
|
+
if (!step.name || typeof step.name !== "string") {
|
|
489
|
+
errors.push(`Step ${index} must have a name (string)`);
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
// Tool inference / Shorthand support
|
|
493
|
+
if (!step.tool) {
|
|
494
|
+
if (step.command) {
|
|
495
|
+
step.tool = "shell";
|
|
496
|
+
}
|
|
497
|
+
else if (step.recipe) {
|
|
498
|
+
step.tool = "recipe";
|
|
499
|
+
}
|
|
500
|
+
else if (step.template) {
|
|
501
|
+
step.tool = "template";
|
|
502
|
+
}
|
|
503
|
+
else if (step.action) {
|
|
504
|
+
step.tool = "action";
|
|
505
|
+
}
|
|
506
|
+
else if (step.codemod) {
|
|
507
|
+
step.tool = "codemod";
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (!step.tool || !TemplateParser.VALID_TOOL_TYPES.includes(step.tool)) {
|
|
511
|
+
errors.push(`Step '${step.name}' must have a valid tool type (${TemplateParser.VALID_TOOL_TYPES.join(", ")})`);
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
// Base step configuration
|
|
515
|
+
const baseStep = {
|
|
516
|
+
name: step.name,
|
|
517
|
+
tool: step.tool,
|
|
518
|
+
};
|
|
519
|
+
// Validate optional base fields
|
|
520
|
+
if (step.description && typeof step.description === "string") {
|
|
521
|
+
baseStep.description = step.description;
|
|
522
|
+
}
|
|
523
|
+
if (step.when && typeof step.when === "string") {
|
|
524
|
+
// Basic condition validation - check if it looks like a valid expression
|
|
525
|
+
if (TemplateParser.validateConditionExpression(step.when)) {
|
|
526
|
+
baseStep.when = step.when;
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
baseStep.when = step.when;
|
|
530
|
+
warnings.push(`Step '${step.name}' has potentially invalid condition expression`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (step.dependsOn) {
|
|
534
|
+
if (Array.isArray(step.dependsOn)) {
|
|
535
|
+
const validDeps = step.dependsOn.filter((dep) => typeof dep === "string");
|
|
536
|
+
if (validDeps.length !== step.dependsOn.length) {
|
|
537
|
+
warnings.push(`Step '${step.name}' has some invalid dependencies (must be strings)`);
|
|
538
|
+
}
|
|
539
|
+
if (validDeps.length > 0) {
|
|
540
|
+
baseStep.dependsOn = validDeps;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
warnings.push(`Step '${step.name}' dependsOn should be an array of strings`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (step.parallel !== undefined && typeof step.parallel === "boolean") {
|
|
548
|
+
baseStep.parallel = step.parallel;
|
|
549
|
+
}
|
|
550
|
+
if (step.continueOnError !== undefined && typeof step.continueOnError === "boolean") {
|
|
551
|
+
baseStep.continueOnError = step.continueOnError;
|
|
552
|
+
}
|
|
553
|
+
if (step.timeout !== undefined && typeof step.timeout === "number" && step.timeout > 0) {
|
|
554
|
+
baseStep.timeout = step.timeout;
|
|
555
|
+
}
|
|
556
|
+
if (step.retries !== undefined && typeof step.retries === "number" && step.retries >= 0) {
|
|
557
|
+
baseStep.retries = step.retries;
|
|
558
|
+
}
|
|
559
|
+
if (step.tags && Array.isArray(step.tags)) {
|
|
560
|
+
const validTags = step.tags.filter((tag) => typeof tag === "string");
|
|
561
|
+
if (validTags.length > 0) {
|
|
562
|
+
baseStep.tags = validTags;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (step.variables && typeof step.variables === "object" && step.variables !== null) {
|
|
566
|
+
baseStep.variables = step.variables;
|
|
567
|
+
}
|
|
568
|
+
if (step.environment && typeof step.environment === "object" && step.environment !== null) {
|
|
569
|
+
baseStep.environment = step.environment;
|
|
570
|
+
}
|
|
571
|
+
// Tool-specific validation
|
|
572
|
+
switch (step.tool) {
|
|
573
|
+
case "template":
|
|
574
|
+
return TemplateParser.validateTemplateStep(baseStep, step, errors, warnings);
|
|
575
|
+
case "action":
|
|
576
|
+
return TemplateParser.validateActionStep(baseStep, step, errors, warnings);
|
|
577
|
+
case "codemod":
|
|
578
|
+
return TemplateParser.validateCodeModStep(baseStep, step, errors, warnings);
|
|
579
|
+
case "recipe":
|
|
580
|
+
return TemplateParser.validateRecipeStep(baseStep, step, errors, warnings);
|
|
581
|
+
default:
|
|
582
|
+
errors.push(`Step '${step.name}' has unsupported tool type: ${step.tool}`);
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Validate template step configuration
|
|
588
|
+
*/
|
|
589
|
+
static validateTemplateStep(baseStep, step, errors, warnings) {
|
|
590
|
+
if (!step.template || typeof step.template !== "string") {
|
|
591
|
+
errors.push(`Template step '${step.name}' must have a template (string)`);
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
const templateStep = {
|
|
595
|
+
...baseStep,
|
|
596
|
+
template: step.template,
|
|
597
|
+
};
|
|
598
|
+
if (step.outputDir && typeof step.outputDir === "string") {
|
|
599
|
+
templateStep.outputDir = step.outputDir;
|
|
600
|
+
}
|
|
601
|
+
if (step.overwrite !== undefined && typeof step.overwrite === "boolean") {
|
|
602
|
+
templateStep.overwrite = step.overwrite;
|
|
603
|
+
}
|
|
604
|
+
if (step.exclude && Array.isArray(step.exclude)) {
|
|
605
|
+
const validExcludes = step.exclude.filter((pattern) => typeof pattern === "string");
|
|
606
|
+
if (validExcludes.length > 0) {
|
|
607
|
+
templateStep.exclude = validExcludes;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (step.templateConfig && typeof step.templateConfig === "object") {
|
|
611
|
+
templateStep.templateConfig = step.templateConfig;
|
|
612
|
+
}
|
|
613
|
+
return templateStep;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Validate action step configuration
|
|
617
|
+
*/
|
|
618
|
+
static validateActionStep(baseStep, step, errors, warnings) {
|
|
619
|
+
if (!step.action || typeof step.action !== "string") {
|
|
620
|
+
errors.push(`Action step '${step.name}' must have an action (string)`);
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
const actionStep = {
|
|
624
|
+
...baseStep,
|
|
625
|
+
action: step.action,
|
|
626
|
+
};
|
|
627
|
+
if (step.parameters && typeof step.parameters === "object" && step.parameters !== null) {
|
|
628
|
+
actionStep.parameters = step.parameters;
|
|
629
|
+
}
|
|
630
|
+
if (step.dryRun !== undefined && typeof step.dryRun === "boolean") {
|
|
631
|
+
actionStep.dryRun = step.dryRun;
|
|
632
|
+
}
|
|
633
|
+
if (step.force !== undefined && typeof step.force === "boolean") {
|
|
634
|
+
actionStep.force = step.force;
|
|
635
|
+
}
|
|
636
|
+
if (step.actionConfig && typeof step.actionConfig === "object") {
|
|
637
|
+
actionStep.actionConfig = step.actionConfig;
|
|
638
|
+
}
|
|
639
|
+
return actionStep;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Validate codemod step configuration
|
|
643
|
+
*/
|
|
644
|
+
static validateCodeModStep(baseStep, step, errors, warnings) {
|
|
645
|
+
if (!step.codemod || typeof step.codemod !== "string") {
|
|
646
|
+
errors.push(`CodeMod step '${step.name}' must have a codemod (string)`);
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
if (!step.files || !Array.isArray(step.files) || step.files.length === 0) {
|
|
650
|
+
errors.push(`CodeMod step '${step.name}' must have a files array with at least one pattern`);
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
const validFiles = step.files.filter((file) => typeof file === "string");
|
|
654
|
+
if (validFiles.length !== step.files.length) {
|
|
655
|
+
warnings.push(`CodeMod step '${step.name}' has some invalid file patterns (must be strings)`);
|
|
656
|
+
}
|
|
657
|
+
if (validFiles.length === 0) {
|
|
658
|
+
errors.push(`CodeMod step '${step.name}' must have at least one valid file pattern`);
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
const codemodStep = {
|
|
662
|
+
...baseStep,
|
|
663
|
+
codemod: step.codemod,
|
|
664
|
+
files: validFiles,
|
|
665
|
+
};
|
|
666
|
+
if (step.backup !== undefined && typeof step.backup === "boolean") {
|
|
667
|
+
codemodStep.backup = step.backup;
|
|
668
|
+
}
|
|
669
|
+
if (step.parser && ["typescript", "javascript", "json", "auto"].includes(step.parser)) {
|
|
670
|
+
codemodStep.parser = step.parser;
|
|
671
|
+
}
|
|
672
|
+
if (step.parameters && typeof step.parameters === "object" && step.parameters !== null) {
|
|
673
|
+
codemodStep.parameters = step.parameters;
|
|
674
|
+
}
|
|
675
|
+
if (step.force !== undefined && typeof step.force === "boolean") {
|
|
676
|
+
codemodStep.force = step.force;
|
|
677
|
+
}
|
|
678
|
+
if (step.codemodConfig && typeof step.codemodConfig === "object") {
|
|
679
|
+
codemodStep.codemodConfig = step.codemodConfig;
|
|
680
|
+
}
|
|
681
|
+
return codemodStep;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Validate recipe step configuration
|
|
685
|
+
*/
|
|
686
|
+
static validateRecipeStep(baseStep, step, errors, warnings) {
|
|
687
|
+
if (!step.recipe || typeof step.recipe !== "string") {
|
|
688
|
+
errors.push(`Recipe step '${step.name}' must have a recipe (string)`);
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
const recipeStep = {
|
|
692
|
+
...baseStep,
|
|
693
|
+
recipe: step.recipe,
|
|
694
|
+
};
|
|
695
|
+
if (step.version && typeof step.version === "string") {
|
|
696
|
+
recipeStep.version = step.version;
|
|
697
|
+
}
|
|
698
|
+
if (step.inheritVariables !== undefined && typeof step.inheritVariables === "boolean") {
|
|
699
|
+
recipeStep.inheritVariables = step.inheritVariables;
|
|
700
|
+
}
|
|
701
|
+
if (step.variableOverrides &&
|
|
702
|
+
typeof step.variableOverrides === "object" &&
|
|
703
|
+
step.variableOverrides !== null) {
|
|
704
|
+
recipeStep.variableOverrides = step.variableOverrides;
|
|
705
|
+
}
|
|
706
|
+
if (step.recipeConfig && typeof step.recipeConfig === "object") {
|
|
707
|
+
recipeStep.recipeConfig = step.recipeConfig;
|
|
708
|
+
}
|
|
709
|
+
return recipeStep;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Validate step dependencies for circular references
|
|
713
|
+
*/
|
|
714
|
+
static validateStepDependencies(dependencies, errors) {
|
|
715
|
+
const visited = new Set();
|
|
716
|
+
const recursionStack = new Set();
|
|
717
|
+
const detectCycle = (stepName, path) => {
|
|
718
|
+
if (recursionStack.has(stepName)) {
|
|
719
|
+
// Found a cycle - return the cycle path
|
|
720
|
+
const cycleStart = path.indexOf(stepName);
|
|
721
|
+
return path.slice(cycleStart).concat(stepName);
|
|
722
|
+
}
|
|
723
|
+
if (visited.has(stepName)) {
|
|
724
|
+
return null; // Already processed
|
|
725
|
+
}
|
|
726
|
+
visited.add(stepName);
|
|
727
|
+
recursionStack.add(stepName);
|
|
728
|
+
const currentPath = [...path, stepName];
|
|
729
|
+
const stepDeps = dependencies.get(stepName) || [];
|
|
730
|
+
for (const dep of stepDeps) {
|
|
731
|
+
const cycle = detectCycle(dep, currentPath);
|
|
732
|
+
if (cycle) {
|
|
733
|
+
return cycle;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
recursionStack.delete(stepName);
|
|
737
|
+
return null;
|
|
738
|
+
};
|
|
739
|
+
for (const stepName of dependencies.keys()) {
|
|
740
|
+
if (!visited.has(stepName)) {
|
|
741
|
+
const cycle = detectCycle(stepName, []);
|
|
742
|
+
if (cycle) {
|
|
743
|
+
errors.push(`Circular dependency detected: ${cycle.join(" -> ")}`);
|
|
744
|
+
break; // Report only the first cycle found
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Validate that step dependencies reference existing steps
|
|
751
|
+
*/
|
|
752
|
+
static validateStepDependencyReferences(dependencies, stepNames, warnings) {
|
|
753
|
+
for (const [stepName, deps] of dependencies) {
|
|
754
|
+
for (const dep of deps) {
|
|
755
|
+
if (!stepNames.has(dep)) {
|
|
756
|
+
warnings.push(`Step '${stepName}' depends on undefined step: '${dep}'`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Basic validation of condition expressions
|
|
763
|
+
*/
|
|
764
|
+
static validateConditionExpression(condition) {
|
|
765
|
+
// Basic validation - just check if it looks like a reasonable expression
|
|
766
|
+
// More sophisticated validation could be added later
|
|
767
|
+
if (!condition.trim()) {
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
// Check for obviously invalid patterns
|
|
771
|
+
const invalidPatterns = [
|
|
772
|
+
/^[{}()\[\].,;]*$/, // Only punctuation
|
|
773
|
+
/^\d+$/, // Only numbers
|
|
774
|
+
/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*$/, // Only a single identifier
|
|
775
|
+
];
|
|
776
|
+
return !invalidPatterns.some((pattern) => pattern.test(condition.trim()));
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Validate recipe execution settings
|
|
780
|
+
*/
|
|
781
|
+
static validateSettings(settings, warnings) {
|
|
782
|
+
const result = {};
|
|
783
|
+
if (typeof settings !== "object" || settings === null) {
|
|
784
|
+
warnings.push("Settings should be an object");
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
if (settings.timeout !== undefined) {
|
|
788
|
+
if (typeof settings.timeout === "number" && settings.timeout > 0) {
|
|
789
|
+
result.timeout = settings.timeout;
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
warnings.push("Settings timeout should be a positive number");
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (settings.retries !== undefined) {
|
|
796
|
+
if (typeof settings.retries === "number" && settings.retries >= 0) {
|
|
797
|
+
result.retries = settings.retries;
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
warnings.push("Settings retries should be a non-negative number");
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (settings.continueOnError !== undefined) {
|
|
804
|
+
if (typeof settings.continueOnError === "boolean") {
|
|
805
|
+
result.continueOnError = settings.continueOnError;
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
warnings.push("Settings continueOnError should be a boolean");
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (settings.maxParallelSteps !== undefined) {
|
|
812
|
+
if (typeof settings.maxParallelSteps === "number" && settings.maxParallelSteps > 0) {
|
|
813
|
+
result.maxParallelSteps = settings.maxParallelSteps;
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
warnings.push("Settings maxParallelSteps should be a positive number");
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (settings.workingDir !== undefined) {
|
|
820
|
+
if (typeof settings.workingDir === "string") {
|
|
821
|
+
result.workingDir = settings.workingDir;
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
warnings.push("Settings workingDir should be a string");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return result;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Check if this configuration uses the Recipe Step System
|
|
831
|
+
*/
|
|
832
|
+
static isRecipeConfig(config) {
|
|
833
|
+
return Array.isArray(config.steps) && config.steps.length > 0;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Convert a TemplateConfig to RecipeConfig (Recipe Step System)
|
|
837
|
+
*/
|
|
838
|
+
static toRecipeConfig(templateConfig) {
|
|
839
|
+
if (!TemplateParser.isRecipeConfig(templateConfig)) {
|
|
840
|
+
return null; // Not a recipe configuration
|
|
841
|
+
}
|
|
842
|
+
const recipeConfig = {
|
|
843
|
+
name: templateConfig.name,
|
|
844
|
+
variables: templateConfig.variables,
|
|
845
|
+
steps: templateConfig.steps,
|
|
846
|
+
};
|
|
847
|
+
// Copy optional fields
|
|
848
|
+
if (templateConfig.description)
|
|
849
|
+
recipeConfig.description = templateConfig.description;
|
|
850
|
+
if (templateConfig.version)
|
|
851
|
+
recipeConfig.version = templateConfig.version;
|
|
852
|
+
if (templateConfig.author)
|
|
853
|
+
recipeConfig.author = templateConfig.author;
|
|
854
|
+
if (templateConfig.category)
|
|
855
|
+
recipeConfig.category = templateConfig.category;
|
|
856
|
+
if (templateConfig.tags)
|
|
857
|
+
recipeConfig.tags = templateConfig.tags;
|
|
858
|
+
if (templateConfig.outputs)
|
|
859
|
+
recipeConfig.outputs = templateConfig.outputs;
|
|
860
|
+
// Convert examples if present
|
|
861
|
+
if (templateConfig.examples) {
|
|
862
|
+
recipeConfig.examples = templateConfig.examples.map((example) => ({
|
|
863
|
+
title: example.title,
|
|
864
|
+
description: example.description,
|
|
865
|
+
variables: example.variables,
|
|
866
|
+
}));
|
|
867
|
+
}
|
|
868
|
+
// Convert settings if present
|
|
869
|
+
if (templateConfig.settings) {
|
|
870
|
+
recipeConfig.settings = templateConfig.settings;
|
|
871
|
+
}
|
|
872
|
+
return recipeConfig;
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Validate that step configuration matches expected tool schema
|
|
876
|
+
*/
|
|
877
|
+
static validateStepToolConfiguration(step, errors, warnings) {
|
|
878
|
+
switch (step.tool) {
|
|
879
|
+
case "template":
|
|
880
|
+
// Additional template-specific validation can be added here
|
|
881
|
+
break;
|
|
882
|
+
case "action":
|
|
883
|
+
// Additional action-specific validation can be added here
|
|
884
|
+
break;
|
|
885
|
+
case "codemod":
|
|
886
|
+
// Additional codemod-specific validation can be added here
|
|
887
|
+
break;
|
|
888
|
+
case "recipe":
|
|
889
|
+
// Additional recipe-specific validation can be added here
|
|
890
|
+
break;
|
|
891
|
+
default:
|
|
892
|
+
errors.push(`Unknown tool type: ${step.tool}`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
//# sourceMappingURL=template-parser.js.map
|