@agiflowai/scaffold-mcp 0.6.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +11 -71
  2. package/dist/ScaffoldConfigLoader-CI0T6zdG.js +142 -0
  3. package/dist/{ScaffoldConfigLoader-DzcV5a_c.cjs → ScaffoldConfigLoader-DQMCLVGD.cjs} +1 -1
  4. package/dist/ScaffoldConfigLoader-DhthV6xq.js +3 -0
  5. package/dist/ScaffoldService-B3En_m4t.cjs +3 -0
  6. package/dist/{ScaffoldService-BgFWAOLQ.cjs → ScaffoldService-BwDmXt83.cjs} +17 -8
  7. package/dist/ScaffoldService-CJ3vNmAj.js +3 -0
  8. package/dist/ScaffoldService-DB7-Cyod.js +293 -0
  9. package/dist/TemplateService-BZRt3NI8.cjs +3 -0
  10. package/dist/TemplateService-CiZJA06s.js +79 -0
  11. package/dist/TemplateService-DropYdp8.js +3 -0
  12. package/dist/VariableReplacementService-BAwTGv_R.js +3 -0
  13. package/dist/{VariableReplacementService-YUpL5nAC.cjs → VariableReplacementService-CroHkMha.cjs} +1 -1
  14. package/dist/{VariableReplacementService-ClshNY_C.cjs → VariableReplacementService-D0QnWKUW.cjs} +2 -2
  15. package/dist/VariableReplacementService-DRxd9ILB.js +66 -0
  16. package/dist/cli.cjs +779 -0
  17. package/dist/cli.d.cts +1 -0
  18. package/dist/cli.d.ts +1 -0
  19. package/dist/cli.js +774 -0
  20. package/dist/index.cjs +38 -3208
  21. package/dist/index.d.cts +814 -0
  22. package/dist/index.d.ts +815 -0
  23. package/dist/index.js +137 -0
  24. package/dist/stdio-Bxn4A1IU.js +2073 -0
  25. package/dist/stdio-TGsG8akc.cjs +2178 -0
  26. package/package.json +19 -5
  27. package/dist/ScaffoldService-BvD9WvRi.cjs +0 -3
  28. package/dist/TemplateService-B5EZjPB0.cjs +0 -3
  29. /package/dist/{ScaffoldConfigLoader-1Pcv9cxm.cjs → ScaffoldConfigLoader-BrmvENTo.cjs} +0 -0
  30. /package/dist/{TemplateService-_KpkoLfZ.cjs → TemplateService-DRubcvS9.cjs} +0 -0
package/README.md CHANGED
@@ -10,7 +10,6 @@ A Model Context Protocol (MCP) server for scaffolding applications with boilerpl
10
10
  - **Liquid templating**: Use powerful templating engine for dynamic file generation
11
11
  - **Variable replacement**: Customize generated code with context-aware variable substitution
12
12
  - **Dynamic template discovery**: Automatically finds templates in your workspace
13
- - **Template management**: Initialize templates folder and add templates from remote repositories
14
13
  - **Multiple frameworks**: Support for Next.js, Vite React, and custom boilerplates
15
14
  - **Multiple modes**: MCP server mode (stdio/HTTP/SSE) and standalone CLI mode
16
15
  - **MCP integration**: Seamlessly works with Claude Code and other MCP-compatible clients
@@ -149,36 +148,9 @@ Or if installed globally:
149
148
 
150
149
  ### 2. CLI Commands
151
150
 
152
- Use scaffold-mcp as a standalone CLI tool for template management and scaffolding.
151
+ Use scaffold-mcp as a standalone CLI tool for scaffolding projects and adding features.
153
152
 
154
- #### Template Management
155
-
156
- ```bash
157
- # Initialize templates folder and auto-download official templates
158
- scaffold-mcp init
159
-
160
- # Initialize at custom path
161
- scaffold-mcp init --path ./custom-templates
162
-
163
- # Initialize without downloading templates
164
- scaffold-mcp init --no-download
165
-
166
- # Add templates from repositories (full or subdirectory)
167
- scaffold-mcp add --name my-template --url https://github.com/user/template
168
- scaffold-mcp add --name nextjs-custom --url https://github.com/user/repo/tree/main/templates/nextjs
169
- ```
170
-
171
- **What `init` does:**
172
- 1. Creates `templates/` folder in your workspace root
173
- 2. Automatically downloads official templates from [AgiFlow/aicode-toolkit](https://github.com/AgiFlow/aicode-toolkit/tree/main/templates)
174
- 3. Creates a README.md with usage instructions
175
- 4. Skips templates that already exist (safe to re-run)
176
-
177
- **What `add` does:**
178
- 1. Parses GitHub URL to detect full repository vs subdirectory
179
- 2. Downloads template using git clone (full repo) or sparse checkout (subdirectory)
180
- 3. Validates template has required configuration files (scaffold.yaml)
181
- 4. Saves template to your templates folder
153
+ **Note:** Template management (init, add) is now handled by the `@agiflowai/aicode-toolkit` CLI. See [aicode-toolkit documentation](../../apps/aicode-toolkit/README.md) for details.
182
154
 
183
155
  #### Boilerplate Commands
184
156
 
@@ -220,53 +192,21 @@ scaffold-mcp scaffold add scaffold-nextjs-page \
220
192
 
221
193
  ## Quick Start
222
194
 
223
- ### 1. Initialize Templates
195
+ ### 1. Setup Templates
224
196
 
225
- The `init` command sets up your templates folder and **automatically downloads official templates** from the AgiFlow repository:
197
+ For template management (downloading and managing templates), use the `@agiflowai/aicode-toolkit` CLI:
226
198
 
227
199
  ```bash
228
- # Initialize templates folder and download official templates
229
- scaffold-mcp init
200
+ # Initialize workspace and download templates
201
+ npx @agiflowai/aicode-toolkit init
230
202
 
231
- # Or specify a custom path
232
- scaffold-mcp init --path ./my-templates
233
-
234
- # Skip auto-download if you want to add templates manually
235
- scaffold-mcp init --no-download
236
- ```
237
-
238
- **What gets downloaded:**
239
- - ✅ `nextjs-15-drizzle` - Next.js 15 with App Router, TypeScript, Tailwind CSS 4, Storybook, and optional Drizzle ORM
240
- - ✅ More templates coming soon...
241
-
242
- All templates from [github.com/AgiFlow/aicode-toolkit/templates](https://github.com/AgiFlow/aicode-toolkit/tree/main/templates) are automatically pulled into your workspace.
243
-
244
- ### 2. Add Custom Templates
245
-
246
- Add additional templates from GitHub repositories or subdirectories:
247
-
248
- ```bash
249
- # Add a template from a full repository
250
- scaffold-mcp add --name my-template --url https://github.com/yourorg/nextjs-template
251
-
252
- # Add a template from a repository subdirectory (NEW!)
253
- scaffold-mcp add \
254
- --name nextjs-15-drizzle \
255
- --url https://github.com/AgiFlow/aicode-toolkit/tree/main/templates/nextjs-15-drizzle
256
-
257
- # Add to a specific type folder
258
- scaffold-mcp add \
259
- --name react-component \
260
- --url https://github.com/yourorg/react-component-scaffold \
261
- --type scaffold
203
+ # Add custom templates
204
+ npx @agiflowai/aicode-toolkit add --name my-template --url https://github.com/user/template
262
205
  ```
263
206
 
264
- **Supported URL formats:**
265
- - Full repository: `https://github.com/user/repo`
266
- - Subdirectory: `https://github.com/user/repo/tree/branch/path/to/template`
267
- - With `.git` extension: `https://github.com/user/repo.git`
207
+ See the [aicode-toolkit documentation](../../apps/aicode-toolkit/README.md) for complete setup instructions.
268
208
 
269
- ### 3. Create a New Project
209
+ ### 2. Create a New Project
270
210
 
271
211
  ```bash
272
212
  # List available boilerplates
@@ -280,7 +220,7 @@ scaffold-mcp boilerplate create nextjs-15-boilerplate \
280
220
  --vars '{"projectName":"my-app","packageName":"@myorg/my-app","appName":"My App"}'
281
221
  ```
282
222
 
283
- ### 4. Add Features to Existing Projects
223
+ ### 3. Add Features to Existing Projects
284
224
 
285
225
  ```bash
286
226
  # List available features for your project
@@ -0,0 +1,142 @@
1
+ import path from "node:path";
2
+ import yaml from "js-yaml";
3
+ import { z } from "zod";
4
+
5
+ //#region src/services/ScaffoldConfigLoader.ts
6
+ const VariablesSchemaSchema = z.object({
7
+ type: z.literal("object"),
8
+ properties: z.record(z.any()),
9
+ required: z.array(z.string()),
10
+ additionalProperties: z.boolean()
11
+ });
12
+ const ScaffoldConfigEntrySchema = z.object({
13
+ name: z.string(),
14
+ description: z.string().optional(),
15
+ instruction: z.string().optional(),
16
+ targetFolder: z.string().optional(),
17
+ variables_schema: VariablesSchemaSchema,
18
+ includes: z.array(z.string()),
19
+ generator: z.string().optional(),
20
+ patterns: z.array(z.string()).optional()
21
+ });
22
+ const ScaffoldYamlSchema = z.object({
23
+ boilerplate: z.union([ScaffoldConfigEntrySchema, z.array(ScaffoldConfigEntrySchema)]).optional(),
24
+ features: z.union([ScaffoldConfigEntrySchema, z.array(ScaffoldConfigEntrySchema)]).optional()
25
+ }).catchall(z.union([ScaffoldConfigEntrySchema, z.array(ScaffoldConfigEntrySchema)]));
26
+ var ScaffoldConfigLoader = class {
27
+ constructor(fileSystem, templateService) {
28
+ this.fileSystem = fileSystem;
29
+ this.templateService = templateService;
30
+ }
31
+ async parseArchitectConfig(templatePath) {
32
+ const architectPath = path.join(templatePath, "scaffold.yaml");
33
+ if (!await this.fileSystem.pathExists(architectPath)) return null;
34
+ try {
35
+ const content = await this.fileSystem.readFile(architectPath, "utf8");
36
+ const rawConfig = yaml.load(content);
37
+ return ScaffoldYamlSchema.parse(rawConfig);
38
+ } catch (error) {
39
+ if (error instanceof z.ZodError) {
40
+ const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`).join("; ");
41
+ throw new Error(`scaffold.yaml validation failed: ${errorMessages}`);
42
+ }
43
+ throw new Error(`Failed to parse scaffold.yaml: ${error instanceof Error ? error.message : String(error)}`);
44
+ }
45
+ }
46
+ parseIncludeEntry(includeEntry, variables) {
47
+ const [pathPart, conditionsPart] = includeEntry.split("?");
48
+ const conditions = {};
49
+ if (conditionsPart) {
50
+ const conditionPairs = conditionsPart.split("&");
51
+ for (const pair of conditionPairs) {
52
+ const [key, value] = pair.split("=");
53
+ if (key && value) conditions[key.trim()] = value.trim();
54
+ }
55
+ }
56
+ if (pathPart.includes("->")) {
57
+ const [sourcePath, targetPath] = pathPart.split("->").map((p) => p.trim());
58
+ return {
59
+ sourcePath,
60
+ targetPath: this.replaceVariablesInPath(targetPath, variables),
61
+ conditions
62
+ };
63
+ }
64
+ const processedPath = this.replaceVariablesInPath(pathPart.trim(), variables);
65
+ return {
66
+ sourcePath: pathPart.trim(),
67
+ targetPath: processedPath,
68
+ conditions
69
+ };
70
+ }
71
+ replaceVariablesInPath(pathStr, variables) {
72
+ return this.templateService.renderString(pathStr, variables);
73
+ }
74
+ shouldIncludeFile(conditions, variables) {
75
+ if (!conditions || Object.keys(conditions).length === 0) return true;
76
+ for (const [conditionKey, conditionValue] of Object.entries(conditions)) {
77
+ const variableValue = variables[conditionKey];
78
+ if (conditionValue === "true" || conditionValue === "false") {
79
+ const expectedBoolean = conditionValue === "true";
80
+ if (Boolean(variableValue) !== expectedBoolean) return false;
81
+ } else if (String(variableValue) !== conditionValue) return false;
82
+ }
83
+ return true;
84
+ }
85
+ async validateTemplate(templatePath, scaffoldType) {
86
+ const errors = [];
87
+ const missingFiles = [];
88
+ if (!await this.fileSystem.pathExists(templatePath)) {
89
+ errors.push(`Template directory ${templatePath} does not exist`);
90
+ return {
91
+ isValid: false,
92
+ errors,
93
+ missingFiles
94
+ };
95
+ }
96
+ let architectConfig;
97
+ try {
98
+ architectConfig = await this.parseArchitectConfig(templatePath);
99
+ } catch (error) {
100
+ errors.push(`Failed to parse scaffold.yaml: ${error instanceof Error ? error.message : String(error)}`);
101
+ return {
102
+ isValid: false,
103
+ errors,
104
+ missingFiles
105
+ };
106
+ }
107
+ if (!architectConfig) {
108
+ errors.push("scaffold.yaml not found in template directory");
109
+ return {
110
+ isValid: false,
111
+ errors,
112
+ missingFiles
113
+ };
114
+ }
115
+ if (!architectConfig[scaffoldType]) {
116
+ const availableTypes = Object.keys(architectConfig).join(", ");
117
+ errors.push(`Scaffold type '${scaffoldType}' not found in scaffold.yaml. Available types: ${availableTypes}`);
118
+ return {
119
+ isValid: false,
120
+ errors,
121
+ missingFiles
122
+ };
123
+ }
124
+ const config = architectConfig[scaffoldType];
125
+ if (config.includes && Array.isArray(config.includes)) for (const includeFile of config.includes) {
126
+ const parsed = this.parseIncludeEntry(includeFile, {});
127
+ const sourcePath = path.join(templatePath, parsed.sourcePath);
128
+ const liquidSourcePath = `${sourcePath}.liquid`;
129
+ const sourceExists = await this.fileSystem.pathExists(sourcePath);
130
+ const liquidExists = await this.fileSystem.pathExists(liquidSourcePath);
131
+ if (!sourceExists && !liquidExists) missingFiles.push(includeFile);
132
+ }
133
+ return {
134
+ isValid: errors.length === 0 && missingFiles.length === 0,
135
+ errors,
136
+ missingFiles
137
+ };
138
+ }
139
+ };
140
+
141
+ //#endregion
142
+ export { ScaffoldConfigLoader };
@@ -1,3 +1,3 @@
1
- const require_ScaffoldConfigLoader = require('./ScaffoldConfigLoader-1Pcv9cxm.cjs');
1
+ const require_ScaffoldConfigLoader = require('./ScaffoldConfigLoader-BrmvENTo.cjs');
2
2
 
3
3
  exports.ScaffoldConfigLoader = require_ScaffoldConfigLoader.ScaffoldConfigLoader;
@@ -0,0 +1,3 @@
1
+ import { ScaffoldConfigLoader } from "./ScaffoldConfigLoader-CI0T6zdG.js";
2
+
3
+ export { ScaffoldConfigLoader };
@@ -0,0 +1,3 @@
1
+ const require_ScaffoldService = require('./ScaffoldService-BwDmXt83.cjs');
2
+
3
+ exports.ScaffoldService = require_ScaffoldService.ScaffoldService;
@@ -1,8 +1,8 @@
1
1
  const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- let node_path = require("node:path");
3
- node_path = require_chunk.__toESM(node_path);
4
2
  let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
5
3
  __agiflowai_aicode_utils = require_chunk.__toESM(__agiflowai_aicode_utils);
4
+ let node_path = require("node:path");
5
+ node_path = require_chunk.__toESM(node_path);
6
6
  let node_url = require("node:url");
7
7
  node_url = require_chunk.__toESM(node_url);
8
8
 
@@ -123,17 +123,19 @@ var ScaffoldService = class {
123
123
  async useBoilerplate(options) {
124
124
  try {
125
125
  const { projectName, packageName, targetFolder, templateFolder, boilerplateName, variables = {} } = options;
126
- const targetPath = node_path.default.isAbsolute(targetFolder) ? node_path.default.join(targetFolder, projectName) : node_path.default.join(process.cwd(), targetFolder, projectName);
126
+ 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);
127
127
  const templatePath = node_path.default.join(this.templatesRootPath, templateFolder);
128
128
  const validationResult = await this.scaffoldConfigLoader.validateTemplate(templatePath, "boilerplate");
129
129
  if (!validationResult.isValid) return {
130
130
  success: false,
131
131
  message: `Template validation failed: ${[...validationResult.errors, ...validationResult.missingFiles.map((f) => `Template file not found: ${f}`)].join("; ")}`
132
132
  };
133
- if (await this.fileSystem.pathExists(targetPath)) return {
134
- success: false,
135
- message: `Directory ${targetPath} already exists`
136
- };
133
+ if (projectName) {
134
+ if (await this.fileSystem.pathExists(targetPath)) return {
135
+ success: false,
136
+ message: `Directory ${targetPath} already exists`
137
+ };
138
+ }
137
139
  const architectConfig = await this.scaffoldConfigLoader.parseArchitectConfig(templatePath);
138
140
  if (!architectConfig || !architectConfig.boilerplate) return {
139
141
  success: false,
@@ -148,9 +150,10 @@ var ScaffoldService = class {
148
150
  message: `Boilerplate '${boilerplateName}' not found in scaffold configuration`
149
151
  };
150
152
  } else config = architectConfig.boilerplate;
153
+ const effectiveProjectName = projectName || (packageName.includes("/") ? packageName.split("/")[1] : packageName);
151
154
  const allVariables = {
152
155
  ...variables,
153
- projectName,
156
+ projectName: effectiveProjectName,
154
157
  packageName
155
158
  };
156
159
  return await this.processScaffold({
@@ -291,6 +294,12 @@ var ScaffoldService = class {
291
294
  };
292
295
 
293
296
  //#endregion
297
+ Object.defineProperty(exports, 'ScaffoldProcessingService', {
298
+ enumerable: true,
299
+ get: function () {
300
+ return ScaffoldProcessingService;
301
+ }
302
+ });
294
303
  Object.defineProperty(exports, 'ScaffoldService', {
295
304
  enumerable: true,
296
305
  get: function () {
@@ -0,0 +1,3 @@
1
+ import { ScaffoldService } from "./ScaffoldService-DB7-Cyod.js";
2
+
3
+ export { ScaffoldService };
@@ -0,0 +1,293 @@
1
+ import { TemplatesManagerService, log } from "@agiflowai/aicode-utils";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ //#region src/services/ScaffoldProcessingService.ts
6
+ /**
7
+ * Shared service for common scaffolding operations like processing templates and tracking files
8
+ */
9
+ var ScaffoldProcessingService = class {
10
+ constructor(fileSystem, variableReplacer) {
11
+ this.fileSystem = fileSystem;
12
+ this.variableReplacer = variableReplacer;
13
+ }
14
+ /**
15
+ * Process a target path for variable replacement, handling both files and directories
16
+ */
17
+ async processTargetForVariableReplacement(targetPath, variables) {
18
+ if ((await this.fileSystem.stat(targetPath)).isDirectory()) await this.variableReplacer.processFilesForVariableReplacement(targetPath, variables);
19
+ else await this.variableReplacer.replaceVariablesInFile(targetPath, variables);
20
+ }
21
+ /**
22
+ * Track all created files, handling both single files and directories
23
+ */
24
+ async trackCreatedFiles(targetPath, createdFiles) {
25
+ if ((await this.fileSystem.stat(targetPath)).isDirectory()) await this.trackCreatedFilesRecursive(targetPath, createdFiles);
26
+ else createdFiles.push(targetPath);
27
+ }
28
+ /**
29
+ * Track all existing files, handling both single files and directories
30
+ */
31
+ async trackExistingFiles(targetPath, existingFiles) {
32
+ if ((await this.fileSystem.stat(targetPath)).isDirectory()) await this.trackExistingFilesRecursive(targetPath, existingFiles);
33
+ else existingFiles.push(targetPath);
34
+ }
35
+ /**
36
+ * Copy source to target, then process templates and track files
37
+ * Now supports tracking existing files separately from created files
38
+ * Automatically handles .liquid template files by stripping the extension
39
+ */
40
+ async copyAndProcess(sourcePath, targetPath, variables, createdFiles, existingFiles) {
41
+ await this.fileSystem.ensureDir(path.dirname(targetPath));
42
+ if (await this.fileSystem.pathExists(targetPath) && existingFiles) {
43
+ await this.trackExistingFiles(targetPath, existingFiles);
44
+ return;
45
+ }
46
+ let actualSourcePath = sourcePath;
47
+ if (!await this.fileSystem.pathExists(sourcePath)) {
48
+ const liquidSourcePath = `${sourcePath}.liquid`;
49
+ if (await this.fileSystem.pathExists(liquidSourcePath)) actualSourcePath = liquidSourcePath;
50
+ else throw new Error(`Source file not found: ${sourcePath} (also tried ${liquidSourcePath})`);
51
+ }
52
+ await this.fileSystem.copy(actualSourcePath, targetPath);
53
+ await this.processTargetForVariableReplacement(targetPath, variables);
54
+ await this.trackCreatedFiles(targetPath, createdFiles);
55
+ }
56
+ /**
57
+ * Recursively collect all file paths in a directory for created files
58
+ */
59
+ async trackCreatedFilesRecursive(dirPath, createdFiles) {
60
+ let items = [];
61
+ try {
62
+ items = await this.fileSystem.readdir(dirPath);
63
+ } catch (error) {
64
+ log.warn(`Cannot read directory ${dirPath}: ${error}`);
65
+ return;
66
+ }
67
+ for (const item of items) {
68
+ if (!item) continue;
69
+ const itemPath = path.join(dirPath, item);
70
+ try {
71
+ const stat = await this.fileSystem.stat(itemPath);
72
+ if (stat.isDirectory()) await this.trackCreatedFilesRecursive(itemPath, createdFiles);
73
+ else if (stat.isFile()) createdFiles.push(itemPath);
74
+ } catch (error) {
75
+ log.warn(`Cannot stat ${itemPath}: ${error}`);
76
+ }
77
+ }
78
+ }
79
+ /**
80
+ * Recursively collect all file paths in a directory for existing files
81
+ */
82
+ async trackExistingFilesRecursive(dirPath, existingFiles) {
83
+ let items = [];
84
+ try {
85
+ items = await this.fileSystem.readdir(dirPath);
86
+ } catch (error) {
87
+ log.warn(`Cannot read directory ${dirPath}: ${error}`);
88
+ return;
89
+ }
90
+ for (const item of items) {
91
+ if (!item) continue;
92
+ const itemPath = path.join(dirPath, item);
93
+ try {
94
+ const stat = await this.fileSystem.stat(itemPath);
95
+ if (stat.isDirectory()) await this.trackExistingFilesRecursive(itemPath, existingFiles);
96
+ else if (stat.isFile()) existingFiles.push(itemPath);
97
+ } catch (error) {
98
+ log.warn(`Cannot stat ${itemPath}: ${error}`);
99
+ }
100
+ }
101
+ }
102
+ };
103
+
104
+ //#endregion
105
+ //#region src/services/ScaffoldService.ts
106
+ var ScaffoldService = class {
107
+ templatesRootPath;
108
+ processingService;
109
+ constructor(fileSystem, scaffoldConfigLoader, variableReplacer, templatesRootPath) {
110
+ this.fileSystem = fileSystem;
111
+ this.scaffoldConfigLoader = scaffoldConfigLoader;
112
+ this.variableReplacer = variableReplacer;
113
+ this.templatesRootPath = templatesRootPath || TemplatesManagerService.findTemplatesPathSync();
114
+ this.processingService = new ScaffoldProcessingService(fileSystem, variableReplacer);
115
+ }
116
+ /**
117
+ * Scaffold a new project from a boilerplate template
118
+ */
119
+ async useBoilerplate(options) {
120
+ try {
121
+ const { projectName, packageName, targetFolder, templateFolder, boilerplateName, variables = {} } = options;
122
+ const targetPath = path.isAbsolute(targetFolder) ? projectName ? path.join(targetFolder, projectName) : targetFolder : projectName ? path.join(process.cwd(), targetFolder, projectName) : path.join(process.cwd(), targetFolder);
123
+ const templatePath = path.join(this.templatesRootPath, templateFolder);
124
+ const validationResult = await this.scaffoldConfigLoader.validateTemplate(templatePath, "boilerplate");
125
+ if (!validationResult.isValid) return {
126
+ success: false,
127
+ message: `Template validation failed: ${[...validationResult.errors, ...validationResult.missingFiles.map((f) => `Template file not found: ${f}`)].join("; ")}`
128
+ };
129
+ if (projectName) {
130
+ if (await this.fileSystem.pathExists(targetPath)) return {
131
+ success: false,
132
+ message: `Directory ${targetPath} already exists`
133
+ };
134
+ }
135
+ const architectConfig = await this.scaffoldConfigLoader.parseArchitectConfig(templatePath);
136
+ if (!architectConfig || !architectConfig.boilerplate) return {
137
+ success: false,
138
+ message: `Invalid architect configuration: missing 'boilerplate' section in scaffold.yaml`
139
+ };
140
+ const boilerplateArray = architectConfig.boilerplate;
141
+ let config;
142
+ if (Array.isArray(boilerplateArray)) {
143
+ config = boilerplateArray.find((b) => b.name === boilerplateName);
144
+ if (!config) return {
145
+ success: false,
146
+ message: `Boilerplate '${boilerplateName}' not found in scaffold configuration`
147
+ };
148
+ } else config = architectConfig.boilerplate;
149
+ const effectiveProjectName = projectName || (packageName.includes("/") ? packageName.split("/")[1] : packageName);
150
+ const allVariables = {
151
+ ...variables,
152
+ projectName: effectiveProjectName,
153
+ packageName
154
+ };
155
+ return await this.processScaffold({
156
+ config,
157
+ targetPath,
158
+ templatePath,
159
+ allVariables,
160
+ scaffoldType: "boilerplate"
161
+ });
162
+ } catch (error) {
163
+ return {
164
+ success: false,
165
+ message: `Error scaffolding boilerplate: ${error instanceof Error ? error.message : String(error)}`
166
+ };
167
+ }
168
+ }
169
+ /**
170
+ * Scaffold a new feature into an existing project
171
+ */
172
+ async useFeature(options) {
173
+ try {
174
+ const { projectPath, templateFolder, featureName, variables = {} } = options;
175
+ const targetPath = path.resolve(projectPath);
176
+ const templatePath = path.join(this.templatesRootPath, templateFolder);
177
+ const projectName = path.basename(targetPath);
178
+ const validationResult = await this.scaffoldConfigLoader.validateTemplate(templatePath, "features");
179
+ if (!validationResult.isValid) return {
180
+ success: false,
181
+ message: `Template validation failed: ${[...validationResult.errors, ...validationResult.missingFiles.map((f) => `Template file not found: ${f}`)].join("; ")}`
182
+ };
183
+ if (!await this.fileSystem.pathExists(targetPath)) return {
184
+ success: false,
185
+ message: `Target directory ${targetPath} does not exist. Please create the parent directory first.`
186
+ };
187
+ const architectConfig = await this.scaffoldConfigLoader.parseArchitectConfig(templatePath);
188
+ if (!architectConfig || !architectConfig.features) return {
189
+ success: false,
190
+ message: `Invalid architect configuration: missing 'features' section in scaffold.yaml`
191
+ };
192
+ const featureArray = architectConfig.features;
193
+ let config;
194
+ if (Array.isArray(featureArray)) {
195
+ config = featureArray.find((f) => f.name === featureName);
196
+ if (!config) return {
197
+ success: false,
198
+ message: `Feature '${featureName}' not found in scaffold configuration`
199
+ };
200
+ } else config = architectConfig.features;
201
+ const allVariables = {
202
+ ...variables,
203
+ projectName,
204
+ appPath: targetPath,
205
+ appName: projectName
206
+ };
207
+ return await this.processScaffold({
208
+ config,
209
+ targetPath,
210
+ templatePath,
211
+ allVariables,
212
+ scaffoldType: "feature"
213
+ });
214
+ } catch (error) {
215
+ return {
216
+ success: false,
217
+ message: `Error scaffolding feature: ${error instanceof Error ? error.message : String(error)}`
218
+ };
219
+ }
220
+ }
221
+ /**
222
+ * Common scaffolding processing logic shared by both useBoilerplate and useFeature
223
+ */
224
+ async processScaffold(params) {
225
+ const { config, targetPath, templatePath, allVariables, scaffoldType } = params;
226
+ log.debug("Config generator:", config.generator);
227
+ log.debug("Config:", JSON.stringify(config, null, 2));
228
+ if (config.generator) {
229
+ log.info("Using custom generator:", config.generator);
230
+ try {
231
+ const generator = (await import(path.join(templatePath, "generators", config.generator))).default;
232
+ if (typeof generator !== "function") return {
233
+ success: false,
234
+ message: `Invalid generator: ${config.generator} does not export a default function`
235
+ };
236
+ return await generator({
237
+ variables: allVariables,
238
+ config,
239
+ targetPath,
240
+ templatePath,
241
+ fileSystem: this.fileSystem,
242
+ scaffoldConfigLoader: this.scaffoldConfigLoader,
243
+ variableReplacer: this.variableReplacer,
244
+ ScaffoldProcessingService: this.processingService.constructor,
245
+ getRootPath: () => {
246
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
247
+ return path.join(__dirname, "../../../../..");
248
+ },
249
+ getProjectPath: (projectPath) => {
250
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
251
+ const rootPath = path.join(__dirname, "../../../../..");
252
+ return projectPath.replace(rootPath, "").replace("/", "");
253
+ }
254
+ });
255
+ } catch (error) {
256
+ return {
257
+ success: false,
258
+ message: `Error loading or executing generator ${config.generator}: ${error instanceof Error ? error.message : String(error)}`
259
+ };
260
+ }
261
+ }
262
+ const parsedIncludes = [];
263
+ const warnings = [];
264
+ if (config.includes && Array.isArray(config.includes)) for (const includeEntry of config.includes) {
265
+ const parsed = this.scaffoldConfigLoader.parseIncludeEntry(includeEntry, allVariables);
266
+ if (!this.scaffoldConfigLoader.shouldIncludeFile(parsed.conditions, allVariables)) continue;
267
+ parsedIncludes.push(parsed);
268
+ const targetFilePath = path.join(targetPath, parsed.targetPath);
269
+ if (await this.fileSystem.pathExists(targetFilePath)) warnings.push(`File/folder ${parsed.targetPath} already exists and will be preserved`);
270
+ }
271
+ await this.fileSystem.ensureDir(targetPath);
272
+ const createdFiles = [];
273
+ const existingFiles = [];
274
+ for (const parsed of parsedIncludes) {
275
+ const sourcePath = path.join(templatePath, parsed.sourcePath);
276
+ const targetFilePath = path.join(targetPath, parsed.targetPath);
277
+ await this.processingService.copyAndProcess(sourcePath, targetFilePath, allVariables, createdFiles, existingFiles);
278
+ }
279
+ let message = `Successfully scaffolded ${scaffoldType} at ${targetPath}`;
280
+ if (existingFiles.length > 0) message += `. ${existingFiles.length} existing file(s) were preserved`;
281
+ message += ". Run 'pnpm install' to install dependencies.";
282
+ return {
283
+ success: true,
284
+ message,
285
+ warnings: warnings.length > 0 ? warnings : void 0,
286
+ createdFiles: createdFiles.length > 0 ? createdFiles : void 0,
287
+ existingFiles: existingFiles.length > 0 ? existingFiles : void 0
288
+ };
289
+ }
290
+ };
291
+
292
+ //#endregion
293
+ export { ScaffoldProcessingService, ScaffoldService };
@@ -0,0 +1,3 @@
1
+ const require_TemplateService = require('./TemplateService-DRubcvS9.cjs');
2
+
3
+ exports.TemplateService = require_TemplateService.TemplateService;