@agiflowai/aicode-utils 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -748,6 +748,36 @@ var ScaffoldProcessingService = class {
748
748
  }
749
749
  };
750
750
 
751
+ //#endregion
752
+ //#region src/utils/generateStableId.ts
753
+ /**
754
+ * Generate a stable, random ID string
755
+ *
756
+ * @param length - Length of the ID to generate (default: 8)
757
+ * @returns A random alphanumeric ID string (lowercase)
758
+ *
759
+ * @remarks
760
+ * Negative or non-integer lengths are normalized (floored and clamped to 0).
761
+ * Returns empty string for length <= 0.
762
+ *
763
+ * @example
764
+ * ```typescript
765
+ * const id = generateStableId(6); // Returns something like "a3f9k2"
766
+ * ```
767
+ */
768
+ function generateStableId(length = 8) {
769
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
770
+ let result = "";
771
+ const normalizedLength = Math.max(0, Math.floor(length));
772
+ if (normalizedLength === 0) return "";
773
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
774
+ const values = new Uint8Array(normalizedLength);
775
+ crypto.getRandomValues(values);
776
+ for (let i = 0; i < normalizedLength; i++) result += chars[values[i] % 36];
777
+ } else for (let i = 0; i < normalizedLength; i++) result += chars[Math.floor(Math.random() * 36)];
778
+ return result;
779
+ }
780
+
751
781
  //#endregion
752
782
  //#region src/utils/print.ts
753
783
  /**
@@ -995,6 +1025,7 @@ Object.defineProperty(exports, 'fs', {
995
1025
  return node_fs_promises;
996
1026
  }
997
1027
  });
1028
+ exports.generateStableId = generateStableId;
998
1029
  exports.icons = icons;
999
1030
  exports.isMonolith = isMonolith;
1000
1031
  exports.isMonorepo = isMonorepo;
package/dist/index.d.cts CHANGED
@@ -403,6 +403,24 @@ declare const rename: typeof fs.rename;
403
403
  declare const rm: typeof fs.rm;
404
404
  declare const cp: typeof fs.cp;
405
405
  //#endregion
406
+ //#region src/utils/generateStableId.d.ts
407
+ /**
408
+ * Generate a stable, random ID string
409
+ *
410
+ * @param length - Length of the ID to generate (default: 8)
411
+ * @returns A random alphanumeric ID string (lowercase)
412
+ *
413
+ * @remarks
414
+ * Negative or non-integer lengths are normalized (floored and clamped to 0).
415
+ * Returns empty string for length <= 0.
416
+ *
417
+ * @example
418
+ * ```typescript
419
+ * const id = generateStableId(6); // Returns something like "a3f9k2"
420
+ * ```
421
+ */
422
+ declare function generateStableId(length?: number): string;
423
+ //#endregion
406
424
  //#region src/utils/logger.d.ts
407
425
  declare const logger: pino.Logger<never, boolean>;
408
426
  declare const log: {
@@ -580,4 +598,4 @@ declare function isMonorepo(workspaceRoot: string): Promise<boolean>;
580
598
  */
581
599
  declare function isMonolith(workspaceRoot: string): Promise<boolean>;
582
600
  //#endregion
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 };
601
+ 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, generateStableId, 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/dist/index.d.mts CHANGED
@@ -403,6 +403,24 @@ declare const rename: typeof fs.rename;
403
403
  declare const rm: typeof fs.rm;
404
404
  declare const cp: typeof fs.cp;
405
405
  //#endregion
406
+ //#region src/utils/generateStableId.d.ts
407
+ /**
408
+ * Generate a stable, random ID string
409
+ *
410
+ * @param length - Length of the ID to generate (default: 8)
411
+ * @returns A random alphanumeric ID string (lowercase)
412
+ *
413
+ * @remarks
414
+ * Negative or non-integer lengths are normalized (floored and clamped to 0).
415
+ * Returns empty string for length <= 0.
416
+ *
417
+ * @example
418
+ * ```typescript
419
+ * const id = generateStableId(6); // Returns something like "a3f9k2"
420
+ * ```
421
+ */
422
+ declare function generateStableId(length?: number): string;
423
+ //#endregion
406
424
  //#region src/utils/logger.d.ts
407
425
  declare const logger: pino.Logger<never, boolean>;
408
426
  declare const log: {
@@ -580,4 +598,4 @@ declare function isMonorepo(workspaceRoot: string): Promise<boolean>;
580
598
  */
581
599
  declare function isMonolith(workspaceRoot: string): Promise<boolean>;
582
600
  //#endregion
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 };
601
+ 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, generateStableId, 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/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
- import * as path$1 from "node:path";
3
- import path from "node:path";
2
+ import * as path from "node:path";
3
+ import nodePath from "node:path";
4
4
  import * as fs from "node:fs/promises";
5
5
  import { accessSync, mkdirSync, readFileSync, readFileSync as readFileSync$1, statSync, writeFileSync } from "node:fs";
6
6
  import * as os from "node:os";
@@ -89,7 +89,7 @@ async function copy(src, dest) {
89
89
  * Move a file or directory
90
90
  */
91
91
  async function move(src, dest) {
92
- await ensureDir(path.dirname(dest));
92
+ await ensureDir(nodePath.dirname(dest));
93
93
  await fs.rename(src, dest);
94
94
  }
95
95
  /**
@@ -123,7 +123,7 @@ function writeJsonSync(filePath, data, options) {
123
123
  * Output file - writes content ensuring directory exists
124
124
  */
125
125
  async function outputFile(filePath, content) {
126
- await ensureDir(path.dirname(filePath));
126
+ await ensureDir(nodePath.dirname(filePath));
127
127
  await fs.writeFile(filePath, content, "utf-8");
128
128
  }
129
129
  const readFile = fs.readFile;
@@ -138,12 +138,12 @@ const cp = fs.cp;
138
138
 
139
139
  //#endregion
140
140
  //#region src/utils/logger.ts
141
- const logsDir = path$1.join(os.tmpdir(), "scaffold-mcp-logs");
141
+ const logsDir = path.join(os.tmpdir(), "scaffold-mcp-logs");
142
142
  const logger = pino({
143
143
  level: process.env.LOG_LEVEL || "debug",
144
144
  timestamp: pino.stdTimeFunctions.isoTime
145
145
  }, pino.destination({
146
- dest: path$1.join(logsDir, "scaffold-mcp.log"),
146
+ dest: path.join(logsDir, "scaffold-mcp.log"),
147
147
  mkdir: true,
148
148
  sync: true
149
149
  }));
@@ -200,18 +200,18 @@ var TemplatesManagerService = class TemplatesManagerService {
200
200
  */
201
201
  static async findTemplatesPath(startPath = process.cwd()) {
202
202
  const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
203
- const toolkitConfigPath = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
203
+ const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
204
204
  if (await pathExists(toolkitConfigPath)) {
205
205
  const yaml$1 = await import("js-yaml");
206
206
  const content = await fs.readFile(toolkitConfigPath, "utf-8");
207
207
  const config = yaml$1.load(content);
208
208
  if (config?.templatesPath) {
209
- const templatesPath$1 = path.isAbsolute(config.templatesPath) ? config.templatesPath : path.join(workspaceRoot, config.templatesPath);
209
+ const templatesPath$1 = nodePath.isAbsolute(config.templatesPath) ? config.templatesPath : nodePath.join(workspaceRoot, config.templatesPath);
210
210
  if (await pathExists(templatesPath$1)) return templatesPath$1;
211
211
  else throw new Error(`Templates path specified in toolkit.yaml does not exist: ${templatesPath$1}`);
212
212
  }
213
213
  }
214
- const templatesPath = path.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
214
+ const templatesPath = nodePath.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
215
215
  if (await pathExists(templatesPath)) return templatesPath;
216
216
  throw new Error(`Templates folder not found at ${templatesPath}.\nEither create a 'templates' folder or specify templatesPath in toolkit.yaml`);
217
217
  }
@@ -219,12 +219,12 @@ var TemplatesManagerService = class TemplatesManagerService {
219
219
  * Find the workspace root by searching upwards for .git folder
220
220
  */
221
221
  static async findWorkspaceRoot(startPath) {
222
- let currentPath = path.resolve(startPath);
223
- const rootPath = path.parse(currentPath).root;
222
+ let currentPath = nodePath.resolve(startPath);
223
+ const rootPath = nodePath.parse(currentPath).root;
224
224
  while (true) {
225
- if (await pathExists(path.join(currentPath, ".git"))) return currentPath;
225
+ if (await pathExists(nodePath.join(currentPath, ".git"))) return currentPath;
226
226
  if (currentPath === rootPath) return process.cwd();
227
- currentPath = path.dirname(currentPath);
227
+ currentPath = nodePath.dirname(currentPath);
228
228
  }
229
229
  }
230
230
  /**
@@ -237,18 +237,18 @@ var TemplatesManagerService = class TemplatesManagerService {
237
237
  */
238
238
  static findTemplatesPathSync(startPath = process.cwd()) {
239
239
  const workspaceRoot = TemplatesManagerService.findWorkspaceRootSync(startPath);
240
- const toolkitConfigPath = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
240
+ const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
241
241
  if (pathExistsSync(toolkitConfigPath)) {
242
242
  const yaml$1 = __require("js-yaml");
243
243
  const content = readFileSync$1(toolkitConfigPath, "utf-8");
244
244
  const config = yaml$1.load(content);
245
245
  if (config?.templatesPath) {
246
- const templatesPath$1 = path.isAbsolute(config.templatesPath) ? config.templatesPath : path.join(workspaceRoot, config.templatesPath);
246
+ const templatesPath$1 = nodePath.isAbsolute(config.templatesPath) ? config.templatesPath : nodePath.join(workspaceRoot, config.templatesPath);
247
247
  if (pathExistsSync(templatesPath$1)) return templatesPath$1;
248
248
  else throw new Error(`Templates path specified in toolkit.yaml does not exist: ${templatesPath$1}`);
249
249
  }
250
250
  }
251
- const templatesPath = path.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
251
+ const templatesPath = nodePath.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
252
252
  if (pathExistsSync(templatesPath)) return templatesPath;
253
253
  throw new Error(`Templates folder not found at ${templatesPath}.\nEither create a 'templates' folder or specify templatesPath in toolkit.yaml`);
254
254
  }
@@ -256,12 +256,12 @@ var TemplatesManagerService = class TemplatesManagerService {
256
256
  * Find the workspace root synchronously by searching upwards for .git folder
257
257
  */
258
258
  static findWorkspaceRootSync(startPath) {
259
- let currentPath = path.resolve(startPath);
260
- const rootPath = path.parse(currentPath).root;
259
+ let currentPath = nodePath.resolve(startPath);
260
+ const rootPath = nodePath.parse(currentPath).root;
261
261
  while (true) {
262
- if (pathExistsSync(path.join(currentPath, ".git"))) return currentPath;
262
+ if (pathExistsSync(nodePath.join(currentPath, ".git"))) return currentPath;
263
263
  if (currentPath === rootPath) return process.cwd();
264
- currentPath = path.dirname(currentPath);
264
+ currentPath = nodePath.dirname(currentPath);
265
265
  }
266
266
  }
267
267
  /**
@@ -294,7 +294,7 @@ var TemplatesManagerService = class TemplatesManagerService {
294
294
  */
295
295
  static async readToolkitConfig(startPath = process.cwd()) {
296
296
  const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
297
- const toolkitConfigPath = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
297
+ const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
298
298
  if (!await pathExists(toolkitConfigPath)) return null;
299
299
  const yaml$1 = await import("js-yaml");
300
300
  const content = await fs.readFile(toolkitConfigPath, "utf-8");
@@ -308,7 +308,7 @@ var TemplatesManagerService = class TemplatesManagerService {
308
308
  */
309
309
  static readToolkitConfigSync(startPath = process.cwd()) {
310
310
  const workspaceRoot = TemplatesManagerService.findWorkspaceRootSync(startPath);
311
- const toolkitConfigPath = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
311
+ const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
312
312
  if (!pathExistsSync(toolkitConfigPath)) return null;
313
313
  const yaml$1 = __require("js-yaml");
314
314
  const content = readFileSync$1(toolkitConfigPath, "utf-8");
@@ -322,7 +322,7 @@ var TemplatesManagerService = class TemplatesManagerService {
322
322
  */
323
323
  static async writeToolkitConfig(config, startPath = process.cwd()) {
324
324
  const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
325
- const toolkitConfigPath = path.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
325
+ const toolkitConfigPath = nodePath.join(workspaceRoot, TemplatesManagerService.TOOLKIT_CONFIG_FILE);
326
326
  const content = (await import("js-yaml")).dump(config, { indent: 2 });
327
327
  await fs.writeFile(toolkitConfigPath, content, "utf-8");
328
328
  }
@@ -393,13 +393,13 @@ var ProjectConfigResolver = class ProjectConfigResolver {
393
393
  */
394
394
  static async resolveProjectConfig(projectPath, explicitTemplate) {
395
395
  try {
396
- const absolutePath = path.resolve(projectPath);
396
+ const absolutePath = nodePath.resolve(projectPath);
397
397
  if (explicitTemplate) return {
398
398
  type: ProjectType.MONOLITH,
399
399
  sourceTemplate: explicitTemplate,
400
400
  configSource: ConfigSource.TOOLKIT_YAML
401
401
  };
402
- const projectJsonPath = path.join(absolutePath, "project.json");
402
+ const projectJsonPath = nodePath.join(absolutePath, "project.json");
403
403
  if (await pathExists(projectJsonPath)) {
404
404
  const projectJson = await readJson(projectJsonPath);
405
405
  if (projectJson.sourceTemplate && typeof projectJson.sourceTemplate === "string" && projectJson.sourceTemplate.trim()) return {
@@ -490,12 +490,12 @@ Run 'scaffold-mcp scaffold list --help' for more info.`;
490
490
  * @param sourceTemplate - The template identifier
491
491
  */
492
492
  static async createProjectJson(projectPath, projectName, sourceTemplate) {
493
- const projectJsonPath = path.join(projectPath, "project.json");
493
+ const projectJsonPath = nodePath.join(projectPath, "project.json");
494
494
  try {
495
495
  let projectJson;
496
496
  if (await pathExists(projectJsonPath)) projectJson = await readJson(projectJsonPath);
497
497
  else {
498
- const relativePath = path.relative(projectPath, process.cwd());
498
+ const relativePath = nodePath.relative(projectPath, process.cwd());
499
499
  projectJson = {
500
500
  name: projectName,
501
501
  $schema: relativePath ? `${relativePath}/node_modules/nx/schemas/project-schema.json` : "node_modules/nx/schemas/project-schema.json",
@@ -547,15 +547,15 @@ var ProjectFinderService = class {
547
547
  * @returns Project configuration or null if not found
548
548
  */
549
549
  async findProjectForFile(filePath) {
550
- const normalizedPath = path.isAbsolute(filePath) ? filePath : path.join(this.workspaceRoot, filePath);
551
- let currentDir = path.dirname(normalizedPath);
550
+ const normalizedPath = nodePath.isAbsolute(filePath) ? filePath : nodePath.join(this.workspaceRoot, filePath);
551
+ let currentDir = nodePath.dirname(normalizedPath);
552
552
  while (currentDir !== "/" && currentDir.startsWith(this.workspaceRoot)) {
553
- const projectJsonPath = path.join(currentDir, "project.json");
553
+ const projectJsonPath = nodePath.join(currentDir, "project.json");
554
554
  try {
555
555
  const project = await this.loadProjectConfig(projectJsonPath);
556
556
  if (project) return project;
557
557
  } catch {}
558
- currentDir = path.dirname(currentDir);
558
+ currentDir = nodePath.dirname(currentDir);
559
559
  }
560
560
  return null;
561
561
  }
@@ -566,15 +566,15 @@ var ProjectFinderService = class {
566
566
  * @returns Project configuration or null if not found
567
567
  */
568
568
  findProjectForFileSync(filePath) {
569
- const normalizedPath = path.isAbsolute(filePath) ? filePath : path.join(this.workspaceRoot, filePath);
570
- let currentDir = path.dirname(normalizedPath);
569
+ const normalizedPath = nodePath.isAbsolute(filePath) ? filePath : nodePath.join(this.workspaceRoot, filePath);
570
+ let currentDir = nodePath.dirname(normalizedPath);
571
571
  while (currentDir !== "/" && currentDir.startsWith(this.workspaceRoot)) {
572
- const projectJsonPath = path.join(currentDir, "project.json");
572
+ const projectJsonPath = nodePath.join(currentDir, "project.json");
573
573
  try {
574
574
  const project = this.loadProjectConfigSync(projectJsonPath);
575
575
  if (project) return project;
576
576
  } catch {}
577
- currentDir = path.dirname(currentDir);
577
+ currentDir = nodePath.dirname(currentDir);
578
578
  }
579
579
  return null;
580
580
  }
@@ -587,8 +587,8 @@ var ProjectFinderService = class {
587
587
  const content = await fs.readFile(projectJsonPath, "utf-8");
588
588
  const config = JSON.parse(content);
589
589
  const projectConfig = {
590
- name: config.name || path.basename(path.dirname(projectJsonPath)),
591
- root: path.dirname(projectJsonPath),
590
+ name: config.name || nodePath.basename(nodePath.dirname(projectJsonPath)),
591
+ root: nodePath.dirname(projectJsonPath),
592
592
  sourceTemplate: config.sourceTemplate,
593
593
  projectType: config.projectType
594
594
  };
@@ -607,8 +607,8 @@ var ProjectFinderService = class {
607
607
  const content = readFileSync$1(projectJsonPath, "utf-8");
608
608
  const config = JSON.parse(content);
609
609
  const projectConfig = {
610
- name: config.name || path.basename(path.dirname(projectJsonPath)),
611
- root: path.dirname(projectJsonPath),
610
+ name: config.name || nodePath.basename(nodePath.dirname(projectJsonPath)),
611
+ root: nodePath.dirname(projectJsonPath),
612
612
  sourceTemplate: config.sourceTemplate,
613
613
  projectType: config.projectType
614
614
  };
@@ -668,7 +668,7 @@ var ScaffoldProcessingService = class {
668
668
  * Now supports tracking existing files separately from created files
669
669
  */
670
670
  async copyAndProcess(sourcePath, targetPath, variables, createdFiles, existingFiles) {
671
- await this.fileSystem.ensureDir(path.dirname(targetPath));
671
+ await this.fileSystem.ensureDir(nodePath.dirname(targetPath));
672
672
  if (await this.fileSystem.pathExists(targetPath) && existingFiles) {
673
673
  await this.trackExistingFiles(targetPath, existingFiles);
674
674
  return;
@@ -690,7 +690,7 @@ var ScaffoldProcessingService = class {
690
690
  }
691
691
  for (const item of items) {
692
692
  if (!item) continue;
693
- const itemPath = path.join(dirPath, item);
693
+ const itemPath = nodePath.join(dirPath, item);
694
694
  try {
695
695
  const stat$1 = await this.fileSystem.stat(itemPath);
696
696
  if (stat$1.isDirectory()) await this.trackCreatedFilesRecursive(itemPath, createdFiles);
@@ -713,7 +713,7 @@ var ScaffoldProcessingService = class {
713
713
  }
714
714
  for (const item of items) {
715
715
  if (!item) continue;
716
- const itemPath = path.join(dirPath, item);
716
+ const itemPath = nodePath.join(dirPath, item);
717
717
  try {
718
718
  const stat$1 = await this.fileSystem.stat(itemPath);
719
719
  if (stat$1.isDirectory()) await this.trackExistingFilesRecursive(itemPath, existingFiles);
@@ -725,6 +725,36 @@ var ScaffoldProcessingService = class {
725
725
  }
726
726
  };
727
727
 
728
+ //#endregion
729
+ //#region src/utils/generateStableId.ts
730
+ /**
731
+ * Generate a stable, random ID string
732
+ *
733
+ * @param length - Length of the ID to generate (default: 8)
734
+ * @returns A random alphanumeric ID string (lowercase)
735
+ *
736
+ * @remarks
737
+ * Negative or non-integer lengths are normalized (floored and clamped to 0).
738
+ * Returns empty string for length <= 0.
739
+ *
740
+ * @example
741
+ * ```typescript
742
+ * const id = generateStableId(6); // Returns something like "a3f9k2"
743
+ * ```
744
+ */
745
+ function generateStableId(length = 8) {
746
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
747
+ let result = "";
748
+ const normalizedLength = Math.max(0, Math.floor(length));
749
+ if (normalizedLength === 0) return "";
750
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
751
+ const values = new Uint8Array(normalizedLength);
752
+ crypto.getRandomValues(values);
753
+ for (let i = 0; i < normalizedLength; i++) result += chars[values[i] % 36];
754
+ } else for (let i = 0; i < normalizedLength; i++) result += chars[Math.floor(Math.random() * 36)];
755
+ return result;
756
+ }
757
+
728
758
  //#endregion
729
759
  //#region src/utils/print.ts
730
760
  /**
@@ -897,7 +927,7 @@ const MONOREPO_INDICATOR_FILES = [
897
927
  */
898
928
  async function detectProjectType(workspaceRoot) {
899
929
  const indicators = [];
900
- const toolkitYamlPath = path.join(workspaceRoot, "toolkit.yaml");
930
+ const toolkitYamlPath = nodePath.join(workspaceRoot, "toolkit.yaml");
901
931
  if (await pathExists(toolkitYamlPath)) try {
902
932
  const content = await fs.readFile(toolkitYamlPath, "utf-8");
903
933
  const config = yaml.load(content);
@@ -909,14 +939,14 @@ async function detectProjectType(workspaceRoot) {
909
939
  };
910
940
  }
911
941
  } catch {}
912
- for (const filename of MONOREPO_INDICATOR_FILES) if (await pathExists(path.join(workspaceRoot, filename))) {
942
+ for (const filename of MONOREPO_INDICATOR_FILES) if (await pathExists(nodePath.join(workspaceRoot, filename))) {
913
943
  indicators.push(`${filename} found`);
914
944
  return {
915
945
  projectType: ProjectType.MONOREPO,
916
946
  indicators
917
947
  };
918
948
  }
919
- const packageJsonPath = path.join(workspaceRoot, "package.json");
949
+ const packageJsonPath = nodePath.join(workspaceRoot, "package.json");
920
950
  if (await pathExists(packageJsonPath)) try {
921
951
  if ((await readJson(packageJsonPath)).workspaces) {
922
952
  indicators.push("package.json with workspaces found");
@@ -954,4 +984,4 @@ async function isMonolith(workspaceRoot) {
954
984
  }
955
985
 
956
986
  //#endregion
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 };
987
+ export { ConfigSource, ProjectConfigResolver, ProjectFinderService, ProjectType, ScaffoldProcessingService, TemplatesManagerService, accessSync, copy, cp, detectProjectType, ensureDir, ensureDirSync, fs, generateStableId, 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.5",
4
+ "version": "1.0.6",
5
5
  "license": "AGPL-3.0",
6
6
  "author": "AgiflowIO",
7
7
  "repository": {
@@ -40,6 +40,7 @@
40
40
  "@types/node": "^22.0.0",
41
41
  "@types/ora": "^3.2.0",
42
42
  "@vitest/coverage-v8": "^3.0.0",
43
+ "chance": "^1.1.13",
43
44
  "tsdown": "^0.16.4",
44
45
  "typescript": "5.9.3",
45
46
  "vitest": "^3.0.0"