@agiflowai/aicode-utils 1.0.2 → 1.0.4
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/README.md +0 -1
- package/dist/index.cjs +269 -48
- package/dist/index.d.cts +67 -1
- package/dist/{index.d.ts → index.d.mts} +67 -1
- package/dist/{index.js → index.mjs} +271 -82
- package/package.json +4 -6
package/README.md
CHANGED
|
@@ -360,7 +360,6 @@ await TemplatesManagerService.writeToolkitConfig({
|
|
|
360
360
|
|
|
361
361
|
**Dependencies**:
|
|
362
362
|
- `chalk` - Terminal colors and styling
|
|
363
|
-
- `fs-extra` - Enhanced file system operations
|
|
364
363
|
- `js-yaml` - YAML parsing for toolkit.yaml
|
|
365
364
|
- `pino` - Structured logging
|
|
366
365
|
|
package/dist/index.cjs
CHANGED
|
@@ -23,8 +23,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
//#endregion
|
|
24
24
|
let node_path = require("node:path");
|
|
25
25
|
node_path = __toESM(node_path);
|
|
26
|
-
let
|
|
27
|
-
|
|
26
|
+
let node_fs_promises = require("node:fs/promises");
|
|
27
|
+
node_fs_promises = __toESM(node_fs_promises);
|
|
28
|
+
let node_fs = require("node:fs");
|
|
28
29
|
let node_os = require("node:os");
|
|
29
30
|
node_os = __toESM(node_os);
|
|
30
31
|
let pino = require("pino");
|
|
@@ -50,6 +51,114 @@ const ConfigSource = {
|
|
|
50
51
|
TOOLKIT_YAML: "toolkit.yaml"
|
|
51
52
|
};
|
|
52
53
|
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/utils/fsHelpers.ts
|
|
56
|
+
/**
|
|
57
|
+
* Native FS Helper Functions
|
|
58
|
+
*
|
|
59
|
+
* Provides fs-extra-like API using native node:fs/promises
|
|
60
|
+
* to avoid ESM compatibility issues with fs-extra
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* Check if a file or directory exists
|
|
64
|
+
*/
|
|
65
|
+
async function pathExists(filePath) {
|
|
66
|
+
try {
|
|
67
|
+
await node_fs_promises.access(filePath);
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if a file or directory exists (sync)
|
|
75
|
+
*/
|
|
76
|
+
function pathExistsSync(filePath) {
|
|
77
|
+
try {
|
|
78
|
+
(0, node_fs.accessSync)(filePath);
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Ensure a directory exists, creating it recursively if needed
|
|
86
|
+
*/
|
|
87
|
+
async function ensureDir(dirPath) {
|
|
88
|
+
await node_fs_promises.mkdir(dirPath, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Ensure a directory exists (sync), creating it recursively if needed
|
|
92
|
+
*/
|
|
93
|
+
function ensureDirSync(dirPath) {
|
|
94
|
+
(0, node_fs.mkdirSync)(dirPath, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Remove a file or directory recursively
|
|
98
|
+
*/
|
|
99
|
+
async function remove(filePath) {
|
|
100
|
+
await node_fs_promises.rm(filePath, {
|
|
101
|
+
recursive: true,
|
|
102
|
+
force: true
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Copy a file or directory recursively
|
|
107
|
+
*/
|
|
108
|
+
async function copy(src, dest) {
|
|
109
|
+
await node_fs_promises.cp(src, dest, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Move a file or directory
|
|
113
|
+
*/
|
|
114
|
+
async function move(src, dest) {
|
|
115
|
+
await ensureDir(node_path.default.dirname(dest));
|
|
116
|
+
await node_fs_promises.rename(src, dest);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Read and parse a JSON file
|
|
120
|
+
*/
|
|
121
|
+
async function readJson(filePath) {
|
|
122
|
+
const content = await node_fs_promises.readFile(filePath, "utf-8");
|
|
123
|
+
return JSON.parse(content);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Read and parse a JSON file (sync)
|
|
127
|
+
*/
|
|
128
|
+
function readJsonSync(filePath) {
|
|
129
|
+
const content = (0, node_fs.readFileSync)(filePath, "utf-8");
|
|
130
|
+
return JSON.parse(content);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Write an object as JSON to a file
|
|
134
|
+
*/
|
|
135
|
+
async function writeJson(filePath, data, options) {
|
|
136
|
+
const content = JSON.stringify(data, null, options?.spaces ?? 2);
|
|
137
|
+
await node_fs_promises.writeFile(filePath, `${content}\n`, "utf-8");
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Write an object as JSON to a file (sync)
|
|
141
|
+
*/
|
|
142
|
+
function writeJsonSync(filePath, data, options) {
|
|
143
|
+
(0, node_fs.writeFileSync)(filePath, `${JSON.stringify(data, null, options?.spaces ?? 2)}\n`, "utf-8");
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Output file - writes content ensuring directory exists
|
|
147
|
+
*/
|
|
148
|
+
async function outputFile(filePath, content) {
|
|
149
|
+
await ensureDir(node_path.default.dirname(filePath));
|
|
150
|
+
await node_fs_promises.writeFile(filePath, content, "utf-8");
|
|
151
|
+
}
|
|
152
|
+
const readFile = node_fs_promises.readFile;
|
|
153
|
+
const writeFile = node_fs_promises.writeFile;
|
|
154
|
+
const readdir = node_fs_promises.readdir;
|
|
155
|
+
const mkdir = node_fs_promises.mkdir;
|
|
156
|
+
const stat = node_fs_promises.stat;
|
|
157
|
+
const unlink = node_fs_promises.unlink;
|
|
158
|
+
const rename = node_fs_promises.rename;
|
|
159
|
+
const rm = node_fs_promises.rm;
|
|
160
|
+
const cp = node_fs_promises.cp;
|
|
161
|
+
|
|
53
162
|
//#endregion
|
|
54
163
|
//#region src/utils/logger.ts
|
|
55
164
|
const logsDir = node_path.join(node_os.tmpdir(), "scaffold-mcp-logs");
|
|
@@ -70,6 +179,29 @@ const log = {
|
|
|
70
179
|
|
|
71
180
|
//#endregion
|
|
72
181
|
//#region src/services/TemplatesManagerService.ts
|
|
182
|
+
/**
|
|
183
|
+
* TemplatesManagerService
|
|
184
|
+
*
|
|
185
|
+
* DESIGN PATTERNS:
|
|
186
|
+
* - Class-based service pattern for encapsulating business logic
|
|
187
|
+
* - Static methods for utility-like functionality
|
|
188
|
+
* - File system traversal for workspace detection
|
|
189
|
+
* - Configuration-driven template path resolution
|
|
190
|
+
*
|
|
191
|
+
* CODING STANDARDS:
|
|
192
|
+
* - Service class names use PascalCase with 'Service' suffix
|
|
193
|
+
* - Method names use camelCase with descriptive verbs
|
|
194
|
+
* - Return types should be explicit (never use implicit any)
|
|
195
|
+
* - Use async/await for asynchronous operations
|
|
196
|
+
* - Handle errors with try-catch and throw descriptive Error objects
|
|
197
|
+
* - Document public methods with JSDoc comments
|
|
198
|
+
*
|
|
199
|
+
* AVOID:
|
|
200
|
+
* - Side effects in constructors (keep them lightweight)
|
|
201
|
+
* - Mixing concerns (keep services focused on single domain)
|
|
202
|
+
* - Direct coupling to other services (use dependency injection)
|
|
203
|
+
* - Exposing internal implementation details
|
|
204
|
+
*/
|
|
73
205
|
var TemplatesManagerService = class TemplatesManagerService {
|
|
74
206
|
static SCAFFOLD_CONFIG_FILE = "scaffold.yaml";
|
|
75
207
|
static TEMPLATES_FOLDER = "templates";
|
|
@@ -92,18 +224,18 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
92
224
|
static async findTemplatesPath(startPath = process.cwd()) {
|
|
93
225
|
const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
|
|
94
226
|
const toolkitConfigPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
95
|
-
if (await
|
|
227
|
+
if (await pathExists(toolkitConfigPath)) {
|
|
96
228
|
const yaml = await import("js-yaml");
|
|
97
|
-
const content = await
|
|
229
|
+
const content = await node_fs_promises.readFile(toolkitConfigPath, "utf-8");
|
|
98
230
|
const config = yaml.load(content);
|
|
99
231
|
if (config?.templatesPath) {
|
|
100
232
|
const templatesPath$1 = node_path.default.isAbsolute(config.templatesPath) ? config.templatesPath : node_path.default.join(workspaceRoot, config.templatesPath);
|
|
101
|
-
if (await
|
|
233
|
+
if (await pathExists(templatesPath$1)) return templatesPath$1;
|
|
102
234
|
else throw new Error(`Templates path specified in toolkit.yaml does not exist: ${templatesPath$1}`);
|
|
103
235
|
}
|
|
104
236
|
}
|
|
105
237
|
const templatesPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
106
|
-
if (await
|
|
238
|
+
if (await pathExists(templatesPath)) return templatesPath;
|
|
107
239
|
throw new Error(`Templates folder not found at ${templatesPath}.\nEither create a 'templates' folder or specify templatesPath in toolkit.yaml`);
|
|
108
240
|
}
|
|
109
241
|
/**
|
|
@@ -113,8 +245,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
113
245
|
let currentPath = node_path.default.resolve(startPath);
|
|
114
246
|
const rootPath = node_path.default.parse(currentPath).root;
|
|
115
247
|
while (true) {
|
|
116
|
-
|
|
117
|
-
if (await fs_extra.pathExists(gitPath)) return currentPath;
|
|
248
|
+
if (await pathExists(node_path.default.join(currentPath, ".git"))) return currentPath;
|
|
118
249
|
if (currentPath === rootPath) return process.cwd();
|
|
119
250
|
currentPath = node_path.default.dirname(currentPath);
|
|
120
251
|
}
|
|
@@ -130,18 +261,18 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
130
261
|
static findTemplatesPathSync(startPath = process.cwd()) {
|
|
131
262
|
const workspaceRoot = TemplatesManagerService.findWorkspaceRootSync(startPath);
|
|
132
263
|
const toolkitConfigPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
133
|
-
if (
|
|
264
|
+
if (pathExistsSync(toolkitConfigPath)) {
|
|
134
265
|
const yaml = require("js-yaml");
|
|
135
|
-
const content =
|
|
266
|
+
const content = (0, node_fs.readFileSync)(toolkitConfigPath, "utf-8");
|
|
136
267
|
const config = yaml.load(content);
|
|
137
268
|
if (config?.templatesPath) {
|
|
138
269
|
const templatesPath$1 = node_path.default.isAbsolute(config.templatesPath) ? config.templatesPath : node_path.default.join(workspaceRoot, config.templatesPath);
|
|
139
|
-
if (
|
|
270
|
+
if (pathExistsSync(templatesPath$1)) return templatesPath$1;
|
|
140
271
|
else throw new Error(`Templates path specified in toolkit.yaml does not exist: ${templatesPath$1}`);
|
|
141
272
|
}
|
|
142
273
|
}
|
|
143
274
|
const templatesPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
144
|
-
if (
|
|
275
|
+
if (pathExistsSync(templatesPath)) return templatesPath;
|
|
145
276
|
throw new Error(`Templates folder not found at ${templatesPath}.\nEither create a 'templates' folder or specify templatesPath in toolkit.yaml`);
|
|
146
277
|
}
|
|
147
278
|
/**
|
|
@@ -151,8 +282,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
151
282
|
let currentPath = node_path.default.resolve(startPath);
|
|
152
283
|
const rootPath = node_path.default.parse(currentPath).root;
|
|
153
284
|
while (true) {
|
|
154
|
-
|
|
155
|
-
if (fs_extra.pathExistsSync(gitPath)) return currentPath;
|
|
285
|
+
if (pathExistsSync(node_path.default.join(currentPath, ".git"))) return currentPath;
|
|
156
286
|
if (currentPath === rootPath) return process.cwd();
|
|
157
287
|
currentPath = node_path.default.dirname(currentPath);
|
|
158
288
|
}
|
|
@@ -164,8 +294,8 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
164
294
|
* @returns true if templates folder exists and is a directory
|
|
165
295
|
*/
|
|
166
296
|
static async isInitialized(templatesPath) {
|
|
167
|
-
if (!await
|
|
168
|
-
return (await
|
|
297
|
+
if (!await pathExists(templatesPath)) return false;
|
|
298
|
+
return (await node_fs_promises.stat(templatesPath)).isDirectory();
|
|
169
299
|
}
|
|
170
300
|
/**
|
|
171
301
|
* Get the scaffold config file name
|
|
@@ -188,9 +318,9 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
188
318
|
static async readToolkitConfig(startPath = process.cwd()) {
|
|
189
319
|
const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
|
|
190
320
|
const toolkitConfigPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
191
|
-
if (!await
|
|
321
|
+
if (!await pathExists(toolkitConfigPath)) return null;
|
|
192
322
|
const yaml = await import("js-yaml");
|
|
193
|
-
const content = await
|
|
323
|
+
const content = await node_fs_promises.readFile(toolkitConfigPath, "utf-8");
|
|
194
324
|
return yaml.load(content);
|
|
195
325
|
}
|
|
196
326
|
/**
|
|
@@ -202,9 +332,9 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
202
332
|
static readToolkitConfigSync(startPath = process.cwd()) {
|
|
203
333
|
const workspaceRoot = TemplatesManagerService.findWorkspaceRootSync(startPath);
|
|
204
334
|
const toolkitConfigPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
205
|
-
if (!
|
|
335
|
+
if (!pathExistsSync(toolkitConfigPath)) return null;
|
|
206
336
|
const yaml = require("js-yaml");
|
|
207
|
-
const content =
|
|
337
|
+
const content = (0, node_fs.readFileSync)(toolkitConfigPath, "utf-8");
|
|
208
338
|
return yaml.load(content);
|
|
209
339
|
}
|
|
210
340
|
/**
|
|
@@ -217,7 +347,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
217
347
|
const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
|
|
218
348
|
const toolkitConfigPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
219
349
|
const content = (await import("js-yaml")).dump(config, { indent: 2 });
|
|
220
|
-
await
|
|
350
|
+
await node_fs_promises.writeFile(toolkitConfigPath, content, "utf-8");
|
|
221
351
|
}
|
|
222
352
|
/**
|
|
223
353
|
* Get the workspace root directory
|
|
@@ -244,6 +374,28 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
244
374
|
/**
|
|
245
375
|
* ProjectConfigResolver
|
|
246
376
|
*
|
|
377
|
+
* DESIGN PATTERNS:
|
|
378
|
+
* - Class-based service pattern for resolving project configuration
|
|
379
|
+
* - Priority-based configuration resolution (project.json > toolkit.yaml > package.json)
|
|
380
|
+
* - Singleton-like static methods for common operations
|
|
381
|
+
*
|
|
382
|
+
* CODING STANDARDS:
|
|
383
|
+
* - Service class names use PascalCase with 'Service' suffix
|
|
384
|
+
* - Method names use camelCase with descriptive verbs
|
|
385
|
+
* - Return types should be explicit (never use implicit any)
|
|
386
|
+
* - Use async/await for asynchronous operations
|
|
387
|
+
* - Handle errors with try-catch and throw descriptive Error objects
|
|
388
|
+
* - Document public methods with JSDoc comments
|
|
389
|
+
*
|
|
390
|
+
* AVOID:
|
|
391
|
+
* - Side effects in constructors (keep them lightweight)
|
|
392
|
+
* - Mixing concerns (keep services focused on single domain)
|
|
393
|
+
* - Direct coupling to other services (use dependency injection)
|
|
394
|
+
* - Exposing internal implementation details
|
|
395
|
+
*/
|
|
396
|
+
/**
|
|
397
|
+
* ProjectConfigResolver
|
|
398
|
+
*
|
|
247
399
|
* Resolves project configuration from multiple sources with priority:
|
|
248
400
|
* 1. project.json (monorepo - Nx/Lerna/Turborepo)
|
|
249
401
|
* 2. toolkit.yaml at workspace root (monolith)
|
|
@@ -271,8 +423,8 @@ var ProjectConfigResolver = class ProjectConfigResolver {
|
|
|
271
423
|
configSource: ConfigSource.TOOLKIT_YAML
|
|
272
424
|
};
|
|
273
425
|
const projectJsonPath = node_path.default.join(absolutePath, "project.json");
|
|
274
|
-
if (await
|
|
275
|
-
const projectJson = await
|
|
426
|
+
if (await pathExists(projectJsonPath)) {
|
|
427
|
+
const projectJson = await readJson(projectJsonPath);
|
|
276
428
|
if (projectJson.sourceTemplate && typeof projectJson.sourceTemplate === "string" && projectJson.sourceTemplate.trim()) return {
|
|
277
429
|
type: ProjectType.MONOREPO,
|
|
278
430
|
sourceTemplate: projectJson.sourceTemplate.trim(),
|
|
@@ -364,7 +516,7 @@ Run 'scaffold-mcp scaffold list --help' for more info.`;
|
|
|
364
516
|
const projectJsonPath = node_path.default.join(projectPath, "project.json");
|
|
365
517
|
try {
|
|
366
518
|
let projectJson;
|
|
367
|
-
if (await
|
|
519
|
+
if (await pathExists(projectJsonPath)) projectJson = await readJson(projectJsonPath);
|
|
368
520
|
else {
|
|
369
521
|
const relativePath = node_path.default.relative(projectPath, process.cwd());
|
|
370
522
|
projectJson = {
|
|
@@ -375,8 +527,7 @@ Run 'scaffold-mcp scaffold list --help' for more info.`;
|
|
|
375
527
|
};
|
|
376
528
|
}
|
|
377
529
|
projectJson.sourceTemplate = sourceTemplate;
|
|
378
|
-
|
|
379
|
-
await fs_extra.writeFile(projectJsonPath, `${content}\n`);
|
|
530
|
+
await writeJson(projectJsonPath, projectJson);
|
|
380
531
|
log.info(`Created/updated project.json with sourceTemplate: ${sourceTemplate}`);
|
|
381
532
|
} catch (error) {
|
|
382
533
|
throw new Error(`Failed to create project.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -386,6 +537,26 @@ Run 'scaffold-mcp scaffold list --help' for more info.`;
|
|
|
386
537
|
|
|
387
538
|
//#endregion
|
|
388
539
|
//#region src/services/ProjectFinderService.ts
|
|
540
|
+
/**
|
|
541
|
+
* ProjectFinderService
|
|
542
|
+
*
|
|
543
|
+
* DESIGN PATTERNS:
|
|
544
|
+
* - Class-based service pattern for encapsulating business logic
|
|
545
|
+
* - Caching for performance optimization
|
|
546
|
+
* - File system traversal for project detection
|
|
547
|
+
*
|
|
548
|
+
* CODING STANDARDS:
|
|
549
|
+
* - Service class names use PascalCase with 'Service' suffix
|
|
550
|
+
* - Method names use camelCase with descriptive verbs
|
|
551
|
+
* - Return types should be explicit
|
|
552
|
+
* - Use async/await for asynchronous operations
|
|
553
|
+
* - Handle errors with try-catch and throw descriptive Error objects
|
|
554
|
+
*
|
|
555
|
+
* AVOID:
|
|
556
|
+
* - Side effects in constructors
|
|
557
|
+
* - Mixing concerns
|
|
558
|
+
* - Direct coupling to other services
|
|
559
|
+
*/
|
|
389
560
|
var ProjectFinderService = class {
|
|
390
561
|
projectCache = /* @__PURE__ */ new Map();
|
|
391
562
|
workspaceRoot;
|
|
@@ -436,7 +607,7 @@ var ProjectFinderService = class {
|
|
|
436
607
|
async loadProjectConfig(projectJsonPath) {
|
|
437
608
|
if (this.projectCache.has(projectJsonPath)) return this.projectCache.get(projectJsonPath);
|
|
438
609
|
try {
|
|
439
|
-
const content = await
|
|
610
|
+
const content = await node_fs_promises.readFile(projectJsonPath, "utf-8");
|
|
440
611
|
const config = JSON.parse(content);
|
|
441
612
|
const projectConfig = {
|
|
442
613
|
name: config.name || node_path.default.basename(node_path.default.dirname(projectJsonPath)),
|
|
@@ -456,7 +627,7 @@ var ProjectFinderService = class {
|
|
|
456
627
|
loadProjectConfigSync(projectJsonPath) {
|
|
457
628
|
if (this.projectCache.has(projectJsonPath)) return this.projectCache.get(projectJsonPath);
|
|
458
629
|
try {
|
|
459
|
-
const content =
|
|
630
|
+
const content = (0, node_fs.readFileSync)(projectJsonPath, "utf-8");
|
|
460
631
|
const config = JSON.parse(content);
|
|
461
632
|
const projectConfig = {
|
|
462
633
|
name: config.name || node_path.default.basename(node_path.default.dirname(projectJsonPath)),
|
|
@@ -544,9 +715,9 @@ var ScaffoldProcessingService = class {
|
|
|
544
715
|
if (!item) continue;
|
|
545
716
|
const itemPath = node_path.default.join(dirPath, item);
|
|
546
717
|
try {
|
|
547
|
-
const stat = await this.fileSystem.stat(itemPath);
|
|
548
|
-
if (stat.isDirectory()) await this.trackCreatedFilesRecursive(itemPath, createdFiles);
|
|
549
|
-
else if (stat.isFile()) createdFiles.push(itemPath);
|
|
718
|
+
const stat$1 = await this.fileSystem.stat(itemPath);
|
|
719
|
+
if (stat$1.isDirectory()) await this.trackCreatedFilesRecursive(itemPath, createdFiles);
|
|
720
|
+
else if (stat$1.isFile()) createdFiles.push(itemPath);
|
|
550
721
|
} catch (error) {
|
|
551
722
|
console.warn(`Cannot stat ${itemPath}: ${error}`);
|
|
552
723
|
}
|
|
@@ -567,9 +738,9 @@ var ScaffoldProcessingService = class {
|
|
|
567
738
|
if (!item) continue;
|
|
568
739
|
const itemPath = node_path.default.join(dirPath, item);
|
|
569
740
|
try {
|
|
570
|
-
const stat = await this.fileSystem.stat(itemPath);
|
|
571
|
-
if (stat.isDirectory()) await this.trackExistingFilesRecursive(itemPath, existingFiles);
|
|
572
|
-
else if (stat.isFile()) existingFiles.push(itemPath);
|
|
741
|
+
const stat$1 = await this.fileSystem.stat(itemPath);
|
|
742
|
+
if (stat$1.isDirectory()) await this.trackExistingFilesRecursive(itemPath, existingFiles);
|
|
743
|
+
else if (stat$1.isFile()) existingFiles.push(itemPath);
|
|
573
744
|
} catch (error) {
|
|
574
745
|
console.warn(`Cannot stat ${itemPath}: ${error}`);
|
|
575
746
|
}
|
|
@@ -704,6 +875,27 @@ const sections = {
|
|
|
704
875
|
//#endregion
|
|
705
876
|
//#region src/utils/projectTypeDetector.ts
|
|
706
877
|
/**
|
|
878
|
+
* projectTypeDetector Utilities
|
|
879
|
+
*
|
|
880
|
+
* DESIGN PATTERNS:
|
|
881
|
+
* - Pure function pattern: No side effects, deterministic output
|
|
882
|
+
* - Single domain focus: All functions related to project type detection
|
|
883
|
+
* - Composability: Functions can be combined to create complex behavior
|
|
884
|
+
*
|
|
885
|
+
* CODING STANDARDS:
|
|
886
|
+
* - Function names use camelCase with descriptive verbs (validate, format, parse, transform)
|
|
887
|
+
* - All functions should be pure (same input = same output, no side effects)
|
|
888
|
+
* - Use explicit return types
|
|
889
|
+
* - Document complex logic with JSDoc comments
|
|
890
|
+
* - Keep functions small and focused on single responsibility
|
|
891
|
+
*
|
|
892
|
+
* AVOID:
|
|
893
|
+
* - Side effects (mutations, I/O, random values, Date.now(), etc.)
|
|
894
|
+
* - Stateful behavior or closures with mutable state
|
|
895
|
+
* - Dependencies on external services or global variables
|
|
896
|
+
* - Classes (use pure functions instead)
|
|
897
|
+
*/
|
|
898
|
+
/**
|
|
707
899
|
* Monorepo configuration files that indicate a monorepo setup
|
|
708
900
|
*/
|
|
709
901
|
const MONOREPO_INDICATOR_FILES = [
|
|
@@ -729,8 +921,8 @@ const MONOREPO_INDICATOR_FILES = [
|
|
|
729
921
|
async function detectProjectType(workspaceRoot) {
|
|
730
922
|
const indicators = [];
|
|
731
923
|
const toolkitYamlPath = node_path.default.join(workspaceRoot, "toolkit.yaml");
|
|
732
|
-
if (await
|
|
733
|
-
const content = await
|
|
924
|
+
if (await pathExists(toolkitYamlPath)) try {
|
|
925
|
+
const content = await node_fs_promises.readFile(toolkitYamlPath, "utf-8");
|
|
734
926
|
const config = js_yaml.load(content);
|
|
735
927
|
if (config?.projectType) {
|
|
736
928
|
indicators.push(`toolkit.yaml specifies ${config.projectType}`);
|
|
@@ -740,19 +932,16 @@ async function detectProjectType(workspaceRoot) {
|
|
|
740
932
|
};
|
|
741
933
|
}
|
|
742
934
|
} catch {}
|
|
743
|
-
for (const filename of MONOREPO_INDICATOR_FILES) {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
indicators
|
|
750
|
-
};
|
|
751
|
-
}
|
|
935
|
+
for (const filename of MONOREPO_INDICATOR_FILES) if (await pathExists(node_path.default.join(workspaceRoot, filename))) {
|
|
936
|
+
indicators.push(`${filename} found`);
|
|
937
|
+
return {
|
|
938
|
+
projectType: ProjectType.MONOREPO,
|
|
939
|
+
indicators
|
|
940
|
+
};
|
|
752
941
|
}
|
|
753
942
|
const packageJsonPath = node_path.default.join(workspaceRoot, "package.json");
|
|
754
|
-
if (await
|
|
755
|
-
if ((await
|
|
943
|
+
if (await pathExists(packageJsonPath)) try {
|
|
944
|
+
if ((await readJson(packageJsonPath)).workspaces) {
|
|
756
945
|
indicators.push("package.json with workspaces found");
|
|
757
946
|
return {
|
|
758
947
|
projectType: ProjectType.MONOREPO,
|
|
@@ -794,12 +983,44 @@ exports.ProjectFinderService = ProjectFinderService;
|
|
|
794
983
|
exports.ProjectType = ProjectType;
|
|
795
984
|
exports.ScaffoldProcessingService = ScaffoldProcessingService;
|
|
796
985
|
exports.TemplatesManagerService = TemplatesManagerService;
|
|
986
|
+
exports.accessSync = node_fs.accessSync;
|
|
987
|
+
exports.copy = copy;
|
|
988
|
+
exports.cp = cp;
|
|
797
989
|
exports.detectProjectType = detectProjectType;
|
|
990
|
+
exports.ensureDir = ensureDir;
|
|
991
|
+
exports.ensureDirSync = ensureDirSync;
|
|
992
|
+
Object.defineProperty(exports, 'fs', {
|
|
993
|
+
enumerable: true,
|
|
994
|
+
get: function () {
|
|
995
|
+
return node_fs_promises;
|
|
996
|
+
}
|
|
997
|
+
});
|
|
798
998
|
exports.icons = icons;
|
|
799
999
|
exports.isMonolith = isMonolith;
|
|
800
1000
|
exports.isMonorepo = isMonorepo;
|
|
801
1001
|
exports.log = log;
|
|
802
1002
|
exports.logger = logger;
|
|
803
1003
|
exports.messages = messages;
|
|
1004
|
+
exports.mkdir = mkdir;
|
|
1005
|
+
exports.mkdirSync = node_fs.mkdirSync;
|
|
1006
|
+
exports.move = move;
|
|
1007
|
+
exports.outputFile = outputFile;
|
|
1008
|
+
exports.pathExists = pathExists;
|
|
1009
|
+
exports.pathExistsSync = pathExistsSync;
|
|
804
1010
|
exports.print = print;
|
|
805
|
-
exports.
|
|
1011
|
+
exports.readFile = readFile;
|
|
1012
|
+
exports.readFileSync = node_fs.readFileSync;
|
|
1013
|
+
exports.readJson = readJson;
|
|
1014
|
+
exports.readJsonSync = readJsonSync;
|
|
1015
|
+
exports.readdir = readdir;
|
|
1016
|
+
exports.remove = remove;
|
|
1017
|
+
exports.rename = rename;
|
|
1018
|
+
exports.rm = rm;
|
|
1019
|
+
exports.sections = sections;
|
|
1020
|
+
exports.stat = stat;
|
|
1021
|
+
exports.statSync = node_fs.statSync;
|
|
1022
|
+
exports.unlink = unlink;
|
|
1023
|
+
exports.writeFile = writeFile;
|
|
1024
|
+
exports.writeFileSync = node_fs.writeFileSync;
|
|
1025
|
+
exports.writeJson = writeJson;
|
|
1026
|
+
exports.writeJsonSync = writeJsonSync;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { accessSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
1
3
|
import pino from "pino";
|
|
2
4
|
|
|
3
5
|
//#region src/constants/projectType.d.ts
|
|
@@ -43,6 +45,7 @@ interface NxProjectJson {
|
|
|
43
45
|
}
|
|
44
46
|
//#endregion
|
|
45
47
|
//#region src/types/index.d.ts
|
|
48
|
+
|
|
46
49
|
/**
|
|
47
50
|
* Toolkit configuration from toolkit.yaml
|
|
48
51
|
*/
|
|
@@ -337,6 +340,69 @@ declare class TemplatesManagerService {
|
|
|
337
340
|
static getWorkspaceRootSync(startPath?: string): string;
|
|
338
341
|
}
|
|
339
342
|
//#endregion
|
|
343
|
+
//#region src/utils/fsHelpers.d.ts
|
|
344
|
+
/**
|
|
345
|
+
* Check if a file or directory exists
|
|
346
|
+
*/
|
|
347
|
+
declare function pathExists(filePath: string): Promise<boolean>;
|
|
348
|
+
/**
|
|
349
|
+
* Check if a file or directory exists (sync)
|
|
350
|
+
*/
|
|
351
|
+
declare function pathExistsSync(filePath: string): boolean;
|
|
352
|
+
/**
|
|
353
|
+
* Ensure a directory exists, creating it recursively if needed
|
|
354
|
+
*/
|
|
355
|
+
declare function ensureDir(dirPath: string): Promise<void>;
|
|
356
|
+
/**
|
|
357
|
+
* Ensure a directory exists (sync), creating it recursively if needed
|
|
358
|
+
*/
|
|
359
|
+
declare function ensureDirSync(dirPath: string): void;
|
|
360
|
+
/**
|
|
361
|
+
* Remove a file or directory recursively
|
|
362
|
+
*/
|
|
363
|
+
declare function remove(filePath: string): Promise<void>;
|
|
364
|
+
/**
|
|
365
|
+
* Copy a file or directory recursively
|
|
366
|
+
*/
|
|
367
|
+
declare function copy(src: string, dest: string): Promise<void>;
|
|
368
|
+
/**
|
|
369
|
+
* Move a file or directory
|
|
370
|
+
*/
|
|
371
|
+
declare function move(src: string, dest: string): Promise<void>;
|
|
372
|
+
/**
|
|
373
|
+
* Read and parse a JSON file
|
|
374
|
+
*/
|
|
375
|
+
declare function readJson<T = unknown>(filePath: string): Promise<T>;
|
|
376
|
+
/**
|
|
377
|
+
* Read and parse a JSON file (sync)
|
|
378
|
+
*/
|
|
379
|
+
declare function readJsonSync<T = unknown>(filePath: string): T;
|
|
380
|
+
/**
|
|
381
|
+
* Write an object as JSON to a file
|
|
382
|
+
*/
|
|
383
|
+
declare function writeJson(filePath: string, data: unknown, options?: {
|
|
384
|
+
spaces?: number;
|
|
385
|
+
}): Promise<void>;
|
|
386
|
+
/**
|
|
387
|
+
* Write an object as JSON to a file (sync)
|
|
388
|
+
*/
|
|
389
|
+
declare function writeJsonSync(filePath: string, data: unknown, options?: {
|
|
390
|
+
spaces?: number;
|
|
391
|
+
}): void;
|
|
392
|
+
/**
|
|
393
|
+
* Output file - writes content ensuring directory exists
|
|
394
|
+
*/
|
|
395
|
+
declare function outputFile(filePath: string, content: string): Promise<void>;
|
|
396
|
+
declare const readFile: typeof fs.readFile;
|
|
397
|
+
declare const writeFile: typeof fs.writeFile;
|
|
398
|
+
declare const readdir: typeof fs.readdir;
|
|
399
|
+
declare const mkdir: typeof fs.mkdir;
|
|
400
|
+
declare const stat: typeof fs.stat;
|
|
401
|
+
declare const unlink: typeof fs.unlink;
|
|
402
|
+
declare const rename: typeof fs.rename;
|
|
403
|
+
declare const rm: typeof fs.rm;
|
|
404
|
+
declare const cp: typeof fs.cp;
|
|
405
|
+
//#endregion
|
|
340
406
|
//#region src/utils/logger.d.ts
|
|
341
407
|
declare const logger: pino.Logger<never, boolean>;
|
|
342
408
|
declare const log: {
|
|
@@ -514,4 +580,4 @@ declare function isMonorepo(workspaceRoot: string): Promise<boolean>;
|
|
|
514
580
|
*/
|
|
515
581
|
declare function isMonolith(workspaceRoot: string): Promise<boolean>;
|
|
516
582
|
//#endregion
|
|
517
|
-
export { ConfigSource, GeneratorContext, GeneratorFunction, IFileSystemService, IVariableReplacementService, NxProjectJson, ParsedInclude, ProjectConfig, ProjectConfigResolver, ProjectConfigResult, ProjectFinderService, ProjectType, ProjectTypeDetectionResult, ScaffoldProcessingService, ScaffoldResult, TemplatesManagerService, ToolkitConfig, detectProjectType, icons, isMonolith, isMonorepo, log, logger, messages, print, sections };
|
|
583
|
+
export { ConfigSource, GeneratorContext, GeneratorFunction, IFileSystemService, IVariableReplacementService, NxProjectJson, ParsedInclude, ProjectConfig, ProjectConfigResolver, ProjectConfigResult, ProjectFinderService, ProjectType, ProjectTypeDetectionResult, ScaffoldProcessingService, ScaffoldResult, TemplatesManagerService, ToolkitConfig, accessSync, copy, cp, detectProjectType, ensureDir, ensureDirSync, fs, icons, isMonolith, isMonorepo, log, logger, messages, mkdir, mkdirSync, move, outputFile, pathExists, pathExistsSync, print, readFile, readFileSync, readJson, readJsonSync, readdir, remove, rename, rm, sections, stat, statSync, unlink, writeFile, writeFileSync, writeJson, writeJsonSync };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { accessSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
1
3
|
import pino from "pino";
|
|
2
4
|
|
|
3
5
|
//#region src/constants/projectType.d.ts
|
|
@@ -43,6 +45,7 @@ interface NxProjectJson {
|
|
|
43
45
|
}
|
|
44
46
|
//#endregion
|
|
45
47
|
//#region src/types/index.d.ts
|
|
48
|
+
|
|
46
49
|
/**
|
|
47
50
|
* Toolkit configuration from toolkit.yaml
|
|
48
51
|
*/
|
|
@@ -337,6 +340,69 @@ declare class TemplatesManagerService {
|
|
|
337
340
|
static getWorkspaceRootSync(startPath?: string): string;
|
|
338
341
|
}
|
|
339
342
|
//#endregion
|
|
343
|
+
//#region src/utils/fsHelpers.d.ts
|
|
344
|
+
/**
|
|
345
|
+
* Check if a file or directory exists
|
|
346
|
+
*/
|
|
347
|
+
declare function pathExists(filePath: string): Promise<boolean>;
|
|
348
|
+
/**
|
|
349
|
+
* Check if a file or directory exists (sync)
|
|
350
|
+
*/
|
|
351
|
+
declare function pathExistsSync(filePath: string): boolean;
|
|
352
|
+
/**
|
|
353
|
+
* Ensure a directory exists, creating it recursively if needed
|
|
354
|
+
*/
|
|
355
|
+
declare function ensureDir(dirPath: string): Promise<void>;
|
|
356
|
+
/**
|
|
357
|
+
* Ensure a directory exists (sync), creating it recursively if needed
|
|
358
|
+
*/
|
|
359
|
+
declare function ensureDirSync(dirPath: string): void;
|
|
360
|
+
/**
|
|
361
|
+
* Remove a file or directory recursively
|
|
362
|
+
*/
|
|
363
|
+
declare function remove(filePath: string): Promise<void>;
|
|
364
|
+
/**
|
|
365
|
+
* Copy a file or directory recursively
|
|
366
|
+
*/
|
|
367
|
+
declare function copy(src: string, dest: string): Promise<void>;
|
|
368
|
+
/**
|
|
369
|
+
* Move a file or directory
|
|
370
|
+
*/
|
|
371
|
+
declare function move(src: string, dest: string): Promise<void>;
|
|
372
|
+
/**
|
|
373
|
+
* Read and parse a JSON file
|
|
374
|
+
*/
|
|
375
|
+
declare function readJson<T = unknown>(filePath: string): Promise<T>;
|
|
376
|
+
/**
|
|
377
|
+
* Read and parse a JSON file (sync)
|
|
378
|
+
*/
|
|
379
|
+
declare function readJsonSync<T = unknown>(filePath: string): T;
|
|
380
|
+
/**
|
|
381
|
+
* Write an object as JSON to a file
|
|
382
|
+
*/
|
|
383
|
+
declare function writeJson(filePath: string, data: unknown, options?: {
|
|
384
|
+
spaces?: number;
|
|
385
|
+
}): Promise<void>;
|
|
386
|
+
/**
|
|
387
|
+
* Write an object as JSON to a file (sync)
|
|
388
|
+
*/
|
|
389
|
+
declare function writeJsonSync(filePath: string, data: unknown, options?: {
|
|
390
|
+
spaces?: number;
|
|
391
|
+
}): void;
|
|
392
|
+
/**
|
|
393
|
+
* Output file - writes content ensuring directory exists
|
|
394
|
+
*/
|
|
395
|
+
declare function outputFile(filePath: string, content: string): Promise<void>;
|
|
396
|
+
declare const readFile: typeof fs.readFile;
|
|
397
|
+
declare const writeFile: typeof fs.writeFile;
|
|
398
|
+
declare const readdir: typeof fs.readdir;
|
|
399
|
+
declare const mkdir: typeof fs.mkdir;
|
|
400
|
+
declare const stat: typeof fs.stat;
|
|
401
|
+
declare const unlink: typeof fs.unlink;
|
|
402
|
+
declare const rename: typeof fs.rename;
|
|
403
|
+
declare const rm: typeof fs.rm;
|
|
404
|
+
declare const cp: typeof fs.cp;
|
|
405
|
+
//#endregion
|
|
340
406
|
//#region src/utils/logger.d.ts
|
|
341
407
|
declare const logger: pino.Logger<never, boolean>;
|
|
342
408
|
declare const log: {
|
|
@@ -514,4 +580,4 @@ declare function isMonorepo(workspaceRoot: string): Promise<boolean>;
|
|
|
514
580
|
*/
|
|
515
581
|
declare function isMonolith(workspaceRoot: string): Promise<boolean>;
|
|
516
582
|
//#endregion
|
|
517
|
-
export { ConfigSource, GeneratorContext, GeneratorFunction, IFileSystemService, IVariableReplacementService, NxProjectJson, ParsedInclude, ProjectConfig, ProjectConfigResolver, ProjectConfigResult, ProjectFinderService, ProjectType, ProjectTypeDetectionResult, ScaffoldProcessingService, ScaffoldResult, TemplatesManagerService, ToolkitConfig, detectProjectType, icons, isMonolith, isMonorepo, log, logger, messages, print, sections };
|
|
583
|
+
export { ConfigSource, GeneratorContext, GeneratorFunction, IFileSystemService, IVariableReplacementService, NxProjectJson, ParsedInclude, ProjectConfig, ProjectConfigResolver, ProjectConfigResult, ProjectFinderService, ProjectType, ProjectTypeDetectionResult, ScaffoldProcessingService, ScaffoldResult, TemplatesManagerService, ToolkitConfig, accessSync, copy, cp, detectProjectType, ensureDir, ensureDirSync, fs, icons, isMonolith, isMonorepo, log, logger, messages, mkdir, mkdirSync, move, outputFile, pathExists, pathExistsSync, print, readFile, readFileSync, readJson, readJsonSync, readdir, remove, rename, rm, sections, stat, statSync, unlink, writeFile, writeFileSync, writeJson, writeJsonSync };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import * as path
|
|
3
|
-
import
|
|
4
|
-
import * as fs from "fs
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import nodePath from "node:path";
|
|
4
|
+
import * as fs from "node:fs/promises";
|
|
5
|
+
import { accessSync, mkdirSync, readFileSync, readFileSync as readFileSync$1, statSync, writeFileSync } from "node:fs";
|
|
5
6
|
import * as os from "node:os";
|
|
6
7
|
import pino from "pino";
|
|
7
8
|
import * as yaml from "js-yaml";
|
|
@@ -27,14 +28,122 @@ const ConfigSource = {
|
|
|
27
28
|
TOOLKIT_YAML: "toolkit.yaml"
|
|
28
29
|
};
|
|
29
30
|
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/utils/fsHelpers.ts
|
|
33
|
+
/**
|
|
34
|
+
* Native FS Helper Functions
|
|
35
|
+
*
|
|
36
|
+
* Provides fs-extra-like API using native node:fs/promises
|
|
37
|
+
* to avoid ESM compatibility issues with fs-extra
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Check if a file or directory exists
|
|
41
|
+
*/
|
|
42
|
+
async function pathExists(filePath) {
|
|
43
|
+
try {
|
|
44
|
+
await fs.access(filePath);
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check if a file or directory exists (sync)
|
|
52
|
+
*/
|
|
53
|
+
function pathExistsSync(filePath) {
|
|
54
|
+
try {
|
|
55
|
+
accessSync(filePath);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Ensure a directory exists, creating it recursively if needed
|
|
63
|
+
*/
|
|
64
|
+
async function ensureDir(dirPath) {
|
|
65
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Ensure a directory exists (sync), creating it recursively if needed
|
|
69
|
+
*/
|
|
70
|
+
function ensureDirSync(dirPath) {
|
|
71
|
+
mkdirSync(dirPath, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Remove a file or directory recursively
|
|
75
|
+
*/
|
|
76
|
+
async function remove(filePath) {
|
|
77
|
+
await fs.rm(filePath, {
|
|
78
|
+
recursive: true,
|
|
79
|
+
force: true
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Copy a file or directory recursively
|
|
84
|
+
*/
|
|
85
|
+
async function copy(src, dest) {
|
|
86
|
+
await fs.cp(src, dest, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Move a file or directory
|
|
90
|
+
*/
|
|
91
|
+
async function move(src, dest) {
|
|
92
|
+
await ensureDir(nodePath.dirname(dest));
|
|
93
|
+
await fs.rename(src, dest);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Read and parse a JSON file
|
|
97
|
+
*/
|
|
98
|
+
async function readJson(filePath) {
|
|
99
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
100
|
+
return JSON.parse(content);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Read and parse a JSON file (sync)
|
|
104
|
+
*/
|
|
105
|
+
function readJsonSync(filePath) {
|
|
106
|
+
const content = readFileSync(filePath, "utf-8");
|
|
107
|
+
return JSON.parse(content);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Write an object as JSON to a file
|
|
111
|
+
*/
|
|
112
|
+
async function writeJson(filePath, data, options) {
|
|
113
|
+
const content = JSON.stringify(data, null, options?.spaces ?? 2);
|
|
114
|
+
await fs.writeFile(filePath, `${content}\n`, "utf-8");
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Write an object as JSON to a file (sync)
|
|
118
|
+
*/
|
|
119
|
+
function writeJsonSync(filePath, data, options) {
|
|
120
|
+
writeFileSync(filePath, `${JSON.stringify(data, null, options?.spaces ?? 2)}\n`, "utf-8");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Output file - writes content ensuring directory exists
|
|
124
|
+
*/
|
|
125
|
+
async function outputFile(filePath, content) {
|
|
126
|
+
await ensureDir(nodePath.dirname(filePath));
|
|
127
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
128
|
+
}
|
|
129
|
+
const readFile = fs.readFile;
|
|
130
|
+
const writeFile = fs.writeFile;
|
|
131
|
+
const readdir = fs.readdir;
|
|
132
|
+
const mkdir = fs.mkdir;
|
|
133
|
+
const stat = fs.stat;
|
|
134
|
+
const unlink = fs.unlink;
|
|
135
|
+
const rename = fs.rename;
|
|
136
|
+
const rm = fs.rm;
|
|
137
|
+
const cp = fs.cp;
|
|
138
|
+
|
|
30
139
|
//#endregion
|
|
31
140
|
//#region src/utils/logger.ts
|
|
32
|
-
const logsDir = path
|
|
141
|
+
const logsDir = path.join(os.tmpdir(), "scaffold-mcp-logs");
|
|
33
142
|
const logger = pino({
|
|
34
143
|
level: process.env.LOG_LEVEL || "debug",
|
|
35
144
|
timestamp: pino.stdTimeFunctions.isoTime
|
|
36
145
|
}, pino.destination({
|
|
37
|
-
dest: path
|
|
146
|
+
dest: path.join(logsDir, "scaffold-mcp.log"),
|
|
38
147
|
mkdir: true,
|
|
39
148
|
sync: true
|
|
40
149
|
}));
|
|
@@ -47,6 +156,29 @@ const log = {
|
|
|
47
156
|
|
|
48
157
|
//#endregion
|
|
49
158
|
//#region src/services/TemplatesManagerService.ts
|
|
159
|
+
/**
|
|
160
|
+
* TemplatesManagerService
|
|
161
|
+
*
|
|
162
|
+
* DESIGN PATTERNS:
|
|
163
|
+
* - Class-based service pattern for encapsulating business logic
|
|
164
|
+
* - Static methods for utility-like functionality
|
|
165
|
+
* - File system traversal for workspace detection
|
|
166
|
+
* - Configuration-driven template path resolution
|
|
167
|
+
*
|
|
168
|
+
* CODING STANDARDS:
|
|
169
|
+
* - Service class names use PascalCase with 'Service' suffix
|
|
170
|
+
* - Method names use camelCase with descriptive verbs
|
|
171
|
+
* - Return types should be explicit (never use implicit any)
|
|
172
|
+
* - Use async/await for asynchronous operations
|
|
173
|
+
* - Handle errors with try-catch and throw descriptive Error objects
|
|
174
|
+
* - Document public methods with JSDoc comments
|
|
175
|
+
*
|
|
176
|
+
* AVOID:
|
|
177
|
+
* - Side effects in constructors (keep them lightweight)
|
|
178
|
+
* - Mixing concerns (keep services focused on single domain)
|
|
179
|
+
* - Direct coupling to other services (use dependency injection)
|
|
180
|
+
* - Exposing internal implementation details
|
|
181
|
+
*/
|
|
50
182
|
var TemplatesManagerService = class TemplatesManagerService {
|
|
51
183
|
static SCAFFOLD_CONFIG_FILE = "scaffold.yaml";
|
|
52
184
|
static TEMPLATES_FOLDER = "templates";
|
|
@@ -68,32 +200,31 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
68
200
|
*/
|
|
69
201
|
static async findTemplatesPath(startPath = process.cwd()) {
|
|
70
202
|
const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
|
|
71
|
-
const toolkitConfigPath =
|
|
72
|
-
if (await
|
|
203
|
+
const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
204
|
+
if (await pathExists(toolkitConfigPath)) {
|
|
73
205
|
const yaml$1 = await import("js-yaml");
|
|
74
206
|
const content = await fs.readFile(toolkitConfigPath, "utf-8");
|
|
75
207
|
const config = yaml$1.load(content);
|
|
76
208
|
if (config?.templatesPath) {
|
|
77
|
-
const templatesPath$1 =
|
|
78
|
-
if (await
|
|
209
|
+
const templatesPath$1 = nodePath.isAbsolute(config.templatesPath) ? config.templatesPath : nodePath.join(workspaceRoot, config.templatesPath);
|
|
210
|
+
if (await pathExists(templatesPath$1)) return templatesPath$1;
|
|
79
211
|
else throw new Error(`Templates path specified in toolkit.yaml does not exist: ${templatesPath$1}`);
|
|
80
212
|
}
|
|
81
213
|
}
|
|
82
|
-
const templatesPath =
|
|
83
|
-
if (await
|
|
214
|
+
const templatesPath = nodePath.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
215
|
+
if (await pathExists(templatesPath)) return templatesPath;
|
|
84
216
|
throw new Error(`Templates folder not found at ${templatesPath}.\nEither create a 'templates' folder or specify templatesPath in toolkit.yaml`);
|
|
85
217
|
}
|
|
86
218
|
/**
|
|
87
219
|
* Find the workspace root by searching upwards for .git folder
|
|
88
220
|
*/
|
|
89
221
|
static async findWorkspaceRoot(startPath) {
|
|
90
|
-
let currentPath =
|
|
91
|
-
const rootPath =
|
|
222
|
+
let currentPath = nodePath.resolve(startPath);
|
|
223
|
+
const rootPath = nodePath.parse(currentPath).root;
|
|
92
224
|
while (true) {
|
|
93
|
-
|
|
94
|
-
if (await fs.pathExists(gitPath)) return currentPath;
|
|
225
|
+
if (await pathExists(nodePath.join(currentPath, ".git"))) return currentPath;
|
|
95
226
|
if (currentPath === rootPath) return process.cwd();
|
|
96
|
-
currentPath =
|
|
227
|
+
currentPath = nodePath.dirname(currentPath);
|
|
97
228
|
}
|
|
98
229
|
}
|
|
99
230
|
/**
|
|
@@ -106,32 +237,31 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
106
237
|
*/
|
|
107
238
|
static findTemplatesPathSync(startPath = process.cwd()) {
|
|
108
239
|
const workspaceRoot = TemplatesManagerService.findWorkspaceRootSync(startPath);
|
|
109
|
-
const toolkitConfigPath =
|
|
110
|
-
if (
|
|
240
|
+
const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
241
|
+
if (pathExistsSync(toolkitConfigPath)) {
|
|
111
242
|
const yaml$1 = __require("js-yaml");
|
|
112
|
-
const content =
|
|
243
|
+
const content = readFileSync$1(toolkitConfigPath, "utf-8");
|
|
113
244
|
const config = yaml$1.load(content);
|
|
114
245
|
if (config?.templatesPath) {
|
|
115
|
-
const templatesPath$1 =
|
|
116
|
-
if (
|
|
246
|
+
const templatesPath$1 = nodePath.isAbsolute(config.templatesPath) ? config.templatesPath : nodePath.join(workspaceRoot, config.templatesPath);
|
|
247
|
+
if (pathExistsSync(templatesPath$1)) return templatesPath$1;
|
|
117
248
|
else throw new Error(`Templates path specified in toolkit.yaml does not exist: ${templatesPath$1}`);
|
|
118
249
|
}
|
|
119
250
|
}
|
|
120
|
-
const templatesPath =
|
|
121
|
-
if (
|
|
251
|
+
const templatesPath = nodePath.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
252
|
+
if (pathExistsSync(templatesPath)) return templatesPath;
|
|
122
253
|
throw new Error(`Templates folder not found at ${templatesPath}.\nEither create a 'templates' folder or specify templatesPath in toolkit.yaml`);
|
|
123
254
|
}
|
|
124
255
|
/**
|
|
125
256
|
* Find the workspace root synchronously by searching upwards for .git folder
|
|
126
257
|
*/
|
|
127
258
|
static findWorkspaceRootSync(startPath) {
|
|
128
|
-
let currentPath =
|
|
129
|
-
const rootPath =
|
|
259
|
+
let currentPath = nodePath.resolve(startPath);
|
|
260
|
+
const rootPath = nodePath.parse(currentPath).root;
|
|
130
261
|
while (true) {
|
|
131
|
-
|
|
132
|
-
if (fs.pathExistsSync(gitPath)) return currentPath;
|
|
262
|
+
if (pathExistsSync(nodePath.join(currentPath, ".git"))) return currentPath;
|
|
133
263
|
if (currentPath === rootPath) return process.cwd();
|
|
134
|
-
currentPath =
|
|
264
|
+
currentPath = nodePath.dirname(currentPath);
|
|
135
265
|
}
|
|
136
266
|
}
|
|
137
267
|
/**
|
|
@@ -141,7 +271,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
141
271
|
* @returns true if templates folder exists and is a directory
|
|
142
272
|
*/
|
|
143
273
|
static async isInitialized(templatesPath) {
|
|
144
|
-
if (!await
|
|
274
|
+
if (!await pathExists(templatesPath)) return false;
|
|
145
275
|
return (await fs.stat(templatesPath)).isDirectory();
|
|
146
276
|
}
|
|
147
277
|
/**
|
|
@@ -164,8 +294,8 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
164
294
|
*/
|
|
165
295
|
static async readToolkitConfig(startPath = process.cwd()) {
|
|
166
296
|
const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
|
|
167
|
-
const toolkitConfigPath =
|
|
168
|
-
if (!await
|
|
297
|
+
const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
298
|
+
if (!await pathExists(toolkitConfigPath)) return null;
|
|
169
299
|
const yaml$1 = await import("js-yaml");
|
|
170
300
|
const content = await fs.readFile(toolkitConfigPath, "utf-8");
|
|
171
301
|
return yaml$1.load(content);
|
|
@@ -178,10 +308,10 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
178
308
|
*/
|
|
179
309
|
static readToolkitConfigSync(startPath = process.cwd()) {
|
|
180
310
|
const workspaceRoot = TemplatesManagerService.findWorkspaceRootSync(startPath);
|
|
181
|
-
const toolkitConfigPath =
|
|
182
|
-
if (!
|
|
311
|
+
const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
312
|
+
if (!pathExistsSync(toolkitConfigPath)) return null;
|
|
183
313
|
const yaml$1 = __require("js-yaml");
|
|
184
|
-
const content =
|
|
314
|
+
const content = readFileSync$1(toolkitConfigPath, "utf-8");
|
|
185
315
|
return yaml$1.load(content);
|
|
186
316
|
}
|
|
187
317
|
/**
|
|
@@ -192,7 +322,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
192
322
|
*/
|
|
193
323
|
static async writeToolkitConfig(config, startPath = process.cwd()) {
|
|
194
324
|
const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
|
|
195
|
-
const toolkitConfigPath =
|
|
325
|
+
const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
|
|
196
326
|
const content = (await import("js-yaml")).dump(config, { indent: 2 });
|
|
197
327
|
await fs.writeFile(toolkitConfigPath, content, "utf-8");
|
|
198
328
|
}
|
|
@@ -221,6 +351,28 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
221
351
|
/**
|
|
222
352
|
* ProjectConfigResolver
|
|
223
353
|
*
|
|
354
|
+
* DESIGN PATTERNS:
|
|
355
|
+
* - Class-based service pattern for resolving project configuration
|
|
356
|
+
* - Priority-based configuration resolution (project.json > toolkit.yaml > package.json)
|
|
357
|
+
* - Singleton-like static methods for common operations
|
|
358
|
+
*
|
|
359
|
+
* CODING STANDARDS:
|
|
360
|
+
* - Service class names use PascalCase with 'Service' suffix
|
|
361
|
+
* - Method names use camelCase with descriptive verbs
|
|
362
|
+
* - Return types should be explicit (never use implicit any)
|
|
363
|
+
* - Use async/await for asynchronous operations
|
|
364
|
+
* - Handle errors with try-catch and throw descriptive Error objects
|
|
365
|
+
* - Document public methods with JSDoc comments
|
|
366
|
+
*
|
|
367
|
+
* AVOID:
|
|
368
|
+
* - Side effects in constructors (keep them lightweight)
|
|
369
|
+
* - Mixing concerns (keep services focused on single domain)
|
|
370
|
+
* - Direct coupling to other services (use dependency injection)
|
|
371
|
+
* - Exposing internal implementation details
|
|
372
|
+
*/
|
|
373
|
+
/**
|
|
374
|
+
* ProjectConfigResolver
|
|
375
|
+
*
|
|
224
376
|
* Resolves project configuration from multiple sources with priority:
|
|
225
377
|
* 1. project.json (monorepo - Nx/Lerna/Turborepo)
|
|
226
378
|
* 2. toolkit.yaml at workspace root (monolith)
|
|
@@ -241,15 +393,15 @@ var ProjectConfigResolver = class ProjectConfigResolver {
|
|
|
241
393
|
*/
|
|
242
394
|
static async resolveProjectConfig(projectPath, explicitTemplate) {
|
|
243
395
|
try {
|
|
244
|
-
const absolutePath =
|
|
396
|
+
const absolutePath = nodePath.resolve(projectPath);
|
|
245
397
|
if (explicitTemplate) return {
|
|
246
398
|
type: ProjectType.MONOLITH,
|
|
247
399
|
sourceTemplate: explicitTemplate,
|
|
248
400
|
configSource: ConfigSource.TOOLKIT_YAML
|
|
249
401
|
};
|
|
250
|
-
const projectJsonPath =
|
|
251
|
-
if (await
|
|
252
|
-
const projectJson = await
|
|
402
|
+
const projectJsonPath = nodePath.join(absolutePath, "project.json");
|
|
403
|
+
if (await pathExists(projectJsonPath)) {
|
|
404
|
+
const projectJson = await readJson(projectJsonPath);
|
|
253
405
|
if (projectJson.sourceTemplate && typeof projectJson.sourceTemplate === "string" && projectJson.sourceTemplate.trim()) return {
|
|
254
406
|
type: ProjectType.MONOREPO,
|
|
255
407
|
sourceTemplate: projectJson.sourceTemplate.trim(),
|
|
@@ -338,12 +490,12 @@ Run 'scaffold-mcp scaffold list --help' for more info.`;
|
|
|
338
490
|
* @param sourceTemplate - The template identifier
|
|
339
491
|
*/
|
|
340
492
|
static async createProjectJson(projectPath, projectName, sourceTemplate) {
|
|
341
|
-
const projectJsonPath =
|
|
493
|
+
const projectJsonPath = nodePath.join(projectPath, "project.json");
|
|
342
494
|
try {
|
|
343
495
|
let projectJson;
|
|
344
|
-
if (await
|
|
496
|
+
if (await pathExists(projectJsonPath)) projectJson = await readJson(projectJsonPath);
|
|
345
497
|
else {
|
|
346
|
-
const relativePath =
|
|
498
|
+
const relativePath = nodePath.relative(projectPath, process.cwd());
|
|
347
499
|
projectJson = {
|
|
348
500
|
name: projectName,
|
|
349
501
|
$schema: relativePath ? `${relativePath}/node_modules/nx/schemas/project-schema.json` : "node_modules/nx/schemas/project-schema.json",
|
|
@@ -352,8 +504,7 @@ Run 'scaffold-mcp scaffold list --help' for more info.`;
|
|
|
352
504
|
};
|
|
353
505
|
}
|
|
354
506
|
projectJson.sourceTemplate = sourceTemplate;
|
|
355
|
-
|
|
356
|
-
await fs.writeFile(projectJsonPath, `${content}\n`);
|
|
507
|
+
await writeJson(projectJsonPath, projectJson);
|
|
357
508
|
log.info(`Created/updated project.json with sourceTemplate: ${sourceTemplate}`);
|
|
358
509
|
} catch (error) {
|
|
359
510
|
throw new Error(`Failed to create project.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -363,6 +514,26 @@ Run 'scaffold-mcp scaffold list --help' for more info.`;
|
|
|
363
514
|
|
|
364
515
|
//#endregion
|
|
365
516
|
//#region src/services/ProjectFinderService.ts
|
|
517
|
+
/**
|
|
518
|
+
* ProjectFinderService
|
|
519
|
+
*
|
|
520
|
+
* DESIGN PATTERNS:
|
|
521
|
+
* - Class-based service pattern for encapsulating business logic
|
|
522
|
+
* - Caching for performance optimization
|
|
523
|
+
* - File system traversal for project detection
|
|
524
|
+
*
|
|
525
|
+
* CODING STANDARDS:
|
|
526
|
+
* - Service class names use PascalCase with 'Service' suffix
|
|
527
|
+
* - Method names use camelCase with descriptive verbs
|
|
528
|
+
* - Return types should be explicit
|
|
529
|
+
* - Use async/await for asynchronous operations
|
|
530
|
+
* - Handle errors with try-catch and throw descriptive Error objects
|
|
531
|
+
*
|
|
532
|
+
* AVOID:
|
|
533
|
+
* - Side effects in constructors
|
|
534
|
+
* - Mixing concerns
|
|
535
|
+
* - Direct coupling to other services
|
|
536
|
+
*/
|
|
366
537
|
var ProjectFinderService = class {
|
|
367
538
|
projectCache = /* @__PURE__ */ new Map();
|
|
368
539
|
workspaceRoot;
|
|
@@ -376,15 +547,15 @@ var ProjectFinderService = class {
|
|
|
376
547
|
* @returns Project configuration or null if not found
|
|
377
548
|
*/
|
|
378
549
|
async findProjectForFile(filePath) {
|
|
379
|
-
const normalizedPath =
|
|
380
|
-
let currentDir =
|
|
550
|
+
const normalizedPath = nodePath.isAbsolute(filePath) ? filePath : nodePath.join(this.workspaceRoot, filePath);
|
|
551
|
+
let currentDir = nodePath.dirname(normalizedPath);
|
|
381
552
|
while (currentDir !== "/" && currentDir.startsWith(this.workspaceRoot)) {
|
|
382
|
-
const projectJsonPath =
|
|
553
|
+
const projectJsonPath = nodePath.join(currentDir, "project.json");
|
|
383
554
|
try {
|
|
384
555
|
const project = await this.loadProjectConfig(projectJsonPath);
|
|
385
556
|
if (project) return project;
|
|
386
557
|
} catch {}
|
|
387
|
-
currentDir =
|
|
558
|
+
currentDir = nodePath.dirname(currentDir);
|
|
388
559
|
}
|
|
389
560
|
return null;
|
|
390
561
|
}
|
|
@@ -395,15 +566,15 @@ var ProjectFinderService = class {
|
|
|
395
566
|
* @returns Project configuration or null if not found
|
|
396
567
|
*/
|
|
397
568
|
findProjectForFileSync(filePath) {
|
|
398
|
-
const normalizedPath =
|
|
399
|
-
let currentDir =
|
|
569
|
+
const normalizedPath = nodePath.isAbsolute(filePath) ? filePath : nodePath.join(this.workspaceRoot, filePath);
|
|
570
|
+
let currentDir = nodePath.dirname(normalizedPath);
|
|
400
571
|
while (currentDir !== "/" && currentDir.startsWith(this.workspaceRoot)) {
|
|
401
|
-
const projectJsonPath =
|
|
572
|
+
const projectJsonPath = nodePath.join(currentDir, "project.json");
|
|
402
573
|
try {
|
|
403
574
|
const project = this.loadProjectConfigSync(projectJsonPath);
|
|
404
575
|
if (project) return project;
|
|
405
576
|
} catch {}
|
|
406
|
-
currentDir =
|
|
577
|
+
currentDir = nodePath.dirname(currentDir);
|
|
407
578
|
}
|
|
408
579
|
return null;
|
|
409
580
|
}
|
|
@@ -416,8 +587,8 @@ var ProjectFinderService = class {
|
|
|
416
587
|
const content = await fs.readFile(projectJsonPath, "utf-8");
|
|
417
588
|
const config = JSON.parse(content);
|
|
418
589
|
const projectConfig = {
|
|
419
|
-
name: config.name ||
|
|
420
|
-
root:
|
|
590
|
+
name: config.name || nodePath.basename(nodePath.dirname(projectJsonPath)),
|
|
591
|
+
root: nodePath.dirname(projectJsonPath),
|
|
421
592
|
sourceTemplate: config.sourceTemplate,
|
|
422
593
|
projectType: config.projectType
|
|
423
594
|
};
|
|
@@ -433,11 +604,11 @@ var ProjectFinderService = class {
|
|
|
433
604
|
loadProjectConfigSync(projectJsonPath) {
|
|
434
605
|
if (this.projectCache.has(projectJsonPath)) return this.projectCache.get(projectJsonPath);
|
|
435
606
|
try {
|
|
436
|
-
const content =
|
|
607
|
+
const content = readFileSync$1(projectJsonPath, "utf-8");
|
|
437
608
|
const config = JSON.parse(content);
|
|
438
609
|
const projectConfig = {
|
|
439
|
-
name: config.name ||
|
|
440
|
-
root:
|
|
610
|
+
name: config.name || nodePath.basename(nodePath.dirname(projectJsonPath)),
|
|
611
|
+
root: nodePath.dirname(projectJsonPath),
|
|
441
612
|
sourceTemplate: config.sourceTemplate,
|
|
442
613
|
projectType: config.projectType
|
|
443
614
|
};
|
|
@@ -497,7 +668,7 @@ var ScaffoldProcessingService = class {
|
|
|
497
668
|
* Now supports tracking existing files separately from created files
|
|
498
669
|
*/
|
|
499
670
|
async copyAndProcess(sourcePath, targetPath, variables, createdFiles, existingFiles) {
|
|
500
|
-
await this.fileSystem.ensureDir(
|
|
671
|
+
await this.fileSystem.ensureDir(nodePath.dirname(targetPath));
|
|
501
672
|
if (await this.fileSystem.pathExists(targetPath) && existingFiles) {
|
|
502
673
|
await this.trackExistingFiles(targetPath, existingFiles);
|
|
503
674
|
return;
|
|
@@ -519,11 +690,11 @@ var ScaffoldProcessingService = class {
|
|
|
519
690
|
}
|
|
520
691
|
for (const item of items) {
|
|
521
692
|
if (!item) continue;
|
|
522
|
-
const itemPath =
|
|
693
|
+
const itemPath = nodePath.join(dirPath, item);
|
|
523
694
|
try {
|
|
524
|
-
const stat = await this.fileSystem.stat(itemPath);
|
|
525
|
-
if (stat.isDirectory()) await this.trackCreatedFilesRecursive(itemPath, createdFiles);
|
|
526
|
-
else if (stat.isFile()) createdFiles.push(itemPath);
|
|
695
|
+
const stat$1 = await this.fileSystem.stat(itemPath);
|
|
696
|
+
if (stat$1.isDirectory()) await this.trackCreatedFilesRecursive(itemPath, createdFiles);
|
|
697
|
+
else if (stat$1.isFile()) createdFiles.push(itemPath);
|
|
527
698
|
} catch (error) {
|
|
528
699
|
console.warn(`Cannot stat ${itemPath}: ${error}`);
|
|
529
700
|
}
|
|
@@ -542,11 +713,11 @@ var ScaffoldProcessingService = class {
|
|
|
542
713
|
}
|
|
543
714
|
for (const item of items) {
|
|
544
715
|
if (!item) continue;
|
|
545
|
-
const itemPath =
|
|
716
|
+
const itemPath = nodePath.join(dirPath, item);
|
|
546
717
|
try {
|
|
547
|
-
const stat = await this.fileSystem.stat(itemPath);
|
|
548
|
-
if (stat.isDirectory()) await this.trackExistingFilesRecursive(itemPath, existingFiles);
|
|
549
|
-
else if (stat.isFile()) existingFiles.push(itemPath);
|
|
718
|
+
const stat$1 = await this.fileSystem.stat(itemPath);
|
|
719
|
+
if (stat$1.isDirectory()) await this.trackExistingFilesRecursive(itemPath, existingFiles);
|
|
720
|
+
else if (stat$1.isFile()) existingFiles.push(itemPath);
|
|
550
721
|
} catch (error) {
|
|
551
722
|
console.warn(`Cannot stat ${itemPath}: ${error}`);
|
|
552
723
|
}
|
|
@@ -681,6 +852,27 @@ const sections = {
|
|
|
681
852
|
//#endregion
|
|
682
853
|
//#region src/utils/projectTypeDetector.ts
|
|
683
854
|
/**
|
|
855
|
+
* projectTypeDetector Utilities
|
|
856
|
+
*
|
|
857
|
+
* DESIGN PATTERNS:
|
|
858
|
+
* - Pure function pattern: No side effects, deterministic output
|
|
859
|
+
* - Single domain focus: All functions related to project type detection
|
|
860
|
+
* - Composability: Functions can be combined to create complex behavior
|
|
861
|
+
*
|
|
862
|
+
* CODING STANDARDS:
|
|
863
|
+
* - Function names use camelCase with descriptive verbs (validate, format, parse, transform)
|
|
864
|
+
* - All functions should be pure (same input = same output, no side effects)
|
|
865
|
+
* - Use explicit return types
|
|
866
|
+
* - Document complex logic with JSDoc comments
|
|
867
|
+
* - Keep functions small and focused on single responsibility
|
|
868
|
+
*
|
|
869
|
+
* AVOID:
|
|
870
|
+
* - Side effects (mutations, I/O, random values, Date.now(), etc.)
|
|
871
|
+
* - Stateful behavior or closures with mutable state
|
|
872
|
+
* - Dependencies on external services or global variables
|
|
873
|
+
* - Classes (use pure functions instead)
|
|
874
|
+
*/
|
|
875
|
+
/**
|
|
684
876
|
* Monorepo configuration files that indicate a monorepo setup
|
|
685
877
|
*/
|
|
686
878
|
const MONOREPO_INDICATOR_FILES = [
|
|
@@ -705,8 +897,8 @@ const MONOREPO_INDICATOR_FILES = [
|
|
|
705
897
|
*/
|
|
706
898
|
async function detectProjectType(workspaceRoot) {
|
|
707
899
|
const indicators = [];
|
|
708
|
-
const toolkitYamlPath =
|
|
709
|
-
if (await
|
|
900
|
+
const toolkitYamlPath = nodePath.join(workspaceRoot, "toolkit.yaml");
|
|
901
|
+
if (await pathExists(toolkitYamlPath)) try {
|
|
710
902
|
const content = await fs.readFile(toolkitYamlPath, "utf-8");
|
|
711
903
|
const config = yaml.load(content);
|
|
712
904
|
if (config?.projectType) {
|
|
@@ -717,19 +909,16 @@ async function detectProjectType(workspaceRoot) {
|
|
|
717
909
|
};
|
|
718
910
|
}
|
|
719
911
|
} catch {}
|
|
720
|
-
for (const filename of MONOREPO_INDICATOR_FILES) {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const packageJsonPath = path.join(workspaceRoot, "package.json");
|
|
731
|
-
if (await fs.pathExists(packageJsonPath)) try {
|
|
732
|
-
if ((await fs.readJson(packageJsonPath)).workspaces) {
|
|
912
|
+
for (const filename of MONOREPO_INDICATOR_FILES) if (await pathExists(nodePath.join(workspaceRoot, filename))) {
|
|
913
|
+
indicators.push(`${filename} found`);
|
|
914
|
+
return {
|
|
915
|
+
projectType: ProjectType.MONOREPO,
|
|
916
|
+
indicators
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
const packageJsonPath = nodePath.join(workspaceRoot, "package.json");
|
|
920
|
+
if (await pathExists(packageJsonPath)) try {
|
|
921
|
+
if ((await readJson(packageJsonPath)).workspaces) {
|
|
733
922
|
indicators.push("package.json with workspaces found");
|
|
734
923
|
return {
|
|
735
924
|
projectType: ProjectType.MONOREPO,
|
|
@@ -765,4 +954,4 @@ async function isMonolith(workspaceRoot) {
|
|
|
765
954
|
}
|
|
766
955
|
|
|
767
956
|
//#endregion
|
|
768
|
-
export { ConfigSource, ProjectConfigResolver, ProjectFinderService, ProjectType, ScaffoldProcessingService, TemplatesManagerService, detectProjectType, icons, isMonolith, isMonorepo, log, logger, messages, print, sections };
|
|
957
|
+
export { ConfigSource, ProjectConfigResolver, ProjectFinderService, ProjectType, ScaffoldProcessingService, TemplatesManagerService, accessSync, copy, cp, detectProjectType, ensureDir, ensureDirSync, fs, icons, isMonolith, isMonorepo, log, logger, messages, mkdir, mkdirSync, move, outputFile, pathExists, pathExistsSync, print, readFile, readFileSync, readJson, readJsonSync, readdir, remove, rename, rm, sections, stat, statSync, unlink, writeFile, writeFileSync, writeJson, writeJsonSync };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agiflowai/aicode-utils",
|
|
3
3
|
"description": "Shared utilities and types for AI-powered code generation, scaffolding, and analysis",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.4",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"author": "AgiflowIO",
|
|
7
7
|
"repository": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"code-generation"
|
|
24
24
|
],
|
|
25
25
|
"main": "./dist/index.cjs",
|
|
26
|
-
"module": "./dist/index.
|
|
26
|
+
"module": "./dist/index.mjs",
|
|
27
27
|
"types": "./dist/index.d.cts",
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
@@ -31,18 +31,16 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"chalk": "5.6.2",
|
|
34
|
-
"fs-extra": "11.3.2",
|
|
35
34
|
"js-yaml": "4.1.0",
|
|
36
35
|
"ora": "^9.0.0",
|
|
37
36
|
"pino": "^10.0.0"
|
|
38
37
|
},
|
|
39
38
|
"devDependencies": {
|
|
40
|
-
"@types/fs-extra": "^11.0.4",
|
|
41
39
|
"@types/js-yaml": "^4.0.9",
|
|
42
40
|
"@types/node": "^22.0.0",
|
|
43
41
|
"@types/ora": "^3.2.0",
|
|
44
42
|
"@vitest/coverage-v8": "^3.0.0",
|
|
45
|
-
"tsdown": "^0.
|
|
43
|
+
"tsdown": "^0.16.4",
|
|
46
44
|
"typescript": "5.9.3",
|
|
47
45
|
"vitest": "^3.0.0"
|
|
48
46
|
},
|
|
@@ -52,7 +50,7 @@
|
|
|
52
50
|
},
|
|
53
51
|
"exports": {
|
|
54
52
|
".": {
|
|
55
|
-
"import": "./dist/index.
|
|
53
|
+
"import": "./dist/index.mjs",
|
|
56
54
|
"require": "./dist/index.cjs"
|
|
57
55
|
},
|
|
58
56
|
"./package.json": "./package.json"
|