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.
package/dist/cli/index.js CHANGED
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  });
8
8
 
9
9
  // src/cli/index.ts
10
- import { Command as Command11 } from "commander";
10
+ import { Command as Command13 } from "commander";
11
11
 
12
12
  // src/cli/commands/init.ts
13
13
  import { Command } from "commander";
@@ -1205,7 +1205,7 @@ declare module 'fastify' {
1205
1205
 
1206
1206
  // src/cli/commands/generate.ts
1207
1207
  import { Command as Command2 } from "commander";
1208
- import path4 from "path";
1208
+ import path5 from "path";
1209
1209
  import ora2 from "ora";
1210
1210
  import inquirer2 from "inquirer";
1211
1211
  import chalk5 from "chalk";
@@ -1434,11 +1434,11 @@ function displayError(error2) {
1434
1434
  }
1435
1435
  function validateProject() {
1436
1436
  try {
1437
- const fs12 = __require("fs");
1438
- if (!fs12.existsSync("package.json")) {
1437
+ const fs14 = __require("fs");
1438
+ if (!fs14.existsSync("package.json")) {
1439
1439
  return ErrorTypes.NOT_IN_PROJECT();
1440
1440
  }
1441
- const packageJson = JSON.parse(fs12.readFileSync("package.json", "utf-8"));
1441
+ const packageJson = JSON.parse(fs14.readFileSync("package.json", "utf-8"));
1442
1442
  if (!packageJson.dependencies?.fastify) {
1443
1443
  return new ServCraftError("This does not appear to be a ServCraft project", [
1444
1444
  `ServCraft projects require Fastify`,
@@ -2489,6 +2489,39 @@ describe('${pascalName} Integration Tests', () => {
2489
2489
  `;
2490
2490
  }
2491
2491
 
2492
+ // src/cli/utils/template-loader.ts
2493
+ import fs3 from "fs/promises";
2494
+ import path4 from "path";
2495
+ async function loadCustomTemplate(templateType) {
2496
+ const projectRoot = getProjectRoot();
2497
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2498
+ const locations = [
2499
+ path4.join(projectRoot, ".servcraft", "templates", `${templateType}.ts`),
2500
+ path4.join(homeDir, ".servcraft", "templates", `${templateType}.ts`)
2501
+ ];
2502
+ for (const location of locations) {
2503
+ try {
2504
+ await fs3.access(location);
2505
+ const templateModule = await import(`file://${location}`);
2506
+ const functionName = `${templateType.replace(/-/g, "")}Template`;
2507
+ if (templateModule[functionName]) {
2508
+ return templateModule;
2509
+ }
2510
+ } catch {
2511
+ continue;
2512
+ }
2513
+ }
2514
+ return null;
2515
+ }
2516
+ async function getTemplate(templateType, builtInTemplate) {
2517
+ const customTemplate = await loadCustomTemplate(templateType);
2518
+ if (customTemplate) {
2519
+ const functionName = `${templateType.replace(/-/g, "")}Template`;
2520
+ return customTemplate[functionName];
2521
+ }
2522
+ return builtInTemplate;
2523
+ }
2524
+
2492
2525
  // src/cli/commands/generate.ts
2493
2526
  function enableDryRunIfNeeded(options) {
2494
2527
  const dryRun = DryRunManager.getInstance();
@@ -2521,60 +2554,70 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
2521
2554
  const pluralName = pluralize(kebabName);
2522
2555
  const tableName = pluralize(kebabName.replace(/-/g, "_"));
2523
2556
  const validatorType = options.validator || "zod";
2524
- const moduleDir = path4.join(getModulesDir(), kebabName);
2557
+ const moduleDir = path5.join(getModulesDir(), kebabName);
2525
2558
  if (await fileExists(moduleDir)) {
2526
2559
  spinner.stop();
2527
2560
  error(`Module "${kebabName}" already exists`);
2528
2561
  return;
2529
2562
  }
2530
2563
  const hasFields = fields.length > 0;
2564
+ const controllerTpl = await getTemplate("controller", controllerTemplate);
2565
+ const serviceTpl = await getTemplate("service", serviceTemplate);
2566
+ const repositoryTpl = await getTemplate("repository", repositoryTemplate);
2567
+ const typesTpl = await getTemplate("types", typesTemplate);
2568
+ const schemasTpl = await getTemplate("schemas", schemasTemplate);
2569
+ const routesTpl = await getTemplate("routes", routesTemplate);
2570
+ const moduleIndexTpl = await getTemplate("module-index", moduleIndexTemplate);
2531
2571
  const files = [
2532
2572
  {
2533
2573
  name: `${kebabName}.types.ts`,
2534
- content: hasFields ? dynamicTypesTemplate(kebabName, pascalName, fields) : typesTemplate(kebabName, pascalName)
2574
+ content: hasFields ? dynamicTypesTemplate(kebabName, pascalName, fields) : typesTpl(kebabName, pascalName)
2535
2575
  },
2536
2576
  {
2537
2577
  name: `${kebabName}.schemas.ts`,
2538
- content: hasFields ? dynamicSchemasTemplate(kebabName, pascalName, camelName, fields, validatorType) : schemasTemplate(kebabName, pascalName, camelName)
2578
+ content: hasFields ? dynamicSchemasTemplate(kebabName, pascalName, camelName, fields, validatorType) : schemasTpl(kebabName, pascalName, camelName)
2539
2579
  },
2540
2580
  {
2541
2581
  name: `${kebabName}.service.ts`,
2542
- content: serviceTemplate(kebabName, pascalName, camelName)
2582
+ content: serviceTpl(kebabName, pascalName, camelName)
2543
2583
  },
2544
2584
  {
2545
2585
  name: `${kebabName}.controller.ts`,
2546
- content: controllerTemplate(kebabName, pascalName, camelName)
2586
+ content: controllerTpl(kebabName, pascalName, camelName)
2547
2587
  },
2548
- { name: "index.ts", content: moduleIndexTemplate(kebabName, pascalName, camelName) }
2588
+ { name: "index.ts", content: moduleIndexTpl(kebabName, pascalName, camelName) }
2549
2589
  ];
2550
2590
  if (options.repository !== false) {
2551
2591
  files.push({
2552
2592
  name: `${kebabName}.repository.ts`,
2553
- content: repositoryTemplate(kebabName, pascalName, camelName, pluralName)
2593
+ content: repositoryTpl(kebabName, pascalName, camelName, pluralName)
2554
2594
  });
2555
2595
  }
2556
2596
  if (options.routes !== false) {
2557
2597
  files.push({
2558
2598
  name: `${kebabName}.routes.ts`,
2559
- content: routesTemplate(kebabName, pascalName, camelName, pluralName)
2599
+ content: routesTpl(kebabName, pascalName, camelName, pluralName)
2560
2600
  });
2561
2601
  }
2562
2602
  for (const file of files) {
2563
- await writeFile(path4.join(moduleDir, file.name), file.content);
2603
+ await writeFile(path5.join(moduleDir, file.name), file.content);
2564
2604
  }
2565
2605
  if (options.withTests) {
2566
- const testDir = path4.join(moduleDir, "__tests__");
2606
+ const testDir = path5.join(moduleDir, "__tests__");
2607
+ const controllerTestTpl = await getTemplate("controller-test", controllerTestTemplate);
2608
+ const serviceTestTpl = await getTemplate("service-test", serviceTestTemplate);
2609
+ const integrationTestTpl = await getTemplate("integration-test", integrationTestTemplate);
2567
2610
  await writeFile(
2568
- path4.join(testDir, `${kebabName}.controller.test.ts`),
2569
- controllerTestTemplate(kebabName, pascalName, camelName)
2611
+ path5.join(testDir, `${kebabName}.controller.test.ts`),
2612
+ controllerTestTpl(kebabName, pascalName, camelName)
2570
2613
  );
2571
2614
  await writeFile(
2572
- path4.join(testDir, `${kebabName}.service.test.ts`),
2573
- serviceTestTemplate(kebabName, pascalName, camelName)
2615
+ path5.join(testDir, `${kebabName}.service.test.ts`),
2616
+ serviceTestTpl(kebabName, pascalName, camelName)
2574
2617
  );
2575
2618
  await writeFile(
2576
- path4.join(testDir, `${kebabName}.integration.test.ts`),
2577
- integrationTestTemplate(kebabName, pascalName, camelName)
2619
+ path5.join(testDir, `${kebabName}.integration.test.ts`),
2620
+ integrationTestTpl(kebabName, pascalName, camelName)
2578
2621
  );
2579
2622
  }
2580
2623
  spinner.succeed(`Module "${pascalName}" generated successfully!`);
@@ -2632,8 +2675,8 @@ generateCommand.command("controller <name>").alias("c").description("Generate a
2632
2675
  const pascalName = toPascalCase(name);
2633
2676
  const camelName = toCamelCase(name);
2634
2677
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2635
- const moduleDir = path4.join(getModulesDir(), moduleName);
2636
- const filePath = path4.join(moduleDir, `${kebabName}.controller.ts`);
2678
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2679
+ const filePath = path5.join(moduleDir, `${kebabName}.controller.ts`);
2637
2680
  if (await fileExists(filePath)) {
2638
2681
  spinner.stop();
2639
2682
  displayError(ErrorTypes.FILE_ALREADY_EXISTS(`${kebabName}.controller.ts`));
@@ -2656,8 +2699,8 @@ generateCommand.command("service <name>").alias("s").description("Generate a ser
2656
2699
  const pascalName = toPascalCase(name);
2657
2700
  const camelName = toCamelCase(name);
2658
2701
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2659
- const moduleDir = path4.join(getModulesDir(), moduleName);
2660
- const filePath = path4.join(moduleDir, `${kebabName}.service.ts`);
2702
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2703
+ const filePath = path5.join(moduleDir, `${kebabName}.service.ts`);
2661
2704
  if (await fileExists(filePath)) {
2662
2705
  spinner.stop();
2663
2706
  error(`Service "${kebabName}" already exists`);
@@ -2681,8 +2724,8 @@ generateCommand.command("repository <name>").alias("r").description("Generate a
2681
2724
  const camelName = toCamelCase(name);
2682
2725
  const pluralName = pluralize(kebabName);
2683
2726
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2684
- const moduleDir = path4.join(getModulesDir(), moduleName);
2685
- const filePath = path4.join(moduleDir, `${kebabName}.repository.ts`);
2727
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2728
+ const filePath = path5.join(moduleDir, `${kebabName}.repository.ts`);
2686
2729
  if (await fileExists(filePath)) {
2687
2730
  spinner.stop();
2688
2731
  error(`Repository "${kebabName}" already exists`);
@@ -2704,8 +2747,8 @@ generateCommand.command("types <name>").alias("t").description("Generate types/i
2704
2747
  const kebabName = toKebabCase(name);
2705
2748
  const pascalName = toPascalCase(name);
2706
2749
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2707
- const moduleDir = path4.join(getModulesDir(), moduleName);
2708
- const filePath = path4.join(moduleDir, `${kebabName}.types.ts`);
2750
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2751
+ const filePath = path5.join(moduleDir, `${kebabName}.types.ts`);
2709
2752
  if (await fileExists(filePath)) {
2710
2753
  spinner.stop();
2711
2754
  error(`Types file "${kebabName}.types.ts" already exists`);
@@ -2728,8 +2771,8 @@ generateCommand.command("schema <name>").alias("v").description("Generate valida
2728
2771
  const pascalName = toPascalCase(name);
2729
2772
  const camelName = toCamelCase(name);
2730
2773
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2731
- const moduleDir = path4.join(getModulesDir(), moduleName);
2732
- const filePath = path4.join(moduleDir, `${kebabName}.schemas.ts`);
2774
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2775
+ const filePath = path5.join(moduleDir, `${kebabName}.schemas.ts`);
2733
2776
  if (await fileExists(filePath)) {
2734
2777
  spinner.stop();
2735
2778
  error(`Schemas file "${kebabName}.schemas.ts" already exists`);
@@ -2753,8 +2796,8 @@ generateCommand.command("routes <name>").description("Generate routes").option("
2753
2796
  const camelName = toCamelCase(name);
2754
2797
  const pluralName = pluralize(kebabName);
2755
2798
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2756
- const moduleDir = path4.join(getModulesDir(), moduleName);
2757
- const filePath = path4.join(moduleDir, `${kebabName}.routes.ts`);
2799
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2800
+ const filePath = path5.join(moduleDir, `${kebabName}.routes.ts`);
2758
2801
  if (await fileExists(filePath)) {
2759
2802
  spinner.stop();
2760
2803
  error(`Routes file "${kebabName}.routes.ts" already exists`);
@@ -2842,21 +2885,21 @@ async function promptForFields() {
2842
2885
 
2843
2886
  // src/cli/commands/add-module.ts
2844
2887
  import { Command as Command3 } from "commander";
2845
- import path7 from "path";
2888
+ import path8 from "path";
2846
2889
  import ora3 from "ora";
2847
2890
  import chalk7 from "chalk";
2848
- import * as fs5 from "fs/promises";
2891
+ import * as fs6 from "fs/promises";
2849
2892
 
2850
2893
  // src/cli/utils/env-manager.ts
2851
- import * as fs3 from "fs/promises";
2852
- import * as path5 from "path";
2894
+ import * as fs4 from "fs/promises";
2895
+ import * as path6 from "path";
2853
2896
  import { existsSync } from "fs";
2854
2897
  var EnvManager = class {
2855
2898
  envPath;
2856
2899
  envExamplePath;
2857
2900
  constructor(projectRoot) {
2858
- this.envPath = path5.join(projectRoot, ".env");
2859
- this.envExamplePath = path5.join(projectRoot, ".env.example");
2901
+ this.envPath = path6.join(projectRoot, ".env");
2902
+ this.envExamplePath = path6.join(projectRoot, ".env.example");
2860
2903
  }
2861
2904
  /**
2862
2905
  * Add environment variables to .env file
@@ -2867,7 +2910,7 @@ var EnvManager = class {
2867
2910
  let created2 = false;
2868
2911
  let envContent = "";
2869
2912
  if (existsSync(this.envPath)) {
2870
- envContent = await fs3.readFile(this.envPath, "utf-8");
2913
+ envContent = await fs4.readFile(this.envPath, "utf-8");
2871
2914
  } else {
2872
2915
  created2 = true;
2873
2916
  }
@@ -2896,7 +2939,7 @@ var EnvManager = class {
2896
2939
  }
2897
2940
  newContent += "\n";
2898
2941
  }
2899
- await fs3.writeFile(this.envPath, newContent, "utf-8");
2942
+ await fs4.writeFile(this.envPath, newContent, "utf-8");
2900
2943
  if (existsSync(this.envExamplePath)) {
2901
2944
  await this.updateEnvExample(sections);
2902
2945
  }
@@ -2908,7 +2951,7 @@ var EnvManager = class {
2908
2951
  async updateEnvExample(sections) {
2909
2952
  let exampleContent = "";
2910
2953
  if (existsSync(this.envExamplePath)) {
2911
- exampleContent = await fs3.readFile(this.envExamplePath, "utf-8");
2954
+ exampleContent = await fs4.readFile(this.envExamplePath, "utf-8");
2912
2955
  }
2913
2956
  const existingKeys = this.parseExistingKeys(exampleContent);
2914
2957
  let newContent = exampleContent;
@@ -2932,7 +2975,7 @@ var EnvManager = class {
2932
2975
  }
2933
2976
  newContent += "\n";
2934
2977
  }
2935
- await fs3.writeFile(this.envExamplePath, newContent, "utf-8");
2978
+ await fs4.writeFile(this.envExamplePath, newContent, "utf-8");
2936
2979
  }
2937
2980
  /**
2938
2981
  * Parse existing environment variable keys
@@ -3506,34 +3549,34 @@ var EnvManager = class {
3506
3549
  };
3507
3550
 
3508
3551
  // src/cli/utils/template-manager.ts
3509
- import * as fs4 from "fs/promises";
3510
- import * as path6 from "path";
3552
+ import * as fs5 from "fs/promises";
3553
+ import * as path7 from "path";
3511
3554
  import { createHash } from "crypto";
3512
3555
  import { existsSync as existsSync2 } from "fs";
3513
3556
  var TemplateManager = class {
3514
3557
  templatesDir;
3515
3558
  manifestsDir;
3516
3559
  constructor(projectRoot) {
3517
- this.templatesDir = path6.join(projectRoot, ".servcraft", "templates");
3518
- this.manifestsDir = path6.join(projectRoot, ".servcraft", "manifests");
3560
+ this.templatesDir = path7.join(projectRoot, ".servcraft", "templates");
3561
+ this.manifestsDir = path7.join(projectRoot, ".servcraft", "manifests");
3519
3562
  }
3520
3563
  /**
3521
3564
  * Initialize template system
3522
3565
  */
3523
3566
  async initialize() {
3524
- await fs4.mkdir(this.templatesDir, { recursive: true });
3525
- await fs4.mkdir(this.manifestsDir, { recursive: true });
3567
+ await fs5.mkdir(this.templatesDir, { recursive: true });
3568
+ await fs5.mkdir(this.manifestsDir, { recursive: true });
3526
3569
  }
3527
3570
  /**
3528
3571
  * Save module template
3529
3572
  */
3530
3573
  async saveTemplate(moduleName, files) {
3531
3574
  await this.initialize();
3532
- const moduleTemplateDir = path6.join(this.templatesDir, moduleName);
3533
- await fs4.mkdir(moduleTemplateDir, { recursive: true });
3575
+ const moduleTemplateDir = path7.join(this.templatesDir, moduleName);
3576
+ await fs5.mkdir(moduleTemplateDir, { recursive: true });
3534
3577
  for (const [fileName, content] of Object.entries(files)) {
3535
- const filePath = path6.join(moduleTemplateDir, fileName);
3536
- await fs4.writeFile(filePath, content, "utf-8");
3578
+ const filePath = path7.join(moduleTemplateDir, fileName);
3579
+ await fs5.writeFile(filePath, content, "utf-8");
3537
3580
  }
3538
3581
  }
3539
3582
  /**
@@ -3541,8 +3584,8 @@ var TemplateManager = class {
3541
3584
  */
3542
3585
  async getTemplate(moduleName, fileName) {
3543
3586
  try {
3544
- const filePath = path6.join(this.templatesDir, moduleName, fileName);
3545
- return await fs4.readFile(filePath, "utf-8");
3587
+ const filePath = path7.join(this.templatesDir, moduleName, fileName);
3588
+ return await fs5.readFile(filePath, "utf-8");
3546
3589
  } catch {
3547
3590
  return null;
3548
3591
  }
@@ -3566,16 +3609,16 @@ var TemplateManager = class {
3566
3609
  installedAt: /* @__PURE__ */ new Date(),
3567
3610
  updatedAt: /* @__PURE__ */ new Date()
3568
3611
  };
3569
- const manifestPath = path6.join(this.manifestsDir, `${moduleName}.json`);
3570
- await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3612
+ const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3613
+ await fs5.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3571
3614
  }
3572
3615
  /**
3573
3616
  * Get module manifest
3574
3617
  */
3575
3618
  async getManifest(moduleName) {
3576
3619
  try {
3577
- const manifestPath = path6.join(this.manifestsDir, `${moduleName}.json`);
3578
- const content = await fs4.readFile(manifestPath, "utf-8");
3620
+ const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3621
+ const content = await fs5.readFile(manifestPath, "utf-8");
3579
3622
  return JSON.parse(content);
3580
3623
  } catch {
3581
3624
  return null;
@@ -3603,7 +3646,7 @@ var TemplateManager = class {
3603
3646
  }
3604
3647
  const results = [];
3605
3648
  for (const [fileName, fileInfo] of Object.entries(manifest.files)) {
3606
- const filePath = path6.join(moduleDir, fileName);
3649
+ const filePath = path7.join(moduleDir, fileName);
3607
3650
  if (!existsSync2(filePath)) {
3608
3651
  results.push({
3609
3652
  fileName,
@@ -3613,7 +3656,7 @@ var TemplateManager = class {
3613
3656
  });
3614
3657
  continue;
3615
3658
  }
3616
- const currentContent = await fs4.readFile(filePath, "utf-8");
3659
+ const currentContent = await fs5.readFile(filePath, "utf-8");
3617
3660
  const currentHash = this.hashContent(currentContent);
3618
3661
  results.push({
3619
3662
  fileName,
@@ -3629,7 +3672,7 @@ var TemplateManager = class {
3629
3672
  */
3630
3673
  async createBackup(moduleName, moduleDir) {
3631
3674
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
3632
- const backupDir = path6.join(path6.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
3675
+ const backupDir = path7.join(path7.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
3633
3676
  await this.copyDirectory(moduleDir, backupDir);
3634
3677
  return backupDir;
3635
3678
  }
@@ -3637,15 +3680,15 @@ var TemplateManager = class {
3637
3680
  * Copy directory recursively
3638
3681
  */
3639
3682
  async copyDirectory(src, dest) {
3640
- await fs4.mkdir(dest, { recursive: true });
3641
- const entries = await fs4.readdir(src, { withFileTypes: true });
3683
+ await fs5.mkdir(dest, { recursive: true });
3684
+ const entries = await fs5.readdir(src, { withFileTypes: true });
3642
3685
  for (const entry of entries) {
3643
- const srcPath = path6.join(src, entry.name);
3644
- const destPath = path6.join(dest, entry.name);
3686
+ const srcPath = path7.join(src, entry.name);
3687
+ const destPath = path7.join(dest, entry.name);
3645
3688
  if (entry.isDirectory()) {
3646
3689
  await this.copyDirectory(srcPath, destPath);
3647
3690
  } else {
3648
- await fs4.copyFile(srcPath, destPath);
3691
+ await fs5.copyFile(srcPath, destPath);
3649
3692
  }
3650
3693
  }
3651
3694
  }
@@ -3742,8 +3785,8 @@ var TemplateManager = class {
3742
3785
  }
3743
3786
  manifest.files = fileHashes;
3744
3787
  manifest.updatedAt = /* @__PURE__ */ new Date();
3745
- const manifestPath = path6.join(this.manifestsDir, `${moduleName}.json`);
3746
- await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3788
+ const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3789
+ await fs5.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3747
3790
  }
3748
3791
  };
3749
3792
 
@@ -4100,7 +4143,7 @@ var addModuleCommand = new Command3("add").description("Add a pre-built module t
4100
4143
  }
4101
4144
  const spinner = ora3(`Adding ${module.name} module...`).start();
4102
4145
  try {
4103
- const moduleDir = path7.join(getModulesDir(), moduleName);
4146
+ const moduleDir = path8.join(getModulesDir(), moduleName);
4104
4147
  const templateManager = new TemplateManager(process.cwd());
4105
4148
  const moduleExists = await fileExists(moduleDir);
4106
4149
  if (moduleExists) {
@@ -4133,7 +4176,7 @@ var addModuleCommand = new Command3("add").description("Add a pre-built module t
4133
4176
  const backupPath = await templateManager.createBackup(moduleName, moduleDir);
4134
4177
  InteractivePrompt.showBackupCreated(backupPath);
4135
4178
  }
4136
- await fs5.rm(moduleDir, { recursive: true, force: true });
4179
+ await fs6.rm(moduleDir, { recursive: true, force: true });
4137
4180
  await ensureDir(moduleDir);
4138
4181
  await generateModuleFiles(moduleName, moduleDir);
4139
4182
  const files = await getModuleFiles(moduleName, moduleDir);
@@ -4244,7 +4287,7 @@ export * from './auth.schemas.js';
4244
4287
  `
4245
4288
  };
4246
4289
  for (const [name, content] of Object.entries(files)) {
4247
- await writeFile(path7.join(dir, name), content);
4290
+ await writeFile(path8.join(dir, name), content);
4248
4291
  }
4249
4292
  }
4250
4293
  async function generateUsersModule(dir) {
@@ -4284,7 +4327,7 @@ export * from './user.schemas.js';
4284
4327
  `
4285
4328
  };
4286
4329
  for (const [name, content] of Object.entries(files)) {
4287
- await writeFile(path7.join(dir, name), content);
4330
+ await writeFile(path8.join(dir, name), content);
4288
4331
  }
4289
4332
  }
4290
4333
  async function generateEmailModule(dir) {
@@ -4341,7 +4384,7 @@ export { EmailService, emailService } from './email.service.js';
4341
4384
  `
4342
4385
  };
4343
4386
  for (const [name, content] of Object.entries(files)) {
4344
- await writeFile(path7.join(dir, name), content);
4387
+ await writeFile(path8.join(dir, name), content);
4345
4388
  }
4346
4389
  }
4347
4390
  async function generateAuditModule(dir) {
@@ -4385,7 +4428,7 @@ export { AuditService, auditService } from './audit.service.js';
4385
4428
  `
4386
4429
  };
4387
4430
  for (const [name, content] of Object.entries(files)) {
4388
- await writeFile(path7.join(dir, name), content);
4431
+ await writeFile(path8.join(dir, name), content);
4389
4432
  }
4390
4433
  }
4391
4434
  async function generateUploadModule(dir) {
@@ -4411,7 +4454,7 @@ export interface UploadOptions {
4411
4454
  `
4412
4455
  };
4413
4456
  for (const [name, content] of Object.entries(files)) {
4414
- await writeFile(path7.join(dir, name), content);
4457
+ await writeFile(path8.join(dir, name), content);
4415
4458
  }
4416
4459
  }
4417
4460
  async function generateCacheModule(dir) {
@@ -4457,7 +4500,7 @@ export { CacheService, cacheService } from './cache.service.js';
4457
4500
  `
4458
4501
  };
4459
4502
  for (const [name, content] of Object.entries(files)) {
4460
- await writeFile(path7.join(dir, name), content);
4503
+ await writeFile(path8.join(dir, name), content);
4461
4504
  }
4462
4505
  }
4463
4506
  async function generateGenericModule(dir, name) {
@@ -4471,22 +4514,22 @@ export interface ${name.charAt(0).toUpperCase() + name.slice(1)}Data {
4471
4514
  `
4472
4515
  };
4473
4516
  for (const [fileName, content] of Object.entries(files)) {
4474
- await writeFile(path7.join(dir, fileName), content);
4517
+ await writeFile(path8.join(dir, fileName), content);
4475
4518
  }
4476
4519
  }
4477
4520
  async function findServercraftModules() {
4478
- const scriptDir = path7.dirname(new URL(import.meta.url).pathname);
4521
+ const scriptDir = path8.dirname(new URL(import.meta.url).pathname);
4479
4522
  const possiblePaths = [
4480
4523
  // Local node_modules (when servcraft is a dependency)
4481
- path7.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
4524
+ path8.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
4482
4525
  // From dist/cli/index.js -> src/modules (npx or global install)
4483
- path7.resolve(scriptDir, "..", "..", "src", "modules"),
4526
+ path8.resolve(scriptDir, "..", "..", "src", "modules"),
4484
4527
  // From src/cli/commands/add-module.ts -> src/modules (development)
4485
- path7.resolve(scriptDir, "..", "..", "modules")
4528
+ path8.resolve(scriptDir, "..", "..", "modules")
4486
4529
  ];
4487
4530
  for (const p of possiblePaths) {
4488
4531
  try {
4489
- const stats = await fs5.stat(p);
4532
+ const stats = await fs6.stat(p);
4490
4533
  if (stats.isDirectory()) {
4491
4534
  return p;
4492
4535
  }
@@ -4506,7 +4549,7 @@ async function generateModuleFiles(moduleName, moduleDir) {
4506
4549
  const sourceDirName = moduleNameMap[moduleName] || moduleName;
4507
4550
  const servercraftModulesDir = await findServercraftModules();
4508
4551
  if (servercraftModulesDir) {
4509
- const sourceModuleDir = path7.join(servercraftModulesDir, sourceDirName);
4552
+ const sourceModuleDir = path8.join(servercraftModulesDir, sourceDirName);
4510
4553
  if (await fileExists(sourceModuleDir)) {
4511
4554
  await copyModuleFromSource(sourceModuleDir, moduleDir);
4512
4555
  return;
@@ -4536,26 +4579,26 @@ async function generateModuleFiles(moduleName, moduleDir) {
4536
4579
  }
4537
4580
  }
4538
4581
  async function copyModuleFromSource(sourceDir, targetDir) {
4539
- const entries = await fs5.readdir(sourceDir, { withFileTypes: true });
4582
+ const entries = await fs6.readdir(sourceDir, { withFileTypes: true });
4540
4583
  for (const entry of entries) {
4541
- const sourcePath = path7.join(sourceDir, entry.name);
4542
- const targetPath = path7.join(targetDir, entry.name);
4584
+ const sourcePath = path8.join(sourceDir, entry.name);
4585
+ const targetPath = path8.join(targetDir, entry.name);
4543
4586
  if (entry.isDirectory()) {
4544
- await fs5.mkdir(targetPath, { recursive: true });
4587
+ await fs6.mkdir(targetPath, { recursive: true });
4545
4588
  await copyModuleFromSource(sourcePath, targetPath);
4546
4589
  } else {
4547
- await fs5.copyFile(sourcePath, targetPath);
4590
+ await fs6.copyFile(sourcePath, targetPath);
4548
4591
  }
4549
4592
  }
4550
4593
  }
4551
4594
  async function getModuleFiles(moduleName, moduleDir) {
4552
4595
  const files = {};
4553
- const entries = await fs5.readdir(moduleDir);
4596
+ const entries = await fs6.readdir(moduleDir);
4554
4597
  for (const entry of entries) {
4555
- const filePath = path7.join(moduleDir, entry);
4556
- const stat2 = await fs5.stat(filePath);
4598
+ const filePath = path8.join(moduleDir, entry);
4599
+ const stat2 = await fs6.stat(filePath);
4557
4600
  if (stat2.isFile() && entry.endsWith(".ts")) {
4558
- const content = await fs5.readFile(filePath, "utf-8");
4601
+ const content = await fs6.readFile(filePath, "utf-8");
4559
4602
  files[entry] = content;
4560
4603
  }
4561
4604
  }
@@ -4570,8 +4613,8 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
4570
4613
  if (file.isModified) {
4571
4614
  console.log(chalk7.yellow(`
4572
4615
  \u{1F4C4} ${file.fileName}:`));
4573
- const currentPath = path7.join(moduleDir, file.fileName);
4574
- const currentContent = await fs5.readFile(currentPath, "utf-8");
4616
+ const currentPath = path8.join(moduleDir, file.fileName);
4617
+ const currentContent = await fs6.readFile(currentPath, "utf-8");
4575
4618
  const originalContent = await templateManager.getTemplate(moduleName, file.fileName);
4576
4619
  if (originalContent) {
4577
4620
  const diff = templateManager.generateDiff(originalContent, currentContent);
@@ -4583,11 +4626,11 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
4583
4626
  async function performSmartMerge(templateManager, moduleName, moduleDir, _displayName) {
4584
4627
  const spinner = ora3("Analyzing files for merge...").start();
4585
4628
  const newFiles = {};
4586
- const templateDir = path7.join(templateManager["templatesDir"], moduleName);
4629
+ const templateDir = path8.join(templateManager["templatesDir"], moduleName);
4587
4630
  try {
4588
- const entries = await fs5.readdir(templateDir);
4631
+ const entries = await fs6.readdir(templateDir);
4589
4632
  for (const entry of entries) {
4590
- const content = await fs5.readFile(path7.join(templateDir, entry), "utf-8");
4633
+ const content = await fs6.readFile(path8.join(templateDir, entry), "utf-8");
4591
4634
  newFiles[entry] = content;
4592
4635
  }
4593
4636
  } catch {
@@ -4605,7 +4648,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4605
4648
  };
4606
4649
  for (const fileInfo of modifiedFiles) {
4607
4650
  const fileName = fileInfo.fileName;
4608
- const filePath = path7.join(moduleDir, fileName);
4651
+ const filePath = path8.join(moduleDir, fileName);
4609
4652
  const newContent = newFiles[fileName];
4610
4653
  if (!newContent) {
4611
4654
  continue;
@@ -4618,7 +4661,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4618
4661
  } else if (batchAction === "overwrite-all") {
4619
4662
  fileAction = "overwrite";
4620
4663
  } else {
4621
- const currentContent = await fs5.readFile(filePath, "utf-8");
4664
+ const currentContent = await fs6.readFile(filePath, "utf-8");
4622
4665
  const yourLines = currentContent.split("\n").length;
4623
4666
  const newLines = newContent.split("\n").length;
4624
4667
  const choice = await InteractivePrompt.askFileAction(
@@ -4642,20 +4685,20 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4642
4685
  continue;
4643
4686
  }
4644
4687
  if (fileAction === "overwrite") {
4645
- await fs5.writeFile(filePath, newContent, "utf-8");
4688
+ await fs6.writeFile(filePath, newContent, "utf-8");
4646
4689
  stats.overwritten++;
4647
4690
  continue;
4648
4691
  }
4649
4692
  if (fileAction === "merge") {
4650
4693
  const originalContent = await templateManager.getTemplate(moduleName, fileName);
4651
- const currentContent = await fs5.readFile(filePath, "utf-8");
4694
+ const currentContent = await fs6.readFile(filePath, "utf-8");
4652
4695
  if (originalContent) {
4653
4696
  const mergeResult = await templateManager.mergeFiles(
4654
4697
  originalContent,
4655
4698
  currentContent,
4656
4699
  newContent
4657
4700
  );
4658
- await fs5.writeFile(filePath, mergeResult.merged, "utf-8");
4701
+ await fs6.writeFile(filePath, mergeResult.merged, "utf-8");
4659
4702
  if (mergeResult.hasConflicts) {
4660
4703
  stats.conflicts++;
4661
4704
  InteractivePrompt.displayConflicts(mergeResult.conflicts);
@@ -4663,7 +4706,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4663
4706
  stats.merged++;
4664
4707
  }
4665
4708
  } else {
4666
- await fs5.writeFile(filePath, newContent, "utf-8");
4709
+ await fs6.writeFile(filePath, newContent, "utf-8");
4667
4710
  stats.overwritten++;
4668
4711
  }
4669
4712
  }
@@ -4768,14 +4811,14 @@ dbCommand.command("status").description("Show migration status").action(async ()
4768
4811
 
4769
4812
  // src/cli/commands/docs.ts
4770
4813
  import { Command as Command5 } from "commander";
4771
- import path9 from "path";
4772
- import fs7 from "fs/promises";
4814
+ import path10 from "path";
4815
+ import fs8 from "fs/promises";
4773
4816
  import ora6 from "ora";
4774
4817
  import chalk9 from "chalk";
4775
4818
 
4776
4819
  // src/cli/utils/docs-generator.ts
4777
- import fs6 from "fs/promises";
4778
- import path8 from "path";
4820
+ import fs7 from "fs/promises";
4821
+ import path9 from "path";
4779
4822
  import ora5 from "ora";
4780
4823
 
4781
4824
  // src/core/server.ts
@@ -5496,11 +5539,11 @@ function validateQuery(schema, data) {
5496
5539
  function formatZodErrors(error2) {
5497
5540
  const errors = {};
5498
5541
  for (const issue of error2.issues) {
5499
- const path12 = issue.path.join(".") || "root";
5500
- if (!errors[path12]) {
5501
- errors[path12] = [];
5542
+ const path15 = issue.path.join(".") || "root";
5543
+ if (!errors[path15]) {
5544
+ errors[path15] = [];
5502
5545
  }
5503
- errors[path12].push(issue.message);
5546
+ errors[path15].push(issue.message);
5504
5547
  }
5505
5548
  return errors;
5506
5549
  }
@@ -5737,7 +5780,18 @@ function getSkip(params) {
5737
5780
  }
5738
5781
 
5739
5782
  // src/modules/user/user.repository.ts
5740
- import { UserRole, UserStatus } from "@prisma/client";
5783
+ var UserRole = {
5784
+ USER: "USER",
5785
+ MODERATOR: "MODERATOR",
5786
+ ADMIN: "ADMIN",
5787
+ SUPER_ADMIN: "SUPER_ADMIN"
5788
+ };
5789
+ var UserStatus = {
5790
+ ACTIVE: "ACTIVE",
5791
+ INACTIVE: "INACTIVE",
5792
+ SUSPENDED: "SUSPENDED",
5793
+ BANNED: "BANNED"
5794
+ };
5741
5795
  var UserRepository = class {
5742
5796
  /**
5743
5797
  * Find user by ID
@@ -6333,9 +6387,9 @@ async function generateDocs(outputPath = "openapi.json", silent = false) {
6333
6387
  await registerUserModule(app, authService);
6334
6388
  await app.ready();
6335
6389
  const spec = app.swagger();
6336
- const absoluteOutput = path8.resolve(outputPath);
6337
- await fs6.mkdir(path8.dirname(absoluteOutput), { recursive: true });
6338
- await fs6.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
6390
+ const absoluteOutput = path9.resolve(outputPath);
6391
+ await fs7.mkdir(path9.dirname(absoluteOutput), { recursive: true });
6392
+ await fs7.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
6339
6393
  spinner?.succeed(`OpenAPI spec generated at ${absoluteOutput}`);
6340
6394
  await app.close();
6341
6395
  return absoluteOutput;
@@ -6351,10 +6405,10 @@ docsCommand.command("generate").alias("gen").description("Generate OpenAPI/Swagg
6351
6405
  try {
6352
6406
  const outputPath = await generateDocs(options.output, false);
6353
6407
  if (options.format === "yaml") {
6354
- const jsonContent = await fs7.readFile(outputPath, "utf-8");
6408
+ const jsonContent = await fs8.readFile(outputPath, "utf-8");
6355
6409
  const spec = JSON.parse(jsonContent);
6356
6410
  const yamlPath = outputPath.replace(".json", ".yaml");
6357
- await fs7.writeFile(yamlPath, jsonToYaml(spec));
6411
+ await fs8.writeFile(yamlPath, jsonToYaml(spec));
6358
6412
  success(`YAML documentation generated: ${yamlPath}`);
6359
6413
  }
6360
6414
  console.log("\n\u{1F4DA} Documentation URLs:");
@@ -6379,14 +6433,14 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
6379
6433
  const spinner = ora6("Exporting documentation...").start();
6380
6434
  try {
6381
6435
  const projectRoot = getProjectRoot();
6382
- const specPath = path9.join(projectRoot, "openapi.json");
6436
+ const specPath = path10.join(projectRoot, "openapi.json");
6383
6437
  try {
6384
- await fs7.access(specPath);
6438
+ await fs8.access(specPath);
6385
6439
  } catch {
6386
6440
  spinner.text = "Generating OpenAPI spec first...";
6387
6441
  await generateDocs("openapi.json", true);
6388
6442
  }
6389
- const specContent = await fs7.readFile(specPath, "utf-8");
6443
+ const specContent = await fs8.readFile(specPath, "utf-8");
6390
6444
  const spec = JSON.parse(specContent);
6391
6445
  let output;
6392
6446
  let defaultName;
@@ -6406,8 +6460,8 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
6406
6460
  default:
6407
6461
  throw new Error(`Unknown format: ${options.format}`);
6408
6462
  }
6409
- const outPath = path9.join(projectRoot, options.output || defaultName);
6410
- await fs7.writeFile(outPath, output);
6463
+ const outPath = path10.join(projectRoot, options.output || defaultName);
6464
+ await fs8.writeFile(outPath, output);
6411
6465
  spinner.succeed(`Exported to: ${options.output || defaultName}`);
6412
6466
  if (options.format === "postman") {
6413
6467
  info("\n Import in Postman: File > Import > Select file");
@@ -6420,13 +6474,13 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
6420
6474
  docsCommand.command("status").description("Show documentation status").action(async () => {
6421
6475
  const projectRoot = getProjectRoot();
6422
6476
  console.log(chalk9.bold("\n\u{1F4CA} Documentation Status\n"));
6423
- const specPath = path9.join(projectRoot, "openapi.json");
6477
+ const specPath = path10.join(projectRoot, "openapi.json");
6424
6478
  try {
6425
- const stat2 = await fs7.stat(specPath);
6479
+ const stat2 = await fs8.stat(specPath);
6426
6480
  success(
6427
6481
  `openapi.json exists (${formatBytes(stat2.size)}, modified ${formatDate(stat2.mtime)})`
6428
6482
  );
6429
- const content = await fs7.readFile(specPath, "utf-8");
6483
+ const content = await fs8.readFile(specPath, "utf-8");
6430
6484
  const spec = JSON.parse(content);
6431
6485
  const pathCount = Object.keys(spec.paths || {}).length;
6432
6486
  info(` ${pathCount} endpoints documented`);
@@ -6538,7 +6592,7 @@ function formatDate(date) {
6538
6592
  // src/cli/commands/list.ts
6539
6593
  import { Command as Command6 } from "commander";
6540
6594
  import chalk10 from "chalk";
6541
- import fs8 from "fs/promises";
6595
+ import fs9 from "fs/promises";
6542
6596
  var AVAILABLE_MODULES2 = {
6543
6597
  // Core
6544
6598
  auth: {
@@ -6653,7 +6707,7 @@ var AVAILABLE_MODULES2 = {
6653
6707
  async function getInstalledModules() {
6654
6708
  try {
6655
6709
  const modulesDir = getModulesDir();
6656
- const entries = await fs8.readdir(modulesDir, { withFileTypes: true });
6710
+ const entries = await fs9.readdir(modulesDir, { withFileTypes: true });
6657
6711
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
6658
6712
  } catch {
6659
6713
  return [];
@@ -6762,10 +6816,10 @@ var listCommand = new Command6("list").alias("ls").description("List available a
6762
6816
 
6763
6817
  // src/cli/commands/remove.ts
6764
6818
  import { Command as Command7 } from "commander";
6765
- import path10 from "path";
6819
+ import path11 from "path";
6766
6820
  import ora7 from "ora";
6767
6821
  import chalk11 from "chalk";
6768
- import fs9 from "fs/promises";
6822
+ import fs10 from "fs/promises";
6769
6823
  import inquirer4 from "inquirer";
6770
6824
  var removeCommand = new Command7("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) => {
6771
6825
  const projectError = validateProject();
@@ -6774,9 +6828,9 @@ var removeCommand = new Command7("remove").alias("rm").description("Remove an in
6774
6828
  return;
6775
6829
  }
6776
6830
  console.log(chalk11.bold.cyan("\n\u{1F5D1}\uFE0F ServCraft Module Removal\n"));
6777
- const moduleDir = path10.join(getModulesDir(), moduleName);
6831
+ const moduleDir = path11.join(getModulesDir(), moduleName);
6778
6832
  try {
6779
- const exists = await fs9.access(moduleDir).then(() => true).catch(() => false);
6833
+ const exists = await fs10.access(moduleDir).then(() => true).catch(() => false);
6780
6834
  if (!exists) {
6781
6835
  displayError(
6782
6836
  new ServCraftError(`Module "${moduleName}" is not installed`, [
@@ -6786,7 +6840,7 @@ var removeCommand = new Command7("remove").alias("rm").description("Remove an in
6786
6840
  );
6787
6841
  return;
6788
6842
  }
6789
- const files = await fs9.readdir(moduleDir);
6843
+ const files = await fs10.readdir(moduleDir);
6790
6844
  const fileCount = files.length;
6791
6845
  if (!options?.yes) {
6792
6846
  console.log(chalk11.yellow(`\u26A0 This will remove the "${moduleName}" module:`));
@@ -6807,7 +6861,7 @@ var removeCommand = new Command7("remove").alias("rm").description("Remove an in
6807
6861
  }
6808
6862
  }
6809
6863
  const spinner = ora7("Removing module...").start();
6810
- await fs9.rm(moduleDir, { recursive: true, force: true });
6864
+ await fs10.rm(moduleDir, { recursive: true, force: true });
6811
6865
  spinner.succeed(`Module "${moduleName}" removed successfully!`);
6812
6866
  console.log("\n" + chalk11.bold("\u2713 Removed:"));
6813
6867
  success(` src/modules/${moduleName}/ (${fileCount} files)`);
@@ -6833,7 +6887,7 @@ var removeCommand = new Command7("remove").alias("rm").description("Remove an in
6833
6887
  // src/cli/commands/doctor.ts
6834
6888
  import { Command as Command8 } from "commander";
6835
6889
  import chalk12 from "chalk";
6836
- import fs10 from "fs/promises";
6890
+ import fs11 from "fs/promises";
6837
6891
  async function checkNodeVersion() {
6838
6892
  const version = process.version;
6839
6893
  const major = parseInt(version.slice(1).split(".")[0] || "0", 10);
@@ -6850,7 +6904,7 @@ async function checkNodeVersion() {
6850
6904
  async function checkPackageJson() {
6851
6905
  const checks = [];
6852
6906
  try {
6853
- const content = await fs10.readFile("package.json", "utf-8");
6907
+ const content = await fs11.readFile("package.json", "utf-8");
6854
6908
  const pkg = JSON.parse(content);
6855
6909
  checks.push({ name: "package.json", status: "pass", message: "Found" });
6856
6910
  if (pkg.dependencies?.fastify) {
@@ -6878,7 +6932,7 @@ async function checkDirectories() {
6878
6932
  const dirs = ["src", "node_modules", ".git", ".env"];
6879
6933
  for (const dir of dirs) {
6880
6934
  try {
6881
- await fs10.access(dir);
6935
+ await fs11.access(dir);
6882
6936
  checks.push({ name: dir, status: "pass", message: "Exists" });
6883
6937
  } catch {
6884
6938
  const isCritical = dir === "src" || dir === "node_modules";
@@ -6927,12 +6981,12 @@ ${chalk12.green(pass + " passed")} | ${chalk12.yellow(warn2 + " warnings")} | ${
6927
6981
  // src/cli/commands/update.ts
6928
6982
  import { Command as Command9 } from "commander";
6929
6983
  import chalk13 from "chalk";
6930
- import fs11 from "fs/promises";
6931
- import path11 from "path";
6984
+ import fs12 from "fs/promises";
6985
+ import path12 from "path";
6932
6986
  import { fileURLToPath } from "url";
6933
6987
  import inquirer5 from "inquirer";
6934
6988
  var __filename2 = fileURLToPath(import.meta.url);
6935
- var __dirname2 = path11.dirname(__filename2);
6989
+ var __dirname2 = path12.dirname(__filename2);
6936
6990
  var AVAILABLE_MODULES3 = [
6937
6991
  "auth",
6938
6992
  "users",
@@ -6960,7 +7014,7 @@ var AVAILABLE_MODULES3 = [
6960
7014
  async function getInstalledModules2() {
6961
7015
  try {
6962
7016
  const modulesDir = getModulesDir();
6963
- const entries = await fs11.readdir(modulesDir, { withFileTypes: true });
7017
+ const entries = await fs12.readdir(modulesDir, { withFileTypes: true });
6964
7018
  const installedModules = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => AVAILABLE_MODULES3.includes(name));
6965
7019
  return installedModules;
6966
7020
  } catch {
@@ -6968,16 +7022,16 @@ async function getInstalledModules2() {
6968
7022
  }
6969
7023
  }
6970
7024
  async function copyModuleFiles(moduleName, _projectRoot) {
6971
- const cliRoot = path11.resolve(__dirname2, "../../../");
6972
- const sourceModulePath = path11.join(cliRoot, "src", "modules", moduleName);
7025
+ const cliRoot = path12.resolve(__dirname2, "../../../");
7026
+ const sourceModulePath = path12.join(cliRoot, "src", "modules", moduleName);
6973
7027
  const targetModulesDir = getModulesDir();
6974
- const targetModulePath = path11.join(targetModulesDir, moduleName);
7028
+ const targetModulePath = path12.join(targetModulesDir, moduleName);
6975
7029
  try {
6976
- await fs11.access(sourceModulePath);
7030
+ await fs12.access(sourceModulePath);
6977
7031
  } catch {
6978
7032
  throw new Error(`Module source not found: ${moduleName}`);
6979
7033
  }
6980
- await fs11.cp(sourceModulePath, targetModulePath, { recursive: true });
7034
+ await fs12.cp(sourceModulePath, targetModulePath, { recursive: true });
6981
7035
  }
6982
7036
  async function updateModule(moduleName, options) {
6983
7037
  const projectError = validateProject();
@@ -7253,8 +7307,275 @@ var completionCommand = new Command10("completion").description("Generate shell
7253
7307
  }
7254
7308
  });
7255
7309
 
7310
+ // src/cli/commands/scaffold.ts
7311
+ import { Command as Command11 } from "commander";
7312
+ import path13 from "path";
7313
+ import ora8 from "ora";
7314
+ import chalk14 from "chalk";
7315
+ var scaffoldCommand = new Command11("scaffold").description("Generate complete CRUD with Prisma model").argument("<name>", "Resource name (e.g., product, user)").option(
7316
+ "--fields <fields>",
7317
+ 'Field definitions: "name:string email:string? age:number category:relation"'
7318
+ ).option("--validator <type>", "Validator type: zod, joi, yup", "zod").option("--dry-run", "Preview changes without writing files").action(
7319
+ async (name, options) => {
7320
+ const dryRun = DryRunManager.getInstance();
7321
+ if (options.dryRun) {
7322
+ dryRun.enable();
7323
+ console.log(chalk14.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
7324
+ }
7325
+ if (!options.fields) {
7326
+ console.log(chalk14.red("\n\u2717 Error: --fields option is required\n"));
7327
+ console.log(chalk14.gray("Example:"));
7328
+ console.log(
7329
+ chalk14.cyan(
7330
+ ' servcraft scaffold product --fields "name:string price:number category:relation"'
7331
+ )
7332
+ );
7333
+ process.exit(1);
7334
+ }
7335
+ const spinner = ora8("Scaffolding resource...").start();
7336
+ try {
7337
+ const fields = parseFields(options.fields || "");
7338
+ if (!fields || fields.length === 0) {
7339
+ spinner.fail("No valid fields provided");
7340
+ console.log(chalk14.gray(`
7341
+ Received: ${options.fields}`));
7342
+ console.log(chalk14.gray(`Parsed: ${JSON.stringify(fields)}`));
7343
+ process.exit(1);
7344
+ }
7345
+ const kebabName = toKebabCase(name);
7346
+ const pascalName = toPascalCase(name);
7347
+ const camelName = toCamelCase(name);
7348
+ const pluralName = pluralize(camelName);
7349
+ const tableName = pluralize(kebabName);
7350
+ const modulesDir = getModulesDir();
7351
+ const moduleDir = path13.join(modulesDir, kebabName);
7352
+ const controllerTpl = await getTemplate("controller", controllerTemplate);
7353
+ const serviceTpl = await getTemplate("service", serviceTemplate);
7354
+ const repositoryTpl = await getTemplate("repository", repositoryTemplate);
7355
+ const routesTpl = await getTemplate("routes", routesTemplate);
7356
+ const moduleIndexTpl = await getTemplate("module-index", moduleIndexTemplate);
7357
+ const files = [
7358
+ {
7359
+ name: `${kebabName}.types.ts`,
7360
+ content: dynamicTypesTemplate(kebabName, pascalName, fields)
7361
+ },
7362
+ {
7363
+ name: `${kebabName}.schemas.ts`,
7364
+ content: dynamicSchemasTemplate(
7365
+ kebabName,
7366
+ pascalName,
7367
+ camelName,
7368
+ fields,
7369
+ options.validator || "zod"
7370
+ )
7371
+ },
7372
+ {
7373
+ name: `${kebabName}.service.ts`,
7374
+ content: serviceTpl(kebabName, pascalName, camelName)
7375
+ },
7376
+ {
7377
+ name: `${kebabName}.controller.ts`,
7378
+ content: controllerTpl(kebabName, pascalName, camelName)
7379
+ },
7380
+ {
7381
+ name: "index.ts",
7382
+ content: moduleIndexTpl(kebabName, pascalName, camelName)
7383
+ },
7384
+ {
7385
+ name: `${kebabName}.repository.ts`,
7386
+ content: repositoryTpl(kebabName, pascalName, camelName, pluralName)
7387
+ },
7388
+ {
7389
+ name: `${kebabName}.routes.ts`,
7390
+ content: routesTpl(kebabName, pascalName, camelName, pluralName)
7391
+ }
7392
+ ];
7393
+ for (const file of files) {
7394
+ await writeFile(path13.join(moduleDir, file.name), file.content);
7395
+ }
7396
+ const testDir = path13.join(moduleDir, "__tests__");
7397
+ const controllerTestTpl = await getTemplate("controller-test", controllerTestTemplate);
7398
+ const serviceTestTpl = await getTemplate("service-test", serviceTestTemplate);
7399
+ const integrationTestTpl = await getTemplate("integration-test", integrationTestTemplate);
7400
+ await writeFile(
7401
+ path13.join(testDir, `${kebabName}.controller.test.ts`),
7402
+ controllerTestTpl(kebabName, pascalName, camelName)
7403
+ );
7404
+ await writeFile(
7405
+ path13.join(testDir, `${kebabName}.service.test.ts`),
7406
+ serviceTestTpl(kebabName, pascalName, camelName)
7407
+ );
7408
+ await writeFile(
7409
+ path13.join(testDir, `${kebabName}.integration.test.ts`),
7410
+ integrationTestTpl(kebabName, pascalName, camelName)
7411
+ );
7412
+ spinner.succeed(`Resource "${pascalName}" scaffolded successfully!`);
7413
+ console.log("\n" + "\u2500".repeat(70));
7414
+ info("\u{1F4CB} Prisma model to add to schema.prisma:");
7415
+ console.log(chalk14.gray("\n// Copy this to your schema.prisma file:\n"));
7416
+ console.log(dynamicPrismaTemplate(pascalName, tableName, fields));
7417
+ console.log("\u2500".repeat(70));
7418
+ console.log("\n\u{1F4CB} Fields scaffolded:");
7419
+ fields.forEach((f) => {
7420
+ const opts = [];
7421
+ if (f.isOptional) opts.push("optional");
7422
+ if (f.isArray) opts.push("array");
7423
+ if (f.isUnique) opts.push("unique");
7424
+ if (f.relation) opts.push(`relation: ${f.relation.model}`);
7425
+ const optsStr = opts.length > 0 ? ` (${opts.join(", ")})` : "";
7426
+ success(` ${f.name}: ${f.type}${optsStr}`);
7427
+ });
7428
+ console.log("\n\u{1F4C1} Files created:");
7429
+ files.forEach((f) => success(` src/modules/${kebabName}/${f.name}`));
7430
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.controller.test.ts`);
7431
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.service.test.ts`);
7432
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.integration.test.ts`);
7433
+ console.log("\n\u{1F4CC} Next steps:");
7434
+ info(" 1. Add the Prisma model to your schema.prisma file");
7435
+ info(" 2. Run: npx prisma db push (or prisma migrate dev)");
7436
+ info(" 3. Run: npx prisma generate");
7437
+ info(" 4. Register the module routes in your app");
7438
+ info(" 5. Update the test files with actual test data");
7439
+ console.log(
7440
+ chalk14.gray("\n\u{1F4A1} Tip: Use --dry-run to preview changes before applying them\n")
7441
+ );
7442
+ if (options.dryRun) {
7443
+ dryRun.printSummary();
7444
+ }
7445
+ } catch (error2) {
7446
+ spinner.fail("Failed to scaffold resource");
7447
+ if (error2 instanceof Error) {
7448
+ console.error(chalk14.red(`
7449
+ \u2717 ${error2.message}
7450
+ `));
7451
+ console.error(chalk14.gray(error2.stack));
7452
+ }
7453
+ process.exit(1);
7454
+ }
7455
+ }
7456
+ );
7457
+
7458
+ // src/cli/commands/templates.ts
7459
+ import { Command as Command12 } from "commander";
7460
+ import chalk15 from "chalk";
7461
+ import fs13 from "fs/promises";
7462
+ import path14 from "path";
7463
+ var TEMPLATE_TYPES = [
7464
+ "controller",
7465
+ "service",
7466
+ "repository",
7467
+ "types",
7468
+ "schemas",
7469
+ "routes",
7470
+ "module-index",
7471
+ "controller-test",
7472
+ "service-test",
7473
+ "integration-test"
7474
+ ];
7475
+ async function initTemplates() {
7476
+ const projectError = validateProject();
7477
+ if (projectError) {
7478
+ displayError(projectError);
7479
+ return;
7480
+ }
7481
+ const projectRoot = getProjectRoot();
7482
+ const templatesDir = path14.join(projectRoot, ".servcraft", "templates");
7483
+ try {
7484
+ await fs13.mkdir(templatesDir, { recursive: true });
7485
+ console.log(chalk15.cyan("\n\u{1F4C1} Creating custom template directory...\n"));
7486
+ const exampleController = `// Custom controller template
7487
+ // Available variables: name, pascalName, camelName, pluralName
7488
+ export function controllerTemplate(name: string, pascalName: string, camelName: string): string {
7489
+ return \`import type { FastifyRequest, FastifyReply } from 'fastify';
7490
+ import type { \${pascalName}Service } from './\${name}.service.js';
7491
+
7492
+ export class \${pascalName}Controller {
7493
+ constructor(private \${camelName}Service: \${pascalName}Service) {}
7494
+
7495
+ // Add your custom controller methods here
7496
+ async getAll(request: FastifyRequest, reply: FastifyReply) {
7497
+ const data = await this.\${camelName}Service.getAll();
7498
+ return reply.send({ data });
7499
+ }
7500
+ }
7501
+ \`;
7502
+ }
7503
+ `;
7504
+ await fs13.writeFile(
7505
+ path14.join(templatesDir, "controller.example.ts"),
7506
+ exampleController,
7507
+ "utf-8"
7508
+ );
7509
+ console.log(chalk15.green("\u2714 Created template directory: .servcraft/templates/"));
7510
+ console.log(chalk15.green("\u2714 Created example template: controller.example.ts\n"));
7511
+ console.log(chalk15.bold("\u{1F4CB} Available template types:\n"));
7512
+ TEMPLATE_TYPES.forEach((type) => {
7513
+ console.log(chalk15.gray(` \u2022 ${type}.ts`));
7514
+ });
7515
+ console.log(chalk15.yellow("\n\u{1F4A1} To customize a template:"));
7516
+ console.log(chalk15.gray(" 1. Copy the example template"));
7517
+ console.log(chalk15.gray(" 2. Rename it (remove .example)"));
7518
+ console.log(chalk15.gray(" 3. Modify the template code"));
7519
+ console.log(chalk15.gray(" 4. Use --template flag when generating\n"));
7520
+ } catch (error2) {
7521
+ if (error2 instanceof Error) {
7522
+ console.error(chalk15.red(`
7523
+ \u2717 Failed to initialize templates: ${error2.message}
7524
+ `));
7525
+ }
7526
+ }
7527
+ }
7528
+ async function listTemplates() {
7529
+ const projectError = validateProject();
7530
+ if (projectError) {
7531
+ displayError(projectError);
7532
+ return;
7533
+ }
7534
+ const projectRoot = getProjectRoot();
7535
+ const projectTemplatesDir = path14.join(projectRoot, ".servcraft", "templates");
7536
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
7537
+ const userTemplatesDir = path14.join(homeDir, ".servcraft", "templates");
7538
+ console.log(chalk15.bold.cyan("\n\u{1F4CB} Available Templates\n"));
7539
+ console.log(chalk15.bold("Project templates (.servcraft/templates/):"));
7540
+ try {
7541
+ const files = await fs13.readdir(projectTemplatesDir);
7542
+ const templates = files.filter((f) => f.endsWith(".ts") && !f.endsWith(".example.ts"));
7543
+ if (templates.length > 0) {
7544
+ templates.forEach((t) => {
7545
+ console.log(chalk15.green(` \u2713 ${t}`));
7546
+ });
7547
+ } else {
7548
+ console.log(chalk15.gray(" (none)"));
7549
+ }
7550
+ } catch {
7551
+ console.log(chalk15.gray(" (directory not found)"));
7552
+ }
7553
+ console.log(chalk15.bold("\nUser templates (~/.servcraft/templates/):"));
7554
+ try {
7555
+ const files = await fs13.readdir(userTemplatesDir);
7556
+ const templates = files.filter((f) => f.endsWith(".ts") && !f.endsWith(".example.ts"));
7557
+ if (templates.length > 0) {
7558
+ templates.forEach((t) => {
7559
+ console.log(chalk15.green(` \u2713 ${t}`));
7560
+ });
7561
+ } else {
7562
+ console.log(chalk15.gray(" (none)"));
7563
+ }
7564
+ } catch {
7565
+ console.log(chalk15.gray(" (directory not found)"));
7566
+ }
7567
+ console.log(chalk15.bold("\nBuilt-in templates:"));
7568
+ TEMPLATE_TYPES.forEach((t) => {
7569
+ console.log(chalk15.cyan(` \u2022 ${t}.ts`));
7570
+ });
7571
+ console.log(chalk15.gray('\n\u{1F4A1} Run "servcraft templates init" to create custom templates\n'));
7572
+ }
7573
+ var templatesCommand = new Command12("templates").description("Manage code generation templates").addCommand(
7574
+ new Command12("init").description("Initialize custom templates directory").action(initTemplates)
7575
+ ).addCommand(new Command12("list").description("List available templates").action(listTemplates));
7576
+
7256
7577
  // src/cli/index.ts
7257
- var program = new Command11();
7578
+ var program = new Command13();
7258
7579
  program.name("servcraft").description("Servcraft - A modular Node.js backend framework CLI").version("0.1.0");
7259
7580
  program.addCommand(initCommand);
7260
7581
  program.addCommand(generateCommand);
@@ -7266,5 +7587,7 @@ program.addCommand(removeCommand);
7266
7587
  program.addCommand(doctorCommand);
7267
7588
  program.addCommand(updateCommand);
7268
7589
  program.addCommand(completionCommand);
7590
+ program.addCommand(scaffoldCommand);
7591
+ program.addCommand(templatesCommand);
7269
7592
  program.parse();
7270
7593
  //# sourceMappingURL=index.js.map