servcraft 0.3.1 → 0.4.3

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.
@@ -28,7 +28,7 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
28
28
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
29
29
 
30
30
  // src/cli/index.ts
31
- var import_commander11 = require("commander");
31
+ var import_commander13 = require("commander");
32
32
 
33
33
  // src/cli/commands/init.ts
34
34
  var import_commander = require("commander");
@@ -1226,7 +1226,7 @@ declare module 'fastify' {
1226
1226
 
1227
1227
  // src/cli/commands/generate.ts
1228
1228
  var import_commander2 = require("commander");
1229
- var import_path4 = __toESM(require("path"), 1);
1229
+ var import_path5 = __toESM(require("path"), 1);
1230
1230
  var import_ora2 = __toESM(require("ora"), 1);
1231
1231
  var import_inquirer2 = __toESM(require("inquirer"), 1);
1232
1232
  var import_chalk5 = __toESM(require("chalk"), 1);
@@ -1455,11 +1455,11 @@ function displayError(error2) {
1455
1455
  }
1456
1456
  function validateProject() {
1457
1457
  try {
1458
- const fs12 = require("fs");
1459
- if (!fs12.existsSync("package.json")) {
1458
+ const fs14 = require("fs");
1459
+ if (!fs14.existsSync("package.json")) {
1460
1460
  return ErrorTypes.NOT_IN_PROJECT();
1461
1461
  }
1462
- const packageJson = JSON.parse(fs12.readFileSync("package.json", "utf-8"));
1462
+ const packageJson = JSON.parse(fs14.readFileSync("package.json", "utf-8"));
1463
1463
  if (!packageJson.dependencies?.fastify) {
1464
1464
  return new ServCraftError("This does not appear to be a ServCraft project", [
1465
1465
  `ServCraft projects require Fastify`,
@@ -2510,6 +2510,39 @@ describe('${pascalName} Integration Tests', () => {
2510
2510
  `;
2511
2511
  }
2512
2512
 
2513
+ // src/cli/utils/template-loader.ts
2514
+ var import_promises3 = __toESM(require("fs/promises"), 1);
2515
+ var import_path4 = __toESM(require("path"), 1);
2516
+ async function loadCustomTemplate(templateType) {
2517
+ const projectRoot = getProjectRoot();
2518
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2519
+ const locations = [
2520
+ import_path4.default.join(projectRoot, ".servcraft", "templates", `${templateType}.ts`),
2521
+ import_path4.default.join(homeDir, ".servcraft", "templates", `${templateType}.ts`)
2522
+ ];
2523
+ for (const location of locations) {
2524
+ try {
2525
+ await import_promises3.default.access(location);
2526
+ const templateModule = await import(`file://${location}`);
2527
+ const functionName = `${templateType.replace(/-/g, "")}Template`;
2528
+ if (templateModule[functionName]) {
2529
+ return templateModule;
2530
+ }
2531
+ } catch {
2532
+ continue;
2533
+ }
2534
+ }
2535
+ return null;
2536
+ }
2537
+ async function getTemplate(templateType, builtInTemplate) {
2538
+ const customTemplate = await loadCustomTemplate(templateType);
2539
+ if (customTemplate) {
2540
+ const functionName = `${templateType.replace(/-/g, "")}Template`;
2541
+ return customTemplate[functionName];
2542
+ }
2543
+ return builtInTemplate;
2544
+ }
2545
+
2513
2546
  // src/cli/commands/generate.ts
2514
2547
  function enableDryRunIfNeeded(options) {
2515
2548
  const dryRun = DryRunManager.getInstance();
@@ -2542,60 +2575,70 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
2542
2575
  const pluralName = pluralize(kebabName);
2543
2576
  const tableName = pluralize(kebabName.replace(/-/g, "_"));
2544
2577
  const validatorType = options.validator || "zod";
2545
- const moduleDir = import_path4.default.join(getModulesDir(), kebabName);
2578
+ const moduleDir = import_path5.default.join(getModulesDir(), kebabName);
2546
2579
  if (await fileExists(moduleDir)) {
2547
2580
  spinner.stop();
2548
2581
  error(`Module "${kebabName}" already exists`);
2549
2582
  return;
2550
2583
  }
2551
2584
  const hasFields = fields.length > 0;
2585
+ const controllerTpl = await getTemplate("controller", controllerTemplate);
2586
+ const serviceTpl = await getTemplate("service", serviceTemplate);
2587
+ const repositoryTpl = await getTemplate("repository", repositoryTemplate);
2588
+ const typesTpl = await getTemplate("types", typesTemplate);
2589
+ const schemasTpl = await getTemplate("schemas", schemasTemplate);
2590
+ const routesTpl = await getTemplate("routes", routesTemplate);
2591
+ const moduleIndexTpl = await getTemplate("module-index", moduleIndexTemplate);
2552
2592
  const files = [
2553
2593
  {
2554
2594
  name: `${kebabName}.types.ts`,
2555
- content: hasFields ? dynamicTypesTemplate(kebabName, pascalName, fields) : typesTemplate(kebabName, pascalName)
2595
+ content: hasFields ? dynamicTypesTemplate(kebabName, pascalName, fields) : typesTpl(kebabName, pascalName)
2556
2596
  },
2557
2597
  {
2558
2598
  name: `${kebabName}.schemas.ts`,
2559
- content: hasFields ? dynamicSchemasTemplate(kebabName, pascalName, camelName, fields, validatorType) : schemasTemplate(kebabName, pascalName, camelName)
2599
+ content: hasFields ? dynamicSchemasTemplate(kebabName, pascalName, camelName, fields, validatorType) : schemasTpl(kebabName, pascalName, camelName)
2560
2600
  },
2561
2601
  {
2562
2602
  name: `${kebabName}.service.ts`,
2563
- content: serviceTemplate(kebabName, pascalName, camelName)
2603
+ content: serviceTpl(kebabName, pascalName, camelName)
2564
2604
  },
2565
2605
  {
2566
2606
  name: `${kebabName}.controller.ts`,
2567
- content: controllerTemplate(kebabName, pascalName, camelName)
2607
+ content: controllerTpl(kebabName, pascalName, camelName)
2568
2608
  },
2569
- { name: "index.ts", content: moduleIndexTemplate(kebabName, pascalName, camelName) }
2609
+ { name: "index.ts", content: moduleIndexTpl(kebabName, pascalName, camelName) }
2570
2610
  ];
2571
2611
  if (options.repository !== false) {
2572
2612
  files.push({
2573
2613
  name: `${kebabName}.repository.ts`,
2574
- content: repositoryTemplate(kebabName, pascalName, camelName, pluralName)
2614
+ content: repositoryTpl(kebabName, pascalName, camelName, pluralName)
2575
2615
  });
2576
2616
  }
2577
2617
  if (options.routes !== false) {
2578
2618
  files.push({
2579
2619
  name: `${kebabName}.routes.ts`,
2580
- content: routesTemplate(kebabName, pascalName, camelName, pluralName)
2620
+ content: routesTpl(kebabName, pascalName, camelName, pluralName)
2581
2621
  });
2582
2622
  }
2583
2623
  for (const file of files) {
2584
- await writeFile(import_path4.default.join(moduleDir, file.name), file.content);
2624
+ await writeFile(import_path5.default.join(moduleDir, file.name), file.content);
2585
2625
  }
2586
2626
  if (options.withTests) {
2587
- const testDir = import_path4.default.join(moduleDir, "__tests__");
2627
+ const testDir = import_path5.default.join(moduleDir, "__tests__");
2628
+ const controllerTestTpl = await getTemplate("controller-test", controllerTestTemplate);
2629
+ const serviceTestTpl = await getTemplate("service-test", serviceTestTemplate);
2630
+ const integrationTestTpl = await getTemplate("integration-test", integrationTestTemplate);
2588
2631
  await writeFile(
2589
- import_path4.default.join(testDir, `${kebabName}.controller.test.ts`),
2590
- controllerTestTemplate(kebabName, pascalName, camelName)
2632
+ import_path5.default.join(testDir, `${kebabName}.controller.test.ts`),
2633
+ controllerTestTpl(kebabName, pascalName, camelName)
2591
2634
  );
2592
2635
  await writeFile(
2593
- import_path4.default.join(testDir, `${kebabName}.service.test.ts`),
2594
- serviceTestTemplate(kebabName, pascalName, camelName)
2636
+ import_path5.default.join(testDir, `${kebabName}.service.test.ts`),
2637
+ serviceTestTpl(kebabName, pascalName, camelName)
2595
2638
  );
2596
2639
  await writeFile(
2597
- import_path4.default.join(testDir, `${kebabName}.integration.test.ts`),
2598
- integrationTestTemplate(kebabName, pascalName, camelName)
2640
+ import_path5.default.join(testDir, `${kebabName}.integration.test.ts`),
2641
+ integrationTestTpl(kebabName, pascalName, camelName)
2599
2642
  );
2600
2643
  }
2601
2644
  spinner.succeed(`Module "${pascalName}" generated successfully!`);
@@ -2653,8 +2696,8 @@ generateCommand.command("controller <name>").alias("c").description("Generate a
2653
2696
  const pascalName = toPascalCase(name);
2654
2697
  const camelName = toCamelCase(name);
2655
2698
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2656
- const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2657
- const filePath = import_path4.default.join(moduleDir, `${kebabName}.controller.ts`);
2699
+ const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
2700
+ const filePath = import_path5.default.join(moduleDir, `${kebabName}.controller.ts`);
2658
2701
  if (await fileExists(filePath)) {
2659
2702
  spinner.stop();
2660
2703
  displayError(ErrorTypes.FILE_ALREADY_EXISTS(`${kebabName}.controller.ts`));
@@ -2677,8 +2720,8 @@ generateCommand.command("service <name>").alias("s").description("Generate a ser
2677
2720
  const pascalName = toPascalCase(name);
2678
2721
  const camelName = toCamelCase(name);
2679
2722
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2680
- const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2681
- const filePath = import_path4.default.join(moduleDir, `${kebabName}.service.ts`);
2723
+ const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
2724
+ const filePath = import_path5.default.join(moduleDir, `${kebabName}.service.ts`);
2682
2725
  if (await fileExists(filePath)) {
2683
2726
  spinner.stop();
2684
2727
  error(`Service "${kebabName}" already exists`);
@@ -2702,8 +2745,8 @@ generateCommand.command("repository <name>").alias("r").description("Generate a
2702
2745
  const camelName = toCamelCase(name);
2703
2746
  const pluralName = pluralize(kebabName);
2704
2747
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2705
- const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2706
- const filePath = import_path4.default.join(moduleDir, `${kebabName}.repository.ts`);
2748
+ const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
2749
+ const filePath = import_path5.default.join(moduleDir, `${kebabName}.repository.ts`);
2707
2750
  if (await fileExists(filePath)) {
2708
2751
  spinner.stop();
2709
2752
  error(`Repository "${kebabName}" already exists`);
@@ -2725,8 +2768,8 @@ generateCommand.command("types <name>").alias("t").description("Generate types/i
2725
2768
  const kebabName = toKebabCase(name);
2726
2769
  const pascalName = toPascalCase(name);
2727
2770
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2728
- const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2729
- const filePath = import_path4.default.join(moduleDir, `${kebabName}.types.ts`);
2771
+ const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
2772
+ const filePath = import_path5.default.join(moduleDir, `${kebabName}.types.ts`);
2730
2773
  if (await fileExists(filePath)) {
2731
2774
  spinner.stop();
2732
2775
  error(`Types file "${kebabName}.types.ts" already exists`);
@@ -2749,8 +2792,8 @@ generateCommand.command("schema <name>").alias("v").description("Generate valida
2749
2792
  const pascalName = toPascalCase(name);
2750
2793
  const camelName = toCamelCase(name);
2751
2794
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2752
- const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2753
- const filePath = import_path4.default.join(moduleDir, `${kebabName}.schemas.ts`);
2795
+ const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
2796
+ const filePath = import_path5.default.join(moduleDir, `${kebabName}.schemas.ts`);
2754
2797
  if (await fileExists(filePath)) {
2755
2798
  spinner.stop();
2756
2799
  error(`Schemas file "${kebabName}.schemas.ts" already exists`);
@@ -2774,8 +2817,8 @@ generateCommand.command("routes <name>").description("Generate routes").option("
2774
2817
  const camelName = toCamelCase(name);
2775
2818
  const pluralName = pluralize(kebabName);
2776
2819
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2777
- const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2778
- const filePath = import_path4.default.join(moduleDir, `${kebabName}.routes.ts`);
2820
+ const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
2821
+ const filePath = import_path5.default.join(moduleDir, `${kebabName}.routes.ts`);
2779
2822
  if (await fileExists(filePath)) {
2780
2823
  spinner.stop();
2781
2824
  error(`Routes file "${kebabName}.routes.ts" already exists`);
@@ -2863,21 +2906,21 @@ async function promptForFields() {
2863
2906
 
2864
2907
  // src/cli/commands/add-module.ts
2865
2908
  var import_commander3 = require("commander");
2866
- var import_path5 = __toESM(require("path"), 1);
2909
+ var import_path6 = __toESM(require("path"), 1);
2867
2910
  var import_ora3 = __toESM(require("ora"), 1);
2868
2911
  var import_chalk7 = __toESM(require("chalk"), 1);
2869
- var fs5 = __toESM(require("fs/promises"), 1);
2912
+ var fs6 = __toESM(require("fs/promises"), 1);
2870
2913
 
2871
2914
  // src/cli/utils/env-manager.ts
2872
- var fs3 = __toESM(require("fs/promises"), 1);
2873
- var path5 = __toESM(require("path"), 1);
2915
+ var fs4 = __toESM(require("fs/promises"), 1);
2916
+ var path6 = __toESM(require("path"), 1);
2874
2917
  var import_fs = require("fs");
2875
2918
  var EnvManager = class {
2876
2919
  envPath;
2877
2920
  envExamplePath;
2878
2921
  constructor(projectRoot) {
2879
- this.envPath = path5.join(projectRoot, ".env");
2880
- this.envExamplePath = path5.join(projectRoot, ".env.example");
2922
+ this.envPath = path6.join(projectRoot, ".env");
2923
+ this.envExamplePath = path6.join(projectRoot, ".env.example");
2881
2924
  }
2882
2925
  /**
2883
2926
  * Add environment variables to .env file
@@ -2888,7 +2931,7 @@ var EnvManager = class {
2888
2931
  let created2 = false;
2889
2932
  let envContent = "";
2890
2933
  if ((0, import_fs.existsSync)(this.envPath)) {
2891
- envContent = await fs3.readFile(this.envPath, "utf-8");
2934
+ envContent = await fs4.readFile(this.envPath, "utf-8");
2892
2935
  } else {
2893
2936
  created2 = true;
2894
2937
  }
@@ -2917,7 +2960,7 @@ var EnvManager = class {
2917
2960
  }
2918
2961
  newContent += "\n";
2919
2962
  }
2920
- await fs3.writeFile(this.envPath, newContent, "utf-8");
2963
+ await fs4.writeFile(this.envPath, newContent, "utf-8");
2921
2964
  if ((0, import_fs.existsSync)(this.envExamplePath)) {
2922
2965
  await this.updateEnvExample(sections);
2923
2966
  }
@@ -2929,7 +2972,7 @@ var EnvManager = class {
2929
2972
  async updateEnvExample(sections) {
2930
2973
  let exampleContent = "";
2931
2974
  if ((0, import_fs.existsSync)(this.envExamplePath)) {
2932
- exampleContent = await fs3.readFile(this.envExamplePath, "utf-8");
2975
+ exampleContent = await fs4.readFile(this.envExamplePath, "utf-8");
2933
2976
  }
2934
2977
  const existingKeys = this.parseExistingKeys(exampleContent);
2935
2978
  let newContent = exampleContent;
@@ -2953,7 +2996,7 @@ var EnvManager = class {
2953
2996
  }
2954
2997
  newContent += "\n";
2955
2998
  }
2956
- await fs3.writeFile(this.envExamplePath, newContent, "utf-8");
2999
+ await fs4.writeFile(this.envExamplePath, newContent, "utf-8");
2957
3000
  }
2958
3001
  /**
2959
3002
  * Parse existing environment variable keys
@@ -3527,34 +3570,34 @@ var EnvManager = class {
3527
3570
  };
3528
3571
 
3529
3572
  // src/cli/utils/template-manager.ts
3530
- var fs4 = __toESM(require("fs/promises"), 1);
3531
- var path6 = __toESM(require("path"), 1);
3573
+ var fs5 = __toESM(require("fs/promises"), 1);
3574
+ var path7 = __toESM(require("path"), 1);
3532
3575
  var import_crypto = require("crypto");
3533
3576
  var import_fs2 = require("fs");
3534
3577
  var TemplateManager = class {
3535
3578
  templatesDir;
3536
3579
  manifestsDir;
3537
3580
  constructor(projectRoot) {
3538
- this.templatesDir = path6.join(projectRoot, ".servcraft", "templates");
3539
- this.manifestsDir = path6.join(projectRoot, ".servcraft", "manifests");
3581
+ this.templatesDir = path7.join(projectRoot, ".servcraft", "templates");
3582
+ this.manifestsDir = path7.join(projectRoot, ".servcraft", "manifests");
3540
3583
  }
3541
3584
  /**
3542
3585
  * Initialize template system
3543
3586
  */
3544
3587
  async initialize() {
3545
- await fs4.mkdir(this.templatesDir, { recursive: true });
3546
- await fs4.mkdir(this.manifestsDir, { recursive: true });
3588
+ await fs5.mkdir(this.templatesDir, { recursive: true });
3589
+ await fs5.mkdir(this.manifestsDir, { recursive: true });
3547
3590
  }
3548
3591
  /**
3549
3592
  * Save module template
3550
3593
  */
3551
3594
  async saveTemplate(moduleName, files) {
3552
3595
  await this.initialize();
3553
- const moduleTemplateDir = path6.join(this.templatesDir, moduleName);
3554
- await fs4.mkdir(moduleTemplateDir, { recursive: true });
3596
+ const moduleTemplateDir = path7.join(this.templatesDir, moduleName);
3597
+ await fs5.mkdir(moduleTemplateDir, { recursive: true });
3555
3598
  for (const [fileName, content] of Object.entries(files)) {
3556
- const filePath = path6.join(moduleTemplateDir, fileName);
3557
- await fs4.writeFile(filePath, content, "utf-8");
3599
+ const filePath = path7.join(moduleTemplateDir, fileName);
3600
+ await fs5.writeFile(filePath, content, "utf-8");
3558
3601
  }
3559
3602
  }
3560
3603
  /**
@@ -3562,8 +3605,8 @@ var TemplateManager = class {
3562
3605
  */
3563
3606
  async getTemplate(moduleName, fileName) {
3564
3607
  try {
3565
- const filePath = path6.join(this.templatesDir, moduleName, fileName);
3566
- return await fs4.readFile(filePath, "utf-8");
3608
+ const filePath = path7.join(this.templatesDir, moduleName, fileName);
3609
+ return await fs5.readFile(filePath, "utf-8");
3567
3610
  } catch {
3568
3611
  return null;
3569
3612
  }
@@ -3587,16 +3630,16 @@ var TemplateManager = class {
3587
3630
  installedAt: /* @__PURE__ */ new Date(),
3588
3631
  updatedAt: /* @__PURE__ */ new Date()
3589
3632
  };
3590
- const manifestPath = path6.join(this.manifestsDir, `${moduleName}.json`);
3591
- await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3633
+ const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3634
+ await fs5.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3592
3635
  }
3593
3636
  /**
3594
3637
  * Get module manifest
3595
3638
  */
3596
3639
  async getManifest(moduleName) {
3597
3640
  try {
3598
- const manifestPath = path6.join(this.manifestsDir, `${moduleName}.json`);
3599
- const content = await fs4.readFile(manifestPath, "utf-8");
3641
+ const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3642
+ const content = await fs5.readFile(manifestPath, "utf-8");
3600
3643
  return JSON.parse(content);
3601
3644
  } catch {
3602
3645
  return null;
@@ -3624,7 +3667,7 @@ var TemplateManager = class {
3624
3667
  }
3625
3668
  const results = [];
3626
3669
  for (const [fileName, fileInfo] of Object.entries(manifest.files)) {
3627
- const filePath = path6.join(moduleDir, fileName);
3670
+ const filePath = path7.join(moduleDir, fileName);
3628
3671
  if (!(0, import_fs2.existsSync)(filePath)) {
3629
3672
  results.push({
3630
3673
  fileName,
@@ -3634,7 +3677,7 @@ var TemplateManager = class {
3634
3677
  });
3635
3678
  continue;
3636
3679
  }
3637
- const currentContent = await fs4.readFile(filePath, "utf-8");
3680
+ const currentContent = await fs5.readFile(filePath, "utf-8");
3638
3681
  const currentHash = this.hashContent(currentContent);
3639
3682
  results.push({
3640
3683
  fileName,
@@ -3650,7 +3693,7 @@ var TemplateManager = class {
3650
3693
  */
3651
3694
  async createBackup(moduleName, moduleDir) {
3652
3695
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
3653
- const backupDir = path6.join(path6.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
3696
+ const backupDir = path7.join(path7.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
3654
3697
  await this.copyDirectory(moduleDir, backupDir);
3655
3698
  return backupDir;
3656
3699
  }
@@ -3658,15 +3701,15 @@ var TemplateManager = class {
3658
3701
  * Copy directory recursively
3659
3702
  */
3660
3703
  async copyDirectory(src, dest) {
3661
- await fs4.mkdir(dest, { recursive: true });
3662
- const entries = await fs4.readdir(src, { withFileTypes: true });
3704
+ await fs5.mkdir(dest, { recursive: true });
3705
+ const entries = await fs5.readdir(src, { withFileTypes: true });
3663
3706
  for (const entry of entries) {
3664
- const srcPath = path6.join(src, entry.name);
3665
- const destPath = path6.join(dest, entry.name);
3707
+ const srcPath = path7.join(src, entry.name);
3708
+ const destPath = path7.join(dest, entry.name);
3666
3709
  if (entry.isDirectory()) {
3667
3710
  await this.copyDirectory(srcPath, destPath);
3668
3711
  } else {
3669
- await fs4.copyFile(srcPath, destPath);
3712
+ await fs5.copyFile(srcPath, destPath);
3670
3713
  }
3671
3714
  }
3672
3715
  }
@@ -3763,8 +3806,8 @@ var TemplateManager = class {
3763
3806
  }
3764
3807
  manifest.files = fileHashes;
3765
3808
  manifest.updatedAt = /* @__PURE__ */ new Date();
3766
- const manifestPath = path6.join(this.manifestsDir, `${moduleName}.json`);
3767
- await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3809
+ const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3810
+ await fs5.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3768
3811
  }
3769
3812
  };
3770
3813
 
@@ -4121,7 +4164,7 @@ var addModuleCommand = new import_commander3.Command("add").description("Add a p
4121
4164
  }
4122
4165
  const spinner = (0, import_ora3.default)(`Adding ${module2.name} module...`).start();
4123
4166
  try {
4124
- const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
4167
+ const moduleDir = import_path6.default.join(getModulesDir(), moduleName);
4125
4168
  const templateManager = new TemplateManager(process.cwd());
4126
4169
  const moduleExists = await fileExists(moduleDir);
4127
4170
  if (moduleExists) {
@@ -4154,7 +4197,7 @@ var addModuleCommand = new import_commander3.Command("add").description("Add a p
4154
4197
  const backupPath = await templateManager.createBackup(moduleName, moduleDir);
4155
4198
  InteractivePrompt.showBackupCreated(backupPath);
4156
4199
  }
4157
- await fs5.rm(moduleDir, { recursive: true, force: true });
4200
+ await fs6.rm(moduleDir, { recursive: true, force: true });
4158
4201
  await ensureDir(moduleDir);
4159
4202
  await generateModuleFiles(moduleName, moduleDir);
4160
4203
  const files = await getModuleFiles(moduleName, moduleDir);
@@ -4265,7 +4308,7 @@ export * from './auth.schemas.js';
4265
4308
  `
4266
4309
  };
4267
4310
  for (const [name, content] of Object.entries(files)) {
4268
- await writeFile(import_path5.default.join(dir, name), content);
4311
+ await writeFile(import_path6.default.join(dir, name), content);
4269
4312
  }
4270
4313
  }
4271
4314
  async function generateUsersModule(dir) {
@@ -4305,7 +4348,7 @@ export * from './user.schemas.js';
4305
4348
  `
4306
4349
  };
4307
4350
  for (const [name, content] of Object.entries(files)) {
4308
- await writeFile(import_path5.default.join(dir, name), content);
4351
+ await writeFile(import_path6.default.join(dir, name), content);
4309
4352
  }
4310
4353
  }
4311
4354
  async function generateEmailModule(dir) {
@@ -4362,7 +4405,7 @@ export { EmailService, emailService } from './email.service.js';
4362
4405
  `
4363
4406
  };
4364
4407
  for (const [name, content] of Object.entries(files)) {
4365
- await writeFile(import_path5.default.join(dir, name), content);
4408
+ await writeFile(import_path6.default.join(dir, name), content);
4366
4409
  }
4367
4410
  }
4368
4411
  async function generateAuditModule(dir) {
@@ -4406,7 +4449,7 @@ export { AuditService, auditService } from './audit.service.js';
4406
4449
  `
4407
4450
  };
4408
4451
  for (const [name, content] of Object.entries(files)) {
4409
- await writeFile(import_path5.default.join(dir, name), content);
4452
+ await writeFile(import_path6.default.join(dir, name), content);
4410
4453
  }
4411
4454
  }
4412
4455
  async function generateUploadModule(dir) {
@@ -4432,7 +4475,7 @@ export interface UploadOptions {
4432
4475
  `
4433
4476
  };
4434
4477
  for (const [name, content] of Object.entries(files)) {
4435
- await writeFile(import_path5.default.join(dir, name), content);
4478
+ await writeFile(import_path6.default.join(dir, name), content);
4436
4479
  }
4437
4480
  }
4438
4481
  async function generateCacheModule(dir) {
@@ -4478,7 +4521,7 @@ export { CacheService, cacheService } from './cache.service.js';
4478
4521
  `
4479
4522
  };
4480
4523
  for (const [name, content] of Object.entries(files)) {
4481
- await writeFile(import_path5.default.join(dir, name), content);
4524
+ await writeFile(import_path6.default.join(dir, name), content);
4482
4525
  }
4483
4526
  }
4484
4527
  async function generateGenericModule(dir, name) {
@@ -4492,22 +4535,22 @@ export interface ${name.charAt(0).toUpperCase() + name.slice(1)}Data {
4492
4535
  `
4493
4536
  };
4494
4537
  for (const [fileName, content] of Object.entries(files)) {
4495
- await writeFile(import_path5.default.join(dir, fileName), content);
4538
+ await writeFile(import_path6.default.join(dir, fileName), content);
4496
4539
  }
4497
4540
  }
4498
4541
  async function findServercraftModules() {
4499
- const scriptDir = import_path5.default.dirname(new URL(importMetaUrl).pathname);
4542
+ const scriptDir = import_path6.default.dirname(new URL(importMetaUrl).pathname);
4500
4543
  const possiblePaths = [
4501
4544
  // Local node_modules (when servcraft is a dependency)
4502
- import_path5.default.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
4545
+ import_path6.default.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
4503
4546
  // From dist/cli/index.js -> src/modules (npx or global install)
4504
- import_path5.default.resolve(scriptDir, "..", "..", "src", "modules"),
4547
+ import_path6.default.resolve(scriptDir, "..", "..", "src", "modules"),
4505
4548
  // From src/cli/commands/add-module.ts -> src/modules (development)
4506
- import_path5.default.resolve(scriptDir, "..", "..", "modules")
4549
+ import_path6.default.resolve(scriptDir, "..", "..", "modules")
4507
4550
  ];
4508
4551
  for (const p of possiblePaths) {
4509
4552
  try {
4510
- const stats = await fs5.stat(p);
4553
+ const stats = await fs6.stat(p);
4511
4554
  if (stats.isDirectory()) {
4512
4555
  return p;
4513
4556
  }
@@ -4527,7 +4570,7 @@ async function generateModuleFiles(moduleName, moduleDir) {
4527
4570
  const sourceDirName = moduleNameMap[moduleName] || moduleName;
4528
4571
  const servercraftModulesDir = await findServercraftModules();
4529
4572
  if (servercraftModulesDir) {
4530
- const sourceModuleDir = import_path5.default.join(servercraftModulesDir, sourceDirName);
4573
+ const sourceModuleDir = import_path6.default.join(servercraftModulesDir, sourceDirName);
4531
4574
  if (await fileExists(sourceModuleDir)) {
4532
4575
  await copyModuleFromSource(sourceModuleDir, moduleDir);
4533
4576
  return;
@@ -4557,26 +4600,26 @@ async function generateModuleFiles(moduleName, moduleDir) {
4557
4600
  }
4558
4601
  }
4559
4602
  async function copyModuleFromSource(sourceDir, targetDir) {
4560
- const entries = await fs5.readdir(sourceDir, { withFileTypes: true });
4603
+ const entries = await fs6.readdir(sourceDir, { withFileTypes: true });
4561
4604
  for (const entry of entries) {
4562
- const sourcePath = import_path5.default.join(sourceDir, entry.name);
4563
- const targetPath = import_path5.default.join(targetDir, entry.name);
4605
+ const sourcePath = import_path6.default.join(sourceDir, entry.name);
4606
+ const targetPath = import_path6.default.join(targetDir, entry.name);
4564
4607
  if (entry.isDirectory()) {
4565
- await fs5.mkdir(targetPath, { recursive: true });
4608
+ await fs6.mkdir(targetPath, { recursive: true });
4566
4609
  await copyModuleFromSource(sourcePath, targetPath);
4567
4610
  } else {
4568
- await fs5.copyFile(sourcePath, targetPath);
4611
+ await fs6.copyFile(sourcePath, targetPath);
4569
4612
  }
4570
4613
  }
4571
4614
  }
4572
4615
  async function getModuleFiles(moduleName, moduleDir) {
4573
4616
  const files = {};
4574
- const entries = await fs5.readdir(moduleDir);
4617
+ const entries = await fs6.readdir(moduleDir);
4575
4618
  for (const entry of entries) {
4576
- const filePath = import_path5.default.join(moduleDir, entry);
4577
- const stat2 = await fs5.stat(filePath);
4619
+ const filePath = import_path6.default.join(moduleDir, entry);
4620
+ const stat2 = await fs6.stat(filePath);
4578
4621
  if (stat2.isFile() && entry.endsWith(".ts")) {
4579
- const content = await fs5.readFile(filePath, "utf-8");
4622
+ const content = await fs6.readFile(filePath, "utf-8");
4580
4623
  files[entry] = content;
4581
4624
  }
4582
4625
  }
@@ -4591,8 +4634,8 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
4591
4634
  if (file.isModified) {
4592
4635
  console.log(import_chalk7.default.yellow(`
4593
4636
  \u{1F4C4} ${file.fileName}:`));
4594
- const currentPath = import_path5.default.join(moduleDir, file.fileName);
4595
- const currentContent = await fs5.readFile(currentPath, "utf-8");
4637
+ const currentPath = import_path6.default.join(moduleDir, file.fileName);
4638
+ const currentContent = await fs6.readFile(currentPath, "utf-8");
4596
4639
  const originalContent = await templateManager.getTemplate(moduleName, file.fileName);
4597
4640
  if (originalContent) {
4598
4641
  const diff = templateManager.generateDiff(originalContent, currentContent);
@@ -4604,11 +4647,11 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
4604
4647
  async function performSmartMerge(templateManager, moduleName, moduleDir, _displayName) {
4605
4648
  const spinner = (0, import_ora3.default)("Analyzing files for merge...").start();
4606
4649
  const newFiles = {};
4607
- const templateDir = import_path5.default.join(templateManager["templatesDir"], moduleName);
4650
+ const templateDir = import_path6.default.join(templateManager["templatesDir"], moduleName);
4608
4651
  try {
4609
- const entries = await fs5.readdir(templateDir);
4652
+ const entries = await fs6.readdir(templateDir);
4610
4653
  for (const entry of entries) {
4611
- const content = await fs5.readFile(import_path5.default.join(templateDir, entry), "utf-8");
4654
+ const content = await fs6.readFile(import_path6.default.join(templateDir, entry), "utf-8");
4612
4655
  newFiles[entry] = content;
4613
4656
  }
4614
4657
  } catch {
@@ -4626,7 +4669,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4626
4669
  };
4627
4670
  for (const fileInfo of modifiedFiles) {
4628
4671
  const fileName = fileInfo.fileName;
4629
- const filePath = import_path5.default.join(moduleDir, fileName);
4672
+ const filePath = import_path6.default.join(moduleDir, fileName);
4630
4673
  const newContent = newFiles[fileName];
4631
4674
  if (!newContent) {
4632
4675
  continue;
@@ -4639,7 +4682,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4639
4682
  } else if (batchAction === "overwrite-all") {
4640
4683
  fileAction = "overwrite";
4641
4684
  } else {
4642
- const currentContent = await fs5.readFile(filePath, "utf-8");
4685
+ const currentContent = await fs6.readFile(filePath, "utf-8");
4643
4686
  const yourLines = currentContent.split("\n").length;
4644
4687
  const newLines = newContent.split("\n").length;
4645
4688
  const choice = await InteractivePrompt.askFileAction(
@@ -4663,20 +4706,20 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4663
4706
  continue;
4664
4707
  }
4665
4708
  if (fileAction === "overwrite") {
4666
- await fs5.writeFile(filePath, newContent, "utf-8");
4709
+ await fs6.writeFile(filePath, newContent, "utf-8");
4667
4710
  stats.overwritten++;
4668
4711
  continue;
4669
4712
  }
4670
4713
  if (fileAction === "merge") {
4671
4714
  const originalContent = await templateManager.getTemplate(moduleName, fileName);
4672
- const currentContent = await fs5.readFile(filePath, "utf-8");
4715
+ const currentContent = await fs6.readFile(filePath, "utf-8");
4673
4716
  if (originalContent) {
4674
4717
  const mergeResult = await templateManager.mergeFiles(
4675
4718
  originalContent,
4676
4719
  currentContent,
4677
4720
  newContent
4678
4721
  );
4679
- await fs5.writeFile(filePath, mergeResult.merged, "utf-8");
4722
+ await fs6.writeFile(filePath, mergeResult.merged, "utf-8");
4680
4723
  if (mergeResult.hasConflicts) {
4681
4724
  stats.conflicts++;
4682
4725
  InteractivePrompt.displayConflicts(mergeResult.conflicts);
@@ -4684,7 +4727,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4684
4727
  stats.merged++;
4685
4728
  }
4686
4729
  } else {
4687
- await fs5.writeFile(filePath, newContent, "utf-8");
4730
+ await fs6.writeFile(filePath, newContent, "utf-8");
4688
4731
  stats.overwritten++;
4689
4732
  }
4690
4733
  }
@@ -4789,14 +4832,14 @@ dbCommand.command("status").description("Show migration status").action(async ()
4789
4832
 
4790
4833
  // src/cli/commands/docs.ts
4791
4834
  var import_commander5 = require("commander");
4792
- var import_path7 = __toESM(require("path"), 1);
4793
- var import_promises4 = __toESM(require("fs/promises"), 1);
4835
+ var import_path8 = __toESM(require("path"), 1);
4836
+ var import_promises5 = __toESM(require("fs/promises"), 1);
4794
4837
  var import_ora6 = __toESM(require("ora"), 1);
4795
4838
  var import_chalk9 = __toESM(require("chalk"), 1);
4796
4839
 
4797
4840
  // src/cli/utils/docs-generator.ts
4798
- var import_promises3 = __toESM(require("fs/promises"), 1);
4799
- var import_path6 = __toESM(require("path"), 1);
4841
+ var import_promises4 = __toESM(require("fs/promises"), 1);
4842
+ var import_path7 = __toESM(require("path"), 1);
4800
4843
  var import_ora5 = __toESM(require("ora"), 1);
4801
4844
 
4802
4845
  // src/core/server.ts
@@ -5517,11 +5560,11 @@ function validateQuery(schema, data) {
5517
5560
  function formatZodErrors(error2) {
5518
5561
  const errors = {};
5519
5562
  for (const issue of error2.issues) {
5520
- const path12 = issue.path.join(".") || "root";
5521
- if (!errors[path12]) {
5522
- errors[path12] = [];
5563
+ const path15 = issue.path.join(".") || "root";
5564
+ if (!errors[path15]) {
5565
+ errors[path15] = [];
5523
5566
  }
5524
- errors[path12].push(issue.message);
5567
+ errors[path15].push(issue.message);
5525
5568
  }
5526
5569
  return errors;
5527
5570
  }
@@ -5758,7 +5801,18 @@ function getSkip(params) {
5758
5801
  }
5759
5802
 
5760
5803
  // src/modules/user/user.repository.ts
5761
- var import_client2 = require("@prisma/client");
5804
+ var UserRole = {
5805
+ USER: "USER",
5806
+ MODERATOR: "MODERATOR",
5807
+ ADMIN: "ADMIN",
5808
+ SUPER_ADMIN: "SUPER_ADMIN"
5809
+ };
5810
+ var UserStatus = {
5811
+ ACTIVE: "ACTIVE",
5812
+ INACTIVE: "INACTIVE",
5813
+ SUSPENDED: "SUSPENDED",
5814
+ BANNED: "BANNED"
5815
+ };
5762
5816
  var UserRepository = class {
5763
5817
  /**
5764
5818
  * Find user by ID
@@ -5808,7 +5862,7 @@ var UserRepository = class {
5808
5862
  password: data.password,
5809
5863
  name: data.name,
5810
5864
  role: this.mapRoleToEnum(data.role || "user"),
5811
- status: import_client2.UserStatus.ACTIVE,
5865
+ status: UserStatus.ACTIVE,
5812
5866
  emailVerified: false
5813
5867
  }
5814
5868
  });
@@ -5941,22 +5995,22 @@ var UserRepository = class {
5941
5995
  */
5942
5996
  mapRoleToEnum(role) {
5943
5997
  const roleMap = {
5944
- user: import_client2.UserRole.USER,
5945
- moderator: import_client2.UserRole.MODERATOR,
5946
- admin: import_client2.UserRole.ADMIN,
5947
- super_admin: import_client2.UserRole.SUPER_ADMIN
5998
+ user: UserRole.USER,
5999
+ moderator: UserRole.MODERATOR,
6000
+ admin: UserRole.ADMIN,
6001
+ super_admin: UserRole.SUPER_ADMIN
5948
6002
  };
5949
- return roleMap[role] || import_client2.UserRole.USER;
6003
+ return roleMap[role] || UserRole.USER;
5950
6004
  }
5951
6005
  /**
5952
6006
  * Map Prisma enum to application role
5953
6007
  */
5954
6008
  mapEnumToRole(role) {
5955
6009
  const roleMap = {
5956
- [import_client2.UserRole.USER]: "user",
5957
- [import_client2.UserRole.MODERATOR]: "moderator",
5958
- [import_client2.UserRole.ADMIN]: "admin",
5959
- [import_client2.UserRole.SUPER_ADMIN]: "super_admin"
6010
+ [UserRole.USER]: "user",
6011
+ [UserRole.MODERATOR]: "moderator",
6012
+ [UserRole.ADMIN]: "admin",
6013
+ [UserRole.SUPER_ADMIN]: "super_admin"
5960
6014
  };
5961
6015
  return roleMap[role];
5962
6016
  }
@@ -5965,22 +6019,22 @@ var UserRepository = class {
5965
6019
  */
5966
6020
  mapStatusToEnum(status) {
5967
6021
  const statusMap = {
5968
- active: import_client2.UserStatus.ACTIVE,
5969
- inactive: import_client2.UserStatus.INACTIVE,
5970
- suspended: import_client2.UserStatus.SUSPENDED,
5971
- banned: import_client2.UserStatus.BANNED
6022
+ active: UserStatus.ACTIVE,
6023
+ inactive: UserStatus.INACTIVE,
6024
+ suspended: UserStatus.SUSPENDED,
6025
+ banned: UserStatus.BANNED
5972
6026
  };
5973
- return statusMap[status] || import_client2.UserStatus.ACTIVE;
6027
+ return statusMap[status] || UserStatus.ACTIVE;
5974
6028
  }
5975
6029
  /**
5976
6030
  * Map Prisma enum to application status
5977
6031
  */
5978
6032
  mapEnumToStatus(status) {
5979
6033
  const statusMap = {
5980
- [import_client2.UserStatus.ACTIVE]: "active",
5981
- [import_client2.UserStatus.INACTIVE]: "inactive",
5982
- [import_client2.UserStatus.SUSPENDED]: "suspended",
5983
- [import_client2.UserStatus.BANNED]: "banned"
6034
+ [UserStatus.ACTIVE]: "active",
6035
+ [UserStatus.INACTIVE]: "inactive",
6036
+ [UserStatus.SUSPENDED]: "suspended",
6037
+ [UserStatus.BANNED]: "banned"
5984
6038
  };
5985
6039
  return statusMap[status];
5986
6040
  }
@@ -6354,9 +6408,9 @@ async function generateDocs(outputPath = "openapi.json", silent = false) {
6354
6408
  await registerUserModule(app, authService);
6355
6409
  await app.ready();
6356
6410
  const spec = app.swagger();
6357
- const absoluteOutput = import_path6.default.resolve(outputPath);
6358
- await import_promises3.default.mkdir(import_path6.default.dirname(absoluteOutput), { recursive: true });
6359
- await import_promises3.default.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
6411
+ const absoluteOutput = import_path7.default.resolve(outputPath);
6412
+ await import_promises4.default.mkdir(import_path7.default.dirname(absoluteOutput), { recursive: true });
6413
+ await import_promises4.default.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
6360
6414
  spinner?.succeed(`OpenAPI spec generated at ${absoluteOutput}`);
6361
6415
  await app.close();
6362
6416
  return absoluteOutput;
@@ -6372,10 +6426,10 @@ docsCommand.command("generate").alias("gen").description("Generate OpenAPI/Swagg
6372
6426
  try {
6373
6427
  const outputPath = await generateDocs(options.output, false);
6374
6428
  if (options.format === "yaml") {
6375
- const jsonContent = await import_promises4.default.readFile(outputPath, "utf-8");
6429
+ const jsonContent = await import_promises5.default.readFile(outputPath, "utf-8");
6376
6430
  const spec = JSON.parse(jsonContent);
6377
6431
  const yamlPath = outputPath.replace(".json", ".yaml");
6378
- await import_promises4.default.writeFile(yamlPath, jsonToYaml(spec));
6432
+ await import_promises5.default.writeFile(yamlPath, jsonToYaml(spec));
6379
6433
  success(`YAML documentation generated: ${yamlPath}`);
6380
6434
  }
6381
6435
  console.log("\n\u{1F4DA} Documentation URLs:");
@@ -6400,14 +6454,14 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
6400
6454
  const spinner = (0, import_ora6.default)("Exporting documentation...").start();
6401
6455
  try {
6402
6456
  const projectRoot = getProjectRoot();
6403
- const specPath = import_path7.default.join(projectRoot, "openapi.json");
6457
+ const specPath = import_path8.default.join(projectRoot, "openapi.json");
6404
6458
  try {
6405
- await import_promises4.default.access(specPath);
6459
+ await import_promises5.default.access(specPath);
6406
6460
  } catch {
6407
6461
  spinner.text = "Generating OpenAPI spec first...";
6408
6462
  await generateDocs("openapi.json", true);
6409
6463
  }
6410
- const specContent = await import_promises4.default.readFile(specPath, "utf-8");
6464
+ const specContent = await import_promises5.default.readFile(specPath, "utf-8");
6411
6465
  const spec = JSON.parse(specContent);
6412
6466
  let output;
6413
6467
  let defaultName;
@@ -6427,8 +6481,8 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
6427
6481
  default:
6428
6482
  throw new Error(`Unknown format: ${options.format}`);
6429
6483
  }
6430
- const outPath = import_path7.default.join(projectRoot, options.output || defaultName);
6431
- await import_promises4.default.writeFile(outPath, output);
6484
+ const outPath = import_path8.default.join(projectRoot, options.output || defaultName);
6485
+ await import_promises5.default.writeFile(outPath, output);
6432
6486
  spinner.succeed(`Exported to: ${options.output || defaultName}`);
6433
6487
  if (options.format === "postman") {
6434
6488
  info("\n Import in Postman: File > Import > Select file");
@@ -6441,13 +6495,13 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
6441
6495
  docsCommand.command("status").description("Show documentation status").action(async () => {
6442
6496
  const projectRoot = getProjectRoot();
6443
6497
  console.log(import_chalk9.default.bold("\n\u{1F4CA} Documentation Status\n"));
6444
- const specPath = import_path7.default.join(projectRoot, "openapi.json");
6498
+ const specPath = import_path8.default.join(projectRoot, "openapi.json");
6445
6499
  try {
6446
- const stat2 = await import_promises4.default.stat(specPath);
6500
+ const stat2 = await import_promises5.default.stat(specPath);
6447
6501
  success(
6448
6502
  `openapi.json exists (${formatBytes(stat2.size)}, modified ${formatDate(stat2.mtime)})`
6449
6503
  );
6450
- const content = await import_promises4.default.readFile(specPath, "utf-8");
6504
+ const content = await import_promises5.default.readFile(specPath, "utf-8");
6451
6505
  const spec = JSON.parse(content);
6452
6506
  const pathCount = Object.keys(spec.paths || {}).length;
6453
6507
  info(` ${pathCount} endpoints documented`);
@@ -6559,7 +6613,7 @@ function formatDate(date) {
6559
6613
  // src/cli/commands/list.ts
6560
6614
  var import_commander6 = require("commander");
6561
6615
  var import_chalk10 = __toESM(require("chalk"), 1);
6562
- var import_promises5 = __toESM(require("fs/promises"), 1);
6616
+ var import_promises6 = __toESM(require("fs/promises"), 1);
6563
6617
  var AVAILABLE_MODULES2 = {
6564
6618
  // Core
6565
6619
  auth: {
@@ -6674,7 +6728,7 @@ var AVAILABLE_MODULES2 = {
6674
6728
  async function getInstalledModules() {
6675
6729
  try {
6676
6730
  const modulesDir = getModulesDir();
6677
- const entries = await import_promises5.default.readdir(modulesDir, { withFileTypes: true });
6731
+ const entries = await import_promises6.default.readdir(modulesDir, { withFileTypes: true });
6678
6732
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
6679
6733
  } catch {
6680
6734
  return [];
@@ -6783,10 +6837,10 @@ var listCommand = new import_commander6.Command("list").alias("ls").description(
6783
6837
 
6784
6838
  // src/cli/commands/remove.ts
6785
6839
  var import_commander7 = require("commander");
6786
- var import_path8 = __toESM(require("path"), 1);
6840
+ var import_path9 = __toESM(require("path"), 1);
6787
6841
  var import_ora7 = __toESM(require("ora"), 1);
6788
6842
  var import_chalk11 = __toESM(require("chalk"), 1);
6789
- var import_promises6 = __toESM(require("fs/promises"), 1);
6843
+ var import_promises7 = __toESM(require("fs/promises"), 1);
6790
6844
  var import_inquirer4 = __toESM(require("inquirer"), 1);
6791
6845
  var removeCommand = new import_commander7.Command("remove").alias("rm").description("Remove an installed module from your project").argument("<module>", "Module to remove").option("-y, --yes", "Skip confirmation prompt").option("--keep-env", "Keep environment variables").action(async (moduleName, options) => {
6792
6846
  const projectError = validateProject();
@@ -6795,9 +6849,9 @@ var removeCommand = new import_commander7.Command("remove").alias("rm").descript
6795
6849
  return;
6796
6850
  }
6797
6851
  console.log(import_chalk11.default.bold.cyan("\n\u{1F5D1}\uFE0F ServCraft Module Removal\n"));
6798
- const moduleDir = import_path8.default.join(getModulesDir(), moduleName);
6852
+ const moduleDir = import_path9.default.join(getModulesDir(), moduleName);
6799
6853
  try {
6800
- const exists = await import_promises6.default.access(moduleDir).then(() => true).catch(() => false);
6854
+ const exists = await import_promises7.default.access(moduleDir).then(() => true).catch(() => false);
6801
6855
  if (!exists) {
6802
6856
  displayError(
6803
6857
  new ServCraftError(`Module "${moduleName}" is not installed`, [
@@ -6807,7 +6861,7 @@ var removeCommand = new import_commander7.Command("remove").alias("rm").descript
6807
6861
  );
6808
6862
  return;
6809
6863
  }
6810
- const files = await import_promises6.default.readdir(moduleDir);
6864
+ const files = await import_promises7.default.readdir(moduleDir);
6811
6865
  const fileCount = files.length;
6812
6866
  if (!options?.yes) {
6813
6867
  console.log(import_chalk11.default.yellow(`\u26A0 This will remove the "${moduleName}" module:`));
@@ -6828,7 +6882,7 @@ var removeCommand = new import_commander7.Command("remove").alias("rm").descript
6828
6882
  }
6829
6883
  }
6830
6884
  const spinner = (0, import_ora7.default)("Removing module...").start();
6831
- await import_promises6.default.rm(moduleDir, { recursive: true, force: true });
6885
+ await import_promises7.default.rm(moduleDir, { recursive: true, force: true });
6832
6886
  spinner.succeed(`Module "${moduleName}" removed successfully!`);
6833
6887
  console.log("\n" + import_chalk11.default.bold("\u2713 Removed:"));
6834
6888
  success(` src/modules/${moduleName}/ (${fileCount} files)`);
@@ -6854,7 +6908,7 @@ var removeCommand = new import_commander7.Command("remove").alias("rm").descript
6854
6908
  // src/cli/commands/doctor.ts
6855
6909
  var import_commander8 = require("commander");
6856
6910
  var import_chalk12 = __toESM(require("chalk"), 1);
6857
- var import_promises7 = __toESM(require("fs/promises"), 1);
6911
+ var import_promises8 = __toESM(require("fs/promises"), 1);
6858
6912
  async function checkNodeVersion() {
6859
6913
  const version = process.version;
6860
6914
  const major = parseInt(version.slice(1).split(".")[0] || "0", 10);
@@ -6871,7 +6925,7 @@ async function checkNodeVersion() {
6871
6925
  async function checkPackageJson() {
6872
6926
  const checks = [];
6873
6927
  try {
6874
- const content = await import_promises7.default.readFile("package.json", "utf-8");
6928
+ const content = await import_promises8.default.readFile("package.json", "utf-8");
6875
6929
  const pkg = JSON.parse(content);
6876
6930
  checks.push({ name: "package.json", status: "pass", message: "Found" });
6877
6931
  if (pkg.dependencies?.fastify) {
@@ -6899,7 +6953,7 @@ async function checkDirectories() {
6899
6953
  const dirs = ["src", "node_modules", ".git", ".env"];
6900
6954
  for (const dir of dirs) {
6901
6955
  try {
6902
- await import_promises7.default.access(dir);
6956
+ await import_promises8.default.access(dir);
6903
6957
  checks.push({ name: dir, status: "pass", message: "Exists" });
6904
6958
  } catch {
6905
6959
  const isCritical = dir === "src" || dir === "node_modules";
@@ -6948,12 +7002,12 @@ ${import_chalk12.default.green(pass + " passed")} | ${import_chalk12.default.yel
6948
7002
  // src/cli/commands/update.ts
6949
7003
  var import_commander9 = require("commander");
6950
7004
  var import_chalk13 = __toESM(require("chalk"), 1);
6951
- var import_promises8 = __toESM(require("fs/promises"), 1);
6952
- var import_path9 = __toESM(require("path"), 1);
7005
+ var import_promises9 = __toESM(require("fs/promises"), 1);
7006
+ var import_path10 = __toESM(require("path"), 1);
6953
7007
  var import_url = require("url");
6954
7008
  var import_inquirer5 = __toESM(require("inquirer"), 1);
6955
7009
  var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
6956
- var __dirname = import_path9.default.dirname(__filename2);
7010
+ var __dirname = import_path10.default.dirname(__filename2);
6957
7011
  var AVAILABLE_MODULES3 = [
6958
7012
  "auth",
6959
7013
  "users",
@@ -6981,7 +7035,7 @@ var AVAILABLE_MODULES3 = [
6981
7035
  async function getInstalledModules2() {
6982
7036
  try {
6983
7037
  const modulesDir = getModulesDir();
6984
- const entries = await import_promises8.default.readdir(modulesDir, { withFileTypes: true });
7038
+ const entries = await import_promises9.default.readdir(modulesDir, { withFileTypes: true });
6985
7039
  const installedModules = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => AVAILABLE_MODULES3.includes(name));
6986
7040
  return installedModules;
6987
7041
  } catch {
@@ -6989,16 +7043,16 @@ async function getInstalledModules2() {
6989
7043
  }
6990
7044
  }
6991
7045
  async function copyModuleFiles(moduleName, _projectRoot) {
6992
- const cliRoot = import_path9.default.resolve(__dirname, "../../../");
6993
- const sourceModulePath = import_path9.default.join(cliRoot, "src", "modules", moduleName);
7046
+ const cliRoot = import_path10.default.resolve(__dirname, "../../../");
7047
+ const sourceModulePath = import_path10.default.join(cliRoot, "src", "modules", moduleName);
6994
7048
  const targetModulesDir = getModulesDir();
6995
- const targetModulePath = import_path9.default.join(targetModulesDir, moduleName);
7049
+ const targetModulePath = import_path10.default.join(targetModulesDir, moduleName);
6996
7050
  try {
6997
- await import_promises8.default.access(sourceModulePath);
7051
+ await import_promises9.default.access(sourceModulePath);
6998
7052
  } catch {
6999
7053
  throw new Error(`Module source not found: ${moduleName}`);
7000
7054
  }
7001
- await import_promises8.default.cp(sourceModulePath, targetModulePath, { recursive: true });
7055
+ await import_promises9.default.cp(sourceModulePath, targetModulePath, { recursive: true });
7002
7056
  }
7003
7057
  async function updateModule(moduleName, options) {
7004
7058
  const projectError = validateProject();
@@ -7274,8 +7328,275 @@ var completionCommand = new import_commander10.Command("completion").description
7274
7328
  }
7275
7329
  });
7276
7330
 
7331
+ // src/cli/commands/scaffold.ts
7332
+ var import_commander11 = require("commander");
7333
+ var import_path11 = __toESM(require("path"), 1);
7334
+ var import_ora8 = __toESM(require("ora"), 1);
7335
+ var import_chalk14 = __toESM(require("chalk"), 1);
7336
+ var scaffoldCommand = new import_commander11.Command("scaffold").description("Generate complete CRUD with Prisma model").argument("<name>", "Resource name (e.g., product, user)").option(
7337
+ "--fields <fields>",
7338
+ 'Field definitions: "name:string email:string? age:number category:relation"'
7339
+ ).option("--validator <type>", "Validator type: zod, joi, yup", "zod").option("--dry-run", "Preview changes without writing files").action(
7340
+ async (name, options) => {
7341
+ const dryRun = DryRunManager.getInstance();
7342
+ if (options.dryRun) {
7343
+ dryRun.enable();
7344
+ console.log(import_chalk14.default.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
7345
+ }
7346
+ if (!options.fields) {
7347
+ console.log(import_chalk14.default.red("\n\u2717 Error: --fields option is required\n"));
7348
+ console.log(import_chalk14.default.gray("Example:"));
7349
+ console.log(
7350
+ import_chalk14.default.cyan(
7351
+ ' servcraft scaffold product --fields "name:string price:number category:relation"'
7352
+ )
7353
+ );
7354
+ process.exit(1);
7355
+ }
7356
+ const spinner = (0, import_ora8.default)("Scaffolding resource...").start();
7357
+ try {
7358
+ const fields = parseFields(options.fields || "");
7359
+ if (!fields || fields.length === 0) {
7360
+ spinner.fail("No valid fields provided");
7361
+ console.log(import_chalk14.default.gray(`
7362
+ Received: ${options.fields}`));
7363
+ console.log(import_chalk14.default.gray(`Parsed: ${JSON.stringify(fields)}`));
7364
+ process.exit(1);
7365
+ }
7366
+ const kebabName = toKebabCase(name);
7367
+ const pascalName = toPascalCase(name);
7368
+ const camelName = toCamelCase(name);
7369
+ const pluralName = pluralize(camelName);
7370
+ const tableName = pluralize(kebabName);
7371
+ const modulesDir = getModulesDir();
7372
+ const moduleDir = import_path11.default.join(modulesDir, kebabName);
7373
+ const controllerTpl = await getTemplate("controller", controllerTemplate);
7374
+ const serviceTpl = await getTemplate("service", serviceTemplate);
7375
+ const repositoryTpl = await getTemplate("repository", repositoryTemplate);
7376
+ const routesTpl = await getTemplate("routes", routesTemplate);
7377
+ const moduleIndexTpl = await getTemplate("module-index", moduleIndexTemplate);
7378
+ const files = [
7379
+ {
7380
+ name: `${kebabName}.types.ts`,
7381
+ content: dynamicTypesTemplate(kebabName, pascalName, fields)
7382
+ },
7383
+ {
7384
+ name: `${kebabName}.schemas.ts`,
7385
+ content: dynamicSchemasTemplate(
7386
+ kebabName,
7387
+ pascalName,
7388
+ camelName,
7389
+ fields,
7390
+ options.validator || "zod"
7391
+ )
7392
+ },
7393
+ {
7394
+ name: `${kebabName}.service.ts`,
7395
+ content: serviceTpl(kebabName, pascalName, camelName)
7396
+ },
7397
+ {
7398
+ name: `${kebabName}.controller.ts`,
7399
+ content: controllerTpl(kebabName, pascalName, camelName)
7400
+ },
7401
+ {
7402
+ name: "index.ts",
7403
+ content: moduleIndexTpl(kebabName, pascalName, camelName)
7404
+ },
7405
+ {
7406
+ name: `${kebabName}.repository.ts`,
7407
+ content: repositoryTpl(kebabName, pascalName, camelName, pluralName)
7408
+ },
7409
+ {
7410
+ name: `${kebabName}.routes.ts`,
7411
+ content: routesTpl(kebabName, pascalName, camelName, pluralName)
7412
+ }
7413
+ ];
7414
+ for (const file of files) {
7415
+ await writeFile(import_path11.default.join(moduleDir, file.name), file.content);
7416
+ }
7417
+ const testDir = import_path11.default.join(moduleDir, "__tests__");
7418
+ const controllerTestTpl = await getTemplate("controller-test", controllerTestTemplate);
7419
+ const serviceTestTpl = await getTemplate("service-test", serviceTestTemplate);
7420
+ const integrationTestTpl = await getTemplate("integration-test", integrationTestTemplate);
7421
+ await writeFile(
7422
+ import_path11.default.join(testDir, `${kebabName}.controller.test.ts`),
7423
+ controllerTestTpl(kebabName, pascalName, camelName)
7424
+ );
7425
+ await writeFile(
7426
+ import_path11.default.join(testDir, `${kebabName}.service.test.ts`),
7427
+ serviceTestTpl(kebabName, pascalName, camelName)
7428
+ );
7429
+ await writeFile(
7430
+ import_path11.default.join(testDir, `${kebabName}.integration.test.ts`),
7431
+ integrationTestTpl(kebabName, pascalName, camelName)
7432
+ );
7433
+ spinner.succeed(`Resource "${pascalName}" scaffolded successfully!`);
7434
+ console.log("\n" + "\u2500".repeat(70));
7435
+ info("\u{1F4CB} Prisma model to add to schema.prisma:");
7436
+ console.log(import_chalk14.default.gray("\n// Copy this to your schema.prisma file:\n"));
7437
+ console.log(dynamicPrismaTemplate(pascalName, tableName, fields));
7438
+ console.log("\u2500".repeat(70));
7439
+ console.log("\n\u{1F4CB} Fields scaffolded:");
7440
+ fields.forEach((f) => {
7441
+ const opts = [];
7442
+ if (f.isOptional) opts.push("optional");
7443
+ if (f.isArray) opts.push("array");
7444
+ if (f.isUnique) opts.push("unique");
7445
+ if (f.relation) opts.push(`relation: ${f.relation.model}`);
7446
+ const optsStr = opts.length > 0 ? ` (${opts.join(", ")})` : "";
7447
+ success(` ${f.name}: ${f.type}${optsStr}`);
7448
+ });
7449
+ console.log("\n\u{1F4C1} Files created:");
7450
+ files.forEach((f) => success(` src/modules/${kebabName}/${f.name}`));
7451
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.controller.test.ts`);
7452
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.service.test.ts`);
7453
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.integration.test.ts`);
7454
+ console.log("\n\u{1F4CC} Next steps:");
7455
+ info(" 1. Add the Prisma model to your schema.prisma file");
7456
+ info(" 2. Run: npx prisma db push (or prisma migrate dev)");
7457
+ info(" 3. Run: npx prisma generate");
7458
+ info(" 4. Register the module routes in your app");
7459
+ info(" 5. Update the test files with actual test data");
7460
+ console.log(
7461
+ import_chalk14.default.gray("\n\u{1F4A1} Tip: Use --dry-run to preview changes before applying them\n")
7462
+ );
7463
+ if (options.dryRun) {
7464
+ dryRun.printSummary();
7465
+ }
7466
+ } catch (error2) {
7467
+ spinner.fail("Failed to scaffold resource");
7468
+ if (error2 instanceof Error) {
7469
+ console.error(import_chalk14.default.red(`
7470
+ \u2717 ${error2.message}
7471
+ `));
7472
+ console.error(import_chalk14.default.gray(error2.stack));
7473
+ }
7474
+ process.exit(1);
7475
+ }
7476
+ }
7477
+ );
7478
+
7479
+ // src/cli/commands/templates.ts
7480
+ var import_commander12 = require("commander");
7481
+ var import_chalk15 = __toESM(require("chalk"), 1);
7482
+ var import_promises10 = __toESM(require("fs/promises"), 1);
7483
+ var import_path12 = __toESM(require("path"), 1);
7484
+ var TEMPLATE_TYPES = [
7485
+ "controller",
7486
+ "service",
7487
+ "repository",
7488
+ "types",
7489
+ "schemas",
7490
+ "routes",
7491
+ "module-index",
7492
+ "controller-test",
7493
+ "service-test",
7494
+ "integration-test"
7495
+ ];
7496
+ async function initTemplates() {
7497
+ const projectError = validateProject();
7498
+ if (projectError) {
7499
+ displayError(projectError);
7500
+ return;
7501
+ }
7502
+ const projectRoot = getProjectRoot();
7503
+ const templatesDir = import_path12.default.join(projectRoot, ".servcraft", "templates");
7504
+ try {
7505
+ await import_promises10.default.mkdir(templatesDir, { recursive: true });
7506
+ console.log(import_chalk15.default.cyan("\n\u{1F4C1} Creating custom template directory...\n"));
7507
+ const exampleController = `// Custom controller template
7508
+ // Available variables: name, pascalName, camelName, pluralName
7509
+ export function controllerTemplate(name: string, pascalName: string, camelName: string): string {
7510
+ return \`import type { FastifyRequest, FastifyReply } from 'fastify';
7511
+ import type { \${pascalName}Service } from './\${name}.service.js';
7512
+
7513
+ export class \${pascalName}Controller {
7514
+ constructor(private \${camelName}Service: \${pascalName}Service) {}
7515
+
7516
+ // Add your custom controller methods here
7517
+ async getAll(request: FastifyRequest, reply: FastifyReply) {
7518
+ const data = await this.\${camelName}Service.getAll();
7519
+ return reply.send({ data });
7520
+ }
7521
+ }
7522
+ \`;
7523
+ }
7524
+ `;
7525
+ await import_promises10.default.writeFile(
7526
+ import_path12.default.join(templatesDir, "controller.example.ts"),
7527
+ exampleController,
7528
+ "utf-8"
7529
+ );
7530
+ console.log(import_chalk15.default.green("\u2714 Created template directory: .servcraft/templates/"));
7531
+ console.log(import_chalk15.default.green("\u2714 Created example template: controller.example.ts\n"));
7532
+ console.log(import_chalk15.default.bold("\u{1F4CB} Available template types:\n"));
7533
+ TEMPLATE_TYPES.forEach((type) => {
7534
+ console.log(import_chalk15.default.gray(` \u2022 ${type}.ts`));
7535
+ });
7536
+ console.log(import_chalk15.default.yellow("\n\u{1F4A1} To customize a template:"));
7537
+ console.log(import_chalk15.default.gray(" 1. Copy the example template"));
7538
+ console.log(import_chalk15.default.gray(" 2. Rename it (remove .example)"));
7539
+ console.log(import_chalk15.default.gray(" 3. Modify the template code"));
7540
+ console.log(import_chalk15.default.gray(" 4. Use --template flag when generating\n"));
7541
+ } catch (error2) {
7542
+ if (error2 instanceof Error) {
7543
+ console.error(import_chalk15.default.red(`
7544
+ \u2717 Failed to initialize templates: ${error2.message}
7545
+ `));
7546
+ }
7547
+ }
7548
+ }
7549
+ async function listTemplates() {
7550
+ const projectError = validateProject();
7551
+ if (projectError) {
7552
+ displayError(projectError);
7553
+ return;
7554
+ }
7555
+ const projectRoot = getProjectRoot();
7556
+ const projectTemplatesDir = import_path12.default.join(projectRoot, ".servcraft", "templates");
7557
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
7558
+ const userTemplatesDir = import_path12.default.join(homeDir, ".servcraft", "templates");
7559
+ console.log(import_chalk15.default.bold.cyan("\n\u{1F4CB} Available Templates\n"));
7560
+ console.log(import_chalk15.default.bold("Project templates (.servcraft/templates/):"));
7561
+ try {
7562
+ const files = await import_promises10.default.readdir(projectTemplatesDir);
7563
+ const templates = files.filter((f) => f.endsWith(".ts") && !f.endsWith(".example.ts"));
7564
+ if (templates.length > 0) {
7565
+ templates.forEach((t) => {
7566
+ console.log(import_chalk15.default.green(` \u2713 ${t}`));
7567
+ });
7568
+ } else {
7569
+ console.log(import_chalk15.default.gray(" (none)"));
7570
+ }
7571
+ } catch {
7572
+ console.log(import_chalk15.default.gray(" (directory not found)"));
7573
+ }
7574
+ console.log(import_chalk15.default.bold("\nUser templates (~/.servcraft/templates/):"));
7575
+ try {
7576
+ const files = await import_promises10.default.readdir(userTemplatesDir);
7577
+ const templates = files.filter((f) => f.endsWith(".ts") && !f.endsWith(".example.ts"));
7578
+ if (templates.length > 0) {
7579
+ templates.forEach((t) => {
7580
+ console.log(import_chalk15.default.green(` \u2713 ${t}`));
7581
+ });
7582
+ } else {
7583
+ console.log(import_chalk15.default.gray(" (none)"));
7584
+ }
7585
+ } catch {
7586
+ console.log(import_chalk15.default.gray(" (directory not found)"));
7587
+ }
7588
+ console.log(import_chalk15.default.bold("\nBuilt-in templates:"));
7589
+ TEMPLATE_TYPES.forEach((t) => {
7590
+ console.log(import_chalk15.default.cyan(` \u2022 ${t}.ts`));
7591
+ });
7592
+ console.log(import_chalk15.default.gray('\n\u{1F4A1} Run "servcraft templates init" to create custom templates\n'));
7593
+ }
7594
+ var templatesCommand = new import_commander12.Command("templates").description("Manage code generation templates").addCommand(
7595
+ new import_commander12.Command("init").description("Initialize custom templates directory").action(initTemplates)
7596
+ ).addCommand(new import_commander12.Command("list").description("List available templates").action(listTemplates));
7597
+
7277
7598
  // src/cli/index.ts
7278
- var program = new import_commander11.Command();
7599
+ var program = new import_commander13.Command();
7279
7600
  program.name("servcraft").description("Servcraft - A modular Node.js backend framework CLI").version("0.1.0");
7280
7601
  program.addCommand(initCommand);
7281
7602
  program.addCommand(generateCommand);
@@ -7287,5 +7608,7 @@ program.addCommand(removeCommand);
7287
7608
  program.addCommand(doctorCommand);
7288
7609
  program.addCommand(updateCommand);
7289
7610
  program.addCommand(completionCommand);
7611
+ program.addCommand(scaffoldCommand);
7612
+ program.addCommand(templatesCommand);
7290
7613
  program.parse();
7291
7614
  //# sourceMappingURL=index.cjs.map