@agiflowai/aicode-utils 1.0.3 → 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 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 fs_extra = require("fs-extra");
27
- fs_extra = __toESM(fs_extra);
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 fs_extra.pathExists(toolkitConfigPath)) {
227
+ if (await pathExists(toolkitConfigPath)) {
96
228
  const yaml = await import("js-yaml");
97
- const content = await fs_extra.readFile(toolkitConfigPath, "utf-8");
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 fs_extra.pathExists(templatesPath$1)) return templatesPath$1;
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 fs_extra.pathExists(templatesPath)) return templatesPath;
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
- const gitPath = node_path.default.join(currentPath, ".git");
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 (fs_extra.pathExistsSync(toolkitConfigPath)) {
264
+ if (pathExistsSync(toolkitConfigPath)) {
134
265
  const yaml = require("js-yaml");
135
- const content = fs_extra.readFileSync(toolkitConfigPath, "utf-8");
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 (fs_extra.pathExistsSync(templatesPath$1)) return templatesPath$1;
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 (fs_extra.pathExistsSync(templatesPath)) return templatesPath;
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
- const gitPath = node_path.default.join(currentPath, ".git");
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 fs_extra.pathExists(templatesPath)) return false;
168
- return (await fs_extra.stat(templatesPath)).isDirectory();
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 fs_extra.pathExists(toolkitConfigPath)) return null;
321
+ if (!await pathExists(toolkitConfigPath)) return null;
192
322
  const yaml = await import("js-yaml");
193
- const content = await fs_extra.readFile(toolkitConfigPath, "utf-8");
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 (!fs_extra.pathExistsSync(toolkitConfigPath)) return null;
335
+ if (!pathExistsSync(toolkitConfigPath)) return null;
206
336
  const yaml = require("js-yaml");
207
- const content = fs_extra.readFileSync(toolkitConfigPath, "utf-8");
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 fs_extra.writeFile(toolkitConfigPath, content, "utf-8");
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 fs_extra.pathExists(projectJsonPath)) {
275
- const projectJson = await fs_extra.readJson(projectJsonPath);
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 fs_extra.pathExists(projectJsonPath)) projectJson = await fs_extra.readJson(projectJsonPath);
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
- const content = JSON.stringify(projectJson, null, 2);
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 fs_extra.readFile(projectJsonPath, "utf-8");
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 = fs_extra.readFileSync(projectJsonPath, "utf-8");
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 fs_extra.pathExists(toolkitYamlPath)) try {
733
- const content = await fs_extra.readFile(toolkitYamlPath, "utf-8");
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
- const filePath = node_path.default.join(workspaceRoot, filename);
745
- if (await fs_extra.pathExists(filePath)) {
746
- indicators.push(`${filename} found`);
747
- return {
748
- projectType: ProjectType.MONOREPO,
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 fs_extra.pathExists(packageJsonPath)) try {
755
- if ((await fs_extra.readJson(packageJsonPath)).workspaces) {
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.sections = sections;
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$1 from "node:path";
3
- import path from "node:path";
4
- import * as fs from "fs-extra";
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$1.join(os.tmpdir(), "scaffold-mcp-logs");
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$1.join(logsDir, "scaffold-mcp.log"),
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 = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
72
- if (await fs.pathExists(toolkitConfigPath)) {
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 = path.isAbsolute(config.templatesPath) ? config.templatesPath : path.join(workspaceRoot, config.templatesPath);
78
- if (await fs.pathExists(templatesPath$1)) return templatesPath$1;
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 = path.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
83
- if (await fs.pathExists(templatesPath)) return templatesPath;
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 = path.resolve(startPath);
91
- const rootPath = path.parse(currentPath).root;
222
+ let currentPath = nodePath.resolve(startPath);
223
+ const rootPath = nodePath.parse(currentPath).root;
92
224
  while (true) {
93
- const gitPath = path.join(currentPath, ".git");
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 = path.dirname(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 = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
110
- if (fs.pathExistsSync(toolkitConfigPath)) {
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 = fs.readFileSync(toolkitConfigPath, "utf-8");
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 = path.isAbsolute(config.templatesPath) ? config.templatesPath : path.join(workspaceRoot, config.templatesPath);
116
- if (fs.pathExistsSync(templatesPath$1)) return templatesPath$1;
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 = path.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
121
- if (fs.pathExistsSync(templatesPath)) return templatesPath;
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 = path.resolve(startPath);
129
- const rootPath = path.parse(currentPath).root;
259
+ let currentPath = nodePath.resolve(startPath);
260
+ const rootPath = nodePath.parse(currentPath).root;
130
261
  while (true) {
131
- const gitPath = path.join(currentPath, ".git");
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 = path.dirname(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 fs.pathExists(templatesPath)) return false;
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 = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
168
- if (!await fs.pathExists(toolkitConfigPath)) return null;
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 = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
182
- if (!fs.pathExistsSync(toolkitConfigPath)) return null;
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 = fs.readFileSync(toolkitConfigPath, "utf-8");
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 = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
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 = path.resolve(projectPath);
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 = path.join(absolutePath, "project.json");
251
- if (await fs.pathExists(projectJsonPath)) {
252
- const projectJson = await fs.readJson(projectJsonPath);
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 = path.join(projectPath, "project.json");
493
+ const projectJsonPath = nodePath.join(projectPath, "project.json");
342
494
  try {
343
495
  let projectJson;
344
- if (await fs.pathExists(projectJsonPath)) projectJson = await fs.readJson(projectJsonPath);
496
+ if (await pathExists(projectJsonPath)) projectJson = await readJson(projectJsonPath);
345
497
  else {
346
- const relativePath = path.relative(projectPath, process.cwd());
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
- const content = JSON.stringify(projectJson, null, 2);
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 = path.isAbsolute(filePath) ? filePath : path.join(this.workspaceRoot, filePath);
380
- let currentDir = path.dirname(normalizedPath);
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 = path.join(currentDir, "project.json");
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 = path.dirname(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 = path.isAbsolute(filePath) ? filePath : path.join(this.workspaceRoot, filePath);
399
- let currentDir = path.dirname(normalizedPath);
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 = path.join(currentDir, "project.json");
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 = path.dirname(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 || path.basename(path.dirname(projectJsonPath)),
420
- root: path.dirname(projectJsonPath),
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 = fs.readFileSync(projectJsonPath, "utf-8");
607
+ const content = readFileSync$1(projectJsonPath, "utf-8");
437
608
  const config = JSON.parse(content);
438
609
  const projectConfig = {
439
- name: config.name || path.basename(path.dirname(projectJsonPath)),
440
- root: path.dirname(projectJsonPath),
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(path.dirname(targetPath));
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 = path.join(dirPath, item);
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 = path.join(dirPath, item);
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 = path.join(workspaceRoot, "toolkit.yaml");
709
- if (await fs.pathExists(toolkitYamlPath)) try {
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
- const filePath = path.join(workspaceRoot, filename);
722
- if (await fs.pathExists(filePath)) {
723
- indicators.push(`${filename} found`);
724
- return {
725
- projectType: ProjectType.MONOREPO,
726
- indicators
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.3",
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.js",
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.15.6",
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.js",
53
+ "import": "./dist/index.mjs",
56
54
  "require": "./dist/index.cjs"
57
55
  },
58
56
  "./package.json": "./package.json"