@agiflowai/scaffold-mcp 1.0.12 → 1.0.14
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/dist/ListScaffoldingMethodsTool-B49G_iLj.mjs +350 -0
- package/dist/ListScaffoldingMethodsTool-DuYGFDwJ.cjs +376 -0
- package/dist/ScaffoldConfigLoader-8YI7v2GJ.mjs +142 -0
- package/dist/ScaffoldConfigLoader-BWpNpMx-.cjs +150 -0
- package/dist/ScaffoldConfigLoader-DKJtnrWT.mjs +3 -0
- package/dist/ScaffoldConfigLoader-HutEtfaH.cjs +3 -0
- package/dist/ScaffoldService-DSQBnAHm.cjs +308 -0
- package/dist/ScaffoldService-DcsGLMuD.mjs +295 -0
- package/dist/ScaffoldService-DfXjmrNT.cjs +3 -0
- package/dist/ScaffoldService-dL74anIv.mjs +3 -0
- package/dist/TemplateService-7QcWREot.cjs +85 -0
- package/dist/TemplateService-B1bd6iHw.mjs +3 -0
- package/dist/TemplateService-CVDL2uqt.mjs +79 -0
- package/dist/TemplateService-DUbdBOFs.cjs +3 -0
- package/dist/VariableReplacementService-B9RA8D0a.mjs +66 -0
- package/dist/VariableReplacementService-BO-UYgcf.mjs +3 -0
- package/dist/VariableReplacementService-DNYx0Dym.cjs +73 -0
- package/dist/VariableReplacementService-wuYKgeui.cjs +3 -0
- package/dist/chunk-CbDLau6x.cjs +34 -0
- package/dist/cli.cjs +16 -11
- package/dist/cli.mjs +9 -5
- package/dist/index.cjs +11 -7
- package/dist/index.mjs +6 -2
- package/dist/{stdio-DqZJsqKM.mjs → stdio-AbTm52SJ.mjs} +18 -15
- package/dist/{stdio-ohQyWnxq.cjs → stdio-baUp7xGL.cjs} +28 -24
- package/dist/{useScaffoldMethod-CC8ARsl9.mjs → useScaffoldMethod-BMWhFebp.mjs} +5 -7
- package/dist/{useScaffoldMethod-B-q_yBnX.mjs → useScaffoldMethod-CPgJIBHx.mjs} +2 -1
- package/dist/{useScaffoldMethod-DqF6pT1A.cjs → useScaffoldMethod-CcrpFEPv.cjs} +3 -1
- package/dist/{useScaffoldMethod-fzsnprJs.cjs → useScaffoldMethod-DOvwnNOJ.cjs} +9 -10
- package/package.json +3 -3
- package/dist/ListScaffoldingMethodsTool-D3ecmSLf.mjs +0 -994
- package/dist/ListScaffoldingMethodsTool-D7S3xpJE.cjs +0 -1082
|
@@ -1,1082 +0,0 @@
|
|
|
1
|
-
//#region rolldown:runtime
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
-
__defProp(to, key, {
|
|
14
|
-
get: ((k) => from[k]).bind(null, key),
|
|
15
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return to;
|
|
21
|
-
};
|
|
22
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
-
value: mod,
|
|
24
|
-
enumerable: true
|
|
25
|
-
}) : target, mod));
|
|
26
|
-
|
|
27
|
-
//#endregion
|
|
28
|
-
let node_path = require("node:path");
|
|
29
|
-
node_path = __toESM(node_path);
|
|
30
|
-
let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
|
|
31
|
-
let js_yaml = require("js-yaml");
|
|
32
|
-
js_yaml = __toESM(js_yaml);
|
|
33
|
-
let zod = require("zod");
|
|
34
|
-
let node_url = require("node:url");
|
|
35
|
-
let liquidjs = require("liquidjs");
|
|
36
|
-
|
|
37
|
-
//#region src/utils/pagination.ts
|
|
38
|
-
var PaginationHelper = class PaginationHelper {
|
|
39
|
-
/**
|
|
40
|
-
* Default page size for pagination
|
|
41
|
-
*/
|
|
42
|
-
static DEFAULT_PAGE_SIZE = 10;
|
|
43
|
-
/**
|
|
44
|
-
* Decodes a cursor string to extract the start index
|
|
45
|
-
* @param cursor - String representing the start index (e.g., "10")
|
|
46
|
-
* @returns Start index or 0 if invalid/undefined
|
|
47
|
-
*/
|
|
48
|
-
static decodeCursor(cursor) {
|
|
49
|
-
if (!cursor) return 0;
|
|
50
|
-
const index = Number.parseInt(cursor, 10);
|
|
51
|
-
if (Number.isNaN(index) || index < 0) return 0;
|
|
52
|
-
return index;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Encodes an index into a cursor string
|
|
56
|
-
* @param index - Start index to encode
|
|
57
|
-
* @returns Cursor string (e.g., "10")
|
|
58
|
-
*/
|
|
59
|
-
static encodeCursor(index) {
|
|
60
|
-
return index.toString();
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Paginates an array of items
|
|
64
|
-
* @param items - All items to paginate
|
|
65
|
-
* @param cursor - Optional cursor representing the start index
|
|
66
|
-
* @param pageSize - Number of items per page (default: 10)
|
|
67
|
-
* @param includeMeta - Whether to include metadata in response (default: true)
|
|
68
|
-
* @returns Paginated result with items and optional nextCursor
|
|
69
|
-
*/
|
|
70
|
-
static paginate(items, cursor, pageSize = PaginationHelper.DEFAULT_PAGE_SIZE, includeMeta = true) {
|
|
71
|
-
const startIndex = PaginationHelper.decodeCursor(cursor);
|
|
72
|
-
const endIndex = startIndex + pageSize;
|
|
73
|
-
const result = {
|
|
74
|
-
items: items.slice(startIndex, endIndex),
|
|
75
|
-
nextCursor: endIndex < items.length ? PaginationHelper.encodeCursor(endIndex) : void 0
|
|
76
|
-
};
|
|
77
|
-
if (includeMeta) result._meta = {
|
|
78
|
-
total: items.length,
|
|
79
|
-
offset: startIndex,
|
|
80
|
-
limit: pageSize
|
|
81
|
-
};
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
//#endregion
|
|
87
|
-
//#region src/services/FileSystemService.ts
|
|
88
|
-
var FileSystemService = class {
|
|
89
|
-
async pathExists(path$5) {
|
|
90
|
-
return (0, __agiflowai_aicode_utils.pathExists)(path$5);
|
|
91
|
-
}
|
|
92
|
-
async readFile(path$5, encoding = "utf8") {
|
|
93
|
-
return (0, __agiflowai_aicode_utils.readFile)(path$5, encoding);
|
|
94
|
-
}
|
|
95
|
-
async readJson(path$5) {
|
|
96
|
-
return (0, __agiflowai_aicode_utils.readJson)(path$5);
|
|
97
|
-
}
|
|
98
|
-
async writeFile(path$5, content, encoding = "utf8") {
|
|
99
|
-
return (0, __agiflowai_aicode_utils.writeFile)(path$5, content, encoding);
|
|
100
|
-
}
|
|
101
|
-
async ensureDir(path$5) {
|
|
102
|
-
return (0, __agiflowai_aicode_utils.ensureDir)(path$5);
|
|
103
|
-
}
|
|
104
|
-
async copy(src, dest) {
|
|
105
|
-
return (0, __agiflowai_aicode_utils.copy)(src, dest);
|
|
106
|
-
}
|
|
107
|
-
async readdir(path$5) {
|
|
108
|
-
return (0, __agiflowai_aicode_utils.readdir)(path$5);
|
|
109
|
-
}
|
|
110
|
-
async stat(path$5) {
|
|
111
|
-
return (0, __agiflowai_aicode_utils.stat)(path$5);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
//#endregion
|
|
116
|
-
//#region src/services/ScaffoldConfigLoader.ts
|
|
117
|
-
const VariablesSchemaSchema = zod.z.object({
|
|
118
|
-
type: zod.z.literal("object"),
|
|
119
|
-
properties: zod.z.record(zod.z.any()),
|
|
120
|
-
required: zod.z.array(zod.z.string()),
|
|
121
|
-
additionalProperties: zod.z.boolean()
|
|
122
|
-
});
|
|
123
|
-
const ScaffoldConfigEntrySchema = zod.z.object({
|
|
124
|
-
name: zod.z.string(),
|
|
125
|
-
description: zod.z.string().optional(),
|
|
126
|
-
instruction: zod.z.string().optional(),
|
|
127
|
-
targetFolder: zod.z.string().optional(),
|
|
128
|
-
variables_schema: VariablesSchemaSchema,
|
|
129
|
-
includes: zod.z.array(zod.z.string()),
|
|
130
|
-
generator: zod.z.string().optional(),
|
|
131
|
-
patterns: zod.z.array(zod.z.string()).optional()
|
|
132
|
-
});
|
|
133
|
-
const ScaffoldYamlSchema = zod.z.object({
|
|
134
|
-
boilerplate: zod.z.union([ScaffoldConfigEntrySchema, zod.z.array(ScaffoldConfigEntrySchema)]).optional(),
|
|
135
|
-
features: zod.z.union([ScaffoldConfigEntrySchema, zod.z.array(ScaffoldConfigEntrySchema)]).optional()
|
|
136
|
-
}).catchall(zod.z.union([ScaffoldConfigEntrySchema, zod.z.array(ScaffoldConfigEntrySchema)]));
|
|
137
|
-
var ScaffoldConfigLoader = class {
|
|
138
|
-
constructor(fileSystem, templateService) {
|
|
139
|
-
this.fileSystem = fileSystem;
|
|
140
|
-
this.templateService = templateService;
|
|
141
|
-
}
|
|
142
|
-
async parseArchitectConfig(templatePath) {
|
|
143
|
-
const architectPath = node_path.default.join(templatePath, "scaffold.yaml");
|
|
144
|
-
if (!await this.fileSystem.pathExists(architectPath)) return null;
|
|
145
|
-
try {
|
|
146
|
-
const content = await this.fileSystem.readFile(architectPath, "utf8");
|
|
147
|
-
const rawConfig = js_yaml.default.load(content);
|
|
148
|
-
return ScaffoldYamlSchema.parse(rawConfig);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
if (error instanceof zod.z.ZodError) {
|
|
151
|
-
const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`).join("; ");
|
|
152
|
-
throw new Error(`scaffold.yaml validation failed: ${errorMessages}`);
|
|
153
|
-
}
|
|
154
|
-
throw new Error(`Failed to parse scaffold.yaml: ${error instanceof Error ? error.message : String(error)}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
parseIncludeEntry(includeEntry, variables) {
|
|
158
|
-
const [pathPart, conditionsPart] = includeEntry.split("?");
|
|
159
|
-
const conditions = {};
|
|
160
|
-
if (conditionsPart) {
|
|
161
|
-
const conditionPairs = conditionsPart.split("&");
|
|
162
|
-
for (const pair of conditionPairs) {
|
|
163
|
-
const [key, value] = pair.split("=");
|
|
164
|
-
if (key && value) conditions[key.trim()] = value.trim();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (pathPart.includes("->")) {
|
|
168
|
-
const [sourcePath, targetPath] = pathPart.split("->").map((p) => p.trim());
|
|
169
|
-
return {
|
|
170
|
-
sourcePath,
|
|
171
|
-
targetPath: this.replaceVariablesInPath(targetPath, variables),
|
|
172
|
-
conditions
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
const processedPath = this.replaceVariablesInPath(pathPart.trim(), variables);
|
|
176
|
-
return {
|
|
177
|
-
sourcePath: pathPart.trim(),
|
|
178
|
-
targetPath: processedPath,
|
|
179
|
-
conditions
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
replaceVariablesInPath(pathStr, variables) {
|
|
183
|
-
return this.templateService.renderString(pathStr, variables);
|
|
184
|
-
}
|
|
185
|
-
shouldIncludeFile(conditions, variables) {
|
|
186
|
-
if (!conditions || Object.keys(conditions).length === 0) return true;
|
|
187
|
-
for (const [conditionKey, conditionValue] of Object.entries(conditions)) {
|
|
188
|
-
const variableValue = variables[conditionKey];
|
|
189
|
-
if (conditionValue === "true" || conditionValue === "false") {
|
|
190
|
-
const expectedBoolean = conditionValue === "true";
|
|
191
|
-
if (Boolean(variableValue) !== expectedBoolean) return false;
|
|
192
|
-
} else if (String(variableValue) !== conditionValue) return false;
|
|
193
|
-
}
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
async validateTemplate(templatePath, scaffoldType) {
|
|
197
|
-
const errors = [];
|
|
198
|
-
const missingFiles = [];
|
|
199
|
-
if (!await this.fileSystem.pathExists(templatePath)) {
|
|
200
|
-
errors.push(`Template directory ${templatePath} does not exist`);
|
|
201
|
-
return {
|
|
202
|
-
isValid: false,
|
|
203
|
-
errors,
|
|
204
|
-
missingFiles
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
let architectConfig;
|
|
208
|
-
try {
|
|
209
|
-
architectConfig = await this.parseArchitectConfig(templatePath);
|
|
210
|
-
} catch (error) {
|
|
211
|
-
errors.push(`Failed to parse scaffold.yaml: ${error instanceof Error ? error.message : String(error)}`);
|
|
212
|
-
return {
|
|
213
|
-
isValid: false,
|
|
214
|
-
errors,
|
|
215
|
-
missingFiles
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
if (!architectConfig) {
|
|
219
|
-
errors.push("scaffold.yaml not found in template directory");
|
|
220
|
-
return {
|
|
221
|
-
isValid: false,
|
|
222
|
-
errors,
|
|
223
|
-
missingFiles
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
if (!architectConfig[scaffoldType]) {
|
|
227
|
-
const availableTypes = Object.keys(architectConfig).join(", ");
|
|
228
|
-
errors.push(`Scaffold type '${scaffoldType}' not found in scaffold.yaml. Available types: ${availableTypes}`);
|
|
229
|
-
return {
|
|
230
|
-
isValid: false,
|
|
231
|
-
errors,
|
|
232
|
-
missingFiles
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
const config = architectConfig[scaffoldType];
|
|
236
|
-
if (config.includes && Array.isArray(config.includes)) for (const includeFile of config.includes) {
|
|
237
|
-
const parsed = this.parseIncludeEntry(includeFile, {});
|
|
238
|
-
const sourcePath = node_path.default.join(templatePath, parsed.sourcePath);
|
|
239
|
-
const liquidSourcePath = `${sourcePath}.liquid`;
|
|
240
|
-
const sourceExists = await this.fileSystem.pathExists(sourcePath);
|
|
241
|
-
const liquidExists = await this.fileSystem.pathExists(liquidSourcePath);
|
|
242
|
-
if (!sourceExists && !liquidExists) missingFiles.push(includeFile);
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
isValid: errors.length === 0 && missingFiles.length === 0,
|
|
246
|
-
errors,
|
|
247
|
-
missingFiles
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
//#endregion
|
|
253
|
-
//#region src/services/ScaffoldProcessingService.ts
|
|
254
|
-
/**
|
|
255
|
-
* Shared service for common scaffolding operations like processing templates and tracking files
|
|
256
|
-
*/
|
|
257
|
-
var ScaffoldProcessingService = class {
|
|
258
|
-
constructor(fileSystem, variableReplacer) {
|
|
259
|
-
this.fileSystem = fileSystem;
|
|
260
|
-
this.variableReplacer = variableReplacer;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Process a target path for variable replacement, handling both files and directories
|
|
264
|
-
*/
|
|
265
|
-
async processTargetForVariableReplacement(targetPath, variables) {
|
|
266
|
-
if ((await this.fileSystem.stat(targetPath)).isDirectory()) await this.variableReplacer.processFilesForVariableReplacement(targetPath, variables);
|
|
267
|
-
else await this.variableReplacer.replaceVariablesInFile(targetPath, variables);
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Track all created files, handling both single files and directories
|
|
271
|
-
*/
|
|
272
|
-
async trackCreatedFiles(targetPath, createdFiles) {
|
|
273
|
-
if ((await this.fileSystem.stat(targetPath)).isDirectory()) await this.trackCreatedFilesRecursive(targetPath, createdFiles);
|
|
274
|
-
else createdFiles.push(targetPath);
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Track all existing files, handling both single files and directories
|
|
278
|
-
*/
|
|
279
|
-
async trackExistingFiles(targetPath, existingFiles) {
|
|
280
|
-
if ((await this.fileSystem.stat(targetPath)).isDirectory()) await this.trackExistingFilesRecursive(targetPath, existingFiles);
|
|
281
|
-
else existingFiles.push(targetPath);
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Copy source to target, then process templates and track files
|
|
285
|
-
* Now supports tracking existing files separately from created files
|
|
286
|
-
* Automatically handles .liquid template files by stripping the extension
|
|
287
|
-
*/
|
|
288
|
-
async copyAndProcess(sourcePath, targetPath, variables, createdFiles, existingFiles) {
|
|
289
|
-
await this.fileSystem.ensureDir(node_path.default.dirname(targetPath));
|
|
290
|
-
if (await this.fileSystem.pathExists(targetPath) && existingFiles) {
|
|
291
|
-
await this.trackExistingFiles(targetPath, existingFiles);
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
let actualSourcePath = sourcePath;
|
|
295
|
-
if (!await this.fileSystem.pathExists(sourcePath)) {
|
|
296
|
-
const liquidSourcePath = `${sourcePath}.liquid`;
|
|
297
|
-
if (await this.fileSystem.pathExists(liquidSourcePath)) actualSourcePath = liquidSourcePath;
|
|
298
|
-
else throw new Error(`Source file not found: ${sourcePath} (also tried ${liquidSourcePath})`);
|
|
299
|
-
}
|
|
300
|
-
await this.fileSystem.copy(actualSourcePath, targetPath);
|
|
301
|
-
await this.processTargetForVariableReplacement(targetPath, variables);
|
|
302
|
-
await this.trackCreatedFiles(targetPath, createdFiles);
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Recursively collect all file paths in a directory for created files
|
|
306
|
-
*/
|
|
307
|
-
async trackCreatedFilesRecursive(dirPath, createdFiles) {
|
|
308
|
-
let items = [];
|
|
309
|
-
try {
|
|
310
|
-
items = await this.fileSystem.readdir(dirPath);
|
|
311
|
-
} catch (error) {
|
|
312
|
-
__agiflowai_aicode_utils.log.warn(`Cannot read directory ${dirPath}: ${error}`);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
const itemPaths = items.filter((item) => item).map((item) => node_path.default.join(dirPath, item));
|
|
316
|
-
const statResults = await Promise.all(itemPaths.map(async (itemPath) => {
|
|
317
|
-
try {
|
|
318
|
-
return {
|
|
319
|
-
itemPath,
|
|
320
|
-
stat: await this.fileSystem.stat(itemPath),
|
|
321
|
-
error: null
|
|
322
|
-
};
|
|
323
|
-
} catch (error) {
|
|
324
|
-
__agiflowai_aicode_utils.log.warn(`Cannot stat ${itemPath}: ${error}`);
|
|
325
|
-
return {
|
|
326
|
-
itemPath,
|
|
327
|
-
stat: null,
|
|
328
|
-
error
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
}));
|
|
332
|
-
const directories = [];
|
|
333
|
-
for (const { itemPath, stat } of statResults) {
|
|
334
|
-
if (!stat) continue;
|
|
335
|
-
if (stat.isDirectory()) directories.push(itemPath);
|
|
336
|
-
else if (stat.isFile()) createdFiles.push(itemPath);
|
|
337
|
-
}
|
|
338
|
-
await Promise.all(directories.map((dir) => this.trackCreatedFilesRecursive(dir, createdFiles)));
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Recursively collect all file paths in a directory for existing files
|
|
342
|
-
*/
|
|
343
|
-
async trackExistingFilesRecursive(dirPath, existingFiles) {
|
|
344
|
-
let items = [];
|
|
345
|
-
try {
|
|
346
|
-
items = await this.fileSystem.readdir(dirPath);
|
|
347
|
-
} catch (error) {
|
|
348
|
-
__agiflowai_aicode_utils.log.warn(`Cannot read directory ${dirPath}: ${error}`);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
const itemPaths = items.filter((item) => item).map((item) => node_path.default.join(dirPath, item));
|
|
352
|
-
const statResults = await Promise.all(itemPaths.map(async (itemPath) => {
|
|
353
|
-
try {
|
|
354
|
-
return {
|
|
355
|
-
itemPath,
|
|
356
|
-
stat: await this.fileSystem.stat(itemPath),
|
|
357
|
-
error: null
|
|
358
|
-
};
|
|
359
|
-
} catch (error) {
|
|
360
|
-
__agiflowai_aicode_utils.log.warn(`Cannot stat ${itemPath}: ${error}`);
|
|
361
|
-
return {
|
|
362
|
-
itemPath,
|
|
363
|
-
stat: null,
|
|
364
|
-
error
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
}));
|
|
368
|
-
const directories = [];
|
|
369
|
-
for (const { itemPath, stat } of statResults) {
|
|
370
|
-
if (!stat) continue;
|
|
371
|
-
if (stat.isDirectory()) directories.push(itemPath);
|
|
372
|
-
else if (stat.isFile()) existingFiles.push(itemPath);
|
|
373
|
-
}
|
|
374
|
-
await Promise.all(directories.map((dir) => this.trackExistingFilesRecursive(dir, existingFiles)));
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
//#endregion
|
|
379
|
-
//#region src/services/ScaffoldService.ts
|
|
380
|
-
var ScaffoldService = class {
|
|
381
|
-
templatesRootPath;
|
|
382
|
-
processingService;
|
|
383
|
-
constructor(fileSystem, scaffoldConfigLoader, variableReplacer, templatesRootPath) {
|
|
384
|
-
this.fileSystem = fileSystem;
|
|
385
|
-
this.scaffoldConfigLoader = scaffoldConfigLoader;
|
|
386
|
-
this.variableReplacer = variableReplacer;
|
|
387
|
-
const resolvedPath = templatesRootPath || __agiflowai_aicode_utils.TemplatesManagerService.findTemplatesPathSync();
|
|
388
|
-
if (!resolvedPath) throw new Error("Templates folder not found. Please create a \"templates\" folder in your workspace root, or specify \"templatesPath\" in toolkit.yaml to point to your templates directory.");
|
|
389
|
-
this.templatesRootPath = resolvedPath;
|
|
390
|
-
this.processingService = new ScaffoldProcessingService(fileSystem, variableReplacer);
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Scaffold a new project from a boilerplate template
|
|
394
|
-
*/
|
|
395
|
-
async useBoilerplate(options) {
|
|
396
|
-
try {
|
|
397
|
-
const { projectName, packageName, targetFolder, templateFolder, boilerplateName, variables = {} } = options;
|
|
398
|
-
const targetPath = node_path.default.isAbsolute(targetFolder) ? projectName ? node_path.default.join(targetFolder, projectName) : targetFolder : projectName ? node_path.default.join(process.cwd(), targetFolder, projectName) : node_path.default.join(process.cwd(), targetFolder);
|
|
399
|
-
const templatePath = node_path.default.join(this.templatesRootPath, templateFolder);
|
|
400
|
-
const validationResult = await this.scaffoldConfigLoader.validateTemplate(templatePath, "boilerplate");
|
|
401
|
-
if (!validationResult.isValid) return {
|
|
402
|
-
success: false,
|
|
403
|
-
message: `Template validation failed: ${[...validationResult.errors, ...validationResult.missingFiles.map((f) => `Template file not found: ${f}`)].join("; ")}`
|
|
404
|
-
};
|
|
405
|
-
if (projectName) {
|
|
406
|
-
if (await this.fileSystem.pathExists(targetPath)) return {
|
|
407
|
-
success: false,
|
|
408
|
-
message: `Directory ${targetPath} already exists`
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
const architectConfig = await this.scaffoldConfigLoader.parseArchitectConfig(templatePath);
|
|
412
|
-
if (!architectConfig || !architectConfig.boilerplate) return {
|
|
413
|
-
success: false,
|
|
414
|
-
message: `Invalid architect configuration: missing 'boilerplate' section in scaffold.yaml`
|
|
415
|
-
};
|
|
416
|
-
const boilerplateArray = architectConfig.boilerplate;
|
|
417
|
-
let config;
|
|
418
|
-
if (Array.isArray(boilerplateArray)) {
|
|
419
|
-
config = boilerplateArray.find((b) => b.name === boilerplateName);
|
|
420
|
-
if (!config) return {
|
|
421
|
-
success: false,
|
|
422
|
-
message: `Boilerplate '${boilerplateName}' not found in scaffold configuration`
|
|
423
|
-
};
|
|
424
|
-
} else config = architectConfig.boilerplate;
|
|
425
|
-
const effectiveProjectName = projectName || (packageName.includes("/") ? packageName.split("/")[1] : packageName);
|
|
426
|
-
const allVariables = {
|
|
427
|
-
...variables,
|
|
428
|
-
projectName: effectiveProjectName,
|
|
429
|
-
packageName
|
|
430
|
-
};
|
|
431
|
-
return await this.processScaffold({
|
|
432
|
-
config,
|
|
433
|
-
targetPath,
|
|
434
|
-
templatePath,
|
|
435
|
-
allVariables,
|
|
436
|
-
scaffoldType: "boilerplate"
|
|
437
|
-
});
|
|
438
|
-
} catch (error) {
|
|
439
|
-
return {
|
|
440
|
-
success: false,
|
|
441
|
-
message: `Error scaffolding boilerplate: ${error instanceof Error ? error.message : String(error)}`
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Scaffold a new feature into an existing project
|
|
447
|
-
*/
|
|
448
|
-
async useFeature(options) {
|
|
449
|
-
try {
|
|
450
|
-
const { projectPath, templateFolder, featureName, variables = {} } = options;
|
|
451
|
-
const targetPath = node_path.default.resolve(projectPath);
|
|
452
|
-
const templatePath = node_path.default.join(this.templatesRootPath, templateFolder);
|
|
453
|
-
const projectName = node_path.default.basename(targetPath);
|
|
454
|
-
const validationResult = await this.scaffoldConfigLoader.validateTemplate(templatePath, "features");
|
|
455
|
-
if (!validationResult.isValid) return {
|
|
456
|
-
success: false,
|
|
457
|
-
message: `Template validation failed: ${[...validationResult.errors, ...validationResult.missingFiles.map((f) => `Template file not found: ${f}`)].join("; ")}`
|
|
458
|
-
};
|
|
459
|
-
if (!await this.fileSystem.pathExists(targetPath)) return {
|
|
460
|
-
success: false,
|
|
461
|
-
message: `Target directory ${targetPath} does not exist. Please create the parent directory first.`
|
|
462
|
-
};
|
|
463
|
-
const architectConfig = await this.scaffoldConfigLoader.parseArchitectConfig(templatePath);
|
|
464
|
-
if (!architectConfig || !architectConfig.features) return {
|
|
465
|
-
success: false,
|
|
466
|
-
message: `Invalid architect configuration: missing 'features' section in scaffold.yaml`
|
|
467
|
-
};
|
|
468
|
-
const featureArray = architectConfig.features;
|
|
469
|
-
let config;
|
|
470
|
-
if (Array.isArray(featureArray)) {
|
|
471
|
-
config = featureArray.find((f) => f.name === featureName);
|
|
472
|
-
if (!config) return {
|
|
473
|
-
success: false,
|
|
474
|
-
message: `Feature '${featureName}' not found in scaffold configuration`
|
|
475
|
-
};
|
|
476
|
-
} else config = architectConfig.features;
|
|
477
|
-
const allVariables = {
|
|
478
|
-
...variables,
|
|
479
|
-
projectName,
|
|
480
|
-
appPath: targetPath,
|
|
481
|
-
appName: projectName
|
|
482
|
-
};
|
|
483
|
-
return await this.processScaffold({
|
|
484
|
-
config,
|
|
485
|
-
targetPath,
|
|
486
|
-
templatePath,
|
|
487
|
-
allVariables,
|
|
488
|
-
scaffoldType: "feature"
|
|
489
|
-
});
|
|
490
|
-
} catch (error) {
|
|
491
|
-
return {
|
|
492
|
-
success: false,
|
|
493
|
-
message: `Error scaffolding feature: ${error instanceof Error ? error.message : String(error)}`
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Common scaffolding processing logic shared by both useBoilerplate and useFeature
|
|
499
|
-
*/
|
|
500
|
-
async processScaffold(params) {
|
|
501
|
-
const { config, targetPath, templatePath, allVariables, scaffoldType } = params;
|
|
502
|
-
__agiflowai_aicode_utils.log.debug("Config generator:", config.generator);
|
|
503
|
-
__agiflowai_aicode_utils.log.debug("Config:", JSON.stringify(config, null, 2));
|
|
504
|
-
if (config.generator) {
|
|
505
|
-
__agiflowai_aicode_utils.log.info("Using custom generator:", config.generator);
|
|
506
|
-
try {
|
|
507
|
-
const generator = (await import(node_path.default.join(templatePath, "generators", config.generator))).default;
|
|
508
|
-
if (typeof generator !== "function") return {
|
|
509
|
-
success: false,
|
|
510
|
-
message: `Invalid generator: ${config.generator} does not export a default function`
|
|
511
|
-
};
|
|
512
|
-
return await generator({
|
|
513
|
-
variables: allVariables,
|
|
514
|
-
config,
|
|
515
|
-
targetPath,
|
|
516
|
-
templatePath,
|
|
517
|
-
fileSystem: this.fileSystem,
|
|
518
|
-
scaffoldConfigLoader: this.scaffoldConfigLoader,
|
|
519
|
-
variableReplacer: this.variableReplacer,
|
|
520
|
-
ScaffoldProcessingService: this.processingService.constructor,
|
|
521
|
-
getRootPath: () => {
|
|
522
|
-
const __dirname$1 = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
523
|
-
return node_path.default.join(__dirname$1, "../../../../..");
|
|
524
|
-
},
|
|
525
|
-
getProjectPath: (projectPath) => {
|
|
526
|
-
const __dirname$1 = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
527
|
-
const rootPath = node_path.default.join(__dirname$1, "../../../../..");
|
|
528
|
-
return projectPath.replace(rootPath, "").replace("/", "");
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
} catch (error) {
|
|
532
|
-
return {
|
|
533
|
-
success: false,
|
|
534
|
-
message: `Error loading or executing generator ${config.generator}: ${error instanceof Error ? error.message : String(error)}`
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
const parsedIncludes = [];
|
|
539
|
-
const warnings = [];
|
|
540
|
-
if (config.includes && Array.isArray(config.includes)) {
|
|
541
|
-
const filteredIncludes = config.includes.map((includeEntry) => this.scaffoldConfigLoader.parseIncludeEntry(includeEntry, allVariables)).filter((parsed) => this.scaffoldConfigLoader.shouldIncludeFile(parsed.conditions, allVariables));
|
|
542
|
-
const existsResults = await Promise.all(filteredIncludes.map(async (parsed) => {
|
|
543
|
-
const targetFilePath = node_path.default.join(targetPath, parsed.targetPath);
|
|
544
|
-
return {
|
|
545
|
-
parsed,
|
|
546
|
-
exists: await this.fileSystem.pathExists(targetFilePath)
|
|
547
|
-
};
|
|
548
|
-
}));
|
|
549
|
-
for (const { parsed, exists } of existsResults) {
|
|
550
|
-
parsedIncludes.push(parsed);
|
|
551
|
-
if (exists) warnings.push(`File/folder ${parsed.targetPath} already exists and will be preserved`);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
await this.fileSystem.ensureDir(targetPath);
|
|
555
|
-
const createdFiles = [];
|
|
556
|
-
const existingFiles = [];
|
|
557
|
-
await Promise.all(parsedIncludes.map(async (parsed) => {
|
|
558
|
-
const sourcePath = node_path.default.join(templatePath, parsed.sourcePath);
|
|
559
|
-
const targetFilePath = node_path.default.join(targetPath, parsed.targetPath);
|
|
560
|
-
await this.processingService.copyAndProcess(sourcePath, targetFilePath, allVariables, createdFiles, existingFiles);
|
|
561
|
-
}));
|
|
562
|
-
let message = `Successfully scaffolded ${scaffoldType} at ${targetPath}`;
|
|
563
|
-
if (existingFiles.length > 0) message += `. ${existingFiles.length} existing file(s) were preserved`;
|
|
564
|
-
message += ". Run 'pnpm install' to install dependencies.";
|
|
565
|
-
return {
|
|
566
|
-
success: true,
|
|
567
|
-
message,
|
|
568
|
-
warnings: warnings.length > 0 ? warnings : void 0,
|
|
569
|
-
createdFiles: createdFiles.length > 0 ? createdFiles : void 0,
|
|
570
|
-
existingFiles: existingFiles.length > 0 ? existingFiles : void 0
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
//#endregion
|
|
576
|
-
//#region src/services/TemplateService.ts
|
|
577
|
-
var TemplateService = class {
|
|
578
|
-
liquid;
|
|
579
|
-
constructor() {
|
|
580
|
-
this.liquid = new liquidjs.Liquid({
|
|
581
|
-
strictFilters: false,
|
|
582
|
-
strictVariables: false
|
|
583
|
-
});
|
|
584
|
-
this.setupCustomFilters();
|
|
585
|
-
__agiflowai_aicode_utils.log.info("TemplateService initialized");
|
|
586
|
-
}
|
|
587
|
-
toPascalCase(str) {
|
|
588
|
-
const camelCase = str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
|
|
589
|
-
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
|
|
590
|
-
}
|
|
591
|
-
setupCustomFilters() {
|
|
592
|
-
this.liquid.registerFilter("camelCase", (str) => {
|
|
593
|
-
return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
|
|
594
|
-
});
|
|
595
|
-
this.liquid.registerFilter("pascalCase", (str) => {
|
|
596
|
-
return this.toPascalCase(str);
|
|
597
|
-
});
|
|
598
|
-
this.liquid.registerFilter("titleCase", (str) => {
|
|
599
|
-
return this.toPascalCase(str);
|
|
600
|
-
});
|
|
601
|
-
this.liquid.registerFilter("kebabCase", (str) => {
|
|
602
|
-
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
603
|
-
});
|
|
604
|
-
this.liquid.registerFilter("snakeCase", (str) => {
|
|
605
|
-
return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
|
|
606
|
-
});
|
|
607
|
-
this.liquid.registerFilter("upperCase", (str) => {
|
|
608
|
-
return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toUpperCase();
|
|
609
|
-
});
|
|
610
|
-
this.liquid.registerFilter("lower", (str) => str.toLowerCase());
|
|
611
|
-
this.liquid.registerFilter("upper", (str) => str.toUpperCase());
|
|
612
|
-
this.liquid.registerFilter("pluralize", (str) => {
|
|
613
|
-
if (str.endsWith("y")) return `${str.slice(0, -1)}ies`;
|
|
614
|
-
else if (str.endsWith("s") || str.endsWith("sh") || str.endsWith("ch") || str.endsWith("x") || str.endsWith("z")) return `${str}es`;
|
|
615
|
-
else return `${str}s`;
|
|
616
|
-
});
|
|
617
|
-
this.liquid.registerFilter("singularize", (str) => {
|
|
618
|
-
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
619
|
-
else if (str.endsWith("es")) return str.slice(0, -2);
|
|
620
|
-
else if (str.endsWith("s") && !str.endsWith("ss")) return str.slice(0, -1);
|
|
621
|
-
else return str;
|
|
622
|
-
});
|
|
623
|
-
this.liquid.registerFilter("strip", (str) => {
|
|
624
|
-
return str.trim();
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
renderString(template, variables) {
|
|
628
|
-
try {
|
|
629
|
-
__agiflowai_aicode_utils.log.debug("Rendering template", {
|
|
630
|
-
variables,
|
|
631
|
-
templatePreview: template.substring(0, 100)
|
|
632
|
-
});
|
|
633
|
-
const result = this.liquid.parseAndRenderSync(template, variables);
|
|
634
|
-
__agiflowai_aicode_utils.log.debug("Rendered template", { resultPreview: result.substring(0, 100) });
|
|
635
|
-
return result;
|
|
636
|
-
} catch (error) {
|
|
637
|
-
__agiflowai_aicode_utils.log.error("LiquidJS rendering error", {
|
|
638
|
-
error: error instanceof Error ? error.message : String(error),
|
|
639
|
-
templatePreview: template.substring(0, 200),
|
|
640
|
-
variables
|
|
641
|
-
});
|
|
642
|
-
return template;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
containsTemplateVariables(content) {
|
|
646
|
-
return [/\{\{.*?\}\}/, /\{%.*?%\}/].some((pattern) => pattern.test(content));
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
//#endregion
|
|
651
|
-
//#region src/services/VariableReplacementService.ts
|
|
652
|
-
var VariableReplacementService = class {
|
|
653
|
-
binaryExtensions = [
|
|
654
|
-
".png",
|
|
655
|
-
".jpg",
|
|
656
|
-
".jpeg",
|
|
657
|
-
".gif",
|
|
658
|
-
".ico",
|
|
659
|
-
".woff",
|
|
660
|
-
".woff2",
|
|
661
|
-
".ttf",
|
|
662
|
-
".eot",
|
|
663
|
-
".pdf",
|
|
664
|
-
".zip",
|
|
665
|
-
".tar",
|
|
666
|
-
".gz",
|
|
667
|
-
".exe",
|
|
668
|
-
".dll",
|
|
669
|
-
".so",
|
|
670
|
-
".dylib"
|
|
671
|
-
];
|
|
672
|
-
constructor(fileSystem, templateService) {
|
|
673
|
-
this.fileSystem = fileSystem;
|
|
674
|
-
this.templateService = templateService;
|
|
675
|
-
}
|
|
676
|
-
async processFilesForVariableReplacement(dirPath, variables) {
|
|
677
|
-
let items = [];
|
|
678
|
-
try {
|
|
679
|
-
items = await this.fileSystem.readdir(dirPath);
|
|
680
|
-
} catch (error) {
|
|
681
|
-
__agiflowai_aicode_utils.log.warn(`Skipping directory ${dirPath}: ${error}`);
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
684
|
-
const itemPaths = items.filter((item) => item).map((item) => node_path.default.join(dirPath, item));
|
|
685
|
-
const statResults = await Promise.all(itemPaths.map(async (itemPath) => {
|
|
686
|
-
try {
|
|
687
|
-
return {
|
|
688
|
-
itemPath,
|
|
689
|
-
stat: await this.fileSystem.stat(itemPath),
|
|
690
|
-
error: null
|
|
691
|
-
};
|
|
692
|
-
} catch (error) {
|
|
693
|
-
__agiflowai_aicode_utils.log.warn(`Skipping item ${itemPath}: ${error}`);
|
|
694
|
-
return {
|
|
695
|
-
itemPath,
|
|
696
|
-
stat: null,
|
|
697
|
-
error
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
}));
|
|
701
|
-
const directories = [];
|
|
702
|
-
const files = [];
|
|
703
|
-
for (const { itemPath, stat } of statResults) {
|
|
704
|
-
if (!stat) continue;
|
|
705
|
-
if (stat.isDirectory()) directories.push(itemPath);
|
|
706
|
-
else if (stat.isFile()) files.push(itemPath);
|
|
707
|
-
}
|
|
708
|
-
await Promise.all([...files.map((file) => this.replaceVariablesInFile(file, variables)), ...directories.map((dir) => this.processFilesForVariableReplacement(dir, variables))]);
|
|
709
|
-
}
|
|
710
|
-
async replaceVariablesInFile(filePath, variables) {
|
|
711
|
-
try {
|
|
712
|
-
if (this.isBinaryFile(filePath)) return;
|
|
713
|
-
const content = await this.fileSystem.readFile(filePath, "utf8");
|
|
714
|
-
const renderedContent = this.templateService.renderString(content, variables);
|
|
715
|
-
await this.fileSystem.writeFile(filePath, renderedContent, "utf8");
|
|
716
|
-
} catch (error) {
|
|
717
|
-
__agiflowai_aicode_utils.log.warn(`Skipping file ${filePath}: ${error}`);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
isBinaryFile(filePath) {
|
|
721
|
-
const ext = node_path.default.extname(filePath).toLowerCase();
|
|
722
|
-
return this.binaryExtensions.includes(ext);
|
|
723
|
-
}
|
|
724
|
-
};
|
|
725
|
-
|
|
726
|
-
//#endregion
|
|
727
|
-
//#region src/services/ScaffoldingMethodsService.ts
|
|
728
|
-
var ScaffoldingMethodsService = class {
|
|
729
|
-
templateService;
|
|
730
|
-
constructor(fileSystem, templatesRootPath) {
|
|
731
|
-
this.fileSystem = fileSystem;
|
|
732
|
-
this.templatesRootPath = templatesRootPath;
|
|
733
|
-
this.templateService = new TemplateService();
|
|
734
|
-
}
|
|
735
|
-
async listScaffoldingMethods(projectPath, cursor) {
|
|
736
|
-
const absoluteProjectPath = node_path.default.resolve(projectPath);
|
|
737
|
-
const sourceTemplate = (await __agiflowai_aicode_utils.ProjectConfigResolver.resolveProjectConfig(absoluteProjectPath)).sourceTemplate;
|
|
738
|
-
return this.listScaffoldingMethodsByTemplate(sourceTemplate, cursor);
|
|
739
|
-
}
|
|
740
|
-
async listScaffoldingMethodsByTemplate(templateName, cursor) {
|
|
741
|
-
const templatePath = await this.findTemplatePath(templateName);
|
|
742
|
-
if (!templatePath) throw new Error(`Template not found for sourceTemplate: ${templateName}`);
|
|
743
|
-
const fullTemplatePath = node_path.default.join(this.templatesRootPath, templatePath);
|
|
744
|
-
const scaffoldYamlPath = node_path.default.join(fullTemplatePath, "scaffold.yaml");
|
|
745
|
-
if (!await this.fileSystem.pathExists(scaffoldYamlPath)) throw new Error(`scaffold.yaml not found at ${scaffoldYamlPath}`);
|
|
746
|
-
const scaffoldContent = await this.fileSystem.readFile(scaffoldYamlPath, "utf8");
|
|
747
|
-
const architectConfig = js_yaml.default.load(scaffoldContent);
|
|
748
|
-
const methods = [];
|
|
749
|
-
if (architectConfig.features && Array.isArray(architectConfig.features)) architectConfig.features.forEach((feature) => {
|
|
750
|
-
const featureName = feature.name || `scaffold-${templateName}`;
|
|
751
|
-
methods.push({
|
|
752
|
-
name: featureName,
|
|
753
|
-
description: feature.description || "",
|
|
754
|
-
instruction: feature.instruction || "",
|
|
755
|
-
variables_schema: feature.variables_schema || {
|
|
756
|
-
type: "object",
|
|
757
|
-
properties: {},
|
|
758
|
-
required: [],
|
|
759
|
-
additionalProperties: false
|
|
760
|
-
},
|
|
761
|
-
generator: feature.generator
|
|
762
|
-
});
|
|
763
|
-
});
|
|
764
|
-
const paginatedResult = PaginationHelper.paginate(methods, cursor);
|
|
765
|
-
return {
|
|
766
|
-
sourceTemplate: templateName,
|
|
767
|
-
templatePath,
|
|
768
|
-
methods: paginatedResult.items,
|
|
769
|
-
nextCursor: paginatedResult.nextCursor,
|
|
770
|
-
_meta: paginatedResult._meta
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* Gets scaffolding methods with instructions rendered using provided variables
|
|
775
|
-
*/
|
|
776
|
-
async listScaffoldingMethodsWithVariables(projectPath, variables, cursor) {
|
|
777
|
-
const result = await this.listScaffoldingMethods(projectPath, cursor);
|
|
778
|
-
const processedMethods = result.methods.map((method) => ({
|
|
779
|
-
...method,
|
|
780
|
-
instruction: method.instruction ? this.processScaffoldInstruction(method.instruction, variables) : void 0
|
|
781
|
-
}));
|
|
782
|
-
return {
|
|
783
|
-
...result,
|
|
784
|
-
methods: processedMethods
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* Processes scaffold instruction with template service
|
|
789
|
-
*/
|
|
790
|
-
processScaffoldInstruction(instruction, variables) {
|
|
791
|
-
if (this.templateService.containsTemplateVariables(instruction)) return this.templateService.renderString(instruction, variables);
|
|
792
|
-
return instruction;
|
|
793
|
-
}
|
|
794
|
-
async findTemplatePath(sourceTemplate) {
|
|
795
|
-
const templateDirs = await this.discoverTemplateDirs();
|
|
796
|
-
if (templateDirs.includes(sourceTemplate)) return sourceTemplate;
|
|
797
|
-
for (const templateDir of templateDirs) {
|
|
798
|
-
const templatePath = node_path.default.join(this.templatesRootPath, templateDir);
|
|
799
|
-
const scaffoldYamlPath = node_path.default.join(templatePath, "scaffold.yaml");
|
|
800
|
-
if (await this.fileSystem.pathExists(scaffoldYamlPath)) try {
|
|
801
|
-
const scaffoldContent = await this.fileSystem.readFile(scaffoldYamlPath, "utf8");
|
|
802
|
-
const architectConfig = js_yaml.default.load(scaffoldContent);
|
|
803
|
-
if (architectConfig.boilerplate && Array.isArray(architectConfig.boilerplate)) {
|
|
804
|
-
for (const boilerplate of architectConfig.boilerplate) if (boilerplate.name?.includes(sourceTemplate)) return templateDir;
|
|
805
|
-
}
|
|
806
|
-
} catch (error) {
|
|
807
|
-
__agiflowai_aicode_utils.log.warn(`Failed to read scaffold.yaml at ${scaffoldYamlPath}:`, error);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return null;
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Resolves the project path, handling both monorepo and monolith cases
|
|
814
|
-
* Uses ProjectConfigResolver to find the correct workspace/project root
|
|
815
|
-
*/
|
|
816
|
-
async resolveProjectPath(projectPath) {
|
|
817
|
-
const absolutePath = node_path.default.resolve(projectPath);
|
|
818
|
-
return (await __agiflowai_aicode_utils.ProjectConfigResolver.resolveProjectConfig(absolutePath)).workspaceRoot || absolutePath;
|
|
819
|
-
}
|
|
820
|
-
/**
|
|
821
|
-
* Dynamically discovers all template directories
|
|
822
|
-
* Supports both flat structure (templates/nextjs-15) and nested structure (templates/apps/nextjs-15)
|
|
823
|
-
**/
|
|
824
|
-
async discoverTemplateDirs() {
|
|
825
|
-
const templateDirs = [];
|
|
826
|
-
try {
|
|
827
|
-
const itemPaths = (await this.fileSystem.readdir(this.templatesRootPath)).map((item) => ({
|
|
828
|
-
item,
|
|
829
|
-
itemPath: node_path.default.join(this.templatesRootPath, item)
|
|
830
|
-
}));
|
|
831
|
-
const itemResults = await Promise.all(itemPaths.map(async ({ item, itemPath }) => {
|
|
832
|
-
try {
|
|
833
|
-
if (!(await this.fileSystem.stat(itemPath)).isDirectory()) return {
|
|
834
|
-
item,
|
|
835
|
-
itemPath,
|
|
836
|
-
isDir: false,
|
|
837
|
-
hasScaffold: false
|
|
838
|
-
};
|
|
839
|
-
const scaffoldYamlPath = node_path.default.join(itemPath, "scaffold.yaml");
|
|
840
|
-
return {
|
|
841
|
-
item,
|
|
842
|
-
itemPath,
|
|
843
|
-
isDir: true,
|
|
844
|
-
hasScaffold: await this.fileSystem.pathExists(scaffoldYamlPath)
|
|
845
|
-
};
|
|
846
|
-
} catch {
|
|
847
|
-
return {
|
|
848
|
-
item,
|
|
849
|
-
itemPath,
|
|
850
|
-
isDir: false,
|
|
851
|
-
hasScaffold: false
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
}));
|
|
855
|
-
const categoryDirs = [];
|
|
856
|
-
for (const result of itemResults) {
|
|
857
|
-
if (!result.isDir) continue;
|
|
858
|
-
if (result.hasScaffold) templateDirs.push(result.item);
|
|
859
|
-
else categoryDirs.push({
|
|
860
|
-
item: result.item,
|
|
861
|
-
itemPath: result.itemPath
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
const nestedResults = await Promise.all(categoryDirs.map(async ({ item, itemPath }) => {
|
|
865
|
-
const found = [];
|
|
866
|
-
try {
|
|
867
|
-
const subItems = await this.fileSystem.readdir(itemPath);
|
|
868
|
-
const subResults = await Promise.all(subItems.map(async (subItem) => {
|
|
869
|
-
const subItemPath = node_path.default.join(itemPath, subItem);
|
|
870
|
-
try {
|
|
871
|
-
if (!(await this.fileSystem.stat(subItemPath)).isDirectory()) return null;
|
|
872
|
-
const subScaffoldYamlPath = node_path.default.join(subItemPath, "scaffold.yaml");
|
|
873
|
-
if (await this.fileSystem.pathExists(subScaffoldYamlPath)) return node_path.default.join(item, subItem);
|
|
874
|
-
} catch {
|
|
875
|
-
return null;
|
|
876
|
-
}
|
|
877
|
-
return null;
|
|
878
|
-
}));
|
|
879
|
-
for (const relativePath of subResults) if (relativePath) found.push(relativePath);
|
|
880
|
-
} catch (error) {
|
|
881
|
-
__agiflowai_aicode_utils.log.warn(`Failed to read subdirectories in ${itemPath}:`, error);
|
|
882
|
-
}
|
|
883
|
-
return found;
|
|
884
|
-
}));
|
|
885
|
-
for (const dirs of nestedResults) templateDirs.push(...dirs);
|
|
886
|
-
} catch (error) {
|
|
887
|
-
__agiflowai_aicode_utils.log.warn(`Failed to read templates root directory ${this.templatesRootPath}:`, error);
|
|
888
|
-
}
|
|
889
|
-
return templateDirs;
|
|
890
|
-
}
|
|
891
|
-
async useScaffoldMethod(request) {
|
|
892
|
-
const { projectPath, scaffold_feature_name, variables, sessionId } = request;
|
|
893
|
-
const absoluteProjectPath = await this.resolveProjectPath(projectPath);
|
|
894
|
-
const scaffoldingMethods = await this.listScaffoldingMethods(absoluteProjectPath);
|
|
895
|
-
const method = scaffoldingMethods.methods.find((m) => m.name === scaffold_feature_name);
|
|
896
|
-
if (!method) {
|
|
897
|
-
const availableMethods = scaffoldingMethods.methods.map((m) => m.name).join(", ");
|
|
898
|
-
throw new Error(`Scaffold method '${scaffold_feature_name}' not found. Available methods: ${availableMethods}`);
|
|
899
|
-
}
|
|
900
|
-
const templateService = new TemplateService();
|
|
901
|
-
const scaffoldConfigLoader = new ScaffoldConfigLoader(this.fileSystem, templateService);
|
|
902
|
-
const variableReplacer = new VariableReplacementService(this.fileSystem, templateService);
|
|
903
|
-
const scaffoldService = new ScaffoldService(this.fileSystem, scaffoldConfigLoader, variableReplacer, this.templatesRootPath);
|
|
904
|
-
const projectName = node_path.default.basename(absoluteProjectPath);
|
|
905
|
-
const result = await scaffoldService.useFeature({
|
|
906
|
-
projectPath: absoluteProjectPath,
|
|
907
|
-
templateFolder: scaffoldingMethods.templatePath,
|
|
908
|
-
featureName: scaffold_feature_name,
|
|
909
|
-
variables: {
|
|
910
|
-
...variables,
|
|
911
|
-
appPath: absoluteProjectPath,
|
|
912
|
-
appName: projectName
|
|
913
|
-
}
|
|
914
|
-
});
|
|
915
|
-
if (!result.success) throw new Error(result.message);
|
|
916
|
-
if (sessionId && result.createdFiles && result.createdFiles.length > 0) try {
|
|
917
|
-
const { ExecutionLogService, DECISION_ALLOW } = await import("@agiflowai/hooks-adapter");
|
|
918
|
-
await new ExecutionLogService(sessionId).logExecution({
|
|
919
|
-
filePath: absoluteProjectPath,
|
|
920
|
-
operation: "scaffold",
|
|
921
|
-
decision: DECISION_ALLOW,
|
|
922
|
-
generatedFiles: result.createdFiles
|
|
923
|
-
});
|
|
924
|
-
} catch (error) {
|
|
925
|
-
__agiflowai_aicode_utils.log.warn("Failed to log scaffold execution:", error);
|
|
926
|
-
}
|
|
927
|
-
return {
|
|
928
|
-
success: true,
|
|
929
|
-
message: `
|
|
930
|
-
Successfully scaffolded ${scaffold_feature_name} in ${projectPath}.
|
|
931
|
-
Please follow this **instruction**: \n ${method.instruction ? this.processScaffoldInstruction(method.instruction, variables) : ""}.
|
|
932
|
-
-> Create or update the plan based on the instruction.
|
|
933
|
-
`,
|
|
934
|
-
warnings: result.warnings,
|
|
935
|
-
createdFiles: result.createdFiles,
|
|
936
|
-
existingFiles: result.existingFiles
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
};
|
|
940
|
-
|
|
941
|
-
//#endregion
|
|
942
|
-
//#region src/instructions/tools/list-scaffolding-methods/description.md?raw
|
|
943
|
-
var description_default = "Lists all available scaffolding methods (features) that can be added to an existing project{% if not isMonolith %} or for a specific template{% endif %}.\n\nThis tool:\n{% if isMonolith %}\n- Reads your project's sourceTemplate from toolkit.yaml at workspace root\n{% else %}\n- Reads the project's sourceTemplate from project.json (monorepo) or toolkit.yaml (monolith), OR\n- Directly uses the provided templateName to list available features\n{% endif %}\n- Returns available features for that template type\n- Provides variable schemas for each scaffolding method\n- Shows descriptions of what each method creates\n\nUse this FIRST when adding features to understand:\n- What scaffolding methods are available\n- What variables each method requires\n- What files/features will be generated\n\nExample methods might include:\n- Adding new React routes (for React apps)\n- Creating API endpoints (for backend projects)\n- Adding new components (for frontend projects)\n- Setting up database models (for API projects)\n";
|
|
944
|
-
|
|
945
|
-
//#endregion
|
|
946
|
-
//#region src/tools/ListScaffoldingMethodsTool.ts
|
|
947
|
-
var ListScaffoldingMethodsTool = class ListScaffoldingMethodsTool {
|
|
948
|
-
static TOOL_NAME = "list-scaffolding-methods";
|
|
949
|
-
fileSystemService;
|
|
950
|
-
scaffoldingMethodsService;
|
|
951
|
-
templateService;
|
|
952
|
-
isMonolith;
|
|
953
|
-
constructor(templatesPath, isMonolith = false) {
|
|
954
|
-
this.fileSystemService = new FileSystemService();
|
|
955
|
-
this.scaffoldingMethodsService = new ScaffoldingMethodsService(this.fileSystemService, templatesPath);
|
|
956
|
-
this.templateService = new TemplateService();
|
|
957
|
-
this.isMonolith = isMonolith;
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* Get the tool definition for MCP
|
|
961
|
-
*/
|
|
962
|
-
getDefinition() {
|
|
963
|
-
const description = this.templateService.renderString(description_default, { isMonolith: this.isMonolith });
|
|
964
|
-
const properties = { cursor: {
|
|
965
|
-
type: "string",
|
|
966
|
-
description: "Optional pagination cursor to fetch the next page of results. Omit to fetch the first page."
|
|
967
|
-
} };
|
|
968
|
-
if (!this.isMonolith) {
|
|
969
|
-
properties.projectPath = {
|
|
970
|
-
type: "string",
|
|
971
|
-
description: "Absolute path to the project directory (for monorepo: containing project.json; for monolith: workspace root with toolkit.yaml). Either projectPath or templateName is required."
|
|
972
|
-
};
|
|
973
|
-
properties.templateName = {
|
|
974
|
-
type: "string",
|
|
975
|
-
description: "Name of the template to list scaffolding methods for (e.g., \"nextjs-15\", \"typescript-mcp-package\"). Either projectPath or templateName is required."
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
return {
|
|
979
|
-
name: ListScaffoldingMethodsTool.TOOL_NAME,
|
|
980
|
-
description: description.trim(),
|
|
981
|
-
inputSchema: {
|
|
982
|
-
type: "object",
|
|
983
|
-
properties,
|
|
984
|
-
additionalProperties: false
|
|
985
|
-
}
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
/**
|
|
989
|
-
* Execute the tool
|
|
990
|
-
*/
|
|
991
|
-
async execute(args) {
|
|
992
|
-
try {
|
|
993
|
-
const { projectPath, templateName, cursor } = args;
|
|
994
|
-
let result;
|
|
995
|
-
if (this.isMonolith) try {
|
|
996
|
-
const resolvedTemplateName = (await __agiflowai_aicode_utils.ProjectConfigResolver.resolveProjectConfig(process.cwd())).sourceTemplate;
|
|
997
|
-
result = await this.scaffoldingMethodsService.listScaffoldingMethodsByTemplate(resolvedTemplateName, cursor);
|
|
998
|
-
} catch (error) {
|
|
999
|
-
throw new Error(`Failed to read template name from configuration: ${error instanceof Error ? error.message : String(error)}`);
|
|
1000
|
-
}
|
|
1001
|
-
else {
|
|
1002
|
-
if (!projectPath && !templateName) throw new Error("Either projectPath or templateName must be provided");
|
|
1003
|
-
if (projectPath) result = await this.scaffoldingMethodsService.listScaffoldingMethods(projectPath, cursor);
|
|
1004
|
-
else result = await this.scaffoldingMethodsService.listScaffoldingMethodsByTemplate(templateName, cursor);
|
|
1005
|
-
}
|
|
1006
|
-
return { content: [{
|
|
1007
|
-
type: "text",
|
|
1008
|
-
text: JSON.stringify(result, null, 2)
|
|
1009
|
-
}] };
|
|
1010
|
-
} catch (error) {
|
|
1011
|
-
return {
|
|
1012
|
-
content: [{
|
|
1013
|
-
type: "text",
|
|
1014
|
-
text: `Error listing scaffolding methods: ${error instanceof Error ? error.message : String(error)}`
|
|
1015
|
-
}],
|
|
1016
|
-
isError: true
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
};
|
|
1021
|
-
|
|
1022
|
-
//#endregion
|
|
1023
|
-
Object.defineProperty(exports, 'FileSystemService', {
|
|
1024
|
-
enumerable: true,
|
|
1025
|
-
get: function () {
|
|
1026
|
-
return FileSystemService;
|
|
1027
|
-
}
|
|
1028
|
-
});
|
|
1029
|
-
Object.defineProperty(exports, 'ListScaffoldingMethodsTool', {
|
|
1030
|
-
enumerable: true,
|
|
1031
|
-
get: function () {
|
|
1032
|
-
return ListScaffoldingMethodsTool;
|
|
1033
|
-
}
|
|
1034
|
-
});
|
|
1035
|
-
Object.defineProperty(exports, 'PaginationHelper', {
|
|
1036
|
-
enumerable: true,
|
|
1037
|
-
get: function () {
|
|
1038
|
-
return PaginationHelper;
|
|
1039
|
-
}
|
|
1040
|
-
});
|
|
1041
|
-
Object.defineProperty(exports, 'ScaffoldConfigLoader', {
|
|
1042
|
-
enumerable: true,
|
|
1043
|
-
get: function () {
|
|
1044
|
-
return ScaffoldConfigLoader;
|
|
1045
|
-
}
|
|
1046
|
-
});
|
|
1047
|
-
Object.defineProperty(exports, 'ScaffoldProcessingService', {
|
|
1048
|
-
enumerable: true,
|
|
1049
|
-
get: function () {
|
|
1050
|
-
return ScaffoldProcessingService;
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
Object.defineProperty(exports, 'ScaffoldService', {
|
|
1054
|
-
enumerable: true,
|
|
1055
|
-
get: function () {
|
|
1056
|
-
return ScaffoldService;
|
|
1057
|
-
}
|
|
1058
|
-
});
|
|
1059
|
-
Object.defineProperty(exports, 'ScaffoldingMethodsService', {
|
|
1060
|
-
enumerable: true,
|
|
1061
|
-
get: function () {
|
|
1062
|
-
return ScaffoldingMethodsService;
|
|
1063
|
-
}
|
|
1064
|
-
});
|
|
1065
|
-
Object.defineProperty(exports, 'TemplateService', {
|
|
1066
|
-
enumerable: true,
|
|
1067
|
-
get: function () {
|
|
1068
|
-
return TemplateService;
|
|
1069
|
-
}
|
|
1070
|
-
});
|
|
1071
|
-
Object.defineProperty(exports, 'VariableReplacementService', {
|
|
1072
|
-
enumerable: true,
|
|
1073
|
-
get: function () {
|
|
1074
|
-
return VariableReplacementService;
|
|
1075
|
-
}
|
|
1076
|
-
});
|
|
1077
|
-
Object.defineProperty(exports, '__toESM', {
|
|
1078
|
-
enumerable: true,
|
|
1079
|
-
get: function () {
|
|
1080
|
-
return __toESM;
|
|
1081
|
-
}
|
|
1082
|
-
});
|