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.
package/dist/cli/index.js CHANGED
@@ -1,21 +1,316 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
+ }) : x)(function(x) {
9
+ if (typeof require !== "undefined") return require.apply(this, arguments);
10
+ throw Error('Dynamic require of "' + x + '" is not supported');
11
+ });
12
+ var __esm = (fn, res) => function __init() {
13
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // node_modules/tsup/assets/esm_shims.js
30
+ import path from "path";
31
+ import { fileURLToPath } from "url";
32
+ var init_esm_shims = __esm({
33
+ "node_modules/tsup/assets/esm_shims.js"() {
34
+ "use strict";
35
+ }
36
+ });
37
+
38
+ // src/cli/utils/error-handler.ts
39
+ var error_handler_exports = {};
40
+ __export(error_handler_exports, {
41
+ ErrorTypes: () => ErrorTypes,
42
+ ServCraftError: () => ServCraftError,
43
+ displayError: () => displayError,
44
+ handleSystemError: () => handleSystemError,
45
+ validateProject: () => validateProject
46
+ });
47
+ import chalk4 from "chalk";
48
+ function displayError(error2) {
49
+ console.error("\n" + chalk4.red.bold("\u2717 Error: ") + chalk4.red(error2.message));
50
+ if (error2 instanceof ServCraftError) {
51
+ if (error2.suggestions.length > 0) {
52
+ console.log("\n" + chalk4.yellow.bold("\u{1F4A1} Suggestions:"));
53
+ error2.suggestions.forEach((suggestion) => {
54
+ console.log(chalk4.yellow(" \u2022 ") + suggestion);
55
+ });
56
+ }
57
+ if (error2.docsLink) {
58
+ console.log("\n" + chalk4.blue.bold("\u{1F4DA} Documentation: ") + chalk4.blue.underline(error2.docsLink));
59
+ }
60
+ }
61
+ console.log();
62
+ }
63
+ function handleSystemError(err) {
64
+ switch (err.code) {
65
+ case "ENOENT":
66
+ return new ServCraftError(
67
+ `File or directory not found: ${err.path}`,
68
+ [`Check if the path exists`, `Create the directory first`]
69
+ );
70
+ case "EACCES":
71
+ case "EPERM":
72
+ return new ServCraftError(
73
+ `Permission denied: ${err.path}`,
74
+ [
75
+ `Check file permissions`,
76
+ `Try running with elevated privileges (not recommended)`,
77
+ `Change ownership of the directory`
78
+ ]
79
+ );
80
+ case "EEXIST":
81
+ return new ServCraftError(
82
+ `File or directory already exists: ${err.path}`,
83
+ [`Use a different name`, `Remove the existing file first`, `Use ${chalk4.cyan("--force")} to overwrite`]
84
+ );
85
+ case "ENOTDIR":
86
+ return new ServCraftError(
87
+ `Not a directory: ${err.path}`,
88
+ [`Check the path`, `A file exists where a directory is expected`]
89
+ );
90
+ case "EISDIR":
91
+ return new ServCraftError(
92
+ `Is a directory: ${err.path}`,
93
+ [`Cannot perform this operation on a directory`, `Did you mean to target a file?`]
94
+ );
95
+ default:
96
+ return new ServCraftError(
97
+ err.message,
98
+ [`Check system error code: ${err.code}`, `Review the error details above`]
99
+ );
100
+ }
101
+ }
102
+ function validateProject() {
103
+ try {
104
+ const fs10 = __require("fs");
105
+ const path12 = __require("path");
106
+ if (!fs10.existsSync("package.json")) {
107
+ return ErrorTypes.NOT_IN_PROJECT();
108
+ }
109
+ const packageJson = JSON.parse(fs10.readFileSync("package.json", "utf-8"));
110
+ if (!packageJson.dependencies?.fastify) {
111
+ return new ServCraftError(
112
+ "This does not appear to be a ServCraft project",
113
+ [
114
+ `ServCraft projects require Fastify`,
115
+ `Run ${chalk4.cyan("servcraft init")} to create a new project`
116
+ ]
117
+ );
118
+ }
119
+ return null;
120
+ } catch (err) {
121
+ return new ServCraftError(
122
+ "Failed to validate project",
123
+ [`Ensure you are in the project root directory`, `Check if ${chalk4.yellow("package.json")} is valid`]
124
+ );
125
+ }
126
+ }
127
+ var ServCraftError, ErrorTypes;
128
+ var init_error_handler = __esm({
129
+ "src/cli/utils/error-handler.ts"() {
130
+ "use strict";
131
+ init_esm_shims();
132
+ ServCraftError = class extends Error {
133
+ suggestions;
134
+ docsLink;
135
+ constructor(message, suggestions = [], docsLink) {
136
+ super(message);
137
+ this.name = "ServCraftError";
138
+ this.suggestions = suggestions;
139
+ this.docsLink = docsLink;
140
+ }
141
+ };
142
+ ErrorTypes = {
143
+ MODULE_NOT_FOUND: (moduleName) => new ServCraftError(
144
+ `Module "${moduleName}" not found`,
145
+ [
146
+ `Run ${chalk4.cyan("servcraft list")} to see available modules`,
147
+ `Check the spelling of the module name`,
148
+ `Visit ${chalk4.blue("https://github.com/Le-Sourcier/servcraft#modules")} for module list`
149
+ ],
150
+ "https://github.com/Le-Sourcier/servcraft#add-pre-built-modules"
151
+ ),
152
+ MODULE_ALREADY_EXISTS: (moduleName) => new ServCraftError(
153
+ `Module "${moduleName}" already exists`,
154
+ [
155
+ `Use ${chalk4.cyan("servcraft add " + moduleName + " --force")} to overwrite`,
156
+ `Use ${chalk4.cyan("servcraft add " + moduleName + " --update")} to update`,
157
+ `Use ${chalk4.cyan("servcraft add " + moduleName + " --skip-existing")} to skip`
158
+ ]
159
+ ),
160
+ NOT_IN_PROJECT: () => new ServCraftError(
161
+ "Not in a ServCraft project directory",
162
+ [
163
+ `Run ${chalk4.cyan("servcraft init")} to create a new project`,
164
+ `Navigate to your ServCraft project directory`,
165
+ `Check if ${chalk4.yellow("package.json")} exists`
166
+ ],
167
+ "https://github.com/Le-Sourcier/servcraft#initialize-project"
168
+ ),
169
+ FILE_ALREADY_EXISTS: (fileName) => new ServCraftError(
170
+ `File "${fileName}" already exists`,
171
+ [
172
+ `Use ${chalk4.cyan("--force")} flag to overwrite`,
173
+ `Choose a different name`,
174
+ `Delete the existing file first`
175
+ ]
176
+ ),
177
+ INVALID_DATABASE: (database) => new ServCraftError(
178
+ `Invalid database type: "${database}"`,
179
+ [
180
+ `Valid options: ${chalk4.cyan("postgresql, mysql, sqlite, mongodb, none")}`,
181
+ `Use ${chalk4.cyan("servcraft init --db postgresql")} for PostgreSQL`
182
+ ]
183
+ ),
184
+ INVALID_VALIDATOR: (validator) => new ServCraftError(
185
+ `Invalid validator type: "${validator}"`,
186
+ [`Valid options: ${chalk4.cyan("zod, joi, yup")}`, `Default is ${chalk4.cyan("zod")}`]
187
+ ),
188
+ MISSING_DEPENDENCY: (dependency, command) => new ServCraftError(
189
+ `Missing dependency: "${dependency}"`,
190
+ [`Run ${chalk4.cyan(command)} to install`, `Check your ${chalk4.yellow("package.json")}`]
191
+ ),
192
+ INVALID_FIELD_FORMAT: (field) => new ServCraftError(
193
+ `Invalid field format: "${field}"`,
194
+ [
195
+ `Expected format: ${chalk4.cyan("name:type")}`,
196
+ `Example: ${chalk4.cyan("name:string age:number isActive:boolean")}`,
197
+ `Supported types: string, number, boolean, date`
198
+ ]
199
+ ),
200
+ GIT_NOT_INITIALIZED: () => new ServCraftError(
201
+ "Git repository not initialized",
202
+ [
203
+ `Run ${chalk4.cyan("git init")} to initialize git`,
204
+ `This is required for some ServCraft features`
205
+ ]
206
+ )
207
+ };
208
+ }
209
+ });
2
210
 
3
211
  // src/cli/index.ts
4
- import { Command as Command6 } from "commander";
212
+ init_esm_shims();
213
+ import { Command as Command9 } from "commander";
5
214
 
6
215
  // src/cli/commands/init.ts
216
+ init_esm_shims();
7
217
  import { Command } from "commander";
8
- import path2 from "path";
218
+ import path4 from "path";
9
219
  import fs2 from "fs/promises";
10
220
  import ora from "ora";
11
221
  import inquirer from "inquirer";
12
- import chalk2 from "chalk";
222
+ import chalk3 from "chalk";
13
223
  import { execSync } from "child_process";
14
224
 
15
225
  // src/cli/utils/helpers.ts
226
+ init_esm_shims();
16
227
  import fs from "fs/promises";
17
- import path from "path";
228
+ import path3 from "path";
229
+ import chalk2 from "chalk";
230
+
231
+ // src/cli/utils/dry-run.ts
232
+ init_esm_shims();
18
233
  import chalk from "chalk";
234
+ import path2 from "path";
235
+ var DryRunManager = class _DryRunManager {
236
+ static instance;
237
+ enabled = false;
238
+ operations = [];
239
+ constructor() {
240
+ }
241
+ static getInstance() {
242
+ if (!_DryRunManager.instance) {
243
+ _DryRunManager.instance = new _DryRunManager();
244
+ }
245
+ return _DryRunManager.instance;
246
+ }
247
+ enable() {
248
+ this.enabled = true;
249
+ this.operations = [];
250
+ }
251
+ disable() {
252
+ this.enabled = false;
253
+ this.operations = [];
254
+ }
255
+ isEnabled() {
256
+ return this.enabled;
257
+ }
258
+ addOperation(operation) {
259
+ if (this.enabled) {
260
+ this.operations.push(operation);
261
+ }
262
+ }
263
+ getOperations() {
264
+ return [...this.operations];
265
+ }
266
+ printSummary() {
267
+ if (!this.enabled || this.operations.length === 0) {
268
+ return;
269
+ }
270
+ console.log(chalk.bold.yellow("\n\u{1F4CB} Dry Run - Preview of changes:\n"));
271
+ console.log(chalk.gray("No files will be written. Remove --dry-run to apply changes.\n"));
272
+ const createOps = this.operations.filter((op) => op.type === "create");
273
+ const modifyOps = this.operations.filter((op) => op.type === "modify");
274
+ const deleteOps = this.operations.filter((op) => op.type === "delete");
275
+ if (createOps.length > 0) {
276
+ console.log(chalk.green.bold(`
277
+ \u2713 Files to be created (${createOps.length}):`));
278
+ createOps.forEach((op) => {
279
+ const size = op.content ? `${op.content.length} bytes` : "unknown size";
280
+ console.log(` ${chalk.green("+")} ${chalk.cyan(op.path)} ${chalk.gray(`(${size})`)}`);
281
+ });
282
+ }
283
+ if (modifyOps.length > 0) {
284
+ console.log(chalk.yellow.bold(`
285
+ ~ Files to be modified (${modifyOps.length}):`));
286
+ modifyOps.forEach((op) => {
287
+ console.log(` ${chalk.yellow("~")} ${chalk.cyan(op.path)}`);
288
+ });
289
+ }
290
+ if (deleteOps.length > 0) {
291
+ console.log(chalk.red.bold(`
292
+ - Files to be deleted (${deleteOps.length}):`));
293
+ deleteOps.forEach((op) => {
294
+ console.log(` ${chalk.red("-")} ${chalk.cyan(op.path)}`);
295
+ });
296
+ }
297
+ console.log(chalk.gray("\n" + "\u2500".repeat(60)));
298
+ console.log(
299
+ chalk.bold(` Total operations: ${this.operations.length}`) + chalk.gray(
300
+ ` (${createOps.length} create, ${modifyOps.length} modify, ${deleteOps.length} delete)`
301
+ )
302
+ );
303
+ console.log(chalk.gray("\u2500".repeat(60)));
304
+ console.log(chalk.yellow("\n\u26A0 This was a dry run. No files were created or modified."));
305
+ console.log(chalk.gray(" Remove --dry-run to apply these changes.\n"));
306
+ }
307
+ // Helper to format file path relative to cwd
308
+ relativePath(filePath) {
309
+ return path2.relative(process.cwd(), filePath);
310
+ }
311
+ };
312
+
313
+ // src/cli/utils/helpers.ts
19
314
  function toPascalCase(str) {
20
315
  return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
21
316
  }
@@ -47,39 +342,54 @@ async function ensureDir(dirPath) {
47
342
  await fs.mkdir(dirPath, { recursive: true });
48
343
  }
49
344
  async function writeFile(filePath, content) {
50
- await ensureDir(path.dirname(filePath));
345
+ const dryRun = DryRunManager.getInstance();
346
+ if (dryRun.isEnabled()) {
347
+ dryRun.addOperation({
348
+ type: "create",
349
+ path: dryRun.relativePath(filePath),
350
+ content,
351
+ size: content.length
352
+ });
353
+ return;
354
+ }
355
+ await ensureDir(path3.dirname(filePath));
51
356
  await fs.writeFile(filePath, content, "utf-8");
52
357
  }
53
358
  function success(message) {
54
- console.log(chalk.green("\u2713"), message);
359
+ console.log(chalk2.green("\u2713"), message);
55
360
  }
56
361
  function error(message) {
57
- console.error(chalk.red("\u2717"), message);
362
+ console.error(chalk2.red("\u2717"), message);
58
363
  }
59
364
  function warn(message) {
60
- console.log(chalk.yellow("\u26A0"), message);
365
+ console.log(chalk2.yellow("\u26A0"), message);
61
366
  }
62
367
  function info(message) {
63
- console.log(chalk.blue("\u2139"), message);
368
+ console.log(chalk2.blue("\u2139"), message);
64
369
  }
65
370
  function getProjectRoot() {
66
371
  return process.cwd();
67
372
  }
68
373
  function getSourceDir() {
69
- return path.join(getProjectRoot(), "src");
374
+ return path3.join(getProjectRoot(), "src");
70
375
  }
71
376
  function getModulesDir() {
72
- return path.join(getSourceDir(), "modules");
377
+ return path3.join(getSourceDir(), "modules");
73
378
  }
74
379
 
75
380
  // src/cli/commands/init.ts
76
- var initCommand = new 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(
381
+ var initCommand = new 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(
77
382
  async (name, cmdOptions) => {
383
+ const dryRun = DryRunManager.getInstance();
384
+ if (cmdOptions?.dryRun) {
385
+ dryRun.enable();
386
+ console.log(chalk3.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
387
+ }
78
388
  console.log(
79
- chalk2.blue(`
389
+ chalk3.blue(`
80
390
  \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
81
391
  \u2551 \u2551
82
- \u2551 ${chalk2.bold("\u{1F680} Servcraft Project Generator")} \u2551
392
+ \u2551 ${chalk3.bold("\u{1F680} Servcraft Project Generator")} \u2551
83
393
  \u2551 \u2551
84
394
  \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
85
395
  `)
@@ -174,7 +484,7 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
174
484
  orm: db === "mongodb" ? "mongoose" : db === "none" ? "none" : "prisma"
175
485
  };
176
486
  }
177
- const projectDir = path2.resolve(process.cwd(), options.name);
487
+ const projectDir = path4.resolve(process.cwd(), options.name);
178
488
  const spinner = ora("Creating project...").start();
179
489
  try {
180
490
  try {
@@ -188,21 +498,21 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
188
498
  spinner.text = "Generating project files...";
189
499
  const packageJson = generatePackageJson(options);
190
500
  await writeFile(
191
- path2.join(projectDir, "package.json"),
501
+ path4.join(projectDir, "package.json"),
192
502
  JSON.stringify(packageJson, null, 2)
193
503
  );
194
504
  if (options.language === "typescript") {
195
- await writeFile(path2.join(projectDir, "tsconfig.json"), generateTsConfig(options));
196
- await writeFile(path2.join(projectDir, "tsup.config.ts"), generateTsupConfig(options));
505
+ await writeFile(path4.join(projectDir, "tsconfig.json"), generateTsConfig(options));
506
+ await writeFile(path4.join(projectDir, "tsup.config.ts"), generateTsupConfig(options));
197
507
  } else {
198
- await writeFile(path2.join(projectDir, "jsconfig.json"), generateJsConfig(options));
508
+ await writeFile(path4.join(projectDir, "jsconfig.json"), generateJsConfig(options));
199
509
  }
200
- await writeFile(path2.join(projectDir, ".env.example"), generateEnvExample(options));
201
- await writeFile(path2.join(projectDir, ".env"), generateEnvExample(options));
202
- await writeFile(path2.join(projectDir, ".gitignore"), generateGitignore());
203
- await writeFile(path2.join(projectDir, "Dockerfile"), generateDockerfile(options));
510
+ await writeFile(path4.join(projectDir, ".env.example"), generateEnvExample(options));
511
+ await writeFile(path4.join(projectDir, ".env"), generateEnvExample(options));
512
+ await writeFile(path4.join(projectDir, ".gitignore"), generateGitignore());
513
+ await writeFile(path4.join(projectDir, "Dockerfile"), generateDockerfile(options));
204
514
  await writeFile(
205
- path2.join(projectDir, "docker-compose.yml"),
515
+ path4.join(projectDir, "docker-compose.yml"),
206
516
  generateDockerCompose(options)
207
517
  );
208
518
  const ext = options.language === "typescript" ? "ts" : options.moduleSystem === "esm" ? "js" : "cjs";
@@ -223,59 +533,63 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
223
533
  dirs.push("src/database/models");
224
534
  }
225
535
  for (const dir of dirs) {
226
- await ensureDir(path2.join(projectDir, dir));
536
+ await ensureDir(path4.join(projectDir, dir));
227
537
  }
228
- await writeFile(path2.join(projectDir, `src/index.${ext}`), generateEntryFile(options));
538
+ await writeFile(path4.join(projectDir, `src/index.${ext}`), generateEntryFile(options));
229
539
  await writeFile(
230
- path2.join(projectDir, `src/core/server.${ext}`),
540
+ path4.join(projectDir, `src/core/server.${ext}`),
231
541
  generateServerFile(options)
232
542
  );
233
543
  await writeFile(
234
- path2.join(projectDir, `src/core/logger.${ext}`),
544
+ path4.join(projectDir, `src/core/logger.${ext}`),
235
545
  generateLoggerFile(options)
236
546
  );
237
547
  await writeFile(
238
- path2.join(projectDir, `src/config/index.${ext}`),
548
+ path4.join(projectDir, `src/config/index.${ext}`),
239
549
  generateConfigFile(options)
240
550
  );
241
551
  await writeFile(
242
- path2.join(projectDir, `src/middleware/index.${ext}`),
552
+ path4.join(projectDir, `src/middleware/index.${ext}`),
243
553
  generateMiddlewareFile(options)
244
554
  );
245
555
  await writeFile(
246
- path2.join(projectDir, `src/utils/index.${ext}`),
556
+ path4.join(projectDir, `src/utils/index.${ext}`),
247
557
  generateUtilsFile(options)
248
558
  );
249
559
  await writeFile(
250
- path2.join(projectDir, `src/types/index.${ext}`),
560
+ path4.join(projectDir, `src/types/index.${ext}`),
251
561
  generateTypesFile(options)
252
562
  );
253
563
  if (options.orm === "prisma") {
254
564
  await writeFile(
255
- path2.join(projectDir, "prisma/schema.prisma"),
565
+ path4.join(projectDir, "prisma/schema.prisma"),
256
566
  generatePrismaSchema(options)
257
567
  );
258
568
  } else if (options.orm === "mongoose") {
259
569
  await writeFile(
260
- path2.join(projectDir, `src/database/connection.${ext}`),
570
+ path4.join(projectDir, `src/database/connection.${ext}`),
261
571
  generateMongooseConnection(options)
262
572
  );
263
573
  await writeFile(
264
- path2.join(projectDir, `src/database/models/user.model.${ext}`),
574
+ path4.join(projectDir, `src/database/models/user.model.${ext}`),
265
575
  generateMongooseUserModel(options)
266
576
  );
267
577
  }
268
578
  spinner.succeed("Project files generated!");
269
- const installSpinner = ora("Installing dependencies...").start();
270
- try {
271
- execSync("npm install", { cwd: projectDir, stdio: "pipe" });
272
- installSpinner.succeed("Dependencies installed!");
273
- } catch {
274
- installSpinner.warn("Failed to install dependencies automatically");
275
- warn(' Run "npm install" manually in the project directory');
579
+ if (!cmdOptions?.dryRun) {
580
+ const installSpinner = ora("Installing dependencies...").start();
581
+ try {
582
+ execSync("npm install", { cwd: projectDir, stdio: "pipe" });
583
+ installSpinner.succeed("Dependencies installed!");
584
+ } catch {
585
+ installSpinner.warn("Failed to install dependencies automatically");
586
+ warn(' Run "npm install" manually in the project directory');
587
+ }
588
+ }
589
+ if (!cmdOptions?.dryRun) {
590
+ console.log("\n" + chalk3.green("\u2728 Project created successfully!"));
276
591
  }
277
- console.log("\n" + chalk2.green("\u2728 Project created successfully!"));
278
- console.log("\n" + chalk2.bold("\u{1F4C1} Project structure:"));
592
+ console.log("\n" + chalk3.bold("\u{1F4C1} Project structure:"));
279
593
  console.log(`
280
594
  ${options.name}/
281
595
  \u251C\u2500\u2500 src/
@@ -290,19 +604,22 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
290
604
  \u251C\u2500\u2500 docker-compose.yml
291
605
  \u2514\u2500\u2500 package.json
292
606
  `);
293
- console.log(chalk2.bold("\u{1F680} Get started:"));
607
+ console.log(chalk3.bold("\u{1F680} Get started:"));
294
608
  console.log(`
295
- ${chalk2.cyan(`cd ${options.name}`)}
296
- ${options.database !== "none" ? chalk2.cyan("npm run db:push # Setup database") : ""}
297
- ${chalk2.cyan("npm run dev # Start development server")}
609
+ ${chalk3.cyan(`cd ${options.name}`)}
610
+ ${options.database !== "none" ? chalk3.cyan("npm run db:push # Setup database") : ""}
611
+ ${chalk3.cyan("npm run dev # Start development server")}
298
612
  `);
299
- console.log(chalk2.bold("\u{1F4DA} Available commands:"));
613
+ console.log(chalk3.bold("\u{1F4DA} Available commands:"));
300
614
  console.log(`
301
- ${chalk2.yellow("servcraft generate module <name>")} Generate a new module
302
- ${chalk2.yellow("servcraft generate controller <name>")} Generate a controller
303
- ${chalk2.yellow("servcraft generate service <name>")} Generate a service
304
- ${chalk2.yellow("servcraft add auth")} Add authentication module
615
+ ${chalk3.yellow("servcraft generate module <name>")} Generate a new module
616
+ ${chalk3.yellow("servcraft generate controller <name>")} Generate a controller
617
+ ${chalk3.yellow("servcraft generate service <name>")} Generate a service
618
+ ${chalk3.yellow("servcraft add auth")} Add authentication module
305
619
  `);
620
+ if (cmdOptions?.dryRun) {
621
+ dryRun.printSummary();
622
+ }
306
623
  } catch (err) {
307
624
  spinner.fail("Failed to create project");
308
625
  error(err instanceof Error ? err.message : String(err));
@@ -1093,12 +1410,15 @@ declare module 'fastify' {
1093
1410
  }
1094
1411
 
1095
1412
  // src/cli/commands/generate.ts
1413
+ init_esm_shims();
1096
1414
  import { Command as Command2 } from "commander";
1097
- import path3 from "path";
1415
+ import path5 from "path";
1098
1416
  import ora2 from "ora";
1099
1417
  import inquirer2 from "inquirer";
1418
+ import chalk5 from "chalk";
1100
1419
 
1101
1420
  // src/cli/utils/field-parser.ts
1421
+ init_esm_shims();
1102
1422
  var tsTypeMap = {
1103
1423
  string: "string",
1104
1424
  number: "number",
@@ -1240,7 +1560,11 @@ function parseFields(fieldsStr) {
1240
1560
  return fieldsStr.split(/\s+/).filter(Boolean).map(parseField);
1241
1561
  }
1242
1562
 
1563
+ // src/cli/commands/generate.ts
1564
+ init_error_handler();
1565
+
1243
1566
  // src/cli/templates/controller.ts
1567
+ init_esm_shims();
1244
1568
  function controllerTemplate(name, pascalName, camelName) {
1245
1569
  return `import type { FastifyRequest, FastifyReply } from 'fastify';
1246
1570
  import type { ${pascalName}Service } from './${name}.service.js';
@@ -1310,6 +1634,7 @@ export function create${pascalName}Controller(${camelName}Service: ${pascalName}
1310
1634
  }
1311
1635
 
1312
1636
  // src/cli/templates/service.ts
1637
+ init_esm_shims();
1313
1638
  function serviceTemplate(name, pascalName, camelName) {
1314
1639
  return `import type { PaginatedResult, PaginationParams } from '../../types/index.js';
1315
1640
  import { NotFoundError, ConflictError } from '../../utils/errors.js';
@@ -1370,6 +1695,7 @@ export function create${pascalName}Service(repository?: ${pascalName}Repository)
1370
1695
  }
1371
1696
 
1372
1697
  // src/cli/templates/repository.ts
1698
+ init_esm_shims();
1373
1699
  function repositoryTemplate(name, pascalName, camelName, pluralName) {
1374
1700
  return `import { randomUUID } from 'crypto';
1375
1701
  import type { PaginatedResult, PaginationParams } from '../../types/index.js';
@@ -1476,6 +1802,7 @@ export function create${pascalName}Repository(): ${pascalName}Repository {
1476
1802
  }
1477
1803
 
1478
1804
  // src/cli/templates/types.ts
1805
+ init_esm_shims();
1479
1806
  function typesTemplate(name, pascalName) {
1480
1807
  return `import type { BaseEntity } from '../../types/index.js';
1481
1808
 
@@ -1505,6 +1832,7 @@ export interface ${pascalName}Filters {
1505
1832
  }
1506
1833
 
1507
1834
  // src/cli/templates/schemas.ts
1835
+ init_esm_shims();
1508
1836
  function schemasTemplate(name, pascalName, camelName) {
1509
1837
  return `import { z } from 'zod';
1510
1838
 
@@ -1533,6 +1861,7 @@ export type ${pascalName}QueryInput = z.infer<typeof ${camelName}QuerySchema>;
1533
1861
  }
1534
1862
 
1535
1863
  // src/cli/templates/routes.ts
1864
+ init_esm_shims();
1536
1865
  function routesTemplate(name, pascalName, camelName, pluralName) {
1537
1866
  return `import type { FastifyInstance } from 'fastify';
1538
1867
  import type { ${pascalName}Controller } from './${name}.controller.js';
@@ -1585,6 +1914,7 @@ export function register${pascalName}Routes(
1585
1914
  }
1586
1915
 
1587
1916
  // src/cli/templates/module-index.ts
1917
+ init_esm_shims();
1588
1918
  function moduleIndexTemplate(name, pascalName, camelName) {
1589
1919
  return `import type { FastifyInstance } from 'fastify';
1590
1920
  import { logger } from '../../core/logger.js';
@@ -1620,6 +1950,7 @@ export * from './${name}.schemas.js';
1620
1950
  }
1621
1951
 
1622
1952
  // src/cli/templates/prisma-model.ts
1953
+ init_esm_shims();
1623
1954
  function prismaModelTemplate(name, pascalName, tableName) {
1624
1955
  return `
1625
1956
  // Add this model to your prisma/schema.prisma file
@@ -1639,6 +1970,7 @@ model ${pascalName} {
1639
1970
  }
1640
1971
 
1641
1972
  // src/cli/templates/dynamic-types.ts
1973
+ init_esm_shims();
1642
1974
  function dynamicTypesTemplate(name, pascalName, fields) {
1643
1975
  const fieldLines = fields.map((field) => {
1644
1976
  const tsType = tsTypeMap[field.type];
@@ -1683,6 +2015,7 @@ ${fields.filter((f) => ["string", "enum", "boolean"].includes(f.type)).map((f) =
1683
2015
  }
1684
2016
 
1685
2017
  // src/cli/templates/dynamic-schemas.ts
2018
+ init_esm_shims();
1686
2019
  function dynamicSchemasTemplate(name, pascalName, camelName, fields, validator = "zod") {
1687
2020
  switch (validator) {
1688
2021
  case "joi":
@@ -1861,6 +2194,7 @@ function getJsType(field) {
1861
2194
  }
1862
2195
 
1863
2196
  // src/cli/templates/dynamic-prisma.ts
2197
+ init_esm_shims();
1864
2198
  function dynamicPrismaTemplate(modelName, tableName, fields) {
1865
2199
  const fieldLines = [];
1866
2200
  for (const field of fields) {
@@ -1929,10 +2263,23 @@ ${indexLines.join("\n")}
1929
2263
  }
1930
2264
 
1931
2265
  // src/cli/commands/generate.ts
2266
+ function enableDryRunIfNeeded(options) {
2267
+ const dryRun = DryRunManager.getInstance();
2268
+ if (options.dryRun) {
2269
+ dryRun.enable();
2270
+ console.log(chalk5.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
2271
+ }
2272
+ }
2273
+ function showDryRunSummary(options) {
2274
+ if (options.dryRun) {
2275
+ DryRunManager.getInstance().printSummary();
2276
+ }
2277
+ }
1932
2278
  var generateCommand = new Command2("generate").alias("g").description("Generate resources (module, controller, service, etc.)");
1933
2279
  generateCommand.command("module <name> [fields...]").alias("m").description(
1934
2280
  "Generate a complete module with controller, service, repository, types, schemas, and routes"
1935
- ).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) => {
2281
+ ).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) => {
2282
+ enableDryRunIfNeeded(options);
1936
2283
  let fields = [];
1937
2284
  if (options.interactive) {
1938
2285
  fields = await promptForFields();
@@ -1947,7 +2294,7 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
1947
2294
  const pluralName = pluralize(kebabName);
1948
2295
  const tableName = pluralize(kebabName.replace(/-/g, "_"));
1949
2296
  const validatorType = options.validator || "zod";
1950
- const moduleDir = path3.join(getModulesDir(), kebabName);
2297
+ const moduleDir = path5.join(getModulesDir(), kebabName);
1951
2298
  if (await fileExists(moduleDir)) {
1952
2299
  spinner.stop();
1953
2300
  error(`Module "${kebabName}" already exists`);
@@ -1986,7 +2333,7 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
1986
2333
  });
1987
2334
  }
1988
2335
  for (const file of files) {
1989
- await writeFile(path3.join(moduleDir, file.name), file.content);
2336
+ await writeFile(path5.join(moduleDir, file.name), file.content);
1990
2337
  }
1991
2338
  spinner.succeed(`Module "${pascalName}" generated successfully!`);
1992
2339
  if (options.prisma || hasFields) {
@@ -2024,42 +2371,46 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
2024
2371
  info(` ${hasFields ? "3" : "4"}. Add the Prisma model to schema.prisma`);
2025
2372
  info(` ${hasFields ? "4" : "5"}. Run: npm run db:migrate`);
2026
2373
  }
2374
+ showDryRunSummary(options);
2027
2375
  } catch (err) {
2028
2376
  spinner.fail("Failed to generate module");
2029
2377
  error(err instanceof Error ? err.message : String(err));
2030
2378
  }
2031
2379
  });
2032
- generateCommand.command("controller <name>").alias("c").description("Generate a controller").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2380
+ 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) => {
2381
+ enableDryRunIfNeeded(options);
2033
2382
  const spinner = ora2("Generating controller...").start();
2034
2383
  try {
2035
2384
  const kebabName = toKebabCase(name);
2036
2385
  const pascalName = toPascalCase(name);
2037
2386
  const camelName = toCamelCase(name);
2038
2387
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2039
- const moduleDir = path3.join(getModulesDir(), moduleName);
2040
- const filePath = path3.join(moduleDir, `${kebabName}.controller.ts`);
2388
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2389
+ const filePath = path5.join(moduleDir, `${kebabName}.controller.ts`);
2041
2390
  if (await fileExists(filePath)) {
2042
2391
  spinner.stop();
2043
- error(`Controller "${kebabName}" already exists`);
2392
+ displayError(ErrorTypes.FILE_ALREADY_EXISTS(`${kebabName}.controller.ts`));
2044
2393
  return;
2045
2394
  }
2046
2395
  await writeFile(filePath, controllerTemplate(kebabName, pascalName, camelName));
2047
2396
  spinner.succeed(`Controller "${pascalName}Controller" generated!`);
2048
2397
  success(` src/modules/${moduleName}/${kebabName}.controller.ts`);
2398
+ showDryRunSummary(options);
2049
2399
  } catch (err) {
2050
2400
  spinner.fail("Failed to generate controller");
2051
2401
  error(err instanceof Error ? err.message : String(err));
2052
2402
  }
2053
2403
  });
2054
- generateCommand.command("service <name>").alias("s").description("Generate a service").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2404
+ 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) => {
2405
+ enableDryRunIfNeeded(options);
2055
2406
  const spinner = ora2("Generating service...").start();
2056
2407
  try {
2057
2408
  const kebabName = toKebabCase(name);
2058
2409
  const pascalName = toPascalCase(name);
2059
2410
  const camelName = toCamelCase(name);
2060
2411
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2061
- const moduleDir = path3.join(getModulesDir(), moduleName);
2062
- const filePath = path3.join(moduleDir, `${kebabName}.service.ts`);
2412
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2413
+ const filePath = path5.join(moduleDir, `${kebabName}.service.ts`);
2063
2414
  if (await fileExists(filePath)) {
2064
2415
  spinner.stop();
2065
2416
  error(`Service "${kebabName}" already exists`);
@@ -2068,12 +2419,14 @@ generateCommand.command("service <name>").alias("s").description("Generate a ser
2068
2419
  await writeFile(filePath, serviceTemplate(kebabName, pascalName, camelName));
2069
2420
  spinner.succeed(`Service "${pascalName}Service" generated!`);
2070
2421
  success(` src/modules/${moduleName}/${kebabName}.service.ts`);
2422
+ showDryRunSummary(options);
2071
2423
  } catch (err) {
2072
2424
  spinner.fail("Failed to generate service");
2073
2425
  error(err instanceof Error ? err.message : String(err));
2074
2426
  }
2075
2427
  });
2076
- generateCommand.command("repository <name>").alias("r").description("Generate a repository").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2428
+ 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) => {
2429
+ enableDryRunIfNeeded(options);
2077
2430
  const spinner = ora2("Generating repository...").start();
2078
2431
  try {
2079
2432
  const kebabName = toKebabCase(name);
@@ -2081,8 +2434,8 @@ generateCommand.command("repository <name>").alias("r").description("Generate a
2081
2434
  const camelName = toCamelCase(name);
2082
2435
  const pluralName = pluralize(kebabName);
2083
2436
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2084
- const moduleDir = path3.join(getModulesDir(), moduleName);
2085
- const filePath = path3.join(moduleDir, `${kebabName}.repository.ts`);
2437
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2438
+ const filePath = path5.join(moduleDir, `${kebabName}.repository.ts`);
2086
2439
  if (await fileExists(filePath)) {
2087
2440
  spinner.stop();
2088
2441
  error(`Repository "${kebabName}" already exists`);
@@ -2091,19 +2444,21 @@ generateCommand.command("repository <name>").alias("r").description("Generate a
2091
2444
  await writeFile(filePath, repositoryTemplate(kebabName, pascalName, camelName, pluralName));
2092
2445
  spinner.succeed(`Repository "${pascalName}Repository" generated!`);
2093
2446
  success(` src/modules/${moduleName}/${kebabName}.repository.ts`);
2447
+ showDryRunSummary(options);
2094
2448
  } catch (err) {
2095
2449
  spinner.fail("Failed to generate repository");
2096
2450
  error(err instanceof Error ? err.message : String(err));
2097
2451
  }
2098
2452
  });
2099
- generateCommand.command("types <name>").alias("t").description("Generate types/interfaces").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2453
+ 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) => {
2454
+ enableDryRunIfNeeded(options);
2100
2455
  const spinner = ora2("Generating types...").start();
2101
2456
  try {
2102
2457
  const kebabName = toKebabCase(name);
2103
2458
  const pascalName = toPascalCase(name);
2104
2459
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2105
- const moduleDir = path3.join(getModulesDir(), moduleName);
2106
- const filePath = path3.join(moduleDir, `${kebabName}.types.ts`);
2460
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2461
+ const filePath = path5.join(moduleDir, `${kebabName}.types.ts`);
2107
2462
  if (await fileExists(filePath)) {
2108
2463
  spinner.stop();
2109
2464
  error(`Types file "${kebabName}.types.ts" already exists`);
@@ -2112,20 +2467,22 @@ generateCommand.command("types <name>").alias("t").description("Generate types/i
2112
2467
  await writeFile(filePath, typesTemplate(kebabName, pascalName));
2113
2468
  spinner.succeed(`Types for "${pascalName}" generated!`);
2114
2469
  success(` src/modules/${moduleName}/${kebabName}.types.ts`);
2470
+ showDryRunSummary(options);
2115
2471
  } catch (err) {
2116
2472
  spinner.fail("Failed to generate types");
2117
2473
  error(err instanceof Error ? err.message : String(err));
2118
2474
  }
2119
2475
  });
2120
- generateCommand.command("schema <name>").alias("v").description("Generate validation schemas").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2476
+ 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) => {
2477
+ enableDryRunIfNeeded(options);
2121
2478
  const spinner = ora2("Generating schemas...").start();
2122
2479
  try {
2123
2480
  const kebabName = toKebabCase(name);
2124
2481
  const pascalName = toPascalCase(name);
2125
2482
  const camelName = toCamelCase(name);
2126
2483
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2127
- const moduleDir = path3.join(getModulesDir(), moduleName);
2128
- const filePath = path3.join(moduleDir, `${kebabName}.schemas.ts`);
2484
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2485
+ const filePath = path5.join(moduleDir, `${kebabName}.schemas.ts`);
2129
2486
  if (await fileExists(filePath)) {
2130
2487
  spinner.stop();
2131
2488
  error(`Schemas file "${kebabName}.schemas.ts" already exists`);
@@ -2134,12 +2491,14 @@ generateCommand.command("schema <name>").alias("v").description("Generate valida
2134
2491
  await writeFile(filePath, schemasTemplate(kebabName, pascalName, camelName));
2135
2492
  spinner.succeed(`Schemas for "${pascalName}" generated!`);
2136
2493
  success(` src/modules/${moduleName}/${kebabName}.schemas.ts`);
2494
+ showDryRunSummary(options);
2137
2495
  } catch (err) {
2138
2496
  spinner.fail("Failed to generate schemas");
2139
2497
  error(err instanceof Error ? err.message : String(err));
2140
2498
  }
2141
2499
  });
2142
- generateCommand.command("routes <name>").description("Generate routes").option("-m, --module <module>", "Target module name").action(async (name, options) => {
2500
+ 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) => {
2501
+ enableDryRunIfNeeded(options);
2143
2502
  const spinner = ora2("Generating routes...").start();
2144
2503
  try {
2145
2504
  const kebabName = toKebabCase(name);
@@ -2147,8 +2506,8 @@ generateCommand.command("routes <name>").description("Generate routes").option("
2147
2506
  const camelName = toCamelCase(name);
2148
2507
  const pluralName = pluralize(kebabName);
2149
2508
  const moduleName = options.module ? toKebabCase(options.module) : kebabName;
2150
- const moduleDir = path3.join(getModulesDir(), moduleName);
2151
- const filePath = path3.join(moduleDir, `${kebabName}.routes.ts`);
2509
+ const moduleDir = path5.join(getModulesDir(), moduleName);
2510
+ const filePath = path5.join(moduleDir, `${kebabName}.routes.ts`);
2152
2511
  if (await fileExists(filePath)) {
2153
2512
  spinner.stop();
2154
2513
  error(`Routes file "${kebabName}.routes.ts" already exists`);
@@ -2157,6 +2516,7 @@ generateCommand.command("routes <name>").description("Generate routes").option("
2157
2516
  await writeFile(filePath, routesTemplate(kebabName, pascalName, camelName, pluralName));
2158
2517
  spinner.succeed(`Routes for "${pascalName}" generated!`);
2159
2518
  success(` src/modules/${moduleName}/${kebabName}.routes.ts`);
2519
+ showDryRunSummary(options);
2160
2520
  } catch (err) {
2161
2521
  spinner.fail("Failed to generate routes");
2162
2522
  error(err instanceof Error ? err.message : String(err));
@@ -2234,22 +2594,24 @@ async function promptForFields() {
2234
2594
  }
2235
2595
 
2236
2596
  // src/cli/commands/add-module.ts
2597
+ init_esm_shims();
2237
2598
  import { Command as Command3 } from "commander";
2238
- import path6 from "path";
2599
+ import path8 from "path";
2239
2600
  import ora3 from "ora";
2240
- import chalk4 from "chalk";
2601
+ import chalk7 from "chalk";
2241
2602
  import * as fs5 from "fs/promises";
2242
2603
 
2243
2604
  // src/cli/utils/env-manager.ts
2605
+ init_esm_shims();
2244
2606
  import * as fs3 from "fs/promises";
2245
- import * as path4 from "path";
2607
+ import * as path6 from "path";
2246
2608
  import { existsSync } from "fs";
2247
2609
  var EnvManager = class {
2248
2610
  envPath;
2249
2611
  envExamplePath;
2250
2612
  constructor(projectRoot) {
2251
- this.envPath = path4.join(projectRoot, ".env");
2252
- this.envExamplePath = path4.join(projectRoot, ".env.example");
2613
+ this.envPath = path6.join(projectRoot, ".env");
2614
+ this.envExamplePath = path6.join(projectRoot, ".env.example");
2253
2615
  }
2254
2616
  /**
2255
2617
  * Add environment variables to .env file
@@ -2899,16 +3261,17 @@ var EnvManager = class {
2899
3261
  };
2900
3262
 
2901
3263
  // src/cli/utils/template-manager.ts
3264
+ init_esm_shims();
2902
3265
  import * as fs4 from "fs/promises";
2903
- import * as path5 from "path";
3266
+ import * as path7 from "path";
2904
3267
  import { createHash } from "crypto";
2905
3268
  import { existsSync as existsSync2 } from "fs";
2906
3269
  var TemplateManager = class {
2907
3270
  templatesDir;
2908
3271
  manifestsDir;
2909
3272
  constructor(projectRoot) {
2910
- this.templatesDir = path5.join(projectRoot, ".servcraft", "templates");
2911
- this.manifestsDir = path5.join(projectRoot, ".servcraft", "manifests");
3273
+ this.templatesDir = path7.join(projectRoot, ".servcraft", "templates");
3274
+ this.manifestsDir = path7.join(projectRoot, ".servcraft", "manifests");
2912
3275
  }
2913
3276
  /**
2914
3277
  * Initialize template system
@@ -2922,10 +3285,10 @@ var TemplateManager = class {
2922
3285
  */
2923
3286
  async saveTemplate(moduleName, files) {
2924
3287
  await this.initialize();
2925
- const moduleTemplateDir = path5.join(this.templatesDir, moduleName);
3288
+ const moduleTemplateDir = path7.join(this.templatesDir, moduleName);
2926
3289
  await fs4.mkdir(moduleTemplateDir, { recursive: true });
2927
3290
  for (const [fileName, content] of Object.entries(files)) {
2928
- const filePath = path5.join(moduleTemplateDir, fileName);
3291
+ const filePath = path7.join(moduleTemplateDir, fileName);
2929
3292
  await fs4.writeFile(filePath, content, "utf-8");
2930
3293
  }
2931
3294
  }
@@ -2934,7 +3297,7 @@ var TemplateManager = class {
2934
3297
  */
2935
3298
  async getTemplate(moduleName, fileName) {
2936
3299
  try {
2937
- const filePath = path5.join(this.templatesDir, moduleName, fileName);
3300
+ const filePath = path7.join(this.templatesDir, moduleName, fileName);
2938
3301
  return await fs4.readFile(filePath, "utf-8");
2939
3302
  } catch {
2940
3303
  return null;
@@ -2959,7 +3322,7 @@ var TemplateManager = class {
2959
3322
  installedAt: /* @__PURE__ */ new Date(),
2960
3323
  updatedAt: /* @__PURE__ */ new Date()
2961
3324
  };
2962
- const manifestPath = path5.join(this.manifestsDir, `${moduleName}.json`);
3325
+ const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
2963
3326
  await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
2964
3327
  }
2965
3328
  /**
@@ -2967,7 +3330,7 @@ var TemplateManager = class {
2967
3330
  */
2968
3331
  async getManifest(moduleName) {
2969
3332
  try {
2970
- const manifestPath = path5.join(this.manifestsDir, `${moduleName}.json`);
3333
+ const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
2971
3334
  const content = await fs4.readFile(manifestPath, "utf-8");
2972
3335
  return JSON.parse(content);
2973
3336
  } catch {
@@ -2996,7 +3359,7 @@ var TemplateManager = class {
2996
3359
  }
2997
3360
  const results = [];
2998
3361
  for (const [fileName, fileInfo] of Object.entries(manifest.files)) {
2999
- const filePath = path5.join(moduleDir, fileName);
3362
+ const filePath = path7.join(moduleDir, fileName);
3000
3363
  if (!existsSync2(filePath)) {
3001
3364
  results.push({
3002
3365
  fileName,
@@ -3022,7 +3385,7 @@ var TemplateManager = class {
3022
3385
  */
3023
3386
  async createBackup(moduleName, moduleDir) {
3024
3387
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
3025
- const backupDir = path5.join(path5.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
3388
+ const backupDir = path7.join(path7.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
3026
3389
  await this.copyDirectory(moduleDir, backupDir);
3027
3390
  return backupDir;
3028
3391
  }
@@ -3033,8 +3396,8 @@ var TemplateManager = class {
3033
3396
  await fs4.mkdir(dest, { recursive: true });
3034
3397
  const entries = await fs4.readdir(src, { withFileTypes: true });
3035
3398
  for (const entry of entries) {
3036
- const srcPath = path5.join(src, entry.name);
3037
- const destPath = path5.join(dest, entry.name);
3399
+ const srcPath = path7.join(src, entry.name);
3400
+ const destPath = path7.join(dest, entry.name);
3038
3401
  if (entry.isDirectory()) {
3039
3402
  await this.copyDirectory(srcPath, destPath);
3040
3403
  } else {
@@ -3135,23 +3498,24 @@ var TemplateManager = class {
3135
3498
  }
3136
3499
  manifest.files = fileHashes;
3137
3500
  manifest.updatedAt = /* @__PURE__ */ new Date();
3138
- const manifestPath = path5.join(this.manifestsDir, `${moduleName}.json`);
3501
+ const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3139
3502
  await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3140
3503
  }
3141
3504
  };
3142
3505
 
3143
3506
  // src/cli/utils/interactive-prompt.ts
3507
+ init_esm_shims();
3144
3508
  import inquirer3 from "inquirer";
3145
- import chalk3 from "chalk";
3509
+ import chalk6 from "chalk";
3146
3510
  var InteractivePrompt = class {
3147
3511
  /**
3148
3512
  * Ask what to do when module already exists
3149
3513
  */
3150
3514
  static async askModuleExists(moduleName, hasModifications) {
3151
- console.log(chalk3.yellow(`
3515
+ console.log(chalk6.yellow(`
3152
3516
  \u26A0\uFE0F Module "${moduleName}" already exists`));
3153
3517
  if (hasModifications) {
3154
- console.log(chalk3.yellow(" Some files have been modified by you.\n"));
3518
+ console.log(chalk6.yellow(" Some files have been modified by you.\n"));
3155
3519
  }
3156
3520
  const { action } = await inquirer3.prompt([
3157
3521
  {
@@ -3189,12 +3553,12 @@ var InteractivePrompt = class {
3189
3553
  * Ask what to do with a specific file
3190
3554
  */
3191
3555
  static async askFileAction(fileName, isModified, yourLines, newLines) {
3192
- console.log(chalk3.cyan(`
3556
+ console.log(chalk6.cyan(`
3193
3557
  \u{1F4C1} ${fileName}`));
3194
3558
  console.log(
3195
- chalk3.gray(` Your version: ${yourLines} lines${isModified ? " (modified)" : ""}`)
3559
+ chalk6.gray(` Your version: ${yourLines} lines${isModified ? " (modified)" : ""}`)
3196
3560
  );
3197
- console.log(chalk3.gray(` New version: ${newLines} lines
3561
+ console.log(chalk6.gray(` New version: ${newLines} lines
3198
3562
  `));
3199
3563
  const { action } = await inquirer3.prompt([
3200
3564
  {
@@ -3246,7 +3610,7 @@ var InteractivePrompt = class {
3246
3610
  * Display diff and ask to continue
3247
3611
  */
3248
3612
  static async showDiffAndAsk(diff) {
3249
- console.log(chalk3.cyan("\n\u{1F4CA} Differences:\n"));
3613
+ console.log(chalk6.cyan("\n\u{1F4CA} Differences:\n"));
3250
3614
  console.log(diff);
3251
3615
  return await this.confirm("\nDo you want to proceed with this change?", true);
3252
3616
  }
@@ -3254,41 +3618,41 @@ var InteractivePrompt = class {
3254
3618
  * Display merge conflicts
3255
3619
  */
3256
3620
  static displayConflicts(conflicts) {
3257
- console.log(chalk3.red("\n\u26A0\uFE0F Merge Conflicts Detected:\n"));
3621
+ console.log(chalk6.red("\n\u26A0\uFE0F Merge Conflicts Detected:\n"));
3258
3622
  conflicts.forEach((conflict, i) => {
3259
- console.log(chalk3.yellow(` ${i + 1}. ${conflict}`));
3623
+ console.log(chalk6.yellow(` ${i + 1}. ${conflict}`));
3260
3624
  });
3261
- console.log(chalk3.gray("\n Conflict markers have been added to the file:"));
3262
- console.log(chalk3.gray(" <<<<<<< YOUR VERSION"));
3263
- console.log(chalk3.gray(" ... your code ..."));
3264
- console.log(chalk3.gray(" ======="));
3265
- console.log(chalk3.gray(" ... new code ..."));
3266
- console.log(chalk3.gray(" >>>>>>> NEW VERSION\n"));
3625
+ console.log(chalk6.gray("\n Conflict markers have been added to the file:"));
3626
+ console.log(chalk6.gray(" <<<<<<< YOUR VERSION"));
3627
+ console.log(chalk6.gray(" ... your code ..."));
3628
+ console.log(chalk6.gray(" ======="));
3629
+ console.log(chalk6.gray(" ... new code ..."));
3630
+ console.log(chalk6.gray(" >>>>>>> NEW VERSION\n"));
3267
3631
  }
3268
3632
  /**
3269
3633
  * Show backup location
3270
3634
  */
3271
3635
  static showBackupCreated(backupPath) {
3272
- console.log(chalk3.green(`
3273
- \u2713 Backup created: ${chalk3.cyan(backupPath)}`));
3636
+ console.log(chalk6.green(`
3637
+ \u2713 Backup created: ${chalk6.cyan(backupPath)}`));
3274
3638
  }
3275
3639
  /**
3276
3640
  * Show merge summary
3277
3641
  */
3278
3642
  static showMergeSummary(stats) {
3279
- console.log(chalk3.bold("\n\u{1F4CA} Merge Summary:\n"));
3643
+ console.log(chalk6.bold("\n\u{1F4CA} Merge Summary:\n"));
3280
3644
  if (stats.merged > 0) {
3281
- console.log(chalk3.green(` \u2713 Merged: ${stats.merged} file(s)`));
3645
+ console.log(chalk6.green(` \u2713 Merged: ${stats.merged} file(s)`));
3282
3646
  }
3283
3647
  if (stats.kept > 0) {
3284
- console.log(chalk3.blue(` \u2192 Kept: ${stats.kept} file(s)`));
3648
+ console.log(chalk6.blue(` \u2192 Kept: ${stats.kept} file(s)`));
3285
3649
  }
3286
3650
  if (stats.overwritten > 0) {
3287
- console.log(chalk3.yellow(` \u26A0 Overwritten: ${stats.overwritten} file(s)`));
3651
+ console.log(chalk6.yellow(` \u26A0 Overwritten: ${stats.overwritten} file(s)`));
3288
3652
  }
3289
3653
  if (stats.conflicts > 0) {
3290
- console.log(chalk3.red(` \u26A0 Conflicts: ${stats.conflicts} file(s)`));
3291
- console.log(chalk3.gray("\n Please resolve conflicts manually before committing.\n"));
3654
+ console.log(chalk6.red(` \u26A0 Conflicts: ${stats.conflicts} file(s)`));
3655
+ console.log(chalk6.gray("\n Please resolve conflicts manually before committing.\n"));
3292
3656
  }
3293
3657
  }
3294
3658
  /**
@@ -3326,6 +3690,7 @@ var InteractivePrompt = class {
3326
3690
  };
3327
3691
 
3328
3692
  // src/cli/commands/add-module.ts
3693
+ init_error_handler();
3329
3694
  var AVAILABLE_MODULES = {
3330
3695
  auth: {
3331
3696
  name: "Authentication",
@@ -3460,31 +3825,40 @@ var AVAILABLE_MODULES = {
3460
3825
  var addModuleCommand = new Command3("add").description("Add a pre-built module to your project").argument(
3461
3826
  "[module]",
3462
3827
  "Module to add (auth, users, email, audit, upload, cache, notifications, settings)"
3463
- ).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(
3828
+ ).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(
3464
3829
  async (moduleName, options) => {
3465
3830
  if (options?.list || !moduleName) {
3466
- console.log(chalk4.bold("\n\u{1F4E6} Available Modules:\n"));
3831
+ console.log(chalk7.bold("\n\u{1F4E6} Available Modules:\n"));
3467
3832
  for (const [key, mod] of Object.entries(AVAILABLE_MODULES)) {
3468
- console.log(` ${chalk4.cyan(key.padEnd(15))} ${mod.name}`);
3469
- console.log(` ${" ".repeat(15)} ${chalk4.gray(mod.description)}
3833
+ console.log(` ${chalk7.cyan(key.padEnd(15))} ${mod.name}`);
3834
+ console.log(` ${" ".repeat(15)} ${chalk7.gray(mod.description)}
3470
3835
  `);
3471
3836
  }
3472
- console.log(chalk4.bold("Usage:"));
3473
- console.log(` ${chalk4.yellow("servcraft add auth")} Add authentication module`);
3474
- console.log(` ${chalk4.yellow("servcraft add users")} Add user management module`);
3475
- console.log(` ${chalk4.yellow("servcraft add email")} Add email service module
3837
+ console.log(chalk7.bold("Usage:"));
3838
+ console.log(` ${chalk7.yellow("servcraft add auth")} Add authentication module`);
3839
+ console.log(` ${chalk7.yellow("servcraft add users")} Add user management module`);
3840
+ console.log(` ${chalk7.yellow("servcraft add email")} Add email service module
3476
3841
  `);
3477
3842
  return;
3478
3843
  }
3844
+ const dryRun = DryRunManager.getInstance();
3845
+ if (options?.dryRun) {
3846
+ dryRun.enable();
3847
+ console.log(chalk7.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
3848
+ }
3479
3849
  const module = AVAILABLE_MODULES[moduleName];
3480
3850
  if (!module) {
3481
- error(`Unknown module: ${moduleName}`);
3482
- info('Run "servcraft add --list" to see available modules');
3851
+ displayError(ErrorTypes.MODULE_NOT_FOUND(moduleName));
3852
+ return;
3853
+ }
3854
+ const projectError = validateProject();
3855
+ if (projectError) {
3856
+ displayError(projectError);
3483
3857
  return;
3484
3858
  }
3485
3859
  const spinner = ora3(`Adding ${module.name} module...`).start();
3486
3860
  try {
3487
- const moduleDir = path6.join(getModulesDir(), moduleName);
3861
+ const moduleDir = path8.join(getModulesDir(), moduleName);
3488
3862
  const templateManager = new TemplateManager(process.cwd());
3489
3863
  const moduleExists = await fileExists(moduleDir);
3490
3864
  if (moduleExists) {
@@ -3552,16 +3926,16 @@ var addModuleCommand = new Command3("add").description("Add a pre-built module t
3552
3926
  info("\n\u{1F4DD} Created new .env file");
3553
3927
  }
3554
3928
  if (result.added.length > 0) {
3555
- console.log(chalk4.bold("\n\u2705 Added to .env:"));
3929
+ console.log(chalk7.bold("\n\u2705 Added to .env:"));
3556
3930
  result.added.forEach((key) => success(` ${key}`));
3557
3931
  }
3558
3932
  if (result.skipped.length > 0) {
3559
- console.log(chalk4.bold("\n\u23ED\uFE0F Already in .env (skipped):"));
3933
+ console.log(chalk7.bold("\n\u23ED\uFE0F Already in .env (skipped):"));
3560
3934
  result.skipped.forEach((key) => info(` ${key}`));
3561
3935
  }
3562
3936
  const requiredVars = envSections.flatMap((section) => section.variables).filter((v) => v.required && !v.value).map((v) => v.key);
3563
3937
  if (requiredVars.length > 0) {
3564
- console.log(chalk4.bold("\n\u26A0\uFE0F Required configuration:"));
3938
+ console.log(chalk7.bold("\n\u26A0\uFE0F Required configuration:"));
3565
3939
  requiredVars.forEach((key) => warn(` ${key} - Please configure this variable`));
3566
3940
  }
3567
3941
  } catch (err) {
@@ -3569,10 +3943,15 @@ var addModuleCommand = new Command3("add").description("Add a pre-built module t
3569
3943
  error(err instanceof Error ? err.message : String(err));
3570
3944
  }
3571
3945
  }
3572
- console.log("\n\u{1F4CC} Next steps:");
3573
- info(" 1. Configure environment variables in .env (if needed)");
3574
- info(" 2. Register the module in your main app file");
3575
- info(" 3. Run database migrations if needed");
3946
+ if (!options?.dryRun) {
3947
+ console.log("\n\u{1F4CC} Next steps:");
3948
+ info(" 1. Configure environment variables in .env (if needed)");
3949
+ info(" 2. Register the module in your main app file");
3950
+ info(" 3. Run database migrations if needed");
3951
+ }
3952
+ if (options?.dryRun) {
3953
+ dryRun.printSummary();
3954
+ }
3576
3955
  } catch (err) {
3577
3956
  spinner.fail("Failed to add module");
3578
3957
  error(err instanceof Error ? err.message : String(err));
@@ -3623,7 +4002,7 @@ export * from './auth.schemas.js';
3623
4002
  `
3624
4003
  };
3625
4004
  for (const [name, content] of Object.entries(files)) {
3626
- await writeFile(path6.join(dir, name), content);
4005
+ await writeFile(path8.join(dir, name), content);
3627
4006
  }
3628
4007
  }
3629
4008
  async function generateUsersModule(dir) {
@@ -3663,7 +4042,7 @@ export * from './user.schemas.js';
3663
4042
  `
3664
4043
  };
3665
4044
  for (const [name, content] of Object.entries(files)) {
3666
- await writeFile(path6.join(dir, name), content);
4045
+ await writeFile(path8.join(dir, name), content);
3667
4046
  }
3668
4047
  }
3669
4048
  async function generateEmailModule(dir) {
@@ -3720,7 +4099,7 @@ export { EmailService, emailService } from './email.service.js';
3720
4099
  `
3721
4100
  };
3722
4101
  for (const [name, content] of Object.entries(files)) {
3723
- await writeFile(path6.join(dir, name), content);
4102
+ await writeFile(path8.join(dir, name), content);
3724
4103
  }
3725
4104
  }
3726
4105
  async function generateAuditModule(dir) {
@@ -3764,7 +4143,7 @@ export { AuditService, auditService } from './audit.service.js';
3764
4143
  `
3765
4144
  };
3766
4145
  for (const [name, content] of Object.entries(files)) {
3767
- await writeFile(path6.join(dir, name), content);
4146
+ await writeFile(path8.join(dir, name), content);
3768
4147
  }
3769
4148
  }
3770
4149
  async function generateUploadModule(dir) {
@@ -3790,7 +4169,7 @@ export interface UploadOptions {
3790
4169
  `
3791
4170
  };
3792
4171
  for (const [name, content] of Object.entries(files)) {
3793
- await writeFile(path6.join(dir, name), content);
4172
+ await writeFile(path8.join(dir, name), content);
3794
4173
  }
3795
4174
  }
3796
4175
  async function generateCacheModule(dir) {
@@ -3836,7 +4215,7 @@ export { CacheService, cacheService } from './cache.service.js';
3836
4215
  `
3837
4216
  };
3838
4217
  for (const [name, content] of Object.entries(files)) {
3839
- await writeFile(path6.join(dir, name), content);
4218
+ await writeFile(path8.join(dir, name), content);
3840
4219
  }
3841
4220
  }
3842
4221
  async function generateGenericModule(dir, name) {
@@ -3850,18 +4229,18 @@ export interface ${name.charAt(0).toUpperCase() + name.slice(1)}Data {
3850
4229
  `
3851
4230
  };
3852
4231
  for (const [fileName, content] of Object.entries(files)) {
3853
- await writeFile(path6.join(dir, fileName), content);
4232
+ await writeFile(path8.join(dir, fileName), content);
3854
4233
  }
3855
4234
  }
3856
4235
  async function findServercraftModules() {
3857
- const scriptDir = path6.dirname(new URL(import.meta.url).pathname);
4236
+ const scriptDir = path8.dirname(new URL(import.meta.url).pathname);
3858
4237
  const possiblePaths = [
3859
4238
  // Local node_modules (when servcraft is a dependency)
3860
- path6.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
4239
+ path8.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
3861
4240
  // From dist/cli/index.js -> src/modules (npx or global install)
3862
- path6.resolve(scriptDir, "..", "..", "src", "modules"),
4241
+ path8.resolve(scriptDir, "..", "..", "src", "modules"),
3863
4242
  // From src/cli/commands/add-module.ts -> src/modules (development)
3864
- path6.resolve(scriptDir, "..", "..", "modules")
4243
+ path8.resolve(scriptDir, "..", "..", "modules")
3865
4244
  ];
3866
4245
  for (const p of possiblePaths) {
3867
4246
  try {
@@ -3885,7 +4264,7 @@ async function generateModuleFiles(moduleName, moduleDir) {
3885
4264
  const sourceDirName = moduleNameMap[moduleName] || moduleName;
3886
4265
  const servercraftModulesDir = await findServercraftModules();
3887
4266
  if (servercraftModulesDir) {
3888
- const sourceModuleDir = path6.join(servercraftModulesDir, sourceDirName);
4267
+ const sourceModuleDir = path8.join(servercraftModulesDir, sourceDirName);
3889
4268
  if (await fileExists(sourceModuleDir)) {
3890
4269
  await copyModuleFromSource(sourceModuleDir, moduleDir);
3891
4270
  return;
@@ -3917,8 +4296,8 @@ async function generateModuleFiles(moduleName, moduleDir) {
3917
4296
  async function copyModuleFromSource(sourceDir, targetDir) {
3918
4297
  const entries = await fs5.readdir(sourceDir, { withFileTypes: true });
3919
4298
  for (const entry of entries) {
3920
- const sourcePath = path6.join(sourceDir, entry.name);
3921
- const targetPath = path6.join(targetDir, entry.name);
4299
+ const sourcePath = path8.join(sourceDir, entry.name);
4300
+ const targetPath = path8.join(targetDir, entry.name);
3922
4301
  if (entry.isDirectory()) {
3923
4302
  await fs5.mkdir(targetPath, { recursive: true });
3924
4303
  await copyModuleFromSource(sourcePath, targetPath);
@@ -3931,7 +4310,7 @@ async function getModuleFiles(moduleName, moduleDir) {
3931
4310
  const files = {};
3932
4311
  const entries = await fs5.readdir(moduleDir);
3933
4312
  for (const entry of entries) {
3934
- const filePath = path6.join(moduleDir, entry);
4313
+ const filePath = path8.join(moduleDir, entry);
3935
4314
  const stat2 = await fs5.stat(filePath);
3936
4315
  if (stat2.isFile() && entry.endsWith(".ts")) {
3937
4316
  const content = await fs5.readFile(filePath, "utf-8");
@@ -3942,14 +4321,14 @@ async function getModuleFiles(moduleName, moduleDir) {
3942
4321
  }
3943
4322
  async function showDiffForModule(templateManager, moduleName, moduleDir) {
3944
4323
  const modifiedFiles = await templateManager.getModifiedFiles(moduleName, moduleDir);
3945
- console.log(chalk4.cyan(`
4324
+ console.log(chalk7.cyan(`
3946
4325
  \u{1F4CA} Changes in module "${moduleName}":
3947
4326
  `));
3948
4327
  for (const file of modifiedFiles) {
3949
4328
  if (file.isModified) {
3950
- console.log(chalk4.yellow(`
4329
+ console.log(chalk7.yellow(`
3951
4330
  \u{1F4C4} ${file.fileName}:`));
3952
- const currentPath = path6.join(moduleDir, file.fileName);
4331
+ const currentPath = path8.join(moduleDir, file.fileName);
3953
4332
  const currentContent = await fs5.readFile(currentPath, "utf-8");
3954
4333
  const originalContent = await templateManager.getTemplate(moduleName, file.fileName);
3955
4334
  if (originalContent) {
@@ -3962,11 +4341,11 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
3962
4341
  async function performSmartMerge(templateManager, moduleName, moduleDir, _displayName) {
3963
4342
  const spinner = ora3("Analyzing files for merge...").start();
3964
4343
  const newFiles = {};
3965
- const templateDir = path6.join(templateManager["templatesDir"], moduleName);
4344
+ const templateDir = path8.join(templateManager["templatesDir"], moduleName);
3966
4345
  try {
3967
4346
  const entries = await fs5.readdir(templateDir);
3968
4347
  for (const entry of entries) {
3969
- const content = await fs5.readFile(path6.join(templateDir, entry), "utf-8");
4348
+ const content = await fs5.readFile(path8.join(templateDir, entry), "utf-8");
3970
4349
  newFiles[entry] = content;
3971
4350
  }
3972
4351
  } catch {
@@ -3984,7 +4363,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
3984
4363
  };
3985
4364
  for (const fileInfo of modifiedFiles) {
3986
4365
  const fileName = fileInfo.fileName;
3987
- const filePath = path6.join(moduleDir, fileName);
4366
+ const filePath = path8.join(moduleDir, fileName);
3988
4367
  const newContent = newFiles[fileName];
3989
4368
  if (!newContent) {
3990
4369
  continue;
@@ -4053,10 +4432,11 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4053
4432
  }
4054
4433
 
4055
4434
  // src/cli/commands/db.ts
4435
+ init_esm_shims();
4056
4436
  import { Command as Command4 } from "commander";
4057
4437
  import { execSync as execSync2, spawn } from "child_process";
4058
4438
  import ora4 from "ora";
4059
- import chalk5 from "chalk";
4439
+ import chalk8 from "chalk";
4060
4440
  var dbCommand = new Command4("db").description("Database management commands");
4061
4441
  dbCommand.command("migrate").description("Run database migrations").option("-n, --name <name>", "Migration name").action(async (options) => {
4062
4442
  const spinner = ora4("Running migrations...").start();
@@ -4113,7 +4493,7 @@ dbCommand.command("seed").description("Run database seed").action(async () => {
4113
4493
  });
4114
4494
  dbCommand.command("reset").description("Reset database (drop all data and re-run migrations)").option("-f, --force", "Skip confirmation").action(async (options) => {
4115
4495
  if (!options.force) {
4116
- console.log(chalk5.yellow("\n\u26A0\uFE0F WARNING: This will delete all data in your database!\n"));
4496
+ console.log(chalk8.yellow("\n\u26A0\uFE0F WARNING: This will delete all data in your database!\n"));
4117
4497
  const readline = await import("readline");
4118
4498
  const rl = readline.createInterface({
4119
4499
  input: process.stdin,
@@ -4146,21 +4526,25 @@ dbCommand.command("status").description("Show migration status").action(async ()
4146
4526
  });
4147
4527
 
4148
4528
  // src/cli/commands/docs.ts
4529
+ init_esm_shims();
4149
4530
  import { Command as Command5 } from "commander";
4150
- import path8 from "path";
4531
+ import path10 from "path";
4151
4532
  import fs7 from "fs/promises";
4152
4533
  import ora6 from "ora";
4153
- import chalk6 from "chalk";
4534
+ import chalk9 from "chalk";
4154
4535
 
4155
4536
  // src/cli/utils/docs-generator.ts
4537
+ init_esm_shims();
4156
4538
  import fs6 from "fs/promises";
4157
- import path7 from "path";
4539
+ import path9 from "path";
4158
4540
  import ora5 from "ora";
4159
4541
 
4160
4542
  // src/core/server.ts
4543
+ init_esm_shims();
4161
4544
  import Fastify from "fastify";
4162
4545
 
4163
4546
  // src/core/logger.ts
4547
+ init_esm_shims();
4164
4548
  import pino from "pino";
4165
4549
  var defaultConfig = {
4166
4550
  level: process.env.LOG_LEVEL || "info",
@@ -4293,7 +4677,14 @@ function createServer(config2 = {}) {
4293
4677
  return new Server(config2);
4294
4678
  }
4295
4679
 
4680
+ // src/middleware/index.ts
4681
+ init_esm_shims();
4682
+
4683
+ // src/middleware/error-handler.ts
4684
+ init_esm_shims();
4685
+
4296
4686
  // src/utils/errors.ts
4687
+ init_esm_shims();
4297
4688
  var AppError = class _AppError extends Error {
4298
4689
  statusCode;
4299
4690
  isOperational;
@@ -4347,7 +4738,11 @@ function isAppError(error2) {
4347
4738
  return error2 instanceof AppError;
4348
4739
  }
4349
4740
 
4741
+ // src/config/index.ts
4742
+ init_esm_shims();
4743
+
4350
4744
  // src/config/env.ts
4745
+ init_esm_shims();
4351
4746
  import { z } from "zod";
4352
4747
  import dotenv from "dotenv";
4353
4748
  dotenv.config();
@@ -4493,6 +4888,7 @@ function registerErrorHandler(app) {
4493
4888
  }
4494
4889
 
4495
4890
  // src/middleware/security.ts
4891
+ init_esm_shims();
4496
4892
  import helmet from "@fastify/helmet";
4497
4893
  import cors from "@fastify/cors";
4498
4894
  import rateLimit from "@fastify/rate-limit";
@@ -4552,7 +4948,11 @@ async function registerSecurity(app, options = {}) {
4552
4948
  }
4553
4949
  }
4554
4950
 
4951
+ // src/modules/swagger/index.ts
4952
+ init_esm_shims();
4953
+
4555
4954
  // src/modules/swagger/swagger.service.ts
4955
+ init_esm_shims();
4556
4956
  import swagger from "@fastify/swagger";
4557
4957
  import swaggerUi from "@fastify/swagger-ui";
4558
4958
  var defaultConfig3 = {
@@ -4612,11 +5012,16 @@ async function registerSwagger(app, customConfig) {
4612
5012
  logger.info("Swagger documentation registered at /docs");
4613
5013
  }
4614
5014
 
5015
+ // src/modules/swagger/schema-builder.ts
5016
+ init_esm_shims();
5017
+
4615
5018
  // src/modules/auth/index.ts
5019
+ init_esm_shims();
4616
5020
  import jwt from "@fastify/jwt";
4617
5021
  import cookie from "@fastify/cookie";
4618
5022
 
4619
5023
  // src/modules/auth/auth.service.ts
5024
+ init_esm_shims();
4620
5025
  import bcrypt from "bcryptjs";
4621
5026
  import { Redis } from "ioredis";
4622
5027
  var AuthService = class {
@@ -4815,7 +5220,11 @@ function createAuthService(app) {
4815
5220
  return new AuthService(app);
4816
5221
  }
4817
5222
 
5223
+ // src/modules/auth/auth.controller.ts
5224
+ init_esm_shims();
5225
+
4818
5226
  // src/modules/auth/schemas.ts
5227
+ init_esm_shims();
4819
5228
  import { z as z2 } from "zod";
4820
5229
  var loginSchema = z2.object({
4821
5230
  email: z2.string().email("Invalid email address"),
@@ -4842,6 +5251,7 @@ var changePasswordSchema = z2.object({
4842
5251
  });
4843
5252
 
4844
5253
  // src/utils/response.ts
5254
+ init_esm_shims();
4845
5255
  function success2(reply, data, statusCode = 200) {
4846
5256
  const response = {
4847
5257
  success: true,
@@ -4857,6 +5267,7 @@ function noContent(reply) {
4857
5267
  }
4858
5268
 
4859
5269
  // src/modules/validation/validator.ts
5270
+ init_esm_shims();
4860
5271
  import { z as z3 } from "zod";
4861
5272
  function validateBody(schema, data) {
4862
5273
  const result = schema.safeParse(data);
@@ -4875,11 +5286,11 @@ function validateQuery(schema, data) {
4875
5286
  function formatZodErrors(error2) {
4876
5287
  const errors = {};
4877
5288
  for (const issue of error2.issues) {
4878
- const path9 = issue.path.join(".") || "root";
4879
- if (!errors[path9]) {
4880
- errors[path9] = [];
5289
+ const path12 = issue.path.join(".") || "root";
5290
+ if (!errors[path12]) {
5291
+ errors[path12] = [];
4881
5292
  }
4882
- errors[path9].push(issue.message);
5293
+ errors[path12].push(issue.message);
4883
5294
  }
4884
5295
  return errors;
4885
5296
  }
@@ -5027,7 +5438,11 @@ function createAuthController(authService, userService) {
5027
5438
  return new AuthController(authService, userService);
5028
5439
  }
5029
5440
 
5441
+ // src/modules/auth/auth.routes.ts
5442
+ init_esm_shims();
5443
+
5030
5444
  // src/modules/auth/auth.middleware.ts
5445
+ init_esm_shims();
5031
5446
  function createAuthMiddleware(authService) {
5032
5447
  return async function authenticate(request, _reply) {
5033
5448
  const authHeader = request.headers.authorization;
@@ -5070,7 +5485,14 @@ function registerAuthRoutes(app, controller, authService) {
5070
5485
  );
5071
5486
  }
5072
5487
 
5488
+ // src/modules/user/user.service.ts
5489
+ init_esm_shims();
5490
+
5491
+ // src/modules/user/user.repository.ts
5492
+ init_esm_shims();
5493
+
5073
5494
  // src/database/prisma.ts
5495
+ init_esm_shims();
5074
5496
  import { PrismaClient } from "@prisma/client";
5075
5497
  var prismaClientSingleton = () => {
5076
5498
  return new PrismaClient({
@@ -5084,6 +5506,7 @@ if (!isProduction()) {
5084
5506
  }
5085
5507
 
5086
5508
  // src/utils/pagination.ts
5509
+ init_esm_shims();
5087
5510
  var DEFAULT_PAGE = 1;
5088
5511
  var DEFAULT_LIMIT = 20;
5089
5512
  var MAX_LIMIT = 100;
@@ -5348,6 +5771,7 @@ function createUserRepository() {
5348
5771
  }
5349
5772
 
5350
5773
  // src/modules/user/types.ts
5774
+ init_esm_shims();
5351
5775
  var DEFAULT_ROLE_PERMISSIONS = {
5352
5776
  user: ["profile:read", "profile:update"],
5353
5777
  moderator: [
@@ -5478,6 +5902,9 @@ function createUserService(repository) {
5478
5902
  return new UserService(repository || createUserRepository());
5479
5903
  }
5480
5904
 
5905
+ // src/modules/auth/types.ts
5906
+ init_esm_shims();
5907
+
5481
5908
  // src/modules/auth/index.ts
5482
5909
  async function registerAuthModule(app) {
5483
5910
  await app.register(jwt, {
@@ -5497,7 +5924,14 @@ async function registerAuthModule(app) {
5497
5924
  logger.info("Auth module registered");
5498
5925
  }
5499
5926
 
5927
+ // src/modules/user/index.ts
5928
+ init_esm_shims();
5929
+
5930
+ // src/modules/user/user.controller.ts
5931
+ init_esm_shims();
5932
+
5500
5933
  // src/modules/user/schemas.ts
5934
+ init_esm_shims();
5501
5935
  import { z as z4 } from "zod";
5502
5936
  var userStatusEnum = z4.enum(["active", "inactive", "suspended", "banned"]);
5503
5937
  var userRoleEnum = z4.enum(["user", "admin", "moderator", "super_admin"]);
@@ -5624,6 +6058,7 @@ function createUserController(userService) {
5624
6058
  }
5625
6059
 
5626
6060
  // src/modules/user/user.routes.ts
6061
+ init_esm_shims();
5627
6062
  var idParamsSchema = {
5628
6063
  type: "object",
5629
6064
  properties: {
@@ -5712,8 +6147,8 @@ async function generateDocs(outputPath = "openapi.json", silent = false) {
5712
6147
  await registerUserModule(app, authService);
5713
6148
  await app.ready();
5714
6149
  const spec = app.swagger();
5715
- const absoluteOutput = path7.resolve(outputPath);
5716
- await fs6.mkdir(path7.dirname(absoluteOutput), { recursive: true });
6150
+ const absoluteOutput = path9.resolve(outputPath);
6151
+ await fs6.mkdir(path9.dirname(absoluteOutput), { recursive: true });
5717
6152
  await fs6.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
5718
6153
  spinner?.succeed(`OpenAPI spec generated at ${absoluteOutput}`);
5719
6154
  await app.close();
@@ -5758,7 +6193,7 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
5758
6193
  const spinner = ora6("Exporting documentation...").start();
5759
6194
  try {
5760
6195
  const projectRoot = getProjectRoot();
5761
- const specPath = path8.join(projectRoot, "openapi.json");
6196
+ const specPath = path10.join(projectRoot, "openapi.json");
5762
6197
  try {
5763
6198
  await fs7.access(specPath);
5764
6199
  } catch {
@@ -5785,7 +6220,7 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
5785
6220
  default:
5786
6221
  throw new Error(`Unknown format: ${options.format}`);
5787
6222
  }
5788
- const outPath = path8.join(projectRoot, options.output || defaultName);
6223
+ const outPath = path10.join(projectRoot, options.output || defaultName);
5789
6224
  await fs7.writeFile(outPath, output);
5790
6225
  spinner.succeed(`Exported to: ${options.output || defaultName}`);
5791
6226
  if (options.format === "postman") {
@@ -5798,8 +6233,8 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
5798
6233
  });
5799
6234
  docsCommand.command("status").description("Show documentation status").action(async () => {
5800
6235
  const projectRoot = getProjectRoot();
5801
- console.log(chalk6.bold("\n\u{1F4CA} Documentation Status\n"));
5802
- const specPath = path8.join(projectRoot, "openapi.json");
6236
+ console.log(chalk9.bold("\n\u{1F4CA} Documentation Status\n"));
6237
+ const specPath = path10.join(projectRoot, "openapi.json");
5803
6238
  try {
5804
6239
  const stat2 = await fs7.stat(specPath);
5805
6240
  success(
@@ -5914,13 +6349,325 @@ function formatDate(date) {
5914
6349
  });
5915
6350
  }
5916
6351
 
6352
+ // src/cli/commands/list.ts
6353
+ init_esm_shims();
6354
+ import { Command as Command6 } from "commander";
6355
+ import chalk10 from "chalk";
6356
+ import fs8 from "fs/promises";
6357
+ var AVAILABLE_MODULES2 = {
6358
+ // Core
6359
+ auth: {
6360
+ name: "Authentication",
6361
+ description: "JWT authentication with access/refresh tokens",
6362
+ category: "Core"
6363
+ },
6364
+ users: {
6365
+ name: "User Management",
6366
+ description: "User CRUD with RBAC (roles & permissions)",
6367
+ category: "Core"
6368
+ },
6369
+ email: {
6370
+ name: "Email Service",
6371
+ description: "SMTP email with templates (Handlebars)",
6372
+ category: "Core"
6373
+ },
6374
+ // Security
6375
+ mfa: {
6376
+ name: "MFA/TOTP",
6377
+ description: "Two-factor authentication with QR codes",
6378
+ category: "Security"
6379
+ },
6380
+ oauth: {
6381
+ name: "OAuth",
6382
+ description: "Social login (Google, GitHub, Facebook, Twitter, Apple)",
6383
+ category: "Security"
6384
+ },
6385
+ "rate-limit": {
6386
+ name: "Rate Limiting",
6387
+ description: "Advanced rate limiting with multiple algorithms",
6388
+ category: "Security"
6389
+ },
6390
+ // Data & Storage
6391
+ cache: {
6392
+ name: "Redis Cache",
6393
+ description: "Redis caching with TTL & invalidation",
6394
+ category: "Data & Storage"
6395
+ },
6396
+ upload: {
6397
+ name: "File Upload",
6398
+ description: "File upload with local/S3/Cloudinary storage",
6399
+ category: "Data & Storage"
6400
+ },
6401
+ search: {
6402
+ name: "Search",
6403
+ description: "Full-text search with Elasticsearch/Meilisearch",
6404
+ category: "Data & Storage"
6405
+ },
6406
+ // Communication
6407
+ notification: {
6408
+ name: "Notifications",
6409
+ description: "Email, SMS, Push notifications",
6410
+ category: "Communication"
6411
+ },
6412
+ webhook: {
6413
+ name: "Webhooks",
6414
+ description: "Outgoing webhooks with HMAC signatures & retry",
6415
+ category: "Communication"
6416
+ },
6417
+ websocket: {
6418
+ name: "WebSockets",
6419
+ description: "Real-time communication with Socket.io",
6420
+ category: "Communication"
6421
+ },
6422
+ // Background Processing
6423
+ queue: {
6424
+ name: "Queue/Jobs",
6425
+ description: "Background jobs with Bull/BullMQ & cron scheduling",
6426
+ category: "Background Processing"
6427
+ },
6428
+ "media-processing": {
6429
+ name: "Media Processing",
6430
+ description: "Image/video processing with FFmpeg",
6431
+ category: "Background Processing"
6432
+ },
6433
+ // Monitoring & Analytics
6434
+ audit: {
6435
+ name: "Audit Logs",
6436
+ description: "Activity logging and audit trail",
6437
+ category: "Monitoring & Analytics"
6438
+ },
6439
+ analytics: {
6440
+ name: "Analytics/Metrics",
6441
+ description: "Prometheus metrics & event tracking",
6442
+ category: "Monitoring & Analytics"
6443
+ },
6444
+ // Internationalization
6445
+ i18n: {
6446
+ name: "i18n/Localization",
6447
+ description: "Multi-language support with 7+ locales",
6448
+ category: "Internationalization"
6449
+ },
6450
+ // API Management
6451
+ "feature-flag": {
6452
+ name: "Feature Flags",
6453
+ description: "A/B testing & progressive rollout",
6454
+ category: "API Management"
6455
+ },
6456
+ "api-versioning": {
6457
+ name: "API Versioning",
6458
+ description: "Multiple API versions support",
6459
+ category: "API Management"
6460
+ },
6461
+ // Payments
6462
+ payment: {
6463
+ name: "Payments",
6464
+ description: "Payment processing (Stripe, PayPal, Mobile Money)",
6465
+ category: "Payments"
6466
+ }
6467
+ };
6468
+ async function getInstalledModules() {
6469
+ try {
6470
+ const modulesDir = getModulesDir();
6471
+ const entries = await fs8.readdir(modulesDir, { withFileTypes: true });
6472
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
6473
+ } catch {
6474
+ return [];
6475
+ }
6476
+ }
6477
+ function isServercraftProject() {
6478
+ try {
6479
+ getProjectRoot();
6480
+ return true;
6481
+ } catch {
6482
+ return false;
6483
+ }
6484
+ }
6485
+ var listCommand = new Command6("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(
6486
+ async (options) => {
6487
+ const installedModules = await getInstalledModules();
6488
+ const isProject = isServercraftProject();
6489
+ if (options.json) {
6490
+ const output = {
6491
+ available: Object.entries(AVAILABLE_MODULES2).map(([key, mod]) => ({
6492
+ id: key,
6493
+ ...mod,
6494
+ installed: installedModules.includes(key)
6495
+ }))
6496
+ };
6497
+ if (isProject) {
6498
+ output.installed = installedModules;
6499
+ }
6500
+ console.log(JSON.stringify(output, null, 2));
6501
+ return;
6502
+ }
6503
+ const byCategory = {};
6504
+ for (const [key, mod] of Object.entries(AVAILABLE_MODULES2)) {
6505
+ if (options.category && mod.category.toLowerCase() !== options.category.toLowerCase()) {
6506
+ continue;
6507
+ }
6508
+ if (!byCategory[mod.category]) {
6509
+ byCategory[mod.category] = [];
6510
+ }
6511
+ byCategory[mod.category].push({
6512
+ id: key,
6513
+ name: mod.name,
6514
+ description: mod.description,
6515
+ installed: installedModules.includes(key)
6516
+ });
6517
+ }
6518
+ if (options.installed) {
6519
+ if (!isProject) {
6520
+ console.log(chalk10.yellow("\n\u26A0 Not in a Servcraft project directory\n"));
6521
+ return;
6522
+ }
6523
+ console.log(chalk10.bold("\n\u{1F4E6} Installed Modules:\n"));
6524
+ if (installedModules.length === 0) {
6525
+ console.log(chalk10.gray(" No modules installed yet.\n"));
6526
+ console.log(` Run ${chalk10.cyan("servcraft add <module>")} to add a module.
6527
+ `);
6528
+ return;
6529
+ }
6530
+ for (const modId of installedModules) {
6531
+ const mod = AVAILABLE_MODULES2[modId];
6532
+ if (mod) {
6533
+ console.log(` ${chalk10.green("\u2713")} ${chalk10.cyan(modId.padEnd(18))} ${mod.name}`);
6534
+ } else {
6535
+ console.log(
6536
+ ` ${chalk10.green("\u2713")} ${chalk10.cyan(modId.padEnd(18))} ${chalk10.gray("(custom module)")}`
6537
+ );
6538
+ }
6539
+ }
6540
+ console.log(`
6541
+ Total: ${chalk10.bold(installedModules.length)} module(s) installed
6542
+ `);
6543
+ return;
6544
+ }
6545
+ console.log(chalk10.bold("\n\u{1F4E6} Available Modules\n"));
6546
+ if (isProject) {
6547
+ console.log(
6548
+ chalk10.gray(` ${chalk10.green("\u2713")} = installed ${chalk10.dim("\u25CB")} = not installed
6549
+ `)
6550
+ );
6551
+ }
6552
+ for (const [category, modules] of Object.entries(byCategory)) {
6553
+ console.log(chalk10.bold.blue(` ${category}`));
6554
+ console.log(chalk10.gray(" " + "\u2500".repeat(40)));
6555
+ for (const mod of modules) {
6556
+ const status = isProject ? mod.installed ? chalk10.green("\u2713") : chalk10.dim("\u25CB") : " ";
6557
+ const nameColor = mod.installed ? chalk10.green : chalk10.cyan;
6558
+ console.log(` ${status} ${nameColor(mod.id.padEnd(18))} ${mod.name}`);
6559
+ console.log(` ${chalk10.gray(mod.description)}`);
6560
+ }
6561
+ console.log();
6562
+ }
6563
+ const totalAvailable = Object.keys(AVAILABLE_MODULES2).length;
6564
+ const totalInstalled = installedModules.filter((m) => AVAILABLE_MODULES2[m]).length;
6565
+ console.log(chalk10.gray("\u2500".repeat(50)));
6566
+ console.log(
6567
+ ` ${chalk10.bold(totalAvailable)} modules available` + (isProject ? ` | ${chalk10.green.bold(totalInstalled)} installed` : "")
6568
+ );
6569
+ console.log();
6570
+ console.log(chalk10.bold(" Usage:"));
6571
+ console.log(` ${chalk10.yellow("servcraft add <module>")} Add a module`);
6572
+ console.log(` ${chalk10.yellow("servcraft list --installed")} Show installed only`);
6573
+ console.log(` ${chalk10.yellow("servcraft list --category Security")} Filter by category`);
6574
+ console.log();
6575
+ }
6576
+ );
6577
+
6578
+ // src/cli/commands/remove.ts
6579
+ init_esm_shims();
6580
+ import { Command as Command7 } from "commander";
6581
+ import path11 from "path";
6582
+ import ora7 from "ora";
6583
+ import chalk11 from "chalk";
6584
+ import fs9 from "fs/promises";
6585
+ import inquirer4 from "inquirer";
6586
+ init_error_handler();
6587
+ var removeCommand = new Command7("remove").alias("rm").description("Remove an installed module from your project").argument("<module>", "Module to remove").option("-y, --yes", "Skip confirmation prompt").option("--keep-env", "Keep environment variables").action(async (moduleName, options) => {
6588
+ const projectError = validateProject();
6589
+ if (projectError) {
6590
+ displayError(projectError);
6591
+ return;
6592
+ }
6593
+ console.log(chalk11.bold.cyan("\n\u{1F5D1}\uFE0F ServCraft Module Removal\n"));
6594
+ const moduleDir = path11.join(getModulesDir(), moduleName);
6595
+ try {
6596
+ const exists = await fs9.access(moduleDir).then(() => true).catch(() => false);
6597
+ if (!exists) {
6598
+ displayError(
6599
+ new (init_error_handler(), __toCommonJS(error_handler_exports)).ServCraftError(
6600
+ `Module "${moduleName}" is not installed`,
6601
+ [
6602
+ `Run ${chalk11.cyan("servcraft list --installed")} to see installed modules`,
6603
+ `Check the spelling of the module name`
6604
+ ]
6605
+ )
6606
+ );
6607
+ return;
6608
+ }
6609
+ const files = await fs9.readdir(moduleDir);
6610
+ const fileCount = files.length;
6611
+ if (!options?.yes) {
6612
+ console.log(chalk11.yellow(`\u26A0 This will remove the "${moduleName}" module:`));
6613
+ console.log(chalk11.gray(` Directory: ${moduleDir}`));
6614
+ console.log(chalk11.gray(` Files: ${fileCount} file(s)`));
6615
+ console.log();
6616
+ const { confirm } = await inquirer4.prompt([
6617
+ {
6618
+ type: "confirm",
6619
+ name: "confirm",
6620
+ message: "Are you sure you want to remove this module?",
6621
+ default: false
6622
+ }
6623
+ ]);
6624
+ if (!confirm) {
6625
+ console.log(chalk11.yellow("\n\u2716 Removal cancelled\n"));
6626
+ return;
6627
+ }
6628
+ }
6629
+ const spinner = ora7("Removing module...").start();
6630
+ await fs9.rm(moduleDir, { recursive: true, force: true });
6631
+ spinner.succeed(`Module "${moduleName}" removed successfully!`);
6632
+ console.log("\n" + chalk11.bold("\u2713 Removed:"));
6633
+ success(` src/modules/${moduleName}/ (${fileCount} files)`);
6634
+ if (!options?.keepEnv) {
6635
+ console.log("\n" + chalk11.bold("\u{1F4CC} Manual cleanup needed:"));
6636
+ info(" 1. Remove environment variables related to this module from .env");
6637
+ info(" 2. Remove module imports from your main app file");
6638
+ info(" 3. Remove related database migrations if any");
6639
+ info(" 4. Update your routes if they reference this module");
6640
+ } else {
6641
+ console.log("\n" + chalk11.bold("\u{1F4CC} Manual cleanup needed:"));
6642
+ info(" 1. Environment variables were kept (--keep-env flag)");
6643
+ info(" 2. Remove module imports from your main app file");
6644
+ info(" 3. Update your routes if they reference this module");
6645
+ }
6646
+ console.log();
6647
+ } catch (err) {
6648
+ error(err instanceof Error ? err.message : String(err));
6649
+ console.log();
6650
+ }
6651
+ });
6652
+
6653
+ // src/cli/commands/doctor.ts
6654
+ init_esm_shims();
6655
+ import { Command as Command8 } from "commander";
6656
+ import chalk12 from "chalk";
6657
+ var doctorCommand = new Command8("doctor").description("Diagnose project configuration and dependencies").action(async () => {
6658
+ console.log(chalk12.bold.cyan("\nServCraft Doctor - Coming soon!\n"));
6659
+ });
6660
+
5917
6661
  // src/cli/index.ts
5918
- var program = new Command6();
6662
+ var program = new Command9();
5919
6663
  program.name("servcraft").description("Servcraft - A modular Node.js backend framework CLI").version("0.1.0");
5920
6664
  program.addCommand(initCommand);
5921
6665
  program.addCommand(generateCommand);
5922
6666
  program.addCommand(addModuleCommand);
5923
6667
  program.addCommand(dbCommand);
5924
6668
  program.addCommand(docsCommand);
6669
+ program.addCommand(listCommand);
6670
+ program.addCommand(removeCommand);
6671
+ program.addCommand(doctorCommand);
5925
6672
  program.parse();
5926
6673
  //# sourceMappingURL=index.js.map