servcraft 0.1.7 → 0.2.0

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.
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
9
16
  var __copyProps = (to, from, except, desc) => {
10
17
  if (from && typeof from === "object" || typeof from === "function") {
11
18
  for (let key of __getOwnPropNames(from))
@@ -22,27 +29,294 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
29
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
30
  mod
24
31
  ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
33
 
26
34
  // node_modules/tsup/assets/cjs_shims.js
27
- var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
28
- var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
35
+ var getImportMetaUrl, importMetaUrl;
36
+ var init_cjs_shims = __esm({
37
+ "node_modules/tsup/assets/cjs_shims.js"() {
38
+ "use strict";
39
+ getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
40
+ importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
41
+ }
42
+ });
43
+
44
+ // src/cli/utils/error-handler.ts
45
+ var error_handler_exports = {};
46
+ __export(error_handler_exports, {
47
+ ErrorTypes: () => ErrorTypes,
48
+ ServCraftError: () => ServCraftError,
49
+ displayError: () => displayError,
50
+ handleSystemError: () => handleSystemError,
51
+ validateProject: () => validateProject
52
+ });
53
+ function displayError(error2) {
54
+ console.error("\n" + import_chalk4.default.red.bold("\u2717 Error: ") + import_chalk4.default.red(error2.message));
55
+ if (error2 instanceof ServCraftError) {
56
+ if (error2.suggestions.length > 0) {
57
+ console.log("\n" + import_chalk4.default.yellow.bold("\u{1F4A1} Suggestions:"));
58
+ error2.suggestions.forEach((suggestion) => {
59
+ console.log(import_chalk4.default.yellow(" \u2022 ") + suggestion);
60
+ });
61
+ }
62
+ if (error2.docsLink) {
63
+ console.log("\n" + import_chalk4.default.blue.bold("\u{1F4DA} Documentation: ") + import_chalk4.default.blue.underline(error2.docsLink));
64
+ }
65
+ }
66
+ console.log();
67
+ }
68
+ function handleSystemError(err) {
69
+ switch (err.code) {
70
+ case "ENOENT":
71
+ return new ServCraftError(
72
+ `File or directory not found: ${err.path}`,
73
+ [`Check if the path exists`, `Create the directory first`]
74
+ );
75
+ case "EACCES":
76
+ case "EPERM":
77
+ return new ServCraftError(
78
+ `Permission denied: ${err.path}`,
79
+ [
80
+ `Check file permissions`,
81
+ `Try running with elevated privileges (not recommended)`,
82
+ `Change ownership of the directory`
83
+ ]
84
+ );
85
+ case "EEXIST":
86
+ return new ServCraftError(
87
+ `File or directory already exists: ${err.path}`,
88
+ [`Use a different name`, `Remove the existing file first`, `Use ${import_chalk4.default.cyan("--force")} to overwrite`]
89
+ );
90
+ case "ENOTDIR":
91
+ return new ServCraftError(
92
+ `Not a directory: ${err.path}`,
93
+ [`Check the path`, `A file exists where a directory is expected`]
94
+ );
95
+ case "EISDIR":
96
+ return new ServCraftError(
97
+ `Is a directory: ${err.path}`,
98
+ [`Cannot perform this operation on a directory`, `Did you mean to target a file?`]
99
+ );
100
+ default:
101
+ return new ServCraftError(
102
+ err.message,
103
+ [`Check system error code: ${err.code}`, `Review the error details above`]
104
+ );
105
+ }
106
+ }
107
+ function validateProject() {
108
+ try {
109
+ const fs10 = require("fs");
110
+ const path11 = require("path");
111
+ if (!fs10.existsSync("package.json")) {
112
+ return ErrorTypes.NOT_IN_PROJECT();
113
+ }
114
+ const packageJson = JSON.parse(fs10.readFileSync("package.json", "utf-8"));
115
+ if (!packageJson.dependencies?.fastify) {
116
+ return new ServCraftError(
117
+ "This does not appear to be a ServCraft project",
118
+ [
119
+ `ServCraft projects require Fastify`,
120
+ `Run ${import_chalk4.default.cyan("servcraft init")} to create a new project`
121
+ ]
122
+ );
123
+ }
124
+ return null;
125
+ } catch (err) {
126
+ return new ServCraftError(
127
+ "Failed to validate project",
128
+ [`Ensure you are in the project root directory`, `Check if ${import_chalk4.default.yellow("package.json")} is valid`]
129
+ );
130
+ }
131
+ }
132
+ var import_chalk4, ServCraftError, ErrorTypes;
133
+ var init_error_handler = __esm({
134
+ "src/cli/utils/error-handler.ts"() {
135
+ "use strict";
136
+ init_cjs_shims();
137
+ import_chalk4 = __toESM(require("chalk"), 1);
138
+ ServCraftError = class extends Error {
139
+ suggestions;
140
+ docsLink;
141
+ constructor(message, suggestions = [], docsLink) {
142
+ super(message);
143
+ this.name = "ServCraftError";
144
+ this.suggestions = suggestions;
145
+ this.docsLink = docsLink;
146
+ }
147
+ };
148
+ ErrorTypes = {
149
+ MODULE_NOT_FOUND: (moduleName) => new ServCraftError(
150
+ `Module "${moduleName}" not found`,
151
+ [
152
+ `Run ${import_chalk4.default.cyan("servcraft list")} to see available modules`,
153
+ `Check the spelling of the module name`,
154
+ `Visit ${import_chalk4.default.blue("https://github.com/Le-Sourcier/servcraft#modules")} for module list`
155
+ ],
156
+ "https://github.com/Le-Sourcier/servcraft#add-pre-built-modules"
157
+ ),
158
+ MODULE_ALREADY_EXISTS: (moduleName) => new ServCraftError(
159
+ `Module "${moduleName}" already exists`,
160
+ [
161
+ `Use ${import_chalk4.default.cyan("servcraft add " + moduleName + " --force")} to overwrite`,
162
+ `Use ${import_chalk4.default.cyan("servcraft add " + moduleName + " --update")} to update`,
163
+ `Use ${import_chalk4.default.cyan("servcraft add " + moduleName + " --skip-existing")} to skip`
164
+ ]
165
+ ),
166
+ NOT_IN_PROJECT: () => new ServCraftError(
167
+ "Not in a ServCraft project directory",
168
+ [
169
+ `Run ${import_chalk4.default.cyan("servcraft init")} to create a new project`,
170
+ `Navigate to your ServCraft project directory`,
171
+ `Check if ${import_chalk4.default.yellow("package.json")} exists`
172
+ ],
173
+ "https://github.com/Le-Sourcier/servcraft#initialize-project"
174
+ ),
175
+ FILE_ALREADY_EXISTS: (fileName) => new ServCraftError(
176
+ `File "${fileName}" already exists`,
177
+ [
178
+ `Use ${import_chalk4.default.cyan("--force")} flag to overwrite`,
179
+ `Choose a different name`,
180
+ `Delete the existing file first`
181
+ ]
182
+ ),
183
+ INVALID_DATABASE: (database) => new ServCraftError(
184
+ `Invalid database type: "${database}"`,
185
+ [
186
+ `Valid options: ${import_chalk4.default.cyan("postgresql, mysql, sqlite, mongodb, none")}`,
187
+ `Use ${import_chalk4.default.cyan("servcraft init --db postgresql")} for PostgreSQL`
188
+ ]
189
+ ),
190
+ INVALID_VALIDATOR: (validator) => new ServCraftError(
191
+ `Invalid validator type: "${validator}"`,
192
+ [`Valid options: ${import_chalk4.default.cyan("zod, joi, yup")}`, `Default is ${import_chalk4.default.cyan("zod")}`]
193
+ ),
194
+ MISSING_DEPENDENCY: (dependency, command) => new ServCraftError(
195
+ `Missing dependency: "${dependency}"`,
196
+ [`Run ${import_chalk4.default.cyan(command)} to install`, `Check your ${import_chalk4.default.yellow("package.json")}`]
197
+ ),
198
+ INVALID_FIELD_FORMAT: (field) => new ServCraftError(
199
+ `Invalid field format: "${field}"`,
200
+ [
201
+ `Expected format: ${import_chalk4.default.cyan("name:type")}`,
202
+ `Example: ${import_chalk4.default.cyan("name:string age:number isActive:boolean")}`,
203
+ `Supported types: string, number, boolean, date`
204
+ ]
205
+ ),
206
+ GIT_NOT_INITIALIZED: () => new ServCraftError(
207
+ "Git repository not initialized",
208
+ [
209
+ `Run ${import_chalk4.default.cyan("git init")} to initialize git`,
210
+ `This is required for some ServCraft features`
211
+ ]
212
+ )
213
+ };
214
+ }
215
+ });
29
216
 
30
217
  // src/cli/index.ts
31
- var import_commander6 = require("commander");
218
+ init_cjs_shims();
219
+ var import_commander9 = require("commander");
32
220
 
33
221
  // src/cli/commands/init.ts
222
+ init_cjs_shims();
34
223
  var import_commander = require("commander");
35
- var import_path2 = __toESM(require("path"), 1);
224
+ var import_path3 = __toESM(require("path"), 1);
36
225
  var import_promises2 = __toESM(require("fs/promises"), 1);
37
226
  var import_ora = __toESM(require("ora"), 1);
38
227
  var import_inquirer = __toESM(require("inquirer"), 1);
39
- var import_chalk2 = __toESM(require("chalk"), 1);
228
+ var import_chalk3 = __toESM(require("chalk"), 1);
40
229
  var import_child_process = require("child_process");
41
230
 
42
231
  // src/cli/utils/helpers.ts
232
+ init_cjs_shims();
43
233
  var import_promises = __toESM(require("fs/promises"), 1);
44
- var import_path = __toESM(require("path"), 1);
234
+ var import_path2 = __toESM(require("path"), 1);
235
+ var import_chalk2 = __toESM(require("chalk"), 1);
236
+
237
+ // src/cli/utils/dry-run.ts
238
+ init_cjs_shims();
45
239
  var import_chalk = __toESM(require("chalk"), 1);
240
+ var import_path = __toESM(require("path"), 1);
241
+ var DryRunManager = class _DryRunManager {
242
+ static instance;
243
+ enabled = false;
244
+ operations = [];
245
+ constructor() {
246
+ }
247
+ static getInstance() {
248
+ if (!_DryRunManager.instance) {
249
+ _DryRunManager.instance = new _DryRunManager();
250
+ }
251
+ return _DryRunManager.instance;
252
+ }
253
+ enable() {
254
+ this.enabled = true;
255
+ this.operations = [];
256
+ }
257
+ disable() {
258
+ this.enabled = false;
259
+ this.operations = [];
260
+ }
261
+ isEnabled() {
262
+ return this.enabled;
263
+ }
264
+ addOperation(operation) {
265
+ if (this.enabled) {
266
+ this.operations.push(operation);
267
+ }
268
+ }
269
+ getOperations() {
270
+ return [...this.operations];
271
+ }
272
+ printSummary() {
273
+ if (!this.enabled || this.operations.length === 0) {
274
+ return;
275
+ }
276
+ console.log(import_chalk.default.bold.yellow("\n\u{1F4CB} Dry Run - Preview of changes:\n"));
277
+ console.log(import_chalk.default.gray("No files will be written. Remove --dry-run to apply changes.\n"));
278
+ const createOps = this.operations.filter((op) => op.type === "create");
279
+ const modifyOps = this.operations.filter((op) => op.type === "modify");
280
+ const deleteOps = this.operations.filter((op) => op.type === "delete");
281
+ if (createOps.length > 0) {
282
+ console.log(import_chalk.default.green.bold(`
283
+ \u2713 Files to be created (${createOps.length}):`));
284
+ createOps.forEach((op) => {
285
+ const size = op.content ? `${op.content.length} bytes` : "unknown size";
286
+ console.log(` ${import_chalk.default.green("+")} ${import_chalk.default.cyan(op.path)} ${import_chalk.default.gray(`(${size})`)}`);
287
+ });
288
+ }
289
+ if (modifyOps.length > 0) {
290
+ console.log(import_chalk.default.yellow.bold(`
291
+ ~ Files to be modified (${modifyOps.length}):`));
292
+ modifyOps.forEach((op) => {
293
+ console.log(` ${import_chalk.default.yellow("~")} ${import_chalk.default.cyan(op.path)}`);
294
+ });
295
+ }
296
+ if (deleteOps.length > 0) {
297
+ console.log(import_chalk.default.red.bold(`
298
+ - Files to be deleted (${deleteOps.length}):`));
299
+ deleteOps.forEach((op) => {
300
+ console.log(` ${import_chalk.default.red("-")} ${import_chalk.default.cyan(op.path)}`);
301
+ });
302
+ }
303
+ console.log(import_chalk.default.gray("\n" + "\u2500".repeat(60)));
304
+ console.log(
305
+ import_chalk.default.bold(` Total operations: ${this.operations.length}`) + import_chalk.default.gray(
306
+ ` (${createOps.length} create, ${modifyOps.length} modify, ${deleteOps.length} delete)`
307
+ )
308
+ );
309
+ console.log(import_chalk.default.gray("\u2500".repeat(60)));
310
+ console.log(import_chalk.default.yellow("\n\u26A0 This was a dry run. No files were created or modified."));
311
+ console.log(import_chalk.default.gray(" Remove --dry-run to apply these changes.\n"));
312
+ }
313
+ // Helper to format file path relative to cwd
314
+ relativePath(filePath) {
315
+ return import_path.default.relative(process.cwd(), filePath);
316
+ }
317
+ };
318
+
319
+ // src/cli/utils/helpers.ts
46
320
  function toPascalCase(str) {
47
321
  return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
48
322
  }
@@ -74,39 +348,54 @@ async function ensureDir(dirPath) {
74
348
  await import_promises.default.mkdir(dirPath, { recursive: true });
75
349
  }
76
350
  async function writeFile(filePath, content) {
77
- await ensureDir(import_path.default.dirname(filePath));
351
+ const dryRun = DryRunManager.getInstance();
352
+ if (dryRun.isEnabled()) {
353
+ dryRun.addOperation({
354
+ type: "create",
355
+ path: dryRun.relativePath(filePath),
356
+ content,
357
+ size: content.length
358
+ });
359
+ return;
360
+ }
361
+ await ensureDir(import_path2.default.dirname(filePath));
78
362
  await import_promises.default.writeFile(filePath, content, "utf-8");
79
363
  }
80
364
  function success(message) {
81
- console.log(import_chalk.default.green("\u2713"), message);
365
+ console.log(import_chalk2.default.green("\u2713"), message);
82
366
  }
83
367
  function error(message) {
84
- console.error(import_chalk.default.red("\u2717"), message);
368
+ console.error(import_chalk2.default.red("\u2717"), message);
85
369
  }
86
370
  function warn(message) {
87
- console.log(import_chalk.default.yellow("\u26A0"), message);
371
+ console.log(import_chalk2.default.yellow("\u26A0"), message);
88
372
  }
89
373
  function info(message) {
90
- console.log(import_chalk.default.blue("\u2139"), message);
374
+ console.log(import_chalk2.default.blue("\u2139"), message);
91
375
  }
92
376
  function getProjectRoot() {
93
377
  return process.cwd();
94
378
  }
95
379
  function getSourceDir() {
96
- return import_path.default.join(getProjectRoot(), "src");
380
+ return import_path2.default.join(getProjectRoot(), "src");
97
381
  }
98
382
  function getModulesDir() {
99
- return import_path.default.join(getSourceDir(), "modules");
383
+ return import_path2.default.join(getSourceDir(), "modules");
100
384
  }
101
385
 
102
386
  // src/cli/commands/init.ts
103
- var initCommand = new import_commander.Command("init").alias("new").description("Initialize a new Servcraft project").argument("[name]", "Project name").option("-y, --yes", "Skip prompts and use defaults").option("--ts, --typescript", "Use TypeScript (default)").option("--js, --javascript", "Use JavaScript").option("--esm", "Use ES Modules (import/export) - default").option("--cjs, --commonjs", "Use CommonJS (require/module.exports)").option("--db <database>", "Database type (postgresql, mysql, sqlite, mongodb, none)").action(
387
+ var initCommand = new import_commander.Command("init").alias("new").description("Initialize a new Servcraft project").argument("[name]", "Project name").option("-y, --yes", "Skip prompts and use defaults").option("--ts, --typescript", "Use TypeScript (default)").option("--js, --javascript", "Use JavaScript").option("--esm", "Use ES Modules (import/export) - default").option("--cjs, --commonjs", "Use CommonJS (require/module.exports)").option("--db <database>", "Database type (postgresql, mysql, sqlite, mongodb, none)").option("--dry-run", "Preview changes without writing files").action(
104
388
  async (name, cmdOptions) => {
389
+ const dryRun = DryRunManager.getInstance();
390
+ if (cmdOptions?.dryRun) {
391
+ dryRun.enable();
392
+ console.log(import_chalk3.default.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
393
+ }
105
394
  console.log(
106
- import_chalk2.default.blue(`
395
+ import_chalk3.default.blue(`
107
396
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
108
397
  \u2551 \u2551
109
- \u2551 ${import_chalk2.default.bold("\u{1F680} Servcraft Project Generator")} \u2551
398
+ \u2551 ${import_chalk3.default.bold("\u{1F680} Servcraft Project Generator")} \u2551
110
399
  \u2551 \u2551
111
400
  \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
112
401
  `)
@@ -201,7 +490,7 @@ var initCommand = new import_commander.Command("init").alias("new").description(
201
490
  orm: db === "mongodb" ? "mongoose" : db === "none" ? "none" : "prisma"
202
491
  };
203
492
  }
204
- const projectDir = import_path2.default.resolve(process.cwd(), options.name);
493
+ const projectDir = import_path3.default.resolve(process.cwd(), options.name);
205
494
  const spinner = (0, import_ora.default)("Creating project...").start();
206
495
  try {
207
496
  try {
@@ -215,21 +504,21 @@ var initCommand = new import_commander.Command("init").alias("new").description(
215
504
  spinner.text = "Generating project files...";
216
505
  const packageJson = generatePackageJson(options);
217
506
  await writeFile(
218
- import_path2.default.join(projectDir, "package.json"),
507
+ import_path3.default.join(projectDir, "package.json"),
219
508
  JSON.stringify(packageJson, null, 2)
220
509
  );
221
510
  if (options.language === "typescript") {
222
- await writeFile(import_path2.default.join(projectDir, "tsconfig.json"), generateTsConfig(options));
223
- await writeFile(import_path2.default.join(projectDir, "tsup.config.ts"), generateTsupConfig(options));
511
+ await writeFile(import_path3.default.join(projectDir, "tsconfig.json"), generateTsConfig(options));
512
+ await writeFile(import_path3.default.join(projectDir, "tsup.config.ts"), generateTsupConfig(options));
224
513
  } else {
225
- await writeFile(import_path2.default.join(projectDir, "jsconfig.json"), generateJsConfig(options));
514
+ await writeFile(import_path3.default.join(projectDir, "jsconfig.json"), generateJsConfig(options));
226
515
  }
227
- await writeFile(import_path2.default.join(projectDir, ".env.example"), generateEnvExample(options));
228
- await writeFile(import_path2.default.join(projectDir, ".env"), generateEnvExample(options));
229
- await writeFile(import_path2.default.join(projectDir, ".gitignore"), generateGitignore());
230
- await writeFile(import_path2.default.join(projectDir, "Dockerfile"), generateDockerfile(options));
516
+ await writeFile(import_path3.default.join(projectDir, ".env.example"), generateEnvExample(options));
517
+ await writeFile(import_path3.default.join(projectDir, ".env"), generateEnvExample(options));
518
+ await writeFile(import_path3.default.join(projectDir, ".gitignore"), generateGitignore());
519
+ await writeFile(import_path3.default.join(projectDir, "Dockerfile"), generateDockerfile(options));
231
520
  await writeFile(
232
- import_path2.default.join(projectDir, "docker-compose.yml"),
521
+ import_path3.default.join(projectDir, "docker-compose.yml"),
233
522
  generateDockerCompose(options)
234
523
  );
235
524
  const ext = options.language === "typescript" ? "ts" : options.moduleSystem === "esm" ? "js" : "cjs";
@@ -250,59 +539,63 @@ var initCommand = new import_commander.Command("init").alias("new").description(
250
539
  dirs.push("src/database/models");
251
540
  }
252
541
  for (const dir of dirs) {
253
- await ensureDir(import_path2.default.join(projectDir, dir));
542
+ await ensureDir(import_path3.default.join(projectDir, dir));
254
543
  }
255
- await writeFile(import_path2.default.join(projectDir, `src/index.${ext}`), generateEntryFile(options));
544
+ await writeFile(import_path3.default.join(projectDir, `src/index.${ext}`), generateEntryFile(options));
256
545
  await writeFile(
257
- import_path2.default.join(projectDir, `src/core/server.${ext}`),
546
+ import_path3.default.join(projectDir, `src/core/server.${ext}`),
258
547
  generateServerFile(options)
259
548
  );
260
549
  await writeFile(
261
- import_path2.default.join(projectDir, `src/core/logger.${ext}`),
550
+ import_path3.default.join(projectDir, `src/core/logger.${ext}`),
262
551
  generateLoggerFile(options)
263
552
  );
264
553
  await writeFile(
265
- import_path2.default.join(projectDir, `src/config/index.${ext}`),
554
+ import_path3.default.join(projectDir, `src/config/index.${ext}`),
266
555
  generateConfigFile(options)
267
556
  );
268
557
  await writeFile(
269
- import_path2.default.join(projectDir, `src/middleware/index.${ext}`),
558
+ import_path3.default.join(projectDir, `src/middleware/index.${ext}`),
270
559
  generateMiddlewareFile(options)
271
560
  );
272
561
  await writeFile(
273
- import_path2.default.join(projectDir, `src/utils/index.${ext}`),
562
+ import_path3.default.join(projectDir, `src/utils/index.${ext}`),
274
563
  generateUtilsFile(options)
275
564
  );
276
565
  await writeFile(
277
- import_path2.default.join(projectDir, `src/types/index.${ext}`),
566
+ import_path3.default.join(projectDir, `src/types/index.${ext}`),
278
567
  generateTypesFile(options)
279
568
  );
280
569
  if (options.orm === "prisma") {
281
570
  await writeFile(
282
- import_path2.default.join(projectDir, "prisma/schema.prisma"),
571
+ import_path3.default.join(projectDir, "prisma/schema.prisma"),
283
572
  generatePrismaSchema(options)
284
573
  );
285
574
  } else if (options.orm === "mongoose") {
286
575
  await writeFile(
287
- import_path2.default.join(projectDir, `src/database/connection.${ext}`),
576
+ import_path3.default.join(projectDir, `src/database/connection.${ext}`),
288
577
  generateMongooseConnection(options)
289
578
  );
290
579
  await writeFile(
291
- import_path2.default.join(projectDir, `src/database/models/user.model.${ext}`),
580
+ import_path3.default.join(projectDir, `src/database/models/user.model.${ext}`),
292
581
  generateMongooseUserModel(options)
293
582
  );
294
583
  }
295
584
  spinner.succeed("Project files generated!");
296
- const installSpinner = (0, import_ora.default)("Installing dependencies...").start();
297
- try {
298
- (0, import_child_process.execSync)("npm install", { cwd: projectDir, stdio: "pipe" });
299
- installSpinner.succeed("Dependencies installed!");
300
- } catch {
301
- installSpinner.warn("Failed to install dependencies automatically");
302
- warn(' Run "npm install" manually in the project directory');
585
+ if (!cmdOptions?.dryRun) {
586
+ const installSpinner = (0, import_ora.default)("Installing dependencies...").start();
587
+ try {
588
+ (0, import_child_process.execSync)("npm install", { cwd: projectDir, stdio: "pipe" });
589
+ installSpinner.succeed("Dependencies installed!");
590
+ } catch {
591
+ installSpinner.warn("Failed to install dependencies automatically");
592
+ warn(' Run "npm install" manually in the project directory');
593
+ }
594
+ }
595
+ if (!cmdOptions?.dryRun) {
596
+ console.log("\n" + import_chalk3.default.green("\u2728 Project created successfully!"));
303
597
  }
304
- console.log("\n" + import_chalk2.default.green("\u2728 Project created successfully!"));
305
- console.log("\n" + import_chalk2.default.bold("\u{1F4C1} Project structure:"));
598
+ console.log("\n" + import_chalk3.default.bold("\u{1F4C1} Project structure:"));
306
599
  console.log(`
307
600
  ${options.name}/
308
601
  \u251C\u2500\u2500 src/
@@ -317,19 +610,22 @@ var initCommand = new import_commander.Command("init").alias("new").description(
317
610
  \u251C\u2500\u2500 docker-compose.yml
318
611
  \u2514\u2500\u2500 package.json
319
612
  `);
320
- console.log(import_chalk2.default.bold("\u{1F680} Get started:"));
613
+ console.log(import_chalk3.default.bold("\u{1F680} Get started:"));
321
614
  console.log(`
322
- ${import_chalk2.default.cyan(`cd ${options.name}`)}
323
- ${options.database !== "none" ? import_chalk2.default.cyan("npm run db:push # Setup database") : ""}
324
- ${import_chalk2.default.cyan("npm run dev # Start development server")}
615
+ ${import_chalk3.default.cyan(`cd ${options.name}`)}
616
+ ${options.database !== "none" ? import_chalk3.default.cyan("npm run db:push # Setup database") : ""}
617
+ ${import_chalk3.default.cyan("npm run dev # Start development server")}
325
618
  `);
326
- console.log(import_chalk2.default.bold("\u{1F4DA} Available commands:"));
619
+ console.log(import_chalk3.default.bold("\u{1F4DA} Available commands:"));
327
620
  console.log(`
328
- ${import_chalk2.default.yellow("servcraft generate module <name>")} Generate a new module
329
- ${import_chalk2.default.yellow("servcraft generate controller <name>")} Generate a controller
330
- ${import_chalk2.default.yellow("servcraft generate service <name>")} Generate a service
331
- ${import_chalk2.default.yellow("servcraft add auth")} Add authentication module
621
+ ${import_chalk3.default.yellow("servcraft generate module <name>")} Generate a new module
622
+ ${import_chalk3.default.yellow("servcraft generate controller <name>")} Generate a controller
623
+ ${import_chalk3.default.yellow("servcraft generate service <name>")} Generate a service
624
+ ${import_chalk3.default.yellow("servcraft add auth")} Add authentication module
332
625
  `);
626
+ if (cmdOptions?.dryRun) {
627
+ dryRun.printSummary();
628
+ }
333
629
  } catch (err) {
334
630
  spinner.fail("Failed to create project");
335
631
  error(err instanceof Error ? err.message : String(err));
@@ -1120,12 +1416,15 @@ declare module 'fastify' {
1120
1416
  }
1121
1417
 
1122
1418
  // src/cli/commands/generate.ts
1419
+ init_cjs_shims();
1123
1420
  var import_commander2 = require("commander");
1124
- var import_path3 = __toESM(require("path"), 1);
1421
+ var import_path4 = __toESM(require("path"), 1);
1125
1422
  var import_ora2 = __toESM(require("ora"), 1);
1126
1423
  var import_inquirer2 = __toESM(require("inquirer"), 1);
1424
+ var import_chalk5 = __toESM(require("chalk"), 1);
1127
1425
 
1128
1426
  // src/cli/utils/field-parser.ts
1427
+ init_cjs_shims();
1129
1428
  var tsTypeMap = {
1130
1429
  string: "string",
1131
1430
  number: "number",
@@ -1267,7 +1566,11 @@ function parseFields(fieldsStr) {
1267
1566
  return fieldsStr.split(/\s+/).filter(Boolean).map(parseField);
1268
1567
  }
1269
1568
 
1569
+ // src/cli/commands/generate.ts
1570
+ init_error_handler();
1571
+
1270
1572
  // src/cli/templates/controller.ts
1573
+ init_cjs_shims();
1271
1574
  function controllerTemplate(name, pascalName, camelName) {
1272
1575
  return `import type { FastifyRequest, FastifyReply } from 'fastify';
1273
1576
  import type { ${pascalName}Service } from './${name}.service.js';
@@ -1337,6 +1640,7 @@ export function create${pascalName}Controller(${camelName}Service: ${pascalName}
1337
1640
  }
1338
1641
 
1339
1642
  // src/cli/templates/service.ts
1643
+ init_cjs_shims();
1340
1644
  function serviceTemplate(name, pascalName, camelName) {
1341
1645
  return `import type { PaginatedResult, PaginationParams } from '../../types/index.js';
1342
1646
  import { NotFoundError, ConflictError } from '../../utils/errors.js';
@@ -1397,6 +1701,7 @@ export function create${pascalName}Service(repository?: ${pascalName}Repository)
1397
1701
  }
1398
1702
 
1399
1703
  // src/cli/templates/repository.ts
1704
+ init_cjs_shims();
1400
1705
  function repositoryTemplate(name, pascalName, camelName, pluralName) {
1401
1706
  return `import { randomUUID } from 'crypto';
1402
1707
  import type { PaginatedResult, PaginationParams } from '../../types/index.js';
@@ -1503,6 +1808,7 @@ export function create${pascalName}Repository(): ${pascalName}Repository {
1503
1808
  }
1504
1809
 
1505
1810
  // src/cli/templates/types.ts
1811
+ init_cjs_shims();
1506
1812
  function typesTemplate(name, pascalName) {
1507
1813
  return `import type { BaseEntity } from '../../types/index.js';
1508
1814
 
@@ -1532,6 +1838,7 @@ export interface ${pascalName}Filters {
1532
1838
  }
1533
1839
 
1534
1840
  // src/cli/templates/schemas.ts
1841
+ init_cjs_shims();
1535
1842
  function schemasTemplate(name, pascalName, camelName) {
1536
1843
  return `import { z } from 'zod';
1537
1844
 
@@ -1560,6 +1867,7 @@ export type ${pascalName}QueryInput = z.infer<typeof ${camelName}QuerySchema>;
1560
1867
  }
1561
1868
 
1562
1869
  // src/cli/templates/routes.ts
1870
+ init_cjs_shims();
1563
1871
  function routesTemplate(name, pascalName, camelName, pluralName) {
1564
1872
  return `import type { FastifyInstance } from 'fastify';
1565
1873
  import type { ${pascalName}Controller } from './${name}.controller.js';
@@ -1612,6 +1920,7 @@ export function register${pascalName}Routes(
1612
1920
  }
1613
1921
 
1614
1922
  // src/cli/templates/module-index.ts
1923
+ init_cjs_shims();
1615
1924
  function moduleIndexTemplate(name, pascalName, camelName) {
1616
1925
  return `import type { FastifyInstance } from 'fastify';
1617
1926
  import { logger } from '../../core/logger.js';
@@ -1647,6 +1956,7 @@ export * from './${name}.schemas.js';
1647
1956
  }
1648
1957
 
1649
1958
  // src/cli/templates/prisma-model.ts
1959
+ init_cjs_shims();
1650
1960
  function prismaModelTemplate(name, pascalName, tableName) {
1651
1961
  return `
1652
1962
  // Add this model to your prisma/schema.prisma file
@@ -1666,6 +1976,7 @@ model ${pascalName} {
1666
1976
  }
1667
1977
 
1668
1978
  // src/cli/templates/dynamic-types.ts
1979
+ init_cjs_shims();
1669
1980
  function dynamicTypesTemplate(name, pascalName, fields) {
1670
1981
  const fieldLines = fields.map((field) => {
1671
1982
  const tsType = tsTypeMap[field.type];
@@ -1710,6 +2021,7 @@ ${fields.filter((f) => ["string", "enum", "boolean"].includes(f.type)).map((f) =
1710
2021
  }
1711
2022
 
1712
2023
  // src/cli/templates/dynamic-schemas.ts
2024
+ init_cjs_shims();
1713
2025
  function dynamicSchemasTemplate(name, pascalName, camelName, fields, validator = "zod") {
1714
2026
  switch (validator) {
1715
2027
  case "joi":
@@ -1888,6 +2200,7 @@ function getJsType(field) {
1888
2200
  }
1889
2201
 
1890
2202
  // src/cli/templates/dynamic-prisma.ts
2203
+ init_cjs_shims();
1891
2204
  function dynamicPrismaTemplate(modelName, tableName, fields) {
1892
2205
  const fieldLines = [];
1893
2206
  for (const field of fields) {
@@ -1956,10 +2269,23 @@ ${indexLines.join("\n")}
1956
2269
  }
1957
2270
 
1958
2271
  // src/cli/commands/generate.ts
2272
+ function enableDryRunIfNeeded(options) {
2273
+ const dryRun = DryRunManager.getInstance();
2274
+ if (options.dryRun) {
2275
+ dryRun.enable();
2276
+ console.log(import_chalk5.default.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
2277
+ }
2278
+ }
2279
+ function showDryRunSummary(options) {
2280
+ if (options.dryRun) {
2281
+ DryRunManager.getInstance().printSummary();
2282
+ }
2283
+ }
1959
2284
  var generateCommand = new import_commander2.Command("generate").alias("g").description("Generate resources (module, controller, service, etc.)");
1960
2285
  generateCommand.command("module <name> [fields...]").alias("m").description(
1961
2286
  "Generate a complete module with controller, service, repository, types, schemas, and routes"
1962
- ).option("--no-routes", "Skip routes generation").option("--no-repository", "Skip repository generation").option("--prisma", "Generate Prisma model suggestion").option("--validator <type>", "Validator type: zod, joi, yup", "zod").option("-i, --interactive", "Interactive mode to define fields").action(async (name, fieldsArgs, options) => {
2287
+ ).option("--no-routes", "Skip routes generation").option("--no-repository", "Skip repository generation").option("--prisma", "Generate Prisma model suggestion").option("--validator <type>", "Validator type: zod, joi, yup", "zod").option("-i, --interactive", "Interactive mode to define fields").option("--dry-run", "Preview changes without writing files").action(async (name, fieldsArgs, options) => {
2288
+ enableDryRunIfNeeded(options);
1963
2289
  let fields = [];
1964
2290
  if (options.interactive) {
1965
2291
  fields = await promptForFields();
@@ -1974,7 +2300,7 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
1974
2300
  const pluralName = pluralize(kebabName);
1975
2301
  const tableName = pluralize(kebabName.replace(/-/g, "_"));
1976
2302
  const validatorType = options.validator || "zod";
1977
- const moduleDir = import_path3.default.join(getModulesDir(), kebabName);
2303
+ const moduleDir = import_path4.default.join(getModulesDir(), kebabName);
1978
2304
  if (await fileExists(moduleDir)) {
1979
2305
  spinner.stop();
1980
2306
  error(`Module "${kebabName}" already exists`);
@@ -2013,7 +2339,7 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
2013
2339
  });
2014
2340
  }
2015
2341
  for (const file of files) {
2016
- await writeFile(import_path3.default.join(moduleDir, file.name), file.content);
2342
+ await writeFile(import_path4.default.join(moduleDir, file.name), file.content);
2017
2343
  }
2018
2344
  spinner.succeed(`Module "${pascalName}" generated successfully!`);
2019
2345
  if (options.prisma || hasFields) {
@@ -2051,42 +2377,46 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
2051
2377
  info(` ${hasFields ? "3" : "4"}. Add the Prisma model to schema.prisma`);
2052
2378
  info(` ${hasFields ? "4" : "5"}. Run: npm run db:migrate`);
2053
2379
  }
2380
+ showDryRunSummary(options);
2054
2381
  } catch (err) {
2055
2382
  spinner.fail("Failed to generate module");
2056
2383
  error(err instanceof Error ? err.message : String(err));
2057
2384
  }
2058
2385
  });
2059
- generateCommand.command("controller <name>").alias("c").description("Generate a controller").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2386
+ generateCommand.command("controller <name>").alias("c").description("Generate a controller").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
2387
+ enableDryRunIfNeeded(options);
2060
2388
  const spinner = (0, import_ora2.default)("Generating controller...").start();
2061
2389
  try {
2062
2390
  const kebabName = toKebabCase(name);
2063
2391
  const pascalName = toPascalCase(name);
2064
2392
  const camelName = toCamelCase(name);
2065
2393
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2066
- const moduleDir = import_path3.default.join(getModulesDir(), moduleName);
2067
- const filePath = import_path3.default.join(moduleDir, `${kebabName}.controller.ts`);
2394
+ const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2395
+ const filePath = import_path4.default.join(moduleDir, `${kebabName}.controller.ts`);
2068
2396
  if (await fileExists(filePath)) {
2069
2397
  spinner.stop();
2070
- error(`Controller "${kebabName}" already exists`);
2398
+ displayError(ErrorTypes.FILE_ALREADY_EXISTS(`${kebabName}.controller.ts`));
2071
2399
  return;
2072
2400
  }
2073
2401
  await writeFile(filePath, controllerTemplate(kebabName, pascalName, camelName));
2074
2402
  spinner.succeed(`Controller "${pascalName}Controller" generated!`);
2075
2403
  success(` src/modules/${moduleName}/${kebabName}.controller.ts`);
2404
+ showDryRunSummary(options);
2076
2405
  } catch (err) {
2077
2406
  spinner.fail("Failed to generate controller");
2078
2407
  error(err instanceof Error ? err.message : String(err));
2079
2408
  }
2080
2409
  });
2081
- generateCommand.command("service <name>").alias("s").description("Generate a service").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2410
+ generateCommand.command("service <name>").alias("s").description("Generate a service").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
2411
+ enableDryRunIfNeeded(options);
2082
2412
  const spinner = (0, import_ora2.default)("Generating service...").start();
2083
2413
  try {
2084
2414
  const kebabName = toKebabCase(name);
2085
2415
  const pascalName = toPascalCase(name);
2086
2416
  const camelName = toCamelCase(name);
2087
2417
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2088
- const moduleDir = import_path3.default.join(getModulesDir(), moduleName);
2089
- const filePath = import_path3.default.join(moduleDir, `${kebabName}.service.ts`);
2418
+ const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2419
+ const filePath = import_path4.default.join(moduleDir, `${kebabName}.service.ts`);
2090
2420
  if (await fileExists(filePath)) {
2091
2421
  spinner.stop();
2092
2422
  error(`Service "${kebabName}" already exists`);
@@ -2095,12 +2425,14 @@ generateCommand.command("service <name>").alias("s").description("Generate a ser
2095
2425
  await writeFile(filePath, serviceTemplate(kebabName, pascalName, camelName));
2096
2426
  spinner.succeed(`Service "${pascalName}Service" generated!`);
2097
2427
  success(` src/modules/${moduleName}/${kebabName}.service.ts`);
2428
+ showDryRunSummary(options);
2098
2429
  } catch (err) {
2099
2430
  spinner.fail("Failed to generate service");
2100
2431
  error(err instanceof Error ? err.message : String(err));
2101
2432
  }
2102
2433
  });
2103
- generateCommand.command("repository <name>").alias("r").description("Generate a repository").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2434
+ generateCommand.command("repository <name>").alias("r").description("Generate a repository").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
2435
+ enableDryRunIfNeeded(options);
2104
2436
  const spinner = (0, import_ora2.default)("Generating repository...").start();
2105
2437
  try {
2106
2438
  const kebabName = toKebabCase(name);
@@ -2108,8 +2440,8 @@ generateCommand.command("repository <name>").alias("r").description("Generate a
2108
2440
  const camelName = toCamelCase(name);
2109
2441
  const pluralName = pluralize(kebabName);
2110
2442
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2111
- const moduleDir = import_path3.default.join(getModulesDir(), moduleName);
2112
- const filePath = import_path3.default.join(moduleDir, `${kebabName}.repository.ts`);
2443
+ const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2444
+ const filePath = import_path4.default.join(moduleDir, `${kebabName}.repository.ts`);
2113
2445
  if (await fileExists(filePath)) {
2114
2446
  spinner.stop();
2115
2447
  error(`Repository "${kebabName}" already exists`);
@@ -2118,19 +2450,21 @@ generateCommand.command("repository <name>").alias("r").description("Generate a
2118
2450
  await writeFile(filePath, repositoryTemplate(kebabName, pascalName, camelName, pluralName));
2119
2451
  spinner.succeed(`Repository "${pascalName}Repository" generated!`);
2120
2452
  success(` src/modules/${moduleName}/${kebabName}.repository.ts`);
2453
+ showDryRunSummary(options);
2121
2454
  } catch (err) {
2122
2455
  spinner.fail("Failed to generate repository");
2123
2456
  error(err instanceof Error ? err.message : String(err));
2124
2457
  }
2125
2458
  });
2126
- generateCommand.command("types <name>").alias("t").description("Generate types/interfaces").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2459
+ generateCommand.command("types <name>").alias("t").description("Generate types/interfaces").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
2460
+ enableDryRunIfNeeded(options);
2127
2461
  const spinner = (0, import_ora2.default)("Generating types...").start();
2128
2462
  try {
2129
2463
  const kebabName = toKebabCase(name);
2130
2464
  const pascalName = toPascalCase(name);
2131
2465
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2132
- const moduleDir = import_path3.default.join(getModulesDir(), moduleName);
2133
- const filePath = import_path3.default.join(moduleDir, `${kebabName}.types.ts`);
2466
+ const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2467
+ const filePath = import_path4.default.join(moduleDir, `${kebabName}.types.ts`);
2134
2468
  if (await fileExists(filePath)) {
2135
2469
  spinner.stop();
2136
2470
  error(`Types file "${kebabName}.types.ts" already exists`);
@@ -2139,20 +2473,22 @@ generateCommand.command("types <name>").alias("t").description("Generate types/i
2139
2473
  await writeFile(filePath, typesTemplate(kebabName, pascalName));
2140
2474
  spinner.succeed(`Types for "${pascalName}" generated!`);
2141
2475
  success(` src/modules/${moduleName}/${kebabName}.types.ts`);
2476
+ showDryRunSummary(options);
2142
2477
  } catch (err) {
2143
2478
  spinner.fail("Failed to generate types");
2144
2479
  error(err instanceof Error ? err.message : String(err));
2145
2480
  }
2146
2481
  });
2147
- generateCommand.command("schema <name>").alias("v").description("Generate validation schemas").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2482
+ generateCommand.command("schema <name>").alias("v").description("Generate validation schemas").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
2483
+ enableDryRunIfNeeded(options);
2148
2484
  const spinner = (0, import_ora2.default)("Generating schemas...").start();
2149
2485
  try {
2150
2486
  const kebabName = toKebabCase(name);
2151
2487
  const pascalName = toPascalCase(name);
2152
2488
  const camelName = toCamelCase(name);
2153
2489
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2154
- const moduleDir = import_path3.default.join(getModulesDir(), moduleName);
2155
- const filePath = import_path3.default.join(moduleDir, `${kebabName}.schemas.ts`);
2490
+ const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2491
+ const filePath = import_path4.default.join(moduleDir, `${kebabName}.schemas.ts`);
2156
2492
  if (await fileExists(filePath)) {
2157
2493
  spinner.stop();
2158
2494
  error(`Schemas file "${kebabName}.schemas.ts" already exists`);
@@ -2161,12 +2497,14 @@ generateCommand.command("schema <name>").alias("v").description("Generate valida
2161
2497
  await writeFile(filePath, schemasTemplate(kebabName, pascalName, camelName));
2162
2498
  spinner.succeed(`Schemas for "${pascalName}" generated!`);
2163
2499
  success(` src/modules/${moduleName}/${kebabName}.schemas.ts`);
2500
+ showDryRunSummary(options);
2164
2501
  } catch (err) {
2165
2502
  spinner.fail("Failed to generate schemas");
2166
2503
  error(err instanceof Error ? err.message : String(err));
2167
2504
  }
2168
2505
  });
2169
- generateCommand.command("routes <name>").description("Generate routes").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2506
+ generateCommand.command("routes <name>").description("Generate routes").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
2507
+ enableDryRunIfNeeded(options);
2170
2508
  const spinner = (0, import_ora2.default)("Generating routes...").start();
2171
2509
  try {
2172
2510
  const kebabName = toKebabCase(name);
@@ -2174,8 +2512,8 @@ generateCommand.command("routes <name>").description("Generate routes").option("
2174
2512
  const camelName = toCamelCase(name);
2175
2513
  const pluralName = pluralize(kebabName);
2176
2514
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2177
- const moduleDir = import_path3.default.join(getModulesDir(), moduleName);
2178
- const filePath = import_path3.default.join(moduleDir, `${kebabName}.routes.ts`);
2515
+ const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
2516
+ const filePath = import_path4.default.join(moduleDir, `${kebabName}.routes.ts`);
2179
2517
  if (await fileExists(filePath)) {
2180
2518
  spinner.stop();
2181
2519
  error(`Routes file "${kebabName}.routes.ts" already exists`);
@@ -2184,6 +2522,7 @@ generateCommand.command("routes <name>").description("Generate routes").option("
2184
2522
  await writeFile(filePath, routesTemplate(kebabName, pascalName, camelName, pluralName));
2185
2523
  spinner.succeed(`Routes for "${pascalName}" generated!`);
2186
2524
  success(` src/modules/${moduleName}/${kebabName}.routes.ts`);
2525
+ showDryRunSummary(options);
2187
2526
  } catch (err) {
2188
2527
  spinner.fail("Failed to generate routes");
2189
2528
  error(err instanceof Error ? err.message : String(err));
@@ -2261,22 +2600,24 @@ async function promptForFields() {
2261
2600
  }
2262
2601
 
2263
2602
  // src/cli/commands/add-module.ts
2603
+ init_cjs_shims();
2264
2604
  var import_commander3 = require("commander");
2265
- var import_path4 = __toESM(require("path"), 1);
2605
+ var import_path5 = __toESM(require("path"), 1);
2266
2606
  var import_ora3 = __toESM(require("ora"), 1);
2267
- var import_chalk4 = __toESM(require("chalk"), 1);
2607
+ var import_chalk7 = __toESM(require("chalk"), 1);
2268
2608
  var fs5 = __toESM(require("fs/promises"), 1);
2269
2609
 
2270
2610
  // src/cli/utils/env-manager.ts
2611
+ init_cjs_shims();
2271
2612
  var fs3 = __toESM(require("fs/promises"), 1);
2272
- var path4 = __toESM(require("path"), 1);
2613
+ var path5 = __toESM(require("path"), 1);
2273
2614
  var import_fs = require("fs");
2274
2615
  var EnvManager = class {
2275
2616
  envPath;
2276
2617
  envExamplePath;
2277
2618
  constructor(projectRoot) {
2278
- this.envPath = path4.join(projectRoot, ".env");
2279
- this.envExamplePath = path4.join(projectRoot, ".env.example");
2619
+ this.envPath = path5.join(projectRoot, ".env");
2620
+ this.envExamplePath = path5.join(projectRoot, ".env.example");
2280
2621
  }
2281
2622
  /**
2282
2623
  * Add environment variables to .env file
@@ -2926,16 +3267,17 @@ var EnvManager = class {
2926
3267
  };
2927
3268
 
2928
3269
  // src/cli/utils/template-manager.ts
3270
+ init_cjs_shims();
2929
3271
  var fs4 = __toESM(require("fs/promises"), 1);
2930
- var path5 = __toESM(require("path"), 1);
3272
+ var path6 = __toESM(require("path"), 1);
2931
3273
  var import_crypto = require("crypto");
2932
3274
  var import_fs2 = require("fs");
2933
3275
  var TemplateManager = class {
2934
3276
  templatesDir;
2935
3277
  manifestsDir;
2936
3278
  constructor(projectRoot) {
2937
- this.templatesDir = path5.join(projectRoot, ".servcraft", "templates");
2938
- this.manifestsDir = path5.join(projectRoot, ".servcraft", "manifests");
3279
+ this.templatesDir = path6.join(projectRoot, ".servcraft", "templates");
3280
+ this.manifestsDir = path6.join(projectRoot, ".servcraft", "manifests");
2939
3281
  }
2940
3282
  /**
2941
3283
  * Initialize template system
@@ -2949,10 +3291,10 @@ var TemplateManager = class {
2949
3291
  */
2950
3292
  async saveTemplate(moduleName, files) {
2951
3293
  await this.initialize();
2952
- const moduleTemplateDir = path5.join(this.templatesDir, moduleName);
3294
+ const moduleTemplateDir = path6.join(this.templatesDir, moduleName);
2953
3295
  await fs4.mkdir(moduleTemplateDir, { recursive: true });
2954
3296
  for (const [fileName, content] of Object.entries(files)) {
2955
- const filePath = path5.join(moduleTemplateDir, fileName);
3297
+ const filePath = path6.join(moduleTemplateDir, fileName);
2956
3298
  await fs4.writeFile(filePath, content, "utf-8");
2957
3299
  }
2958
3300
  }
@@ -2961,7 +3303,7 @@ var TemplateManager = class {
2961
3303
  */
2962
3304
  async getTemplate(moduleName, fileName) {
2963
3305
  try {
2964
- const filePath = path5.join(this.templatesDir, moduleName, fileName);
3306
+ const filePath = path6.join(this.templatesDir, moduleName, fileName);
2965
3307
  return await fs4.readFile(filePath, "utf-8");
2966
3308
  } catch {
2967
3309
  return null;
@@ -2986,7 +3328,7 @@ var TemplateManager = class {
2986
3328
  installedAt: /* @__PURE__ */ new Date(),
2987
3329
  updatedAt: /* @__PURE__ */ new Date()
2988
3330
  };
2989
- const manifestPath = path5.join(this.manifestsDir, `${moduleName}.json`);
3331
+ const manifestPath = path6.join(this.manifestsDir, `${moduleName}.json`);
2990
3332
  await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
2991
3333
  }
2992
3334
  /**
@@ -2994,7 +3336,7 @@ var TemplateManager = class {
2994
3336
  */
2995
3337
  async getManifest(moduleName) {
2996
3338
  try {
2997
- const manifestPath = path5.join(this.manifestsDir, `${moduleName}.json`);
3339
+ const manifestPath = path6.join(this.manifestsDir, `${moduleName}.json`);
2998
3340
  const content = await fs4.readFile(manifestPath, "utf-8");
2999
3341
  return JSON.parse(content);
3000
3342
  } catch {
@@ -3023,7 +3365,7 @@ var TemplateManager = class {
3023
3365
  }
3024
3366
  const results = [];
3025
3367
  for (const [fileName, fileInfo] of Object.entries(manifest.files)) {
3026
- const filePath = path5.join(moduleDir, fileName);
3368
+ const filePath = path6.join(moduleDir, fileName);
3027
3369
  if (!(0, import_fs2.existsSync)(filePath)) {
3028
3370
  results.push({
3029
3371
  fileName,
@@ -3049,7 +3391,7 @@ var TemplateManager = class {
3049
3391
  */
3050
3392
  async createBackup(moduleName, moduleDir) {
3051
3393
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
3052
- const backupDir = path5.join(path5.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
3394
+ const backupDir = path6.join(path6.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
3053
3395
  await this.copyDirectory(moduleDir, backupDir);
3054
3396
  return backupDir;
3055
3397
  }
@@ -3060,8 +3402,8 @@ var TemplateManager = class {
3060
3402
  await fs4.mkdir(dest, { recursive: true });
3061
3403
  const entries = await fs4.readdir(src, { withFileTypes: true });
3062
3404
  for (const entry of entries) {
3063
- const srcPath = path5.join(src, entry.name);
3064
- const destPath = path5.join(dest, entry.name);
3405
+ const srcPath = path6.join(src, entry.name);
3406
+ const destPath = path6.join(dest, entry.name);
3065
3407
  if (entry.isDirectory()) {
3066
3408
  await this.copyDirectory(srcPath, destPath);
3067
3409
  } else {
@@ -3162,23 +3504,24 @@ var TemplateManager = class {
3162
3504
  }
3163
3505
  manifest.files = fileHashes;
3164
3506
  manifest.updatedAt = /* @__PURE__ */ new Date();
3165
- const manifestPath = path5.join(this.manifestsDir, `${moduleName}.json`);
3507
+ const manifestPath = path6.join(this.manifestsDir, `${moduleName}.json`);
3166
3508
  await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3167
3509
  }
3168
3510
  };
3169
3511
 
3170
3512
  // src/cli/utils/interactive-prompt.ts
3513
+ init_cjs_shims();
3171
3514
  var import_inquirer3 = __toESM(require("inquirer"), 1);
3172
- var import_chalk3 = __toESM(require("chalk"), 1);
3515
+ var import_chalk6 = __toESM(require("chalk"), 1);
3173
3516
  var InteractivePrompt = class {
3174
3517
  /**
3175
3518
  * Ask what to do when module already exists
3176
3519
  */
3177
3520
  static async askModuleExists(moduleName, hasModifications) {
3178
- console.log(import_chalk3.default.yellow(`
3521
+ console.log(import_chalk6.default.yellow(`
3179
3522
  \u26A0\uFE0F Module "${moduleName}" already exists`));
3180
3523
  if (hasModifications) {
3181
- console.log(import_chalk3.default.yellow(" Some files have been modified by you.\n"));
3524
+ console.log(import_chalk6.default.yellow(" Some files have been modified by you.\n"));
3182
3525
  }
3183
3526
  const { action } = await import_inquirer3.default.prompt([
3184
3527
  {
@@ -3216,12 +3559,12 @@ var InteractivePrompt = class {
3216
3559
  * Ask what to do with a specific file
3217
3560
  */
3218
3561
  static async askFileAction(fileName, isModified, yourLines, newLines) {
3219
- console.log(import_chalk3.default.cyan(`
3562
+ console.log(import_chalk6.default.cyan(`
3220
3563
  \u{1F4C1} ${fileName}`));
3221
3564
  console.log(
3222
- import_chalk3.default.gray(` Your version: ${yourLines} lines${isModified ? " (modified)" : ""}`)
3565
+ import_chalk6.default.gray(` Your version: ${yourLines} lines${isModified ? " (modified)" : ""}`)
3223
3566
  );
3224
- console.log(import_chalk3.default.gray(` New version: ${newLines} lines
3567
+ console.log(import_chalk6.default.gray(` New version: ${newLines} lines
3225
3568
  `));
3226
3569
  const { action } = await import_inquirer3.default.prompt([
3227
3570
  {
@@ -3273,7 +3616,7 @@ var InteractivePrompt = class {
3273
3616
  * Display diff and ask to continue
3274
3617
  */
3275
3618
  static async showDiffAndAsk(diff) {
3276
- console.log(import_chalk3.default.cyan("\n\u{1F4CA} Differences:\n"));
3619
+ console.log(import_chalk6.default.cyan("\n\u{1F4CA} Differences:\n"));
3277
3620
  console.log(diff);
3278
3621
  return await this.confirm("\nDo you want to proceed with this change?", true);
3279
3622
  }
@@ -3281,41 +3624,41 @@ var InteractivePrompt = class {
3281
3624
  * Display merge conflicts
3282
3625
  */
3283
3626
  static displayConflicts(conflicts) {
3284
- console.log(import_chalk3.default.red("\n\u26A0\uFE0F Merge Conflicts Detected:\n"));
3627
+ console.log(import_chalk6.default.red("\n\u26A0\uFE0F Merge Conflicts Detected:\n"));
3285
3628
  conflicts.forEach((conflict, i) => {
3286
- console.log(import_chalk3.default.yellow(` ${i + 1}. ${conflict}`));
3629
+ console.log(import_chalk6.default.yellow(` ${i + 1}. ${conflict}`));
3287
3630
  });
3288
- console.log(import_chalk3.default.gray("\n Conflict markers have been added to the file:"));
3289
- console.log(import_chalk3.default.gray(" <<<<<<< YOUR VERSION"));
3290
- console.log(import_chalk3.default.gray(" ... your code ..."));
3291
- console.log(import_chalk3.default.gray(" ======="));
3292
- console.log(import_chalk3.default.gray(" ... new code ..."));
3293
- console.log(import_chalk3.default.gray(" >>>>>>> NEW VERSION\n"));
3631
+ console.log(import_chalk6.default.gray("\n Conflict markers have been added to the file:"));
3632
+ console.log(import_chalk6.default.gray(" <<<<<<< YOUR VERSION"));
3633
+ console.log(import_chalk6.default.gray(" ... your code ..."));
3634
+ console.log(import_chalk6.default.gray(" ======="));
3635
+ console.log(import_chalk6.default.gray(" ... new code ..."));
3636
+ console.log(import_chalk6.default.gray(" >>>>>>> NEW VERSION\n"));
3294
3637
  }
3295
3638
  /**
3296
3639
  * Show backup location
3297
3640
  */
3298
3641
  static showBackupCreated(backupPath) {
3299
- console.log(import_chalk3.default.green(`
3300
- \u2713 Backup created: ${import_chalk3.default.cyan(backupPath)}`));
3642
+ console.log(import_chalk6.default.green(`
3643
+ \u2713 Backup created: ${import_chalk6.default.cyan(backupPath)}`));
3301
3644
  }
3302
3645
  /**
3303
3646
  * Show merge summary
3304
3647
  */
3305
3648
  static showMergeSummary(stats) {
3306
- console.log(import_chalk3.default.bold("\n\u{1F4CA} Merge Summary:\n"));
3649
+ console.log(import_chalk6.default.bold("\n\u{1F4CA} Merge Summary:\n"));
3307
3650
  if (stats.merged > 0) {
3308
- console.log(import_chalk3.default.green(` \u2713 Merged: ${stats.merged} file(s)`));
3651
+ console.log(import_chalk6.default.green(` \u2713 Merged: ${stats.merged} file(s)`));
3309
3652
  }
3310
3653
  if (stats.kept > 0) {
3311
- console.log(import_chalk3.default.blue(` \u2192 Kept: ${stats.kept} file(s)`));
3654
+ console.log(import_chalk6.default.blue(` \u2192 Kept: ${stats.kept} file(s)`));
3312
3655
  }
3313
3656
  if (stats.overwritten > 0) {
3314
- console.log(import_chalk3.default.yellow(` \u26A0 Overwritten: ${stats.overwritten} file(s)`));
3657
+ console.log(import_chalk6.default.yellow(` \u26A0 Overwritten: ${stats.overwritten} file(s)`));
3315
3658
  }
3316
3659
  if (stats.conflicts > 0) {
3317
- console.log(import_chalk3.default.red(` \u26A0 Conflicts: ${stats.conflicts} file(s)`));
3318
- console.log(import_chalk3.default.gray("\n Please resolve conflicts manually before committing.\n"));
3660
+ console.log(import_chalk6.default.red(` \u26A0 Conflicts: ${stats.conflicts} file(s)`));
3661
+ console.log(import_chalk6.default.gray("\n Please resolve conflicts manually before committing.\n"));
3319
3662
  }
3320
3663
  }
3321
3664
  /**
@@ -3353,6 +3696,7 @@ var InteractivePrompt = class {
3353
3696
  };
3354
3697
 
3355
3698
  // src/cli/commands/add-module.ts
3699
+ init_error_handler();
3356
3700
  var AVAILABLE_MODULES = {
3357
3701
  auth: {
3358
3702
  name: "Authentication",
@@ -3487,31 +3831,40 @@ var AVAILABLE_MODULES = {
3487
3831
  var addModuleCommand = new import_commander3.Command("add").description("Add a pre-built module to your project").argument(
3488
3832
  "[module]",
3489
3833
  "Module to add (auth, users, email, audit, upload, cache, notifications, settings)"
3490
- ).option("-l, --list", "List available modules").option("-f, --force", "Force overwrite existing module").option("-u, --update", "Update existing module (smart merge)").option("--skip-existing", "Skip if module already exists").action(
3834
+ ).option("-l, --list", "List available modules").option("-f, --force", "Force overwrite existing module").option("-u, --update", "Update existing module (smart merge)").option("--skip-existing", "Skip if module already exists").option("--dry-run", "Preview changes without writing files").action(
3491
3835
  async (moduleName, options) => {
3492
3836
  if (options?.list || !moduleName) {
3493
- console.log(import_chalk4.default.bold("\n\u{1F4E6} Available Modules:\n"));
3837
+ console.log(import_chalk7.default.bold("\n\u{1F4E6} Available Modules:\n"));
3494
3838
  for (const [key, mod] of Object.entries(AVAILABLE_MODULES)) {
3495
- console.log(` ${import_chalk4.default.cyan(key.padEnd(15))} ${mod.name}`);
3496
- console.log(` ${" ".repeat(15)} ${import_chalk4.default.gray(mod.description)}
3839
+ console.log(` ${import_chalk7.default.cyan(key.padEnd(15))} ${mod.name}`);
3840
+ console.log(` ${" ".repeat(15)} ${import_chalk7.default.gray(mod.description)}
3497
3841
  `);
3498
3842
  }
3499
- console.log(import_chalk4.default.bold("Usage:"));
3500
- console.log(` ${import_chalk4.default.yellow("servcraft add auth")} Add authentication module`);
3501
- console.log(` ${import_chalk4.default.yellow("servcraft add users")} Add user management module`);
3502
- console.log(` ${import_chalk4.default.yellow("servcraft add email")} Add email service module
3843
+ console.log(import_chalk7.default.bold("Usage:"));
3844
+ console.log(` ${import_chalk7.default.yellow("servcraft add auth")} Add authentication module`);
3845
+ console.log(` ${import_chalk7.default.yellow("servcraft add users")} Add user management module`);
3846
+ console.log(` ${import_chalk7.default.yellow("servcraft add email")} Add email service module
3503
3847
  `);
3504
3848
  return;
3505
3849
  }
3850
+ const dryRun = DryRunManager.getInstance();
3851
+ if (options?.dryRun) {
3852
+ dryRun.enable();
3853
+ console.log(import_chalk7.default.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
3854
+ }
3506
3855
  const module2 = AVAILABLE_MODULES[moduleName];
3507
3856
  if (!module2) {
3508
- error(`Unknown module: ${moduleName}`);
3509
- info('Run "servcraft add --list" to see available modules');
3857
+ displayError(ErrorTypes.MODULE_NOT_FOUND(moduleName));
3858
+ return;
3859
+ }
3860
+ const projectError = validateProject();
3861
+ if (projectError) {
3862
+ displayError(projectError);
3510
3863
  return;
3511
3864
  }
3512
3865
  const spinner = (0, import_ora3.default)(`Adding ${module2.name} module...`).start();
3513
3866
  try {
3514
- const moduleDir = import_path4.default.join(getModulesDir(), moduleName);
3867
+ const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
3515
3868
  const templateManager = new TemplateManager(process.cwd());
3516
3869
  const moduleExists = await fileExists(moduleDir);
3517
3870
  if (moduleExists) {
@@ -3579,16 +3932,16 @@ var addModuleCommand = new import_commander3.Command("add").description("Add a p
3579
3932
  info("\n\u{1F4DD} Created new .env file");
3580
3933
  }
3581
3934
  if (result.added.length > 0) {
3582
- console.log(import_chalk4.default.bold("\n\u2705 Added to .env:"));
3935
+ console.log(import_chalk7.default.bold("\n\u2705 Added to .env:"));
3583
3936
  result.added.forEach((key) => success(` ${key}`));
3584
3937
  }
3585
3938
  if (result.skipped.length > 0) {
3586
- console.log(import_chalk4.default.bold("\n\u23ED\uFE0F Already in .env (skipped):"));
3939
+ console.log(import_chalk7.default.bold("\n\u23ED\uFE0F Already in .env (skipped):"));
3587
3940
  result.skipped.forEach((key) => info(` ${key}`));
3588
3941
  }
3589
3942
  const requiredVars = envSections.flatMap((section) => section.variables).filter((v) => v.required && !v.value).map((v) => v.key);
3590
3943
  if (requiredVars.length > 0) {
3591
- console.log(import_chalk4.default.bold("\n\u26A0\uFE0F Required configuration:"));
3944
+ console.log(import_chalk7.default.bold("\n\u26A0\uFE0F Required configuration:"));
3592
3945
  requiredVars.forEach((key) => warn(` ${key} - Please configure this variable`));
3593
3946
  }
3594
3947
  } catch (err) {
@@ -3596,10 +3949,15 @@ var addModuleCommand = new import_commander3.Command("add").description("Add a p
3596
3949
  error(err instanceof Error ? err.message : String(err));
3597
3950
  }
3598
3951
  }
3599
- console.log("\n\u{1F4CC} Next steps:");
3600
- info(" 1. Configure environment variables in .env (if needed)");
3601
- info(" 2. Register the module in your main app file");
3602
- info(" 3. Run database migrations if needed");
3952
+ if (!options?.dryRun) {
3953
+ console.log("\n\u{1F4CC} Next steps:");
3954
+ info(" 1. Configure environment variables in .env (if needed)");
3955
+ info(" 2. Register the module in your main app file");
3956
+ info(" 3. Run database migrations if needed");
3957
+ }
3958
+ if (options?.dryRun) {
3959
+ dryRun.printSummary();
3960
+ }
3603
3961
  } catch (err) {
3604
3962
  spinner.fail("Failed to add module");
3605
3963
  error(err instanceof Error ? err.message : String(err));
@@ -3650,7 +4008,7 @@ export * from './auth.schemas.js';
3650
4008
  `
3651
4009
  };
3652
4010
  for (const [name, content] of Object.entries(files)) {
3653
- await writeFile(import_path4.default.join(dir, name), content);
4011
+ await writeFile(import_path5.default.join(dir, name), content);
3654
4012
  }
3655
4013
  }
3656
4014
  async function generateUsersModule(dir) {
@@ -3690,7 +4048,7 @@ export * from './user.schemas.js';
3690
4048
  `
3691
4049
  };
3692
4050
  for (const [name, content] of Object.entries(files)) {
3693
- await writeFile(import_path4.default.join(dir, name), content);
4051
+ await writeFile(import_path5.default.join(dir, name), content);
3694
4052
  }
3695
4053
  }
3696
4054
  async function generateEmailModule(dir) {
@@ -3747,7 +4105,7 @@ export { EmailService, emailService } from './email.service.js';
3747
4105
  `
3748
4106
  };
3749
4107
  for (const [name, content] of Object.entries(files)) {
3750
- await writeFile(import_path4.default.join(dir, name), content);
4108
+ await writeFile(import_path5.default.join(dir, name), content);
3751
4109
  }
3752
4110
  }
3753
4111
  async function generateAuditModule(dir) {
@@ -3791,7 +4149,7 @@ export { AuditService, auditService } from './audit.service.js';
3791
4149
  `
3792
4150
  };
3793
4151
  for (const [name, content] of Object.entries(files)) {
3794
- await writeFile(import_path4.default.join(dir, name), content);
4152
+ await writeFile(import_path5.default.join(dir, name), content);
3795
4153
  }
3796
4154
  }
3797
4155
  async function generateUploadModule(dir) {
@@ -3817,7 +4175,7 @@ export interface UploadOptions {
3817
4175
  `
3818
4176
  };
3819
4177
  for (const [name, content] of Object.entries(files)) {
3820
- await writeFile(import_path4.default.join(dir, name), content);
4178
+ await writeFile(import_path5.default.join(dir, name), content);
3821
4179
  }
3822
4180
  }
3823
4181
  async function generateCacheModule(dir) {
@@ -3863,7 +4221,7 @@ export { CacheService, cacheService } from './cache.service.js';
3863
4221
  `
3864
4222
  };
3865
4223
  for (const [name, content] of Object.entries(files)) {
3866
- await writeFile(import_path4.default.join(dir, name), content);
4224
+ await writeFile(import_path5.default.join(dir, name), content);
3867
4225
  }
3868
4226
  }
3869
4227
  async function generateGenericModule(dir, name) {
@@ -3877,18 +4235,18 @@ export interface ${name.charAt(0).toUpperCase() + name.slice(1)}Data {
3877
4235
  `
3878
4236
  };
3879
4237
  for (const [fileName, content] of Object.entries(files)) {
3880
- await writeFile(import_path4.default.join(dir, fileName), content);
4238
+ await writeFile(import_path5.default.join(dir, fileName), content);
3881
4239
  }
3882
4240
  }
3883
4241
  async function findServercraftModules() {
3884
- const scriptDir = import_path4.default.dirname(new URL(importMetaUrl).pathname);
4242
+ const scriptDir = import_path5.default.dirname(new URL(importMetaUrl).pathname);
3885
4243
  const possiblePaths = [
3886
4244
  // Local node_modules (when servcraft is a dependency)
3887
- import_path4.default.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
4245
+ import_path5.default.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
3888
4246
  // From dist/cli/index.js -> src/modules (npx or global install)
3889
- import_path4.default.resolve(scriptDir, "..", "..", "src", "modules"),
4247
+ import_path5.default.resolve(scriptDir, "..", "..", "src", "modules"),
3890
4248
  // From src/cli/commands/add-module.ts -> src/modules (development)
3891
- import_path4.default.resolve(scriptDir, "..", "..", "modules")
4249
+ import_path5.default.resolve(scriptDir, "..", "..", "modules")
3892
4250
  ];
3893
4251
  for (const p of possiblePaths) {
3894
4252
  try {
@@ -3912,7 +4270,7 @@ async function generateModuleFiles(moduleName, moduleDir) {
3912
4270
  const sourceDirName = moduleNameMap[moduleName] || moduleName;
3913
4271
  const servercraftModulesDir = await findServercraftModules();
3914
4272
  if (servercraftModulesDir) {
3915
- const sourceModuleDir = import_path4.default.join(servercraftModulesDir, sourceDirName);
4273
+ const sourceModuleDir = import_path5.default.join(servercraftModulesDir, sourceDirName);
3916
4274
  if (await fileExists(sourceModuleDir)) {
3917
4275
  await copyModuleFromSource(sourceModuleDir, moduleDir);
3918
4276
  return;
@@ -3944,8 +4302,8 @@ async function generateModuleFiles(moduleName, moduleDir) {
3944
4302
  async function copyModuleFromSource(sourceDir, targetDir) {
3945
4303
  const entries = await fs5.readdir(sourceDir, { withFileTypes: true });
3946
4304
  for (const entry of entries) {
3947
- const sourcePath = import_path4.default.join(sourceDir, entry.name);
3948
- const targetPath = import_path4.default.join(targetDir, entry.name);
4305
+ const sourcePath = import_path5.default.join(sourceDir, entry.name);
4306
+ const targetPath = import_path5.default.join(targetDir, entry.name);
3949
4307
  if (entry.isDirectory()) {
3950
4308
  await fs5.mkdir(targetPath, { recursive: true });
3951
4309
  await copyModuleFromSource(sourcePath, targetPath);
@@ -3958,7 +4316,7 @@ async function getModuleFiles(moduleName, moduleDir) {
3958
4316
  const files = {};
3959
4317
  const entries = await fs5.readdir(moduleDir);
3960
4318
  for (const entry of entries) {
3961
- const filePath = import_path4.default.join(moduleDir, entry);
4319
+ const filePath = import_path5.default.join(moduleDir, entry);
3962
4320
  const stat2 = await fs5.stat(filePath);
3963
4321
  if (stat2.isFile() && entry.endsWith(".ts")) {
3964
4322
  const content = await fs5.readFile(filePath, "utf-8");
@@ -3969,14 +4327,14 @@ async function getModuleFiles(moduleName, moduleDir) {
3969
4327
  }
3970
4328
  async function showDiffForModule(templateManager, moduleName, moduleDir) {
3971
4329
  const modifiedFiles = await templateManager.getModifiedFiles(moduleName, moduleDir);
3972
- console.log(import_chalk4.default.cyan(`
4330
+ console.log(import_chalk7.default.cyan(`
3973
4331
  \u{1F4CA} Changes in module "${moduleName}":
3974
4332
  `));
3975
4333
  for (const file of modifiedFiles) {
3976
4334
  if (file.isModified) {
3977
- console.log(import_chalk4.default.yellow(`
4335
+ console.log(import_chalk7.default.yellow(`
3978
4336
  \u{1F4C4} ${file.fileName}:`));
3979
- const currentPath = import_path4.default.join(moduleDir, file.fileName);
4337
+ const currentPath = import_path5.default.join(moduleDir, file.fileName);
3980
4338
  const currentContent = await fs5.readFile(currentPath, "utf-8");
3981
4339
  const originalContent = await templateManager.getTemplate(moduleName, file.fileName);
3982
4340
  if (originalContent) {
@@ -3989,11 +4347,11 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
3989
4347
  async function performSmartMerge(templateManager, moduleName, moduleDir, _displayName) {
3990
4348
  const spinner = (0, import_ora3.default)("Analyzing files for merge...").start();
3991
4349
  const newFiles = {};
3992
- const templateDir = import_path4.default.join(templateManager["templatesDir"], moduleName);
4350
+ const templateDir = import_path5.default.join(templateManager["templatesDir"], moduleName);
3993
4351
  try {
3994
4352
  const entries = await fs5.readdir(templateDir);
3995
4353
  for (const entry of entries) {
3996
- const content = await fs5.readFile(import_path4.default.join(templateDir, entry), "utf-8");
4354
+ const content = await fs5.readFile(import_path5.default.join(templateDir, entry), "utf-8");
3997
4355
  newFiles[entry] = content;
3998
4356
  }
3999
4357
  } catch {
@@ -4011,7 +4369,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4011
4369
  };
4012
4370
  for (const fileInfo of modifiedFiles) {
4013
4371
  const fileName = fileInfo.fileName;
4014
- const filePath = import_path4.default.join(moduleDir, fileName);
4372
+ const filePath = import_path5.default.join(moduleDir, fileName);
4015
4373
  const newContent = newFiles[fileName];
4016
4374
  if (!newContent) {
4017
4375
  continue;
@@ -4080,10 +4438,11 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4080
4438
  }
4081
4439
 
4082
4440
  // src/cli/commands/db.ts
4441
+ init_cjs_shims();
4083
4442
  var import_commander4 = require("commander");
4084
4443
  var import_child_process2 = require("child_process");
4085
4444
  var import_ora4 = __toESM(require("ora"), 1);
4086
- var import_chalk5 = __toESM(require("chalk"), 1);
4445
+ var import_chalk8 = __toESM(require("chalk"), 1);
4087
4446
  var dbCommand = new import_commander4.Command("db").description("Database management commands");
4088
4447
  dbCommand.command("migrate").description("Run database migrations").option("-n, --name <name>", "Migration name").action(async (options) => {
4089
4448
  const spinner = (0, import_ora4.default)("Running migrations...").start();
@@ -4140,7 +4499,7 @@ dbCommand.command("seed").description("Run database seed").action(async () => {
4140
4499
  });
4141
4500
  dbCommand.command("reset").description("Reset database (drop all data and re-run migrations)").option("-f, --force", "Skip confirmation").action(async (options) => {
4142
4501
  if (!options.force) {
4143
- console.log(import_chalk5.default.yellow("\n\u26A0\uFE0F WARNING: This will delete all data in your database!\n"));
4502
+ console.log(import_chalk8.default.yellow("\n\u26A0\uFE0F WARNING: This will delete all data in your database!\n"));
4144
4503
  const readline = await import("readline");
4145
4504
  const rl = readline.createInterface({
4146
4505
  input: process.stdin,
@@ -4173,21 +4532,25 @@ dbCommand.command("status").description("Show migration status").action(async ()
4173
4532
  });
4174
4533
 
4175
4534
  // src/cli/commands/docs.ts
4535
+ init_cjs_shims();
4176
4536
  var import_commander5 = require("commander");
4177
- var import_path6 = __toESM(require("path"), 1);
4537
+ var import_path7 = __toESM(require("path"), 1);
4178
4538
  var import_promises4 = __toESM(require("fs/promises"), 1);
4179
4539
  var import_ora6 = __toESM(require("ora"), 1);
4180
- var import_chalk6 = __toESM(require("chalk"), 1);
4540
+ var import_chalk9 = __toESM(require("chalk"), 1);
4181
4541
 
4182
4542
  // src/cli/utils/docs-generator.ts
4543
+ init_cjs_shims();
4183
4544
  var import_promises3 = __toESM(require("fs/promises"), 1);
4184
- var import_path5 = __toESM(require("path"), 1);
4545
+ var import_path6 = __toESM(require("path"), 1);
4185
4546
  var import_ora5 = __toESM(require("ora"), 1);
4186
4547
 
4187
4548
  // src/core/server.ts
4549
+ init_cjs_shims();
4188
4550
  var import_fastify = __toESM(require("fastify"), 1);
4189
4551
 
4190
4552
  // src/core/logger.ts
4553
+ init_cjs_shims();
4191
4554
  var import_pino = __toESM(require("pino"), 1);
4192
4555
  var defaultConfig = {
4193
4556
  level: process.env.LOG_LEVEL || "info",
@@ -4320,7 +4683,14 @@ function createServer(config2 = {}) {
4320
4683
  return new Server(config2);
4321
4684
  }
4322
4685
 
4686
+ // src/middleware/index.ts
4687
+ init_cjs_shims();
4688
+
4689
+ // src/middleware/error-handler.ts
4690
+ init_cjs_shims();
4691
+
4323
4692
  // src/utils/errors.ts
4693
+ init_cjs_shims();
4324
4694
  var AppError = class _AppError extends Error {
4325
4695
  statusCode;
4326
4696
  isOperational;
@@ -4374,7 +4744,11 @@ function isAppError(error2) {
4374
4744
  return error2 instanceof AppError;
4375
4745
  }
4376
4746
 
4747
+ // src/config/index.ts
4748
+ init_cjs_shims();
4749
+
4377
4750
  // src/config/env.ts
4751
+ init_cjs_shims();
4378
4752
  var import_zod = require("zod");
4379
4753
  var import_dotenv = __toESM(require("dotenv"), 1);
4380
4754
  import_dotenv.default.config();
@@ -4520,6 +4894,7 @@ function registerErrorHandler(app) {
4520
4894
  }
4521
4895
 
4522
4896
  // src/middleware/security.ts
4897
+ init_cjs_shims();
4523
4898
  var import_helmet = __toESM(require("@fastify/helmet"), 1);
4524
4899
  var import_cors = __toESM(require("@fastify/cors"), 1);
4525
4900
  var import_rate_limit = __toESM(require("@fastify/rate-limit"), 1);
@@ -4579,7 +4954,11 @@ async function registerSecurity(app, options = {}) {
4579
4954
  }
4580
4955
  }
4581
4956
 
4957
+ // src/modules/swagger/index.ts
4958
+ init_cjs_shims();
4959
+
4582
4960
  // src/modules/swagger/swagger.service.ts
4961
+ init_cjs_shims();
4583
4962
  var import_swagger = __toESM(require("@fastify/swagger"), 1);
4584
4963
  var import_swagger_ui = __toESM(require("@fastify/swagger-ui"), 1);
4585
4964
  var defaultConfig3 = {
@@ -4639,11 +5018,16 @@ async function registerSwagger(app, customConfig) {
4639
5018
  logger.info("Swagger documentation registered at /docs");
4640
5019
  }
4641
5020
 
5021
+ // src/modules/swagger/schema-builder.ts
5022
+ init_cjs_shims();
5023
+
4642
5024
  // src/modules/auth/index.ts
5025
+ init_cjs_shims();
4643
5026
  var import_jwt = __toESM(require("@fastify/jwt"), 1);
4644
5027
  var import_cookie = __toESM(require("@fastify/cookie"), 1);
4645
5028
 
4646
5029
  // src/modules/auth/auth.service.ts
5030
+ init_cjs_shims();
4647
5031
  var import_bcryptjs = __toESM(require("bcryptjs"), 1);
4648
5032
  var import_ioredis = require("ioredis");
4649
5033
  var AuthService = class {
@@ -4842,7 +5226,11 @@ function createAuthService(app) {
4842
5226
  return new AuthService(app);
4843
5227
  }
4844
5228
 
5229
+ // src/modules/auth/auth.controller.ts
5230
+ init_cjs_shims();
5231
+
4845
5232
  // src/modules/auth/schemas.ts
5233
+ init_cjs_shims();
4846
5234
  var import_zod2 = require("zod");
4847
5235
  var loginSchema = import_zod2.z.object({
4848
5236
  email: import_zod2.z.string().email("Invalid email address"),
@@ -4869,6 +5257,7 @@ var changePasswordSchema = import_zod2.z.object({
4869
5257
  });
4870
5258
 
4871
5259
  // src/utils/response.ts
5260
+ init_cjs_shims();
4872
5261
  function success2(reply, data, statusCode = 200) {
4873
5262
  const response = {
4874
5263
  success: true,
@@ -4884,6 +5273,7 @@ function noContent(reply) {
4884
5273
  }
4885
5274
 
4886
5275
  // src/modules/validation/validator.ts
5276
+ init_cjs_shims();
4887
5277
  var import_zod3 = require("zod");
4888
5278
  function validateBody(schema, data) {
4889
5279
  const result = schema.safeParse(data);
@@ -4902,11 +5292,11 @@ function validateQuery(schema, data) {
4902
5292
  function formatZodErrors(error2) {
4903
5293
  const errors = {};
4904
5294
  for (const issue of error2.issues) {
4905
- const path9 = issue.path.join(".") || "root";
4906
- if (!errors[path9]) {
4907
- errors[path9] = [];
5295
+ const path11 = issue.path.join(".") || "root";
5296
+ if (!errors[path11]) {
5297
+ errors[path11] = [];
4908
5298
  }
4909
- errors[path9].push(issue.message);
5299
+ errors[path11].push(issue.message);
4910
5300
  }
4911
5301
  return errors;
4912
5302
  }
@@ -5054,7 +5444,11 @@ function createAuthController(authService, userService) {
5054
5444
  return new AuthController(authService, userService);
5055
5445
  }
5056
5446
 
5447
+ // src/modules/auth/auth.routes.ts
5448
+ init_cjs_shims();
5449
+
5057
5450
  // src/modules/auth/auth.middleware.ts
5451
+ init_cjs_shims();
5058
5452
  function createAuthMiddleware(authService) {
5059
5453
  return async function authenticate(request, _reply) {
5060
5454
  const authHeader = request.headers.authorization;
@@ -5097,7 +5491,14 @@ function registerAuthRoutes(app, controller, authService) {
5097
5491
  );
5098
5492
  }
5099
5493
 
5494
+ // src/modules/user/user.service.ts
5495
+ init_cjs_shims();
5496
+
5497
+ // src/modules/user/user.repository.ts
5498
+ init_cjs_shims();
5499
+
5100
5500
  // src/database/prisma.ts
5501
+ init_cjs_shims();
5101
5502
  var import_client = require("@prisma/client");
5102
5503
  var prismaClientSingleton = () => {
5103
5504
  return new import_client.PrismaClient({
@@ -5111,6 +5512,7 @@ if (!isProduction()) {
5111
5512
  }
5112
5513
 
5113
5514
  // src/utils/pagination.ts
5515
+ init_cjs_shims();
5114
5516
  var DEFAULT_PAGE = 1;
5115
5517
  var DEFAULT_LIMIT = 20;
5116
5518
  var MAX_LIMIT = 100;
@@ -5375,6 +5777,7 @@ function createUserRepository() {
5375
5777
  }
5376
5778
 
5377
5779
  // src/modules/user/types.ts
5780
+ init_cjs_shims();
5378
5781
  var DEFAULT_ROLE_PERMISSIONS = {
5379
5782
  user: ["profile:read", "profile:update"],
5380
5783
  moderator: [
@@ -5505,6 +5908,9 @@ function createUserService(repository) {
5505
5908
  return new UserService(repository || createUserRepository());
5506
5909
  }
5507
5910
 
5911
+ // src/modules/auth/types.ts
5912
+ init_cjs_shims();
5913
+
5508
5914
  // src/modules/auth/index.ts
5509
5915
  async function registerAuthModule(app) {
5510
5916
  await app.register(import_jwt.default, {
@@ -5524,7 +5930,14 @@ async function registerAuthModule(app) {
5524
5930
  logger.info("Auth module registered");
5525
5931
  }
5526
5932
 
5933
+ // src/modules/user/index.ts
5934
+ init_cjs_shims();
5935
+
5936
+ // src/modules/user/user.controller.ts
5937
+ init_cjs_shims();
5938
+
5527
5939
  // src/modules/user/schemas.ts
5940
+ init_cjs_shims();
5528
5941
  var import_zod4 = require("zod");
5529
5942
  var userStatusEnum = import_zod4.z.enum(["active", "inactive", "suspended", "banned"]);
5530
5943
  var userRoleEnum = import_zod4.z.enum(["user", "admin", "moderator", "super_admin"]);
@@ -5651,6 +6064,7 @@ function createUserController(userService) {
5651
6064
  }
5652
6065
 
5653
6066
  // src/modules/user/user.routes.ts
6067
+ init_cjs_shims();
5654
6068
  var idParamsSchema = {
5655
6069
  type: "object",
5656
6070
  properties: {
@@ -5739,8 +6153,8 @@ async function generateDocs(outputPath = "openapi.json", silent = false) {
5739
6153
  await registerUserModule(app, authService);
5740
6154
  await app.ready();
5741
6155
  const spec = app.swagger();
5742
- const absoluteOutput = import_path5.default.resolve(outputPath);
5743
- await import_promises3.default.mkdir(import_path5.default.dirname(absoluteOutput), { recursive: true });
6156
+ const absoluteOutput = import_path6.default.resolve(outputPath);
6157
+ await import_promises3.default.mkdir(import_path6.default.dirname(absoluteOutput), { recursive: true });
5744
6158
  await import_promises3.default.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
5745
6159
  spinner?.succeed(`OpenAPI spec generated at ${absoluteOutput}`);
5746
6160
  await app.close();
@@ -5785,7 +6199,7 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
5785
6199
  const spinner = (0, import_ora6.default)("Exporting documentation...").start();
5786
6200
  try {
5787
6201
  const projectRoot = getProjectRoot();
5788
- const specPath = import_path6.default.join(projectRoot, "openapi.json");
6202
+ const specPath = import_path7.default.join(projectRoot, "openapi.json");
5789
6203
  try {
5790
6204
  await import_promises4.default.access(specPath);
5791
6205
  } catch {
@@ -5812,7 +6226,7 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
5812
6226
  default:
5813
6227
  throw new Error(`Unknown format: ${options.format}`);
5814
6228
  }
5815
- const outPath = import_path6.default.join(projectRoot, options.output || defaultName);
6229
+ const outPath = import_path7.default.join(projectRoot, options.output || defaultName);
5816
6230
  await import_promises4.default.writeFile(outPath, output);
5817
6231
  spinner.succeed(`Exported to: ${options.output || defaultName}`);
5818
6232
  if (options.format === "postman") {
@@ -5825,8 +6239,8 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
5825
6239
  });
5826
6240
  docsCommand.command("status").description("Show documentation status").action(async () => {
5827
6241
  const projectRoot = getProjectRoot();
5828
- console.log(import_chalk6.default.bold("\n\u{1F4CA} Documentation Status\n"));
5829
- const specPath = import_path6.default.join(projectRoot, "openapi.json");
6242
+ console.log(import_chalk9.default.bold("\n\u{1F4CA} Documentation Status\n"));
6243
+ const specPath = import_path7.default.join(projectRoot, "openapi.json");
5830
6244
  try {
5831
6245
  const stat2 = await import_promises4.default.stat(specPath);
5832
6246
  success(
@@ -5941,13 +6355,325 @@ function formatDate(date) {
5941
6355
  });
5942
6356
  }
5943
6357
 
6358
+ // src/cli/commands/list.ts
6359
+ init_cjs_shims();
6360
+ var import_commander6 = require("commander");
6361
+ var import_chalk10 = __toESM(require("chalk"), 1);
6362
+ var import_promises5 = __toESM(require("fs/promises"), 1);
6363
+ var AVAILABLE_MODULES2 = {
6364
+ // Core
6365
+ auth: {
6366
+ name: "Authentication",
6367
+ description: "JWT authentication with access/refresh tokens",
6368
+ category: "Core"
6369
+ },
6370
+ users: {
6371
+ name: "User Management",
6372
+ description: "User CRUD with RBAC (roles & permissions)",
6373
+ category: "Core"
6374
+ },
6375
+ email: {
6376
+ name: "Email Service",
6377
+ description: "SMTP email with templates (Handlebars)",
6378
+ category: "Core"
6379
+ },
6380
+ // Security
6381
+ mfa: {
6382
+ name: "MFA/TOTP",
6383
+ description: "Two-factor authentication with QR codes",
6384
+ category: "Security"
6385
+ },
6386
+ oauth: {
6387
+ name: "OAuth",
6388
+ description: "Social login (Google, GitHub, Facebook, Twitter, Apple)",
6389
+ category: "Security"
6390
+ },
6391
+ "rate-limit": {
6392
+ name: "Rate Limiting",
6393
+ description: "Advanced rate limiting with multiple algorithms",
6394
+ category: "Security"
6395
+ },
6396
+ // Data & Storage
6397
+ cache: {
6398
+ name: "Redis Cache",
6399
+ description: "Redis caching with TTL & invalidation",
6400
+ category: "Data & Storage"
6401
+ },
6402
+ upload: {
6403
+ name: "File Upload",
6404
+ description: "File upload with local/S3/Cloudinary storage",
6405
+ category: "Data & Storage"
6406
+ },
6407
+ search: {
6408
+ name: "Search",
6409
+ description: "Full-text search with Elasticsearch/Meilisearch",
6410
+ category: "Data & Storage"
6411
+ },
6412
+ // Communication
6413
+ notification: {
6414
+ name: "Notifications",
6415
+ description: "Email, SMS, Push notifications",
6416
+ category: "Communication"
6417
+ },
6418
+ webhook: {
6419
+ name: "Webhooks",
6420
+ description: "Outgoing webhooks with HMAC signatures & retry",
6421
+ category: "Communication"
6422
+ },
6423
+ websocket: {
6424
+ name: "WebSockets",
6425
+ description: "Real-time communication with Socket.io",
6426
+ category: "Communication"
6427
+ },
6428
+ // Background Processing
6429
+ queue: {
6430
+ name: "Queue/Jobs",
6431
+ description: "Background jobs with Bull/BullMQ & cron scheduling",
6432
+ category: "Background Processing"
6433
+ },
6434
+ "media-processing": {
6435
+ name: "Media Processing",
6436
+ description: "Image/video processing with FFmpeg",
6437
+ category: "Background Processing"
6438
+ },
6439
+ // Monitoring & Analytics
6440
+ audit: {
6441
+ name: "Audit Logs",
6442
+ description: "Activity logging and audit trail",
6443
+ category: "Monitoring & Analytics"
6444
+ },
6445
+ analytics: {
6446
+ name: "Analytics/Metrics",
6447
+ description: "Prometheus metrics & event tracking",
6448
+ category: "Monitoring & Analytics"
6449
+ },
6450
+ // Internationalization
6451
+ i18n: {
6452
+ name: "i18n/Localization",
6453
+ description: "Multi-language support with 7+ locales",
6454
+ category: "Internationalization"
6455
+ },
6456
+ // API Management
6457
+ "feature-flag": {
6458
+ name: "Feature Flags",
6459
+ description: "A/B testing & progressive rollout",
6460
+ category: "API Management"
6461
+ },
6462
+ "api-versioning": {
6463
+ name: "API Versioning",
6464
+ description: "Multiple API versions support",
6465
+ category: "API Management"
6466
+ },
6467
+ // Payments
6468
+ payment: {
6469
+ name: "Payments",
6470
+ description: "Payment processing (Stripe, PayPal, Mobile Money)",
6471
+ category: "Payments"
6472
+ }
6473
+ };
6474
+ async function getInstalledModules() {
6475
+ try {
6476
+ const modulesDir = getModulesDir();
6477
+ const entries = await import_promises5.default.readdir(modulesDir, { withFileTypes: true });
6478
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
6479
+ } catch {
6480
+ return [];
6481
+ }
6482
+ }
6483
+ function isServercraftProject() {
6484
+ try {
6485
+ getProjectRoot();
6486
+ return true;
6487
+ } catch {
6488
+ return false;
6489
+ }
6490
+ }
6491
+ var listCommand = new import_commander6.Command("list").alias("ls").description("List available and installed modules").option("-a, --available", "Show only available modules").option("-i, --installed", "Show only installed modules").option("-c, --category <category>", "Filter by category").option("--json", "Output as JSON").action(
6492
+ async (options) => {
6493
+ const installedModules = await getInstalledModules();
6494
+ const isProject = isServercraftProject();
6495
+ if (options.json) {
6496
+ const output = {
6497
+ available: Object.entries(AVAILABLE_MODULES2).map(([key, mod]) => ({
6498
+ id: key,
6499
+ ...mod,
6500
+ installed: installedModules.includes(key)
6501
+ }))
6502
+ };
6503
+ if (isProject) {
6504
+ output.installed = installedModules;
6505
+ }
6506
+ console.log(JSON.stringify(output, null, 2));
6507
+ return;
6508
+ }
6509
+ const byCategory = {};
6510
+ for (const [key, mod] of Object.entries(AVAILABLE_MODULES2)) {
6511
+ if (options.category && mod.category.toLowerCase() !== options.category.toLowerCase()) {
6512
+ continue;
6513
+ }
6514
+ if (!byCategory[mod.category]) {
6515
+ byCategory[mod.category] = [];
6516
+ }
6517
+ byCategory[mod.category].push({
6518
+ id: key,
6519
+ name: mod.name,
6520
+ description: mod.description,
6521
+ installed: installedModules.includes(key)
6522
+ });
6523
+ }
6524
+ if (options.installed) {
6525
+ if (!isProject) {
6526
+ console.log(import_chalk10.default.yellow("\n\u26A0 Not in a Servcraft project directory\n"));
6527
+ return;
6528
+ }
6529
+ console.log(import_chalk10.default.bold("\n\u{1F4E6} Installed Modules:\n"));
6530
+ if (installedModules.length === 0) {
6531
+ console.log(import_chalk10.default.gray(" No modules installed yet.\n"));
6532
+ console.log(` Run ${import_chalk10.default.cyan("servcraft add <module>")} to add a module.
6533
+ `);
6534
+ return;
6535
+ }
6536
+ for (const modId of installedModules) {
6537
+ const mod = AVAILABLE_MODULES2[modId];
6538
+ if (mod) {
6539
+ console.log(` ${import_chalk10.default.green("\u2713")} ${import_chalk10.default.cyan(modId.padEnd(18))} ${mod.name}`);
6540
+ } else {
6541
+ console.log(
6542
+ ` ${import_chalk10.default.green("\u2713")} ${import_chalk10.default.cyan(modId.padEnd(18))} ${import_chalk10.default.gray("(custom module)")}`
6543
+ );
6544
+ }
6545
+ }
6546
+ console.log(`
6547
+ Total: ${import_chalk10.default.bold(installedModules.length)} module(s) installed
6548
+ `);
6549
+ return;
6550
+ }
6551
+ console.log(import_chalk10.default.bold("\n\u{1F4E6} Available Modules\n"));
6552
+ if (isProject) {
6553
+ console.log(
6554
+ import_chalk10.default.gray(` ${import_chalk10.default.green("\u2713")} = installed ${import_chalk10.default.dim("\u25CB")} = not installed
6555
+ `)
6556
+ );
6557
+ }
6558
+ for (const [category, modules] of Object.entries(byCategory)) {
6559
+ console.log(import_chalk10.default.bold.blue(` ${category}`));
6560
+ console.log(import_chalk10.default.gray(" " + "\u2500".repeat(40)));
6561
+ for (const mod of modules) {
6562
+ const status = isProject ? mod.installed ? import_chalk10.default.green("\u2713") : import_chalk10.default.dim("\u25CB") : " ";
6563
+ const nameColor = mod.installed ? import_chalk10.default.green : import_chalk10.default.cyan;
6564
+ console.log(` ${status} ${nameColor(mod.id.padEnd(18))} ${mod.name}`);
6565
+ console.log(` ${import_chalk10.default.gray(mod.description)}`);
6566
+ }
6567
+ console.log();
6568
+ }
6569
+ const totalAvailable = Object.keys(AVAILABLE_MODULES2).length;
6570
+ const totalInstalled = installedModules.filter((m) => AVAILABLE_MODULES2[m]).length;
6571
+ console.log(import_chalk10.default.gray("\u2500".repeat(50)));
6572
+ console.log(
6573
+ ` ${import_chalk10.default.bold(totalAvailable)} modules available` + (isProject ? ` | ${import_chalk10.default.green.bold(totalInstalled)} installed` : "")
6574
+ );
6575
+ console.log();
6576
+ console.log(import_chalk10.default.bold(" Usage:"));
6577
+ console.log(` ${import_chalk10.default.yellow("servcraft add <module>")} Add a module`);
6578
+ console.log(` ${import_chalk10.default.yellow("servcraft list --installed")} Show installed only`);
6579
+ console.log(` ${import_chalk10.default.yellow("servcraft list --category Security")} Filter by category`);
6580
+ console.log();
6581
+ }
6582
+ );
6583
+
6584
+ // src/cli/commands/remove.ts
6585
+ init_cjs_shims();
6586
+ var import_commander7 = require("commander");
6587
+ var import_path8 = __toESM(require("path"), 1);
6588
+ var import_ora7 = __toESM(require("ora"), 1);
6589
+ var import_chalk11 = __toESM(require("chalk"), 1);
6590
+ var import_promises6 = __toESM(require("fs/promises"), 1);
6591
+ var import_inquirer4 = __toESM(require("inquirer"), 1);
6592
+ init_error_handler();
6593
+ 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) => {
6594
+ const projectError = validateProject();
6595
+ if (projectError) {
6596
+ displayError(projectError);
6597
+ return;
6598
+ }
6599
+ console.log(import_chalk11.default.bold.cyan("\n\u{1F5D1}\uFE0F ServCraft Module Removal\n"));
6600
+ const moduleDir = import_path8.default.join(getModulesDir(), moduleName);
6601
+ try {
6602
+ const exists = await import_promises6.default.access(moduleDir).then(() => true).catch(() => false);
6603
+ if (!exists) {
6604
+ displayError(
6605
+ new (init_error_handler(), __toCommonJS(error_handler_exports)).ServCraftError(
6606
+ `Module "${moduleName}" is not installed`,
6607
+ [
6608
+ `Run ${import_chalk11.default.cyan("servcraft list --installed")} to see installed modules`,
6609
+ `Check the spelling of the module name`
6610
+ ]
6611
+ )
6612
+ );
6613
+ return;
6614
+ }
6615
+ const files = await import_promises6.default.readdir(moduleDir);
6616
+ const fileCount = files.length;
6617
+ if (!options?.yes) {
6618
+ console.log(import_chalk11.default.yellow(`\u26A0 This will remove the "${moduleName}" module:`));
6619
+ console.log(import_chalk11.default.gray(` Directory: ${moduleDir}`));
6620
+ console.log(import_chalk11.default.gray(` Files: ${fileCount} file(s)`));
6621
+ console.log();
6622
+ const { confirm } = await import_inquirer4.default.prompt([
6623
+ {
6624
+ type: "confirm",
6625
+ name: "confirm",
6626
+ message: "Are you sure you want to remove this module?",
6627
+ default: false
6628
+ }
6629
+ ]);
6630
+ if (!confirm) {
6631
+ console.log(import_chalk11.default.yellow("\n\u2716 Removal cancelled\n"));
6632
+ return;
6633
+ }
6634
+ }
6635
+ const spinner = (0, import_ora7.default)("Removing module...").start();
6636
+ await import_promises6.default.rm(moduleDir, { recursive: true, force: true });
6637
+ spinner.succeed(`Module "${moduleName}" removed successfully!`);
6638
+ console.log("\n" + import_chalk11.default.bold("\u2713 Removed:"));
6639
+ success(` src/modules/${moduleName}/ (${fileCount} files)`);
6640
+ if (!options?.keepEnv) {
6641
+ console.log("\n" + import_chalk11.default.bold("\u{1F4CC} Manual cleanup needed:"));
6642
+ info(" 1. Remove environment variables related to this module from .env");
6643
+ info(" 2. Remove module imports from your main app file");
6644
+ info(" 3. Remove related database migrations if any");
6645
+ info(" 4. Update your routes if they reference this module");
6646
+ } else {
6647
+ console.log("\n" + import_chalk11.default.bold("\u{1F4CC} Manual cleanup needed:"));
6648
+ info(" 1. Environment variables were kept (--keep-env flag)");
6649
+ info(" 2. Remove module imports from your main app file");
6650
+ info(" 3. Update your routes if they reference this module");
6651
+ }
6652
+ console.log();
6653
+ } catch (err) {
6654
+ error(err instanceof Error ? err.message : String(err));
6655
+ console.log();
6656
+ }
6657
+ });
6658
+
6659
+ // src/cli/commands/doctor.ts
6660
+ init_cjs_shims();
6661
+ var import_commander8 = require("commander");
6662
+ var import_chalk12 = __toESM(require("chalk"), 1);
6663
+ var doctorCommand = new import_commander8.Command("doctor").description("Diagnose project configuration and dependencies").action(async () => {
6664
+ console.log(import_chalk12.default.bold.cyan("\nServCraft Doctor - Coming soon!\n"));
6665
+ });
6666
+
5944
6667
  // src/cli/index.ts
5945
- var program = new import_commander6.Command();
6668
+ var program = new import_commander9.Command();
5946
6669
  program.name("servcraft").description("Servcraft - A modular Node.js backend framework CLI").version("0.1.0");
5947
6670
  program.addCommand(initCommand);
5948
6671
  program.addCommand(generateCommand);
5949
6672
  program.addCommand(addModuleCommand);
5950
6673
  program.addCommand(dbCommand);
5951
6674
  program.addCommand(docsCommand);
6675
+ program.addCommand(listCommand);
6676
+ program.addCommand(removeCommand);
6677
+ program.addCommand(doctorCommand);
5952
6678
  program.parse();
5953
6679
  //# sourceMappingURL=index.cjs.map