servcraft 0.2.0 → 0.4.2

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,221 +1,17 @@
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
2
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
3
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
4
  }) : x)(function(x) {
9
5
  if (typeof require !== "undefined") return require.apply(this, arguments);
10
6
  throw Error('Dynamic require of "' + x + '" is not supported');
11
7
  });
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
- });
210
8
 
211
9
  // src/cli/index.ts
212
- init_esm_shims();
213
- import { Command as Command9 } from "commander";
10
+ import { Command as Command13 } from "commander";
214
11
 
215
12
  // src/cli/commands/init.ts
216
- init_esm_shims();
217
13
  import { Command } from "commander";
218
- import path4 from "path";
14
+ import path3 from "path";
219
15
  import fs2 from "fs/promises";
220
16
  import ora from "ora";
221
17
  import inquirer from "inquirer";
@@ -223,15 +19,13 @@ import chalk3 from "chalk";
223
19
  import { execSync } from "child_process";
224
20
 
225
21
  // src/cli/utils/helpers.ts
226
- init_esm_shims();
227
22
  import fs from "fs/promises";
228
- import path3 from "path";
23
+ import path2 from "path";
229
24
  import chalk2 from "chalk";
230
25
 
231
26
  // src/cli/utils/dry-run.ts
232
- init_esm_shims();
233
27
  import chalk from "chalk";
234
- import path2 from "path";
28
+ import path from "path";
235
29
  var DryRunManager = class _DryRunManager {
236
30
  static instance;
237
31
  enabled = false;
@@ -306,7 +100,7 @@ var DryRunManager = class _DryRunManager {
306
100
  }
307
101
  // Helper to format file path relative to cwd
308
102
  relativePath(filePath) {
309
- return path2.relative(process.cwd(), filePath);
103
+ return path.relative(process.cwd(), filePath);
310
104
  }
311
105
  };
312
106
 
@@ -352,7 +146,7 @@ async function writeFile(filePath, content) {
352
146
  });
353
147
  return;
354
148
  }
355
- await ensureDir(path3.dirname(filePath));
149
+ await ensureDir(path2.dirname(filePath));
356
150
  await fs.writeFile(filePath, content, "utf-8");
357
151
  }
358
152
  function success(message) {
@@ -371,10 +165,10 @@ function getProjectRoot() {
371
165
  return process.cwd();
372
166
  }
373
167
  function getSourceDir() {
374
- return path3.join(getProjectRoot(), "src");
168
+ return path2.join(getProjectRoot(), "src");
375
169
  }
376
170
  function getModulesDir() {
377
- return path3.join(getSourceDir(), "modules");
171
+ return path2.join(getSourceDir(), "modules");
378
172
  }
379
173
 
380
174
  // src/cli/commands/init.ts
@@ -484,7 +278,7 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
484
278
  orm: db === "mongodb" ? "mongoose" : db === "none" ? "none" : "prisma"
485
279
  };
486
280
  }
487
- const projectDir = path4.resolve(process.cwd(), options.name);
281
+ const projectDir = path3.resolve(process.cwd(), options.name);
488
282
  const spinner = ora("Creating project...").start();
489
283
  try {
490
284
  try {
@@ -498,21 +292,21 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
498
292
  spinner.text = "Generating project files...";
499
293
  const packageJson = generatePackageJson(options);
500
294
  await writeFile(
501
- path4.join(projectDir, "package.json"),
295
+ path3.join(projectDir, "package.json"),
502
296
  JSON.stringify(packageJson, null, 2)
503
297
  );
504
298
  if (options.language === "typescript") {
505
- await writeFile(path4.join(projectDir, "tsconfig.json"), generateTsConfig(options));
506
- await writeFile(path4.join(projectDir, "tsup.config.ts"), generateTsupConfig(options));
299
+ await writeFile(path3.join(projectDir, "tsconfig.json"), generateTsConfig(options));
300
+ await writeFile(path3.join(projectDir, "tsup.config.ts"), generateTsupConfig(options));
507
301
  } else {
508
- await writeFile(path4.join(projectDir, "jsconfig.json"), generateJsConfig(options));
302
+ await writeFile(path3.join(projectDir, "jsconfig.json"), generateJsConfig(options));
509
303
  }
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));
304
+ await writeFile(path3.join(projectDir, ".env.example"), generateEnvExample(options));
305
+ await writeFile(path3.join(projectDir, ".env"), generateEnvExample(options));
306
+ await writeFile(path3.join(projectDir, ".gitignore"), generateGitignore());
307
+ await writeFile(path3.join(projectDir, "Dockerfile"), generateDockerfile(options));
514
308
  await writeFile(
515
- path4.join(projectDir, "docker-compose.yml"),
309
+ path3.join(projectDir, "docker-compose.yml"),
516
310
  generateDockerCompose(options)
517
311
  );
518
312
  const ext = options.language === "typescript" ? "ts" : options.moduleSystem === "esm" ? "js" : "cjs";
@@ -533,45 +327,45 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
533
327
  dirs.push("src/database/models");
534
328
  }
535
329
  for (const dir of dirs) {
536
- await ensureDir(path4.join(projectDir, dir));
330
+ await ensureDir(path3.join(projectDir, dir));
537
331
  }
538
- await writeFile(path4.join(projectDir, `src/index.${ext}`), generateEntryFile(options));
332
+ await writeFile(path3.join(projectDir, `src/index.${ext}`), generateEntryFile(options));
539
333
  await writeFile(
540
- path4.join(projectDir, `src/core/server.${ext}`),
334
+ path3.join(projectDir, `src/core/server.${ext}`),
541
335
  generateServerFile(options)
542
336
  );
543
337
  await writeFile(
544
- path4.join(projectDir, `src/core/logger.${ext}`),
338
+ path3.join(projectDir, `src/core/logger.${ext}`),
545
339
  generateLoggerFile(options)
546
340
  );
547
341
  await writeFile(
548
- path4.join(projectDir, `src/config/index.${ext}`),
342
+ path3.join(projectDir, `src/config/index.${ext}`),
549
343
  generateConfigFile(options)
550
344
  );
551
345
  await writeFile(
552
- path4.join(projectDir, `src/middleware/index.${ext}`),
346
+ path3.join(projectDir, `src/middleware/index.${ext}`),
553
347
  generateMiddlewareFile(options)
554
348
  );
555
349
  await writeFile(
556
- path4.join(projectDir, `src/utils/index.${ext}`),
350
+ path3.join(projectDir, `src/utils/index.${ext}`),
557
351
  generateUtilsFile(options)
558
352
  );
559
353
  await writeFile(
560
- path4.join(projectDir, `src/types/index.${ext}`),
354
+ path3.join(projectDir, `src/types/index.${ext}`),
561
355
  generateTypesFile(options)
562
356
  );
563
357
  if (options.orm === "prisma") {
564
358
  await writeFile(
565
- path4.join(projectDir, "prisma/schema.prisma"),
359
+ path3.join(projectDir, "prisma/schema.prisma"),
566
360
  generatePrismaSchema(options)
567
361
  );
568
362
  } else if (options.orm === "mongoose") {
569
363
  await writeFile(
570
- path4.join(projectDir, `src/database/connection.${ext}`),
364
+ path3.join(projectDir, `src/database/connection.${ext}`),
571
365
  generateMongooseConnection(options)
572
366
  );
573
367
  await writeFile(
574
- path4.join(projectDir, `src/database/models/user.model.${ext}`),
368
+ path3.join(projectDir, `src/database/models/user.model.${ext}`),
575
369
  generateMongooseUserModel(options)
576
370
  );
577
371
  }
@@ -1410,7 +1204,6 @@ declare module 'fastify' {
1410
1204
  }
1411
1205
 
1412
1206
  // src/cli/commands/generate.ts
1413
- init_esm_shims();
1414
1207
  import { Command as Command2 } from "commander";
1415
1208
  import path5 from "path";
1416
1209
  import ora2 from "ora";
@@ -1418,7 +1211,6 @@ import inquirer2 from "inquirer";
1418
1211
  import chalk5 from "chalk";
1419
1212
 
1420
1213
  // src/cli/utils/field-parser.ts
1421
- init_esm_shims();
1422
1214
  var tsTypeMap = {
1423
1215
  string: "string",
1424
1216
  number: "number",
@@ -1560,11 +1352,109 @@ function parseFields(fieldsStr) {
1560
1352
  return fieldsStr.split(/\s+/).filter(Boolean).map(parseField);
1561
1353
  }
1562
1354
 
1563
- // src/cli/commands/generate.ts
1564
- init_error_handler();
1355
+ // src/cli/utils/error-handler.ts
1356
+ import chalk4 from "chalk";
1357
+ var ServCraftError = class extends Error {
1358
+ suggestions;
1359
+ docsLink;
1360
+ constructor(message, suggestions = [], docsLink) {
1361
+ super(message);
1362
+ this.name = "ServCraftError";
1363
+ this.suggestions = suggestions;
1364
+ this.docsLink = docsLink;
1365
+ }
1366
+ };
1367
+ var ErrorTypes = {
1368
+ MODULE_NOT_FOUND: (moduleName) => new ServCraftError(
1369
+ `Module "${moduleName}" not found`,
1370
+ [
1371
+ `Run ${chalk4.cyan("servcraft list")} to see available modules`,
1372
+ `Check the spelling of the module name`,
1373
+ `Visit ${chalk4.blue("https://github.com/Le-Sourcier/servcraft#modules")} for module list`
1374
+ ],
1375
+ "https://github.com/Le-Sourcier/servcraft#add-pre-built-modules"
1376
+ ),
1377
+ MODULE_ALREADY_EXISTS: (moduleName) => new ServCraftError(`Module "${moduleName}" already exists`, [
1378
+ `Use ${chalk4.cyan("servcraft add " + moduleName + " --force")} to overwrite`,
1379
+ `Use ${chalk4.cyan("servcraft add " + moduleName + " --update")} to update`,
1380
+ `Use ${chalk4.cyan("servcraft add " + moduleName + " --skip-existing")} to skip`
1381
+ ]),
1382
+ NOT_IN_PROJECT: () => new ServCraftError(
1383
+ "Not in a ServCraft project directory",
1384
+ [
1385
+ `Run ${chalk4.cyan("servcraft init")} to create a new project`,
1386
+ `Navigate to your ServCraft project directory`,
1387
+ `Check if ${chalk4.yellow("package.json")} exists`
1388
+ ],
1389
+ "https://github.com/Le-Sourcier/servcraft#initialize-project"
1390
+ ),
1391
+ FILE_ALREADY_EXISTS: (fileName) => new ServCraftError(`File "${fileName}" already exists`, [
1392
+ `Use ${chalk4.cyan("--force")} flag to overwrite`,
1393
+ `Choose a different name`,
1394
+ `Delete the existing file first`
1395
+ ]),
1396
+ INVALID_DATABASE: (database) => new ServCraftError(`Invalid database type: "${database}"`, [
1397
+ `Valid options: ${chalk4.cyan("postgresql, mysql, sqlite, mongodb, none")}`,
1398
+ `Use ${chalk4.cyan("servcraft init --db postgresql")} for PostgreSQL`
1399
+ ]),
1400
+ INVALID_VALIDATOR: (validator) => new ServCraftError(`Invalid validator type: "${validator}"`, [
1401
+ `Valid options: ${chalk4.cyan("zod, joi, yup")}`,
1402
+ `Default is ${chalk4.cyan("zod")}`
1403
+ ]),
1404
+ MISSING_DEPENDENCY: (dependency, command) => new ServCraftError(`Missing dependency: "${dependency}"`, [
1405
+ `Run ${chalk4.cyan(command)} to install`,
1406
+ `Check your ${chalk4.yellow("package.json")}`
1407
+ ]),
1408
+ INVALID_FIELD_FORMAT: (field) => new ServCraftError(`Invalid field format: "${field}"`, [
1409
+ `Expected format: ${chalk4.cyan("name:type")}`,
1410
+ `Example: ${chalk4.cyan("name:string age:number isActive:boolean")}`,
1411
+ `Supported types: string, number, boolean, date`
1412
+ ]),
1413
+ GIT_NOT_INITIALIZED: () => new ServCraftError("Git repository not initialized", [
1414
+ `Run ${chalk4.cyan("git init")} to initialize git`,
1415
+ `This is required for some ServCraft features`
1416
+ ])
1417
+ };
1418
+ function displayError(error2) {
1419
+ console.error("\n" + chalk4.red.bold("\u2717 Error: ") + chalk4.red(error2.message));
1420
+ if (error2 instanceof ServCraftError) {
1421
+ if (error2.suggestions.length > 0) {
1422
+ console.log("\n" + chalk4.yellow.bold("\u{1F4A1} Suggestions:"));
1423
+ error2.suggestions.forEach((suggestion) => {
1424
+ console.log(chalk4.yellow(" \u2022 ") + suggestion);
1425
+ });
1426
+ }
1427
+ if (error2.docsLink) {
1428
+ console.log(
1429
+ "\n" + chalk4.blue.bold("\u{1F4DA} Documentation: ") + chalk4.blue.underline(error2.docsLink)
1430
+ );
1431
+ }
1432
+ }
1433
+ console.log();
1434
+ }
1435
+ function validateProject() {
1436
+ try {
1437
+ const fs14 = __require("fs");
1438
+ if (!fs14.existsSync("package.json")) {
1439
+ return ErrorTypes.NOT_IN_PROJECT();
1440
+ }
1441
+ const packageJson = JSON.parse(fs14.readFileSync("package.json", "utf-8"));
1442
+ if (!packageJson.dependencies?.fastify) {
1443
+ return new ServCraftError("This does not appear to be a ServCraft project", [
1444
+ `ServCraft projects require Fastify`,
1445
+ `Run ${chalk4.cyan("servcraft init")} to create a new project`
1446
+ ]);
1447
+ }
1448
+ return null;
1449
+ } catch {
1450
+ return new ServCraftError("Failed to validate project", [
1451
+ `Ensure you are in the project root directory`,
1452
+ `Check if ${chalk4.yellow("package.json")} is valid`
1453
+ ]);
1454
+ }
1455
+ }
1565
1456
 
1566
1457
  // src/cli/templates/controller.ts
1567
- init_esm_shims();
1568
1458
  function controllerTemplate(name, pascalName, camelName) {
1569
1459
  return `import type { FastifyRequest, FastifyReply } from 'fastify';
1570
1460
  import type { ${pascalName}Service } from './${name}.service.js';
@@ -1634,7 +1524,6 @@ export function create${pascalName}Controller(${camelName}Service: ${pascalName}
1634
1524
  }
1635
1525
 
1636
1526
  // src/cli/templates/service.ts
1637
- init_esm_shims();
1638
1527
  function serviceTemplate(name, pascalName, camelName) {
1639
1528
  return `import type { PaginatedResult, PaginationParams } from '../../types/index.js';
1640
1529
  import { NotFoundError, ConflictError } from '../../utils/errors.js';
@@ -1695,7 +1584,6 @@ export function create${pascalName}Service(repository?: ${pascalName}Repository)
1695
1584
  }
1696
1585
 
1697
1586
  // src/cli/templates/repository.ts
1698
- init_esm_shims();
1699
1587
  function repositoryTemplate(name, pascalName, camelName, pluralName) {
1700
1588
  return `import { randomUUID } from 'crypto';
1701
1589
  import type { PaginatedResult, PaginationParams } from '../../types/index.js';
@@ -1802,7 +1690,6 @@ export function create${pascalName}Repository(): ${pascalName}Repository {
1802
1690
  }
1803
1691
 
1804
1692
  // src/cli/templates/types.ts
1805
- init_esm_shims();
1806
1693
  function typesTemplate(name, pascalName) {
1807
1694
  return `import type { BaseEntity } from '../../types/index.js';
1808
1695
 
@@ -1832,7 +1719,6 @@ export interface ${pascalName}Filters {
1832
1719
  }
1833
1720
 
1834
1721
  // src/cli/templates/schemas.ts
1835
- init_esm_shims();
1836
1722
  function schemasTemplate(name, pascalName, camelName) {
1837
1723
  return `import { z } from 'zod';
1838
1724
 
@@ -1861,7 +1747,6 @@ export type ${pascalName}QueryInput = z.infer<typeof ${camelName}QuerySchema>;
1861
1747
  }
1862
1748
 
1863
1749
  // src/cli/templates/routes.ts
1864
- init_esm_shims();
1865
1750
  function routesTemplate(name, pascalName, camelName, pluralName) {
1866
1751
  return `import type { FastifyInstance } from 'fastify';
1867
1752
  import type { ${pascalName}Controller } from './${name}.controller.js';
@@ -1914,7 +1799,6 @@ export function register${pascalName}Routes(
1914
1799
  }
1915
1800
 
1916
1801
  // src/cli/templates/module-index.ts
1917
- init_esm_shims();
1918
1802
  function moduleIndexTemplate(name, pascalName, camelName) {
1919
1803
  return `import type { FastifyInstance } from 'fastify';
1920
1804
  import { logger } from '../../core/logger.js';
@@ -1950,7 +1834,6 @@ export * from './${name}.schemas.js';
1950
1834
  }
1951
1835
 
1952
1836
  // src/cli/templates/prisma-model.ts
1953
- init_esm_shims();
1954
1837
  function prismaModelTemplate(name, pascalName, tableName) {
1955
1838
  return `
1956
1839
  // Add this model to your prisma/schema.prisma file
@@ -1970,7 +1853,6 @@ model ${pascalName} {
1970
1853
  }
1971
1854
 
1972
1855
  // src/cli/templates/dynamic-types.ts
1973
- init_esm_shims();
1974
1856
  function dynamicTypesTemplate(name, pascalName, fields) {
1975
1857
  const fieldLines = fields.map((field) => {
1976
1858
  const tsType = tsTypeMap[field.type];
@@ -2015,7 +1897,6 @@ ${fields.filter((f) => ["string", "enum", "boolean"].includes(f.type)).map((f) =
2015
1897
  }
2016
1898
 
2017
1899
  // src/cli/templates/dynamic-schemas.ts
2018
- init_esm_shims();
2019
1900
  function dynamicSchemasTemplate(name, pascalName, camelName, fields, validator = "zod") {
2020
1901
  switch (validator) {
2021
1902
  case "joi":
@@ -2194,7 +2075,6 @@ function getJsType(field) {
2194
2075
  }
2195
2076
 
2196
2077
  // src/cli/templates/dynamic-prisma.ts
2197
- init_esm_shims();
2198
2078
  function dynamicPrismaTemplate(modelName, tableName, fields) {
2199
2079
  const fieldLines = [];
2200
2080
  for (const field of fields) {
@@ -2262,6 +2142,386 @@ ${indexLines.join("\n")}
2262
2142
  `;
2263
2143
  }
2264
2144
 
2145
+ // src/cli/templates/controller-test.ts
2146
+ function controllerTestTemplate(name, pascalName, camelName) {
2147
+ return `import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2148
+ import { build } from '../../app.js';
2149
+ import { FastifyInstance } from 'fastify';
2150
+
2151
+ describe('${pascalName}Controller', () => {
2152
+ let app: FastifyInstance;
2153
+
2154
+ beforeAll(async () => {
2155
+ app = await build();
2156
+ await app.ready();
2157
+ });
2158
+
2159
+ afterAll(async () => {
2160
+ await app.close();
2161
+ });
2162
+
2163
+ describe('GET /${name}', () => {
2164
+ it('should return list of ${name}', async () => {
2165
+ const response = await app.inject({
2166
+ method: 'GET',
2167
+ url: '/${name}',
2168
+ });
2169
+
2170
+ expect(response.statusCode).toBe(200);
2171
+ expect(response.json()).toHaveProperty('data');
2172
+ });
2173
+ });
2174
+
2175
+ describe('GET /${name}/:id', () => {
2176
+ it('should return a single ${camelName}', async () => {
2177
+ // TODO: Create test ${camelName} first
2178
+ const response = await app.inject({
2179
+ method: 'GET',
2180
+ url: '/${name}/1',
2181
+ });
2182
+
2183
+ expect(response.statusCode).toBe(200);
2184
+ expect(response.json()).toHaveProperty('data');
2185
+ });
2186
+
2187
+ it('should return 404 for non-existent ${camelName}', async () => {
2188
+ const response = await app.inject({
2189
+ method: 'GET',
2190
+ url: '/${name}/999999',
2191
+ });
2192
+
2193
+ expect(response.statusCode).toBe(404);
2194
+ });
2195
+ });
2196
+
2197
+ describe('POST /${name}', () => {
2198
+ it('should create a new ${camelName}', async () => {
2199
+ const response = await app.inject({
2200
+ method: 'POST',
2201
+ url: '/${name}',
2202
+ payload: {
2203
+ // TODO: Add required fields
2204
+ },
2205
+ });
2206
+
2207
+ expect(response.statusCode).toBe(201);
2208
+ expect(response.json()).toHaveProperty('data');
2209
+ });
2210
+
2211
+ it('should return 400 for invalid data', async () => {
2212
+ const response = await app.inject({
2213
+ method: 'POST',
2214
+ url: '/${name}',
2215
+ payload: {},
2216
+ });
2217
+
2218
+ expect(response.statusCode).toBe(400);
2219
+ });
2220
+ });
2221
+
2222
+ describe('PUT /${name}/:id', () => {
2223
+ it('should update a ${camelName}', async () => {
2224
+ // TODO: Create test ${camelName} first
2225
+ const response = await app.inject({
2226
+ method: 'PUT',
2227
+ url: '/${name}/1',
2228
+ payload: {
2229
+ // TODO: Add fields to update
2230
+ },
2231
+ });
2232
+
2233
+ expect(response.statusCode).toBe(200);
2234
+ expect(response.json()).toHaveProperty('data');
2235
+ });
2236
+ });
2237
+
2238
+ describe('DELETE /${name}/:id', () => {
2239
+ it('should delete a ${camelName}', async () => {
2240
+ // TODO: Create test ${camelName} first
2241
+ const response = await app.inject({
2242
+ method: 'DELETE',
2243
+ url: '/${name}/1',
2244
+ });
2245
+
2246
+ expect(response.statusCode).toBe(204);
2247
+ });
2248
+ });
2249
+ });
2250
+ `;
2251
+ }
2252
+
2253
+ // src/cli/templates/service-test.ts
2254
+ function serviceTestTemplate(name, pascalName, camelName) {
2255
+ return `import { describe, it, expect, beforeEach } from 'vitest';
2256
+ import { ${pascalName}Service } from '../${name}.service.js';
2257
+
2258
+ describe('${pascalName}Service', () => {
2259
+ let service: ${pascalName}Service;
2260
+
2261
+ beforeEach(() => {
2262
+ service = new ${pascalName}Service();
2263
+ });
2264
+
2265
+ describe('getAll', () => {
2266
+ it('should return all ${name}', async () => {
2267
+ const result = await service.getAll();
2268
+
2269
+ expect(result).toBeDefined();
2270
+ expect(Array.isArray(result)).toBe(true);
2271
+ });
2272
+
2273
+ it('should apply pagination', async () => {
2274
+ const result = await service.getAll({ page: 1, limit: 10 });
2275
+
2276
+ expect(result).toBeDefined();
2277
+ expect(result.length).toBeLessThanOrEqual(10);
2278
+ });
2279
+ });
2280
+
2281
+ describe('getById', () => {
2282
+ it('should return a ${camelName} by id', async () => {
2283
+ // TODO: Create test ${camelName} first
2284
+ const id = '1';
2285
+ const result = await service.getById(id);
2286
+
2287
+ expect(result).toBeDefined();
2288
+ expect(result.id).toBe(id);
2289
+ });
2290
+
2291
+ it('should return null for non-existent id', async () => {
2292
+ const result = await service.getById('999999');
2293
+
2294
+ expect(result).toBeNull();
2295
+ });
2296
+ });
2297
+
2298
+ describe('create', () => {
2299
+ it('should create a new ${camelName}', async () => {
2300
+ const data = {
2301
+ // TODO: Add required fields
2302
+ };
2303
+
2304
+ const result = await service.create(data);
2305
+
2306
+ expect(result).toBeDefined();
2307
+ expect(result.id).toBeDefined();
2308
+ });
2309
+
2310
+ it('should throw error for invalid data', async () => {
2311
+ await expect(service.create({} as any)).rejects.toThrow();
2312
+ });
2313
+ });
2314
+
2315
+ describe('update', () => {
2316
+ it('should update a ${camelName}', async () => {
2317
+ // TODO: Create test ${camelName} first
2318
+ const id = '1';
2319
+ const updates = {
2320
+ // TODO: Add fields to update
2321
+ };
2322
+
2323
+ const result = await service.update(id, updates);
2324
+
2325
+ expect(result).toBeDefined();
2326
+ expect(result.id).toBe(id);
2327
+ });
2328
+
2329
+ it('should return null for non-existent id', async () => {
2330
+ const result = await service.update('999999', {});
2331
+
2332
+ expect(result).toBeNull();
2333
+ });
2334
+ });
2335
+
2336
+ describe('delete', () => {
2337
+ it('should delete a ${camelName}', async () => {
2338
+ // TODO: Create test ${camelName} first
2339
+ const id = '1';
2340
+ const result = await service.delete(id);
2341
+
2342
+ expect(result).toBe(true);
2343
+ });
2344
+
2345
+ it('should return false for non-existent id', async () => {
2346
+ const result = await service.delete('999999');
2347
+
2348
+ expect(result).toBe(false);
2349
+ });
2350
+ });
2351
+ });
2352
+ `;
2353
+ }
2354
+
2355
+ // src/cli/templates/integration-test.ts
2356
+ function integrationTestTemplate(name, pascalName, camelName) {
2357
+ return `import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
2358
+ import { build } from '../../app.js';
2359
+ import { FastifyInstance } from 'fastify';
2360
+ import { prisma } from '../../lib/prisma.js';
2361
+
2362
+ describe('${pascalName} Integration Tests', () => {
2363
+ let app: FastifyInstance;
2364
+
2365
+ beforeAll(async () => {
2366
+ app = await build();
2367
+ await app.ready();
2368
+ });
2369
+
2370
+ afterAll(async () => {
2371
+ await app.close();
2372
+ await prisma.$disconnect();
2373
+ });
2374
+
2375
+ beforeEach(async () => {
2376
+ // Clean up test data
2377
+ // await prisma.${camelName}.deleteMany();
2378
+ });
2379
+
2380
+ describe('Full CRUD workflow', () => {
2381
+ it('should create, read, update, and delete a ${camelName}', async () => {
2382
+ // Create
2383
+ const createResponse = await app.inject({
2384
+ method: 'POST',
2385
+ url: '/${name}',
2386
+ payload: {
2387
+ // TODO: Add required fields
2388
+ },
2389
+ });
2390
+
2391
+ expect(createResponse.statusCode).toBe(201);
2392
+ const created = createResponse.json().data;
2393
+ expect(created.id).toBeDefined();
2394
+
2395
+ // Read
2396
+ const readResponse = await app.inject({
2397
+ method: 'GET',
2398
+ url: \`/${name}/\${created.id}\`,
2399
+ });
2400
+
2401
+ expect(readResponse.statusCode).toBe(200);
2402
+ const read = readResponse.json().data;
2403
+ expect(read.id).toBe(created.id);
2404
+
2405
+ // Update
2406
+ const updateResponse = await app.inject({
2407
+ method: 'PUT',
2408
+ url: \`/${name}/\${created.id}\`,
2409
+ payload: {
2410
+ // TODO: Add fields to update
2411
+ },
2412
+ });
2413
+
2414
+ expect(updateResponse.statusCode).toBe(200);
2415
+ const updated = updateResponse.json().data;
2416
+ expect(updated.id).toBe(created.id);
2417
+
2418
+ // Delete
2419
+ const deleteResponse = await app.inject({
2420
+ method: 'DELETE',
2421
+ url: \`/${name}/\${created.id}\`,
2422
+ });
2423
+
2424
+ expect(deleteResponse.statusCode).toBe(204);
2425
+
2426
+ // Verify deletion
2427
+ const verifyResponse = await app.inject({
2428
+ method: 'GET',
2429
+ url: \`/${name}/\${created.id}\`,
2430
+ });
2431
+
2432
+ expect(verifyResponse.statusCode).toBe(404);
2433
+ });
2434
+ });
2435
+
2436
+ describe('List and pagination', () => {
2437
+ it('should list ${name} with pagination', async () => {
2438
+ // Create multiple ${name}
2439
+ const count = 5;
2440
+ for (let i = 0; i < count; i++) {
2441
+ await app.inject({
2442
+ method: 'POST',
2443
+ url: '/${name}',
2444
+ payload: {
2445
+ // TODO: Add required fields
2446
+ },
2447
+ });
2448
+ }
2449
+
2450
+ // Test pagination
2451
+ const response = await app.inject({
2452
+ method: 'GET',
2453
+ url: '/${name}?page=1&limit=3',
2454
+ });
2455
+
2456
+ expect(response.statusCode).toBe(200);
2457
+ const result = response.json();
2458
+ expect(result.data).toBeDefined();
2459
+ expect(result.data.length).toBeLessThanOrEqual(3);
2460
+ expect(result.total).toBeGreaterThanOrEqual(count);
2461
+ });
2462
+ });
2463
+
2464
+ describe('Validation', () => {
2465
+ it('should validate required fields on create', async () => {
2466
+ const response = await app.inject({
2467
+ method: 'POST',
2468
+ url: '/${name}',
2469
+ payload: {},
2470
+ });
2471
+
2472
+ expect(response.statusCode).toBe(400);
2473
+ expect(response.json()).toHaveProperty('error');
2474
+ });
2475
+
2476
+ it('should validate data types', async () => {
2477
+ const response = await app.inject({
2478
+ method: 'POST',
2479
+ url: '/${name}',
2480
+ payload: {
2481
+ // TODO: Add invalid field types
2482
+ },
2483
+ });
2484
+
2485
+ expect(response.statusCode).toBe(400);
2486
+ });
2487
+ });
2488
+ });
2489
+ `;
2490
+ }
2491
+
2492
+ // src/cli/utils/template-loader.ts
2493
+ import fs3 from "fs/promises";
2494
+ import path4 from "path";
2495
+ async function loadCustomTemplate(templateType) {
2496
+ const projectRoot = getProjectRoot();
2497
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2498
+ const locations = [
2499
+ path4.join(projectRoot, ".servcraft", "templates", `${templateType}.ts`),
2500
+ path4.join(homeDir, ".servcraft", "templates", `${templateType}.ts`)
2501
+ ];
2502
+ for (const location of locations) {
2503
+ try {
2504
+ await fs3.access(location);
2505
+ const templateModule = await import(`file://${location}`);
2506
+ const functionName = `${templateType.replace(/-/g, "")}Template`;
2507
+ if (templateModule[functionName]) {
2508
+ return templateModule;
2509
+ }
2510
+ } catch {
2511
+ continue;
2512
+ }
2513
+ }
2514
+ return null;
2515
+ }
2516
+ async function getTemplate(templateType, builtInTemplate) {
2517
+ const customTemplate = await loadCustomTemplate(templateType);
2518
+ if (customTemplate) {
2519
+ const functionName = `${templateType.replace(/-/g, "")}Template`;
2520
+ return customTemplate[functionName];
2521
+ }
2522
+ return builtInTemplate;
2523
+ }
2524
+
2265
2525
  // src/cli/commands/generate.ts
2266
2526
  function enableDryRunIfNeeded(options) {
2267
2527
  const dryRun = DryRunManager.getInstance();
@@ -2278,7 +2538,7 @@ function showDryRunSummary(options) {
2278
2538
  var generateCommand = new Command2("generate").alias("g").description("Generate resources (module, controller, service, etc.)");
2279
2539
  generateCommand.command("module <name> [fields...]").alias("m").description(
2280
2540
  "Generate a complete module with controller, service, repository, types, schemas, and routes"
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) => {
2541
+ ).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("--with-tests", "Generate test files (__tests__ directory)").option("--dry-run", "Preview changes without writing files").action(async (name, fieldsArgs, options) => {
2282
2542
  enableDryRunIfNeeded(options);
2283
2543
  let fields = [];
2284
2544
  if (options.interactive) {
@@ -2301,40 +2561,65 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
2301
2561
  return;
2302
2562
  }
2303
2563
  const hasFields = fields.length > 0;
2564
+ const controllerTpl = await getTemplate("controller", controllerTemplate);
2565
+ const serviceTpl = await getTemplate("service", serviceTemplate);
2566
+ const repositoryTpl = await getTemplate("repository", repositoryTemplate);
2567
+ const typesTpl = await getTemplate("types", typesTemplate);
2568
+ const schemasTpl = await getTemplate("schemas", schemasTemplate);
2569
+ const routesTpl = await getTemplate("routes", routesTemplate);
2570
+ const moduleIndexTpl = await getTemplate("module-index", moduleIndexTemplate);
2304
2571
  const files = [
2305
2572
  {
2306
2573
  name: `${kebabName}.types.ts`,
2307
- content: hasFields ? dynamicTypesTemplate(kebabName, pascalName, fields) : typesTemplate(kebabName, pascalName)
2574
+ content: hasFields ? dynamicTypesTemplate(kebabName, pascalName, fields) : typesTpl(kebabName, pascalName)
2308
2575
  },
2309
2576
  {
2310
2577
  name: `${kebabName}.schemas.ts`,
2311
- content: hasFields ? dynamicSchemasTemplate(kebabName, pascalName, camelName, fields, validatorType) : schemasTemplate(kebabName, pascalName, camelName)
2578
+ content: hasFields ? dynamicSchemasTemplate(kebabName, pascalName, camelName, fields, validatorType) : schemasTpl(kebabName, pascalName, camelName)
2312
2579
  },
2313
2580
  {
2314
2581
  name: `${kebabName}.service.ts`,
2315
- content: serviceTemplate(kebabName, pascalName, camelName)
2582
+ content: serviceTpl(kebabName, pascalName, camelName)
2316
2583
  },
2317
2584
  {
2318
2585
  name: `${kebabName}.controller.ts`,
2319
- content: controllerTemplate(kebabName, pascalName, camelName)
2586
+ content: controllerTpl(kebabName, pascalName, camelName)
2320
2587
  },
2321
- { name: "index.ts", content: moduleIndexTemplate(kebabName, pascalName, camelName) }
2588
+ { name: "index.ts", content: moduleIndexTpl(kebabName, pascalName, camelName) }
2322
2589
  ];
2323
2590
  if (options.repository !== false) {
2324
2591
  files.push({
2325
2592
  name: `${kebabName}.repository.ts`,
2326
- content: repositoryTemplate(kebabName, pascalName, camelName, pluralName)
2593
+ content: repositoryTpl(kebabName, pascalName, camelName, pluralName)
2327
2594
  });
2328
2595
  }
2329
2596
  if (options.routes !== false) {
2330
2597
  files.push({
2331
2598
  name: `${kebabName}.routes.ts`,
2332
- content: routesTemplate(kebabName, pascalName, camelName, pluralName)
2599
+ content: routesTpl(kebabName, pascalName, camelName, pluralName)
2333
2600
  });
2334
2601
  }
2335
2602
  for (const file of files) {
2336
2603
  await writeFile(path5.join(moduleDir, file.name), file.content);
2337
2604
  }
2605
+ if (options.withTests) {
2606
+ const testDir = path5.join(moduleDir, "__tests__");
2607
+ const controllerTestTpl = await getTemplate("controller-test", controllerTestTemplate);
2608
+ const serviceTestTpl = await getTemplate("service-test", serviceTestTemplate);
2609
+ const integrationTestTpl = await getTemplate("integration-test", integrationTestTemplate);
2610
+ await writeFile(
2611
+ path5.join(testDir, `${kebabName}.controller.test.ts`),
2612
+ controllerTestTpl(kebabName, pascalName, camelName)
2613
+ );
2614
+ await writeFile(
2615
+ path5.join(testDir, `${kebabName}.service.test.ts`),
2616
+ serviceTestTpl(kebabName, pascalName, camelName)
2617
+ );
2618
+ await writeFile(
2619
+ path5.join(testDir, `${kebabName}.integration.test.ts`),
2620
+ integrationTestTpl(kebabName, pascalName, camelName)
2621
+ );
2622
+ }
2338
2623
  spinner.succeed(`Module "${pascalName}" generated successfully!`);
2339
2624
  if (options.prisma || hasFields) {
2340
2625
  console.log("\n" + "\u2500".repeat(50));
@@ -2358,6 +2643,11 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
2358
2643
  }
2359
2644
  console.log("\n\u{1F4C1} Files created:");
2360
2645
  files.forEach((f) => success(` src/modules/${kebabName}/${f.name}`));
2646
+ if (options.withTests) {
2647
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.controller.test.ts`);
2648
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.service.test.ts`);
2649
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.integration.test.ts`);
2650
+ }
2361
2651
  console.log("\n\u{1F4CC} Next steps:");
2362
2652
  if (!hasFields) {
2363
2653
  info(` 1. Update the types in ${kebabName}.types.ts`);
@@ -2594,16 +2884,14 @@ async function promptForFields() {
2594
2884
  }
2595
2885
 
2596
2886
  // src/cli/commands/add-module.ts
2597
- init_esm_shims();
2598
2887
  import { Command as Command3 } from "commander";
2599
2888
  import path8 from "path";
2600
2889
  import ora3 from "ora";
2601
2890
  import chalk7 from "chalk";
2602
- import * as fs5 from "fs/promises";
2891
+ import * as fs6 from "fs/promises";
2603
2892
 
2604
2893
  // src/cli/utils/env-manager.ts
2605
- init_esm_shims();
2606
- import * as fs3 from "fs/promises";
2894
+ import * as fs4 from "fs/promises";
2607
2895
  import * as path6 from "path";
2608
2896
  import { existsSync } from "fs";
2609
2897
  var EnvManager = class {
@@ -2622,7 +2910,7 @@ var EnvManager = class {
2622
2910
  let created2 = false;
2623
2911
  let envContent = "";
2624
2912
  if (existsSync(this.envPath)) {
2625
- envContent = await fs3.readFile(this.envPath, "utf-8");
2913
+ envContent = await fs4.readFile(this.envPath, "utf-8");
2626
2914
  } else {
2627
2915
  created2 = true;
2628
2916
  }
@@ -2651,7 +2939,7 @@ var EnvManager = class {
2651
2939
  }
2652
2940
  newContent += "\n";
2653
2941
  }
2654
- await fs3.writeFile(this.envPath, newContent, "utf-8");
2942
+ await fs4.writeFile(this.envPath, newContent, "utf-8");
2655
2943
  if (existsSync(this.envExamplePath)) {
2656
2944
  await this.updateEnvExample(sections);
2657
2945
  }
@@ -2663,7 +2951,7 @@ var EnvManager = class {
2663
2951
  async updateEnvExample(sections) {
2664
2952
  let exampleContent = "";
2665
2953
  if (existsSync(this.envExamplePath)) {
2666
- exampleContent = await fs3.readFile(this.envExamplePath, "utf-8");
2954
+ exampleContent = await fs4.readFile(this.envExamplePath, "utf-8");
2667
2955
  }
2668
2956
  const existingKeys = this.parseExistingKeys(exampleContent);
2669
2957
  let newContent = exampleContent;
@@ -2687,7 +2975,7 @@ var EnvManager = class {
2687
2975
  }
2688
2976
  newContent += "\n";
2689
2977
  }
2690
- await fs3.writeFile(this.envExamplePath, newContent, "utf-8");
2978
+ await fs4.writeFile(this.envExamplePath, newContent, "utf-8");
2691
2979
  }
2692
2980
  /**
2693
2981
  * Parse existing environment variable keys
@@ -3261,8 +3549,7 @@ var EnvManager = class {
3261
3549
  };
3262
3550
 
3263
3551
  // src/cli/utils/template-manager.ts
3264
- init_esm_shims();
3265
- import * as fs4 from "fs/promises";
3552
+ import * as fs5 from "fs/promises";
3266
3553
  import * as path7 from "path";
3267
3554
  import { createHash } from "crypto";
3268
3555
  import { existsSync as existsSync2 } from "fs";
@@ -3277,8 +3564,8 @@ var TemplateManager = class {
3277
3564
  * Initialize template system
3278
3565
  */
3279
3566
  async initialize() {
3280
- await fs4.mkdir(this.templatesDir, { recursive: true });
3281
- await fs4.mkdir(this.manifestsDir, { recursive: true });
3567
+ await fs5.mkdir(this.templatesDir, { recursive: true });
3568
+ await fs5.mkdir(this.manifestsDir, { recursive: true });
3282
3569
  }
3283
3570
  /**
3284
3571
  * Save module template
@@ -3286,10 +3573,10 @@ var TemplateManager = class {
3286
3573
  async saveTemplate(moduleName, files) {
3287
3574
  await this.initialize();
3288
3575
  const moduleTemplateDir = path7.join(this.templatesDir, moduleName);
3289
- await fs4.mkdir(moduleTemplateDir, { recursive: true });
3576
+ await fs5.mkdir(moduleTemplateDir, { recursive: true });
3290
3577
  for (const [fileName, content] of Object.entries(files)) {
3291
3578
  const filePath = path7.join(moduleTemplateDir, fileName);
3292
- await fs4.writeFile(filePath, content, "utf-8");
3579
+ await fs5.writeFile(filePath, content, "utf-8");
3293
3580
  }
3294
3581
  }
3295
3582
  /**
@@ -3298,7 +3585,7 @@ var TemplateManager = class {
3298
3585
  async getTemplate(moduleName, fileName) {
3299
3586
  try {
3300
3587
  const filePath = path7.join(this.templatesDir, moduleName, fileName);
3301
- return await fs4.readFile(filePath, "utf-8");
3588
+ return await fs5.readFile(filePath, "utf-8");
3302
3589
  } catch {
3303
3590
  return null;
3304
3591
  }
@@ -3323,7 +3610,7 @@ var TemplateManager = class {
3323
3610
  updatedAt: /* @__PURE__ */ new Date()
3324
3611
  };
3325
3612
  const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3326
- await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3613
+ await fs5.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3327
3614
  }
3328
3615
  /**
3329
3616
  * Get module manifest
@@ -3331,7 +3618,7 @@ var TemplateManager = class {
3331
3618
  async getManifest(moduleName) {
3332
3619
  try {
3333
3620
  const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3334
- const content = await fs4.readFile(manifestPath, "utf-8");
3621
+ const content = await fs5.readFile(manifestPath, "utf-8");
3335
3622
  return JSON.parse(content);
3336
3623
  } catch {
3337
3624
  return null;
@@ -3369,7 +3656,7 @@ var TemplateManager = class {
3369
3656
  });
3370
3657
  continue;
3371
3658
  }
3372
- const currentContent = await fs4.readFile(filePath, "utf-8");
3659
+ const currentContent = await fs5.readFile(filePath, "utf-8");
3373
3660
  const currentHash = this.hashContent(currentContent);
3374
3661
  results.push({
3375
3662
  fileName,
@@ -3393,15 +3680,15 @@ var TemplateManager = class {
3393
3680
  * Copy directory recursively
3394
3681
  */
3395
3682
  async copyDirectory(src, dest) {
3396
- await fs4.mkdir(dest, { recursive: true });
3397
- const entries = await fs4.readdir(src, { withFileTypes: true });
3683
+ await fs5.mkdir(dest, { recursive: true });
3684
+ const entries = await fs5.readdir(src, { withFileTypes: true });
3398
3685
  for (const entry of entries) {
3399
3686
  const srcPath = path7.join(src, entry.name);
3400
3687
  const destPath = path7.join(dest, entry.name);
3401
3688
  if (entry.isDirectory()) {
3402
3689
  await this.copyDirectory(srcPath, destPath);
3403
3690
  } else {
3404
- await fs4.copyFile(srcPath, destPath);
3691
+ await fs5.copyFile(srcPath, destPath);
3405
3692
  }
3406
3693
  }
3407
3694
  }
@@ -3499,12 +3786,11 @@ var TemplateManager = class {
3499
3786
  manifest.files = fileHashes;
3500
3787
  manifest.updatedAt = /* @__PURE__ */ new Date();
3501
3788
  const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
3502
- await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3789
+ await fs5.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3503
3790
  }
3504
3791
  };
3505
3792
 
3506
3793
  // src/cli/utils/interactive-prompt.ts
3507
- init_esm_shims();
3508
3794
  import inquirer3 from "inquirer";
3509
3795
  import chalk6 from "chalk";
3510
3796
  var InteractivePrompt = class {
@@ -3690,7 +3976,6 @@ var InteractivePrompt = class {
3690
3976
  };
3691
3977
 
3692
3978
  // src/cli/commands/add-module.ts
3693
- init_error_handler();
3694
3979
  var AVAILABLE_MODULES = {
3695
3980
  auth: {
3696
3981
  name: "Authentication",
@@ -3891,7 +4176,7 @@ var addModuleCommand = new Command3("add").description("Add a pre-built module t
3891
4176
  const backupPath = await templateManager.createBackup(moduleName, moduleDir);
3892
4177
  InteractivePrompt.showBackupCreated(backupPath);
3893
4178
  }
3894
- await fs5.rm(moduleDir, { recursive: true, force: true });
4179
+ await fs6.rm(moduleDir, { recursive: true, force: true });
3895
4180
  await ensureDir(moduleDir);
3896
4181
  await generateModuleFiles(moduleName, moduleDir);
3897
4182
  const files = await getModuleFiles(moduleName, moduleDir);
@@ -4244,7 +4529,7 @@ async function findServercraftModules() {
4244
4529
  ];
4245
4530
  for (const p of possiblePaths) {
4246
4531
  try {
4247
- const stats = await fs5.stat(p);
4532
+ const stats = await fs6.stat(p);
4248
4533
  if (stats.isDirectory()) {
4249
4534
  return p;
4250
4535
  }
@@ -4294,26 +4579,26 @@ async function generateModuleFiles(moduleName, moduleDir) {
4294
4579
  }
4295
4580
  }
4296
4581
  async function copyModuleFromSource(sourceDir, targetDir) {
4297
- const entries = await fs5.readdir(sourceDir, { withFileTypes: true });
4582
+ const entries = await fs6.readdir(sourceDir, { withFileTypes: true });
4298
4583
  for (const entry of entries) {
4299
4584
  const sourcePath = path8.join(sourceDir, entry.name);
4300
4585
  const targetPath = path8.join(targetDir, entry.name);
4301
4586
  if (entry.isDirectory()) {
4302
- await fs5.mkdir(targetPath, { recursive: true });
4587
+ await fs6.mkdir(targetPath, { recursive: true });
4303
4588
  await copyModuleFromSource(sourcePath, targetPath);
4304
4589
  } else {
4305
- await fs5.copyFile(sourcePath, targetPath);
4590
+ await fs6.copyFile(sourcePath, targetPath);
4306
4591
  }
4307
4592
  }
4308
4593
  }
4309
4594
  async function getModuleFiles(moduleName, moduleDir) {
4310
4595
  const files = {};
4311
- const entries = await fs5.readdir(moduleDir);
4596
+ const entries = await fs6.readdir(moduleDir);
4312
4597
  for (const entry of entries) {
4313
4598
  const filePath = path8.join(moduleDir, entry);
4314
- const stat2 = await fs5.stat(filePath);
4599
+ const stat2 = await fs6.stat(filePath);
4315
4600
  if (stat2.isFile() && entry.endsWith(".ts")) {
4316
- const content = await fs5.readFile(filePath, "utf-8");
4601
+ const content = await fs6.readFile(filePath, "utf-8");
4317
4602
  files[entry] = content;
4318
4603
  }
4319
4604
  }
@@ -4329,7 +4614,7 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
4329
4614
  console.log(chalk7.yellow(`
4330
4615
  \u{1F4C4} ${file.fileName}:`));
4331
4616
  const currentPath = path8.join(moduleDir, file.fileName);
4332
- const currentContent = await fs5.readFile(currentPath, "utf-8");
4617
+ const currentContent = await fs6.readFile(currentPath, "utf-8");
4333
4618
  const originalContent = await templateManager.getTemplate(moduleName, file.fileName);
4334
4619
  if (originalContent) {
4335
4620
  const diff = templateManager.generateDiff(originalContent, currentContent);
@@ -4343,9 +4628,9 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4343
4628
  const newFiles = {};
4344
4629
  const templateDir = path8.join(templateManager["templatesDir"], moduleName);
4345
4630
  try {
4346
- const entries = await fs5.readdir(templateDir);
4631
+ const entries = await fs6.readdir(templateDir);
4347
4632
  for (const entry of entries) {
4348
- const content = await fs5.readFile(path8.join(templateDir, entry), "utf-8");
4633
+ const content = await fs6.readFile(path8.join(templateDir, entry), "utf-8");
4349
4634
  newFiles[entry] = content;
4350
4635
  }
4351
4636
  } catch {
@@ -4376,7 +4661,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4376
4661
  } else if (batchAction === "overwrite-all") {
4377
4662
  fileAction = "overwrite";
4378
4663
  } else {
4379
- const currentContent = await fs5.readFile(filePath, "utf-8");
4664
+ const currentContent = await fs6.readFile(filePath, "utf-8");
4380
4665
  const yourLines = currentContent.split("\n").length;
4381
4666
  const newLines = newContent.split("\n").length;
4382
4667
  const choice = await InteractivePrompt.askFileAction(
@@ -4400,20 +4685,20 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4400
4685
  continue;
4401
4686
  }
4402
4687
  if (fileAction === "overwrite") {
4403
- await fs5.writeFile(filePath, newContent, "utf-8");
4688
+ await fs6.writeFile(filePath, newContent, "utf-8");
4404
4689
  stats.overwritten++;
4405
4690
  continue;
4406
4691
  }
4407
4692
  if (fileAction === "merge") {
4408
4693
  const originalContent = await templateManager.getTemplate(moduleName, fileName);
4409
- const currentContent = await fs5.readFile(filePath, "utf-8");
4694
+ const currentContent = await fs6.readFile(filePath, "utf-8");
4410
4695
  if (originalContent) {
4411
4696
  const mergeResult = await templateManager.mergeFiles(
4412
4697
  originalContent,
4413
4698
  currentContent,
4414
4699
  newContent
4415
4700
  );
4416
- await fs5.writeFile(filePath, mergeResult.merged, "utf-8");
4701
+ await fs6.writeFile(filePath, mergeResult.merged, "utf-8");
4417
4702
  if (mergeResult.hasConflicts) {
4418
4703
  stats.conflicts++;
4419
4704
  InteractivePrompt.displayConflicts(mergeResult.conflicts);
@@ -4421,7 +4706,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4421
4706
  stats.merged++;
4422
4707
  }
4423
4708
  } else {
4424
- await fs5.writeFile(filePath, newContent, "utf-8");
4709
+ await fs6.writeFile(filePath, newContent, "utf-8");
4425
4710
  stats.overwritten++;
4426
4711
  }
4427
4712
  }
@@ -4432,7 +4717,6 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
4432
4717
  }
4433
4718
 
4434
4719
  // src/cli/commands/db.ts
4435
- init_esm_shims();
4436
4720
  import { Command as Command4 } from "commander";
4437
4721
  import { execSync as execSync2, spawn } from "child_process";
4438
4722
  import ora4 from "ora";
@@ -4526,25 +4810,21 @@ dbCommand.command("status").description("Show migration status").action(async ()
4526
4810
  });
4527
4811
 
4528
4812
  // src/cli/commands/docs.ts
4529
- init_esm_shims();
4530
4813
  import { Command as Command5 } from "commander";
4531
4814
  import path10 from "path";
4532
- import fs7 from "fs/promises";
4815
+ import fs8 from "fs/promises";
4533
4816
  import ora6 from "ora";
4534
4817
  import chalk9 from "chalk";
4535
4818
 
4536
4819
  // src/cli/utils/docs-generator.ts
4537
- init_esm_shims();
4538
- import fs6 from "fs/promises";
4820
+ import fs7 from "fs/promises";
4539
4821
  import path9 from "path";
4540
4822
  import ora5 from "ora";
4541
4823
 
4542
4824
  // src/core/server.ts
4543
- init_esm_shims();
4544
4825
  import Fastify from "fastify";
4545
4826
 
4546
4827
  // src/core/logger.ts
4547
- init_esm_shims();
4548
4828
  import pino from "pino";
4549
4829
  var defaultConfig = {
4550
4830
  level: process.env.LOG_LEVEL || "info",
@@ -4677,14 +4957,7 @@ function createServer(config2 = {}) {
4677
4957
  return new Server(config2);
4678
4958
  }
4679
4959
 
4680
- // src/middleware/index.ts
4681
- init_esm_shims();
4682
-
4683
- // src/middleware/error-handler.ts
4684
- init_esm_shims();
4685
-
4686
4960
  // src/utils/errors.ts
4687
- init_esm_shims();
4688
4961
  var AppError = class _AppError extends Error {
4689
4962
  statusCode;
4690
4963
  isOperational;
@@ -4738,11 +5011,7 @@ function isAppError(error2) {
4738
5011
  return error2 instanceof AppError;
4739
5012
  }
4740
5013
 
4741
- // src/config/index.ts
4742
- init_esm_shims();
4743
-
4744
5014
  // src/config/env.ts
4745
- init_esm_shims();
4746
5015
  import { z } from "zod";
4747
5016
  import dotenv from "dotenv";
4748
5017
  dotenv.config();
@@ -4888,7 +5157,6 @@ function registerErrorHandler(app) {
4888
5157
  }
4889
5158
 
4890
5159
  // src/middleware/security.ts
4891
- init_esm_shims();
4892
5160
  import helmet from "@fastify/helmet";
4893
5161
  import cors from "@fastify/cors";
4894
5162
  import rateLimit from "@fastify/rate-limit";
@@ -4948,11 +5216,7 @@ async function registerSecurity(app, options = {}) {
4948
5216
  }
4949
5217
  }
4950
5218
 
4951
- // src/modules/swagger/index.ts
4952
- init_esm_shims();
4953
-
4954
5219
  // src/modules/swagger/swagger.service.ts
4955
- init_esm_shims();
4956
5220
  import swagger from "@fastify/swagger";
4957
5221
  import swaggerUi from "@fastify/swagger-ui";
4958
5222
  var defaultConfig3 = {
@@ -5012,16 +5276,11 @@ async function registerSwagger(app, customConfig) {
5012
5276
  logger.info("Swagger documentation registered at /docs");
5013
5277
  }
5014
5278
 
5015
- // src/modules/swagger/schema-builder.ts
5016
- init_esm_shims();
5017
-
5018
5279
  // src/modules/auth/index.ts
5019
- init_esm_shims();
5020
5280
  import jwt from "@fastify/jwt";
5021
5281
  import cookie from "@fastify/cookie";
5022
5282
 
5023
5283
  // src/modules/auth/auth.service.ts
5024
- init_esm_shims();
5025
5284
  import bcrypt from "bcryptjs";
5026
5285
  import { Redis } from "ioredis";
5027
5286
  var AuthService = class {
@@ -5220,11 +5479,7 @@ function createAuthService(app) {
5220
5479
  return new AuthService(app);
5221
5480
  }
5222
5481
 
5223
- // src/modules/auth/auth.controller.ts
5224
- init_esm_shims();
5225
-
5226
5482
  // src/modules/auth/schemas.ts
5227
- init_esm_shims();
5228
5483
  import { z as z2 } from "zod";
5229
5484
  var loginSchema = z2.object({
5230
5485
  email: z2.string().email("Invalid email address"),
@@ -5251,7 +5506,6 @@ var changePasswordSchema = z2.object({
5251
5506
  });
5252
5507
 
5253
5508
  // src/utils/response.ts
5254
- init_esm_shims();
5255
5509
  function success2(reply, data, statusCode = 200) {
5256
5510
  const response = {
5257
5511
  success: true,
@@ -5267,7 +5521,6 @@ function noContent(reply) {
5267
5521
  }
5268
5522
 
5269
5523
  // src/modules/validation/validator.ts
5270
- init_esm_shims();
5271
5524
  import { z as z3 } from "zod";
5272
5525
  function validateBody(schema, data) {
5273
5526
  const result = schema.safeParse(data);
@@ -5286,11 +5539,11 @@ function validateQuery(schema, data) {
5286
5539
  function formatZodErrors(error2) {
5287
5540
  const errors = {};
5288
5541
  for (const issue of error2.issues) {
5289
- const path12 = issue.path.join(".") || "root";
5290
- if (!errors[path12]) {
5291
- errors[path12] = [];
5542
+ const path15 = issue.path.join(".") || "root";
5543
+ if (!errors[path15]) {
5544
+ errors[path15] = [];
5292
5545
  }
5293
- errors[path12].push(issue.message);
5546
+ errors[path15].push(issue.message);
5294
5547
  }
5295
5548
  return errors;
5296
5549
  }
@@ -5438,11 +5691,7 @@ function createAuthController(authService, userService) {
5438
5691
  return new AuthController(authService, userService);
5439
5692
  }
5440
5693
 
5441
- // src/modules/auth/auth.routes.ts
5442
- init_esm_shims();
5443
-
5444
5694
  // src/modules/auth/auth.middleware.ts
5445
- init_esm_shims();
5446
5695
  function createAuthMiddleware(authService) {
5447
5696
  return async function authenticate(request, _reply) {
5448
5697
  const authHeader = request.headers.authorization;
@@ -5485,14 +5734,7 @@ function registerAuthRoutes(app, controller, authService) {
5485
5734
  );
5486
5735
  }
5487
5736
 
5488
- // src/modules/user/user.service.ts
5489
- init_esm_shims();
5490
-
5491
- // src/modules/user/user.repository.ts
5492
- init_esm_shims();
5493
-
5494
5737
  // src/database/prisma.ts
5495
- init_esm_shims();
5496
5738
  import { PrismaClient } from "@prisma/client";
5497
5739
  var prismaClientSingleton = () => {
5498
5740
  return new PrismaClient({
@@ -5506,7 +5748,6 @@ if (!isProduction()) {
5506
5748
  }
5507
5749
 
5508
5750
  // src/utils/pagination.ts
5509
- init_esm_shims();
5510
5751
  var DEFAULT_PAGE = 1;
5511
5752
  var DEFAULT_LIMIT = 20;
5512
5753
  var MAX_LIMIT = 100;
@@ -5771,7 +6012,6 @@ function createUserRepository() {
5771
6012
  }
5772
6013
 
5773
6014
  // src/modules/user/types.ts
5774
- init_esm_shims();
5775
6015
  var DEFAULT_ROLE_PERMISSIONS = {
5776
6016
  user: ["profile:read", "profile:update"],
5777
6017
  moderator: [
@@ -5902,9 +6142,6 @@ function createUserService(repository) {
5902
6142
  return new UserService(repository || createUserRepository());
5903
6143
  }
5904
6144
 
5905
- // src/modules/auth/types.ts
5906
- init_esm_shims();
5907
-
5908
6145
  // src/modules/auth/index.ts
5909
6146
  async function registerAuthModule(app) {
5910
6147
  await app.register(jwt, {
@@ -5924,14 +6161,7 @@ async function registerAuthModule(app) {
5924
6161
  logger.info("Auth module registered");
5925
6162
  }
5926
6163
 
5927
- // src/modules/user/index.ts
5928
- init_esm_shims();
5929
-
5930
- // src/modules/user/user.controller.ts
5931
- init_esm_shims();
5932
-
5933
6164
  // src/modules/user/schemas.ts
5934
- init_esm_shims();
5935
6165
  import { z as z4 } from "zod";
5936
6166
  var userStatusEnum = z4.enum(["active", "inactive", "suspended", "banned"]);
5937
6167
  var userRoleEnum = z4.enum(["user", "admin", "moderator", "super_admin"]);
@@ -6058,7 +6288,6 @@ function createUserController(userService) {
6058
6288
  }
6059
6289
 
6060
6290
  // src/modules/user/user.routes.ts
6061
- init_esm_shims();
6062
6291
  var idParamsSchema = {
6063
6292
  type: "object",
6064
6293
  properties: {
@@ -6148,8 +6377,8 @@ async function generateDocs(outputPath = "openapi.json", silent = false) {
6148
6377
  await app.ready();
6149
6378
  const spec = app.swagger();
6150
6379
  const absoluteOutput = path9.resolve(outputPath);
6151
- await fs6.mkdir(path9.dirname(absoluteOutput), { recursive: true });
6152
- await fs6.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
6380
+ await fs7.mkdir(path9.dirname(absoluteOutput), { recursive: true });
6381
+ await fs7.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
6153
6382
  spinner?.succeed(`OpenAPI spec generated at ${absoluteOutput}`);
6154
6383
  await app.close();
6155
6384
  return absoluteOutput;
@@ -6165,10 +6394,10 @@ docsCommand.command("generate").alias("gen").description("Generate OpenAPI/Swagg
6165
6394
  try {
6166
6395
  const outputPath = await generateDocs(options.output, false);
6167
6396
  if (options.format === "yaml") {
6168
- const jsonContent = await fs7.readFile(outputPath, "utf-8");
6397
+ const jsonContent = await fs8.readFile(outputPath, "utf-8");
6169
6398
  const spec = JSON.parse(jsonContent);
6170
6399
  const yamlPath = outputPath.replace(".json", ".yaml");
6171
- await fs7.writeFile(yamlPath, jsonToYaml(spec));
6400
+ await fs8.writeFile(yamlPath, jsonToYaml(spec));
6172
6401
  success(`YAML documentation generated: ${yamlPath}`);
6173
6402
  }
6174
6403
  console.log("\n\u{1F4DA} Documentation URLs:");
@@ -6195,12 +6424,12 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
6195
6424
  const projectRoot = getProjectRoot();
6196
6425
  const specPath = path10.join(projectRoot, "openapi.json");
6197
6426
  try {
6198
- await fs7.access(specPath);
6427
+ await fs8.access(specPath);
6199
6428
  } catch {
6200
6429
  spinner.text = "Generating OpenAPI spec first...";
6201
6430
  await generateDocs("openapi.json", true);
6202
6431
  }
6203
- const specContent = await fs7.readFile(specPath, "utf-8");
6432
+ const specContent = await fs8.readFile(specPath, "utf-8");
6204
6433
  const spec = JSON.parse(specContent);
6205
6434
  let output;
6206
6435
  let defaultName;
@@ -6221,7 +6450,7 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
6221
6450
  throw new Error(`Unknown format: ${options.format}`);
6222
6451
  }
6223
6452
  const outPath = path10.join(projectRoot, options.output || defaultName);
6224
- await fs7.writeFile(outPath, output);
6453
+ await fs8.writeFile(outPath, output);
6225
6454
  spinner.succeed(`Exported to: ${options.output || defaultName}`);
6226
6455
  if (options.format === "postman") {
6227
6456
  info("\n Import in Postman: File > Import > Select file");
@@ -6236,11 +6465,11 @@ docsCommand.command("status").description("Show documentation status").action(as
6236
6465
  console.log(chalk9.bold("\n\u{1F4CA} Documentation Status\n"));
6237
6466
  const specPath = path10.join(projectRoot, "openapi.json");
6238
6467
  try {
6239
- const stat2 = await fs7.stat(specPath);
6468
+ const stat2 = await fs8.stat(specPath);
6240
6469
  success(
6241
6470
  `openapi.json exists (${formatBytes(stat2.size)}, modified ${formatDate(stat2.mtime)})`
6242
6471
  );
6243
- const content = await fs7.readFile(specPath, "utf-8");
6472
+ const content = await fs8.readFile(specPath, "utf-8");
6244
6473
  const spec = JSON.parse(content);
6245
6474
  const pathCount = Object.keys(spec.paths || {}).length;
6246
6475
  info(` ${pathCount} endpoints documented`);
@@ -6350,10 +6579,9 @@ function formatDate(date) {
6350
6579
  }
6351
6580
 
6352
6581
  // src/cli/commands/list.ts
6353
- init_esm_shims();
6354
6582
  import { Command as Command6 } from "commander";
6355
6583
  import chalk10 from "chalk";
6356
- import fs8 from "fs/promises";
6584
+ import fs9 from "fs/promises";
6357
6585
  var AVAILABLE_MODULES2 = {
6358
6586
  // Core
6359
6587
  auth: {
@@ -6468,7 +6696,7 @@ var AVAILABLE_MODULES2 = {
6468
6696
  async function getInstalledModules() {
6469
6697
  try {
6470
6698
  const modulesDir = getModulesDir();
6471
- const entries = await fs8.readdir(modulesDir, { withFileTypes: true });
6699
+ const entries = await fs9.readdir(modulesDir, { withFileTypes: true });
6472
6700
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
6473
6701
  } catch {
6474
6702
  return [];
@@ -6508,7 +6736,7 @@ var listCommand = new Command6("list").alias("ls").description("List available a
6508
6736
  if (!byCategory[mod.category]) {
6509
6737
  byCategory[mod.category] = [];
6510
6738
  }
6511
- byCategory[mod.category].push({
6739
+ byCategory[mod.category]?.push({
6512
6740
  id: key,
6513
6741
  name: mod.name,
6514
6742
  description: mod.description,
@@ -6576,14 +6804,12 @@ var listCommand = new Command6("list").alias("ls").description("List available a
6576
6804
  );
6577
6805
 
6578
6806
  // src/cli/commands/remove.ts
6579
- init_esm_shims();
6580
6807
  import { Command as Command7 } from "commander";
6581
6808
  import path11 from "path";
6582
6809
  import ora7 from "ora";
6583
6810
  import chalk11 from "chalk";
6584
- import fs9 from "fs/promises";
6811
+ import fs10 from "fs/promises";
6585
6812
  import inquirer4 from "inquirer";
6586
- init_error_handler();
6587
6813
  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
6814
  const projectError = validateProject();
6589
6815
  if (projectError) {
@@ -6593,20 +6819,17 @@ var removeCommand = new Command7("remove").alias("rm").description("Remove an in
6593
6819
  console.log(chalk11.bold.cyan("\n\u{1F5D1}\uFE0F ServCraft Module Removal\n"));
6594
6820
  const moduleDir = path11.join(getModulesDir(), moduleName);
6595
6821
  try {
6596
- const exists = await fs9.access(moduleDir).then(() => true).catch(() => false);
6822
+ const exists = await fs10.access(moduleDir).then(() => true).catch(() => false);
6597
6823
  if (!exists) {
6598
6824
  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
- )
6825
+ new ServCraftError(`Module "${moduleName}" is not installed`, [
6826
+ `Run ${chalk11.cyan("servcraft list --installed")} to see installed modules`,
6827
+ `Check the spelling of the module name`
6828
+ ])
6606
6829
  );
6607
6830
  return;
6608
6831
  }
6609
- const files = await fs9.readdir(moduleDir);
6832
+ const files = await fs10.readdir(moduleDir);
6610
6833
  const fileCount = files.length;
6611
6834
  if (!options?.yes) {
6612
6835
  console.log(chalk11.yellow(`\u26A0 This will remove the "${moduleName}" module:`));
@@ -6627,7 +6850,7 @@ var removeCommand = new Command7("remove").alias("rm").description("Remove an in
6627
6850
  }
6628
6851
  }
6629
6852
  const spinner = ora7("Removing module...").start();
6630
- await fs9.rm(moduleDir, { recursive: true, force: true });
6853
+ await fs10.rm(moduleDir, { recursive: true, force: true });
6631
6854
  spinner.succeed(`Module "${moduleName}" removed successfully!`);
6632
6855
  console.log("\n" + chalk11.bold("\u2713 Removed:"));
6633
6856
  success(` src/modules/${moduleName}/ (${fileCount} files)`);
@@ -6651,15 +6874,697 @@ var removeCommand = new Command7("remove").alias("rm").description("Remove an in
6651
6874
  });
6652
6875
 
6653
6876
  // src/cli/commands/doctor.ts
6654
- init_esm_shims();
6655
6877
  import { Command as Command8 } from "commander";
6656
6878
  import chalk12 from "chalk";
6879
+ import fs11 from "fs/promises";
6880
+ async function checkNodeVersion() {
6881
+ const version = process.version;
6882
+ const major = parseInt(version.slice(1).split(".")[0] || "0", 10);
6883
+ if (major >= 18) {
6884
+ return { name: "Node.js", status: "pass", message: `${version} \u2713` };
6885
+ }
6886
+ return {
6887
+ name: "Node.js",
6888
+ status: "fail",
6889
+ message: `${version} (< 18)`,
6890
+ suggestion: "Upgrade to Node.js 18+"
6891
+ };
6892
+ }
6893
+ async function checkPackageJson() {
6894
+ const checks = [];
6895
+ try {
6896
+ const content = await fs11.readFile("package.json", "utf-8");
6897
+ const pkg = JSON.parse(content);
6898
+ checks.push({ name: "package.json", status: "pass", message: "Found" });
6899
+ if (pkg.dependencies?.fastify) {
6900
+ checks.push({ name: "Fastify", status: "pass", message: "Installed" });
6901
+ } else {
6902
+ checks.push({
6903
+ name: "Fastify",
6904
+ status: "fail",
6905
+ message: "Missing",
6906
+ suggestion: "npm install fastify"
6907
+ });
6908
+ }
6909
+ } catch {
6910
+ checks.push({
6911
+ name: "package.json",
6912
+ status: "fail",
6913
+ message: "Not found",
6914
+ suggestion: "Run servcraft init"
6915
+ });
6916
+ }
6917
+ return checks;
6918
+ }
6919
+ async function checkDirectories() {
6920
+ const checks = [];
6921
+ const dirs = ["src", "node_modules", ".git", ".env"];
6922
+ for (const dir of dirs) {
6923
+ try {
6924
+ await fs11.access(dir);
6925
+ checks.push({ name: dir, status: "pass", message: "Exists" });
6926
+ } catch {
6927
+ const isCritical = dir === "src" || dir === "node_modules";
6928
+ checks.push({
6929
+ name: dir,
6930
+ status: isCritical ? "fail" : "warn",
6931
+ message: "Not found",
6932
+ suggestion: dir === "node_modules" ? "npm install" : dir === ".env" ? "Create .env file" : void 0
6933
+ });
6934
+ }
6935
+ }
6936
+ return checks;
6937
+ }
6657
6938
  var doctorCommand = new Command8("doctor").description("Diagnose project configuration and dependencies").action(async () => {
6658
- console.log(chalk12.bold.cyan("\nServCraft Doctor - Coming soon!\n"));
6939
+ console.log(chalk12.bold.cyan("\n\u{1F50D} ServCraft Doctor\n"));
6940
+ const allChecks = [];
6941
+ allChecks.push(await checkNodeVersion());
6942
+ allChecks.push(...await checkPackageJson());
6943
+ allChecks.push(...await checkDirectories());
6944
+ allChecks.forEach((check) => {
6945
+ const icon = check.status === "pass" ? chalk12.green("\u2713") : check.status === "warn" ? chalk12.yellow("\u26A0") : chalk12.red("\u2717");
6946
+ const color = check.status === "pass" ? chalk12.green : check.status === "warn" ? chalk12.yellow : chalk12.red;
6947
+ console.log(`${icon} ${check.name.padEnd(20)} ${color(check.message)}`);
6948
+ if (check.suggestion) {
6949
+ console.log(chalk12.gray(` \u2192 ${check.suggestion}`));
6950
+ }
6951
+ });
6952
+ const pass = allChecks.filter((c) => c.status === "pass").length;
6953
+ const warn2 = allChecks.filter((c) => c.status === "warn").length;
6954
+ const fail = allChecks.filter((c) => c.status === "fail").length;
6955
+ console.log(chalk12.gray("\n" + "\u2500".repeat(60)));
6956
+ console.log(
6957
+ `
6958
+ ${chalk12.green(pass + " passed")} | ${chalk12.yellow(warn2 + " warnings")} | ${chalk12.red(fail + " failed")}
6959
+ `
6960
+ );
6961
+ if (fail === 0 && warn2 === 0) {
6962
+ console.log(chalk12.green.bold("\u2728 Everything looks good!\n"));
6963
+ } else if (fail > 0) {
6964
+ console.log(chalk12.red.bold("\u2717 Fix critical issues before using ServCraft.\n"));
6965
+ } else {
6966
+ console.log(chalk12.yellow.bold("\u26A0 Some warnings, but should work.\n"));
6967
+ }
6659
6968
  });
6660
6969
 
6970
+ // src/cli/commands/update.ts
6971
+ import { Command as Command9 } from "commander";
6972
+ import chalk13 from "chalk";
6973
+ import fs12 from "fs/promises";
6974
+ import path12 from "path";
6975
+ import { fileURLToPath } from "url";
6976
+ import inquirer5 from "inquirer";
6977
+ var __filename2 = fileURLToPath(import.meta.url);
6978
+ var __dirname2 = path12.dirname(__filename2);
6979
+ var AVAILABLE_MODULES3 = [
6980
+ "auth",
6981
+ "users",
6982
+ "email",
6983
+ "mfa",
6984
+ "oauth",
6985
+ "rate-limit",
6986
+ "cache",
6987
+ "upload",
6988
+ "search",
6989
+ "notification",
6990
+ "webhook",
6991
+ "websocket",
6992
+ "queue",
6993
+ "payment",
6994
+ "i18n",
6995
+ "feature-flag",
6996
+ "analytics",
6997
+ "media-processing",
6998
+ "api-versioning",
6999
+ "audit",
7000
+ "swagger",
7001
+ "validation"
7002
+ ];
7003
+ async function getInstalledModules2() {
7004
+ try {
7005
+ const modulesDir = getModulesDir();
7006
+ const entries = await fs12.readdir(modulesDir, { withFileTypes: true });
7007
+ const installedModules = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => AVAILABLE_MODULES3.includes(name));
7008
+ return installedModules;
7009
+ } catch {
7010
+ return [];
7011
+ }
7012
+ }
7013
+ async function copyModuleFiles(moduleName, _projectRoot) {
7014
+ const cliRoot = path12.resolve(__dirname2, "../../../");
7015
+ const sourceModulePath = path12.join(cliRoot, "src", "modules", moduleName);
7016
+ const targetModulesDir = getModulesDir();
7017
+ const targetModulePath = path12.join(targetModulesDir, moduleName);
7018
+ try {
7019
+ await fs12.access(sourceModulePath);
7020
+ } catch {
7021
+ throw new Error(`Module source not found: ${moduleName}`);
7022
+ }
7023
+ await fs12.cp(sourceModulePath, targetModulePath, { recursive: true });
7024
+ }
7025
+ async function updateModule(moduleName, options) {
7026
+ const projectError = validateProject();
7027
+ if (projectError) {
7028
+ displayError(projectError);
7029
+ return;
7030
+ }
7031
+ const projectRoot = getProjectRoot();
7032
+ const installedModules = await getInstalledModules2();
7033
+ if (!installedModules.includes(moduleName)) {
7034
+ console.log(chalk13.yellow(`
7035
+ \u26A0 Module "${moduleName}" is not installed
7036
+ `));
7037
+ console.log(
7038
+ chalk13.gray(`Run ${chalk13.cyan(`servcraft add ${moduleName}`)} to install it first.
7039
+ `)
7040
+ );
7041
+ return;
7042
+ }
7043
+ if (options.check) {
7044
+ console.log(chalk13.cyan(`
7045
+ \u{1F4E6} Checking updates for "${moduleName}"...
7046
+ `));
7047
+ console.log(chalk13.gray("Note: Version tracking will be implemented in a future release."));
7048
+ console.log(chalk13.gray("Currently, update will always reinstall the latest version.\n"));
7049
+ return;
7050
+ }
7051
+ const { confirmed } = await inquirer5.prompt([
7052
+ {
7053
+ type: "confirm",
7054
+ name: "confirmed",
7055
+ message: `Update "${moduleName}" module? This will overwrite existing files.`,
7056
+ default: false
7057
+ }
7058
+ ]);
7059
+ if (!confirmed) {
7060
+ console.log(chalk13.yellow("\n\u26A0 Update cancelled\n"));
7061
+ return;
7062
+ }
7063
+ console.log(chalk13.cyan(`
7064
+ \u{1F504} Updating "${moduleName}" module...
7065
+ `));
7066
+ try {
7067
+ await copyModuleFiles(moduleName, projectRoot);
7068
+ console.log(chalk13.green(`\u2714 Module "${moduleName}" updated successfully!
7069
+ `));
7070
+ console.log(
7071
+ chalk13.gray("Note: Remember to review any breaking changes in the documentation.\n")
7072
+ );
7073
+ } catch (error2) {
7074
+ if (error2 instanceof Error) {
7075
+ console.error(chalk13.red(`
7076
+ \u2717 Failed to update module: ${error2.message}
7077
+ `));
7078
+ }
7079
+ }
7080
+ }
7081
+ async function updateAllModules(options) {
7082
+ const projectError = validateProject();
7083
+ if (projectError) {
7084
+ displayError(projectError);
7085
+ return;
7086
+ }
7087
+ const installedModules = await getInstalledModules2();
7088
+ if (installedModules.length === 0) {
7089
+ console.log(chalk13.yellow("\n\u26A0 No modules installed\n"));
7090
+ console.log(chalk13.gray(`Run ${chalk13.cyan("servcraft list")} to see available modules.
7091
+ `));
7092
+ return;
7093
+ }
7094
+ if (options.check) {
7095
+ console.log(chalk13.cyan("\n\u{1F4E6} Checking updates for all modules...\n"));
7096
+ console.log(chalk13.bold("Installed modules:"));
7097
+ installedModules.forEach((mod) => {
7098
+ console.log(` \u2022 ${chalk13.cyan(mod)}`);
7099
+ });
7100
+ console.log();
7101
+ console.log(chalk13.gray("Note: Version tracking will be implemented in a future release."));
7102
+ console.log(chalk13.gray("Currently, update will always reinstall the latest version.\n"));
7103
+ return;
7104
+ }
7105
+ console.log(chalk13.cyan(`
7106
+ \u{1F4E6} Found ${installedModules.length} installed module(s):
7107
+ `));
7108
+ installedModules.forEach((mod) => {
7109
+ console.log(` \u2022 ${chalk13.cyan(mod)}`);
7110
+ });
7111
+ console.log();
7112
+ const { confirmed } = await inquirer5.prompt([
7113
+ {
7114
+ type: "confirm",
7115
+ name: "confirmed",
7116
+ message: "Update all modules? This will overwrite existing files.",
7117
+ default: false
7118
+ }
7119
+ ]);
7120
+ if (!confirmed) {
7121
+ console.log(chalk13.yellow("\n\u26A0 Update cancelled\n"));
7122
+ return;
7123
+ }
7124
+ console.log(chalk13.cyan("\n\u{1F504} Updating all modules...\n"));
7125
+ const projectRoot = getProjectRoot();
7126
+ let successCount = 0;
7127
+ let failCount = 0;
7128
+ for (const moduleName of installedModules) {
7129
+ try {
7130
+ await copyModuleFiles(moduleName, projectRoot);
7131
+ console.log(chalk13.green(`\u2714 Updated: ${moduleName}`));
7132
+ successCount++;
7133
+ } catch {
7134
+ console.error(chalk13.red(`\u2717 Failed: ${moduleName}`));
7135
+ failCount++;
7136
+ }
7137
+ }
7138
+ console.log();
7139
+ console.log(
7140
+ chalk13.bold(
7141
+ `
7142
+ \u2714 Update complete: ${chalk13.green(successCount)} succeeded, ${chalk13.red(failCount)} failed
7143
+ `
7144
+ )
7145
+ );
7146
+ if (successCount > 0) {
7147
+ console.log(
7148
+ chalk13.gray("Note: Remember to review any breaking changes in the documentation.\n")
7149
+ );
7150
+ }
7151
+ }
7152
+ var updateCommand = new Command9("update").description("Update installed modules to latest version").argument("[module]", "Specific module to update").option("--check", "Check for updates without applying").option("-y, --yes", "Skip confirmation prompt").action(async (moduleName, options) => {
7153
+ if (moduleName) {
7154
+ await updateModule(moduleName, { check: options?.check });
7155
+ } else {
7156
+ await updateAllModules({ check: options?.check });
7157
+ }
7158
+ });
7159
+
7160
+ // src/cli/commands/completion.ts
7161
+ import { Command as Command10 } from "commander";
7162
+ var bashScript = `
7163
+ # servcraft bash completion script
7164
+ _servcraft_completions() {
7165
+ local cur prev words cword
7166
+ _init_completion || return
7167
+
7168
+ # Main commands
7169
+ local commands="init add generate list remove doctor update completion docs --version --help"
7170
+
7171
+ # Generate subcommands
7172
+ local generate_subcommands="module controller service repository types schema routes m c s r t"
7173
+
7174
+ case "\${words[1]}" in
7175
+ generate|g)
7176
+ if [[ \${cword} -eq 2 ]]; then
7177
+ COMPREPLY=( $(compgen -W "\${generate_subcommands}" -- "\${cur}") )
7178
+ fi
7179
+ ;;
7180
+ add|remove|rm|update)
7181
+ if [[ \${cword} -eq 2 ]]; then
7182
+ # Get available modules
7183
+ local modules="auth cache rate-limit notification payment oauth mfa queue websocket upload"
7184
+ COMPREPLY=( $(compgen -W "\${modules}" -- "\${cur}") )
7185
+ fi
7186
+ ;;
7187
+ completion)
7188
+ if [[ \${cword} -eq 2 ]]; then
7189
+ COMPREPLY=( $(compgen -W "bash zsh" -- "\${cur}") )
7190
+ fi
7191
+ ;;
7192
+ *)
7193
+ if [[ \${cword} -eq 1 ]]; then
7194
+ COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
7195
+ fi
7196
+ ;;
7197
+ esac
7198
+ }
7199
+
7200
+ complete -F _servcraft_completions servcraft
7201
+ `;
7202
+ var zshScript = `
7203
+ #compdef servcraft
7204
+
7205
+ _servcraft() {
7206
+ local context state state_descr line
7207
+ typeset -A opt_args
7208
+
7209
+ _arguments -C \\
7210
+ '1: :_servcraft_commands' \\
7211
+ '*::arg:->args'
7212
+
7213
+ case $state in
7214
+ args)
7215
+ case $line[1] in
7216
+ generate|g)
7217
+ _servcraft_generate
7218
+ ;;
7219
+ add|remove|rm|update)
7220
+ _servcraft_modules
7221
+ ;;
7222
+ completion)
7223
+ _arguments '1: :(bash zsh)'
7224
+ ;;
7225
+ esac
7226
+ ;;
7227
+ esac
7228
+ }
7229
+
7230
+ _servcraft_commands() {
7231
+ local commands
7232
+ commands=(
7233
+ 'init:Initialize a new ServCraft project'
7234
+ 'add:Add a pre-built module to your project'
7235
+ 'generate:Generate code files (controller, service, etc.)'
7236
+ 'list:List available and installed modules'
7237
+ 'remove:Remove an installed module'
7238
+ 'doctor:Diagnose project configuration'
7239
+ 'update:Update installed modules'
7240
+ 'completion:Generate shell completion scripts'
7241
+ 'docs:Open documentation'
7242
+ '--version:Show version'
7243
+ '--help:Show help'
7244
+ )
7245
+ _describe 'command' commands
7246
+ }
7247
+
7248
+ _servcraft_generate() {
7249
+ local subcommands
7250
+ subcommands=(
7251
+ 'module:Generate a complete module (controller + service + routes)'
7252
+ 'controller:Generate a controller'
7253
+ 'service:Generate a service'
7254
+ 'repository:Generate a repository'
7255
+ 'types:Generate TypeScript types'
7256
+ 'schema:Generate validation schema'
7257
+ 'routes:Generate routes file'
7258
+ 'm:Alias for module'
7259
+ 'c:Alias for controller'
7260
+ 's:Alias for service'
7261
+ 'r:Alias for repository'
7262
+ 't:Alias for types'
7263
+ )
7264
+ _describe 'subcommand' subcommands
7265
+ }
7266
+
7267
+ _servcraft_modules() {
7268
+ local modules
7269
+ modules=(
7270
+ 'auth:Authentication & Authorization'
7271
+ 'cache:Redis caching'
7272
+ 'rate-limit:Rate limiting'
7273
+ 'notification:Email/SMS notifications'
7274
+ 'payment:Payment integration'
7275
+ 'oauth:OAuth providers'
7276
+ 'mfa:Multi-factor authentication'
7277
+ 'queue:Background jobs'
7278
+ 'websocket:WebSocket support'
7279
+ 'upload:File upload handling'
7280
+ )
7281
+ _describe 'module' modules
7282
+ }
7283
+
7284
+ _servcraft "$@"
7285
+ `;
7286
+ var completionCommand = new Command10("completion").description("Generate shell completion scripts").argument("<shell>", "Shell type (bash or zsh)").action((shell) => {
7287
+ const shellLower = shell.toLowerCase();
7288
+ if (shellLower === "bash") {
7289
+ console.log(bashScript);
7290
+ } else if (shellLower === "zsh") {
7291
+ console.log(zshScript);
7292
+ } else {
7293
+ console.error(`Unsupported shell: ${shell}`);
7294
+ console.error("Supported shells: bash, zsh");
7295
+ process.exit(1);
7296
+ }
7297
+ });
7298
+
7299
+ // src/cli/commands/scaffold.ts
7300
+ import { Command as Command11 } from "commander";
7301
+ import path13 from "path";
7302
+ import ora8 from "ora";
7303
+ import chalk14 from "chalk";
7304
+ var scaffoldCommand = new Command11("scaffold").description("Generate complete CRUD with Prisma model").argument("<name>", "Resource name (e.g., product, user)").option(
7305
+ "--fields <fields>",
7306
+ 'Field definitions: "name:string email:string? age:number category:relation"'
7307
+ ).option("--validator <type>", "Validator type: zod, joi, yup", "zod").option("--dry-run", "Preview changes without writing files").action(
7308
+ async (name, options) => {
7309
+ const dryRun = DryRunManager.getInstance();
7310
+ if (options.dryRun) {
7311
+ dryRun.enable();
7312
+ console.log(chalk14.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
7313
+ }
7314
+ if (!options.fields) {
7315
+ console.log(chalk14.red("\n\u2717 Error: --fields option is required\n"));
7316
+ console.log(chalk14.gray("Example:"));
7317
+ console.log(
7318
+ chalk14.cyan(
7319
+ ' servcraft scaffold product --fields "name:string price:number category:relation"'
7320
+ )
7321
+ );
7322
+ process.exit(1);
7323
+ }
7324
+ const spinner = ora8("Scaffolding resource...").start();
7325
+ try {
7326
+ const fields = parseFields(options.fields || "");
7327
+ if (!fields || fields.length === 0) {
7328
+ spinner.fail("No valid fields provided");
7329
+ console.log(chalk14.gray(`
7330
+ Received: ${options.fields}`));
7331
+ console.log(chalk14.gray(`Parsed: ${JSON.stringify(fields)}`));
7332
+ process.exit(1);
7333
+ }
7334
+ const kebabName = toKebabCase(name);
7335
+ const pascalName = toPascalCase(name);
7336
+ const camelName = toCamelCase(name);
7337
+ const pluralName = pluralize(camelName);
7338
+ const tableName = pluralize(kebabName);
7339
+ const modulesDir = getModulesDir();
7340
+ const moduleDir = path13.join(modulesDir, kebabName);
7341
+ const controllerTpl = await getTemplate("controller", controllerTemplate);
7342
+ const serviceTpl = await getTemplate("service", serviceTemplate);
7343
+ const repositoryTpl = await getTemplate("repository", repositoryTemplate);
7344
+ const routesTpl = await getTemplate("routes", routesTemplate);
7345
+ const moduleIndexTpl = await getTemplate("module-index", moduleIndexTemplate);
7346
+ const files = [
7347
+ {
7348
+ name: `${kebabName}.types.ts`,
7349
+ content: dynamicTypesTemplate(kebabName, pascalName, fields)
7350
+ },
7351
+ {
7352
+ name: `${kebabName}.schemas.ts`,
7353
+ content: dynamicSchemasTemplate(
7354
+ kebabName,
7355
+ pascalName,
7356
+ camelName,
7357
+ fields,
7358
+ options.validator || "zod"
7359
+ )
7360
+ },
7361
+ {
7362
+ name: `${kebabName}.service.ts`,
7363
+ content: serviceTpl(kebabName, pascalName, camelName)
7364
+ },
7365
+ {
7366
+ name: `${kebabName}.controller.ts`,
7367
+ content: controllerTpl(kebabName, pascalName, camelName)
7368
+ },
7369
+ {
7370
+ name: "index.ts",
7371
+ content: moduleIndexTpl(kebabName, pascalName, camelName)
7372
+ },
7373
+ {
7374
+ name: `${kebabName}.repository.ts`,
7375
+ content: repositoryTpl(kebabName, pascalName, camelName, pluralName)
7376
+ },
7377
+ {
7378
+ name: `${kebabName}.routes.ts`,
7379
+ content: routesTpl(kebabName, pascalName, camelName, pluralName)
7380
+ }
7381
+ ];
7382
+ for (const file of files) {
7383
+ await writeFile(path13.join(moduleDir, file.name), file.content);
7384
+ }
7385
+ const testDir = path13.join(moduleDir, "__tests__");
7386
+ const controllerTestTpl = await getTemplate("controller-test", controllerTestTemplate);
7387
+ const serviceTestTpl = await getTemplate("service-test", serviceTestTemplate);
7388
+ const integrationTestTpl = await getTemplate("integration-test", integrationTestTemplate);
7389
+ await writeFile(
7390
+ path13.join(testDir, `${kebabName}.controller.test.ts`),
7391
+ controllerTestTpl(kebabName, pascalName, camelName)
7392
+ );
7393
+ await writeFile(
7394
+ path13.join(testDir, `${kebabName}.service.test.ts`),
7395
+ serviceTestTpl(kebabName, pascalName, camelName)
7396
+ );
7397
+ await writeFile(
7398
+ path13.join(testDir, `${kebabName}.integration.test.ts`),
7399
+ integrationTestTpl(kebabName, pascalName, camelName)
7400
+ );
7401
+ spinner.succeed(`Resource "${pascalName}" scaffolded successfully!`);
7402
+ console.log("\n" + "\u2500".repeat(70));
7403
+ info("\u{1F4CB} Prisma model to add to schema.prisma:");
7404
+ console.log(chalk14.gray("\n// Copy this to your schema.prisma file:\n"));
7405
+ console.log(dynamicPrismaTemplate(pascalName, tableName, fields));
7406
+ console.log("\u2500".repeat(70));
7407
+ console.log("\n\u{1F4CB} Fields scaffolded:");
7408
+ fields.forEach((f) => {
7409
+ const opts = [];
7410
+ if (f.isOptional) opts.push("optional");
7411
+ if (f.isArray) opts.push("array");
7412
+ if (f.isUnique) opts.push("unique");
7413
+ if (f.relation) opts.push(`relation: ${f.relation.model}`);
7414
+ const optsStr = opts.length > 0 ? ` (${opts.join(", ")})` : "";
7415
+ success(` ${f.name}: ${f.type}${optsStr}`);
7416
+ });
7417
+ console.log("\n\u{1F4C1} Files created:");
7418
+ files.forEach((f) => success(` src/modules/${kebabName}/${f.name}`));
7419
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.controller.test.ts`);
7420
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.service.test.ts`);
7421
+ success(` src/modules/${kebabName}/__tests__/${kebabName}.integration.test.ts`);
7422
+ console.log("\n\u{1F4CC} Next steps:");
7423
+ info(" 1. Add the Prisma model to your schema.prisma file");
7424
+ info(" 2. Run: npx prisma db push (or prisma migrate dev)");
7425
+ info(" 3. Run: npx prisma generate");
7426
+ info(" 4. Register the module routes in your app");
7427
+ info(" 5. Update the test files with actual test data");
7428
+ console.log(
7429
+ chalk14.gray("\n\u{1F4A1} Tip: Use --dry-run to preview changes before applying them\n")
7430
+ );
7431
+ if (options.dryRun) {
7432
+ dryRun.printSummary();
7433
+ }
7434
+ } catch (error2) {
7435
+ spinner.fail("Failed to scaffold resource");
7436
+ if (error2 instanceof Error) {
7437
+ console.error(chalk14.red(`
7438
+ \u2717 ${error2.message}
7439
+ `));
7440
+ console.error(chalk14.gray(error2.stack));
7441
+ }
7442
+ process.exit(1);
7443
+ }
7444
+ }
7445
+ );
7446
+
7447
+ // src/cli/commands/templates.ts
7448
+ import { Command as Command12 } from "commander";
7449
+ import chalk15 from "chalk";
7450
+ import fs13 from "fs/promises";
7451
+ import path14 from "path";
7452
+ var TEMPLATE_TYPES = [
7453
+ "controller",
7454
+ "service",
7455
+ "repository",
7456
+ "types",
7457
+ "schemas",
7458
+ "routes",
7459
+ "module-index",
7460
+ "controller-test",
7461
+ "service-test",
7462
+ "integration-test"
7463
+ ];
7464
+ async function initTemplates() {
7465
+ const projectError = validateProject();
7466
+ if (projectError) {
7467
+ displayError(projectError);
7468
+ return;
7469
+ }
7470
+ const projectRoot = getProjectRoot();
7471
+ const templatesDir = path14.join(projectRoot, ".servcraft", "templates");
7472
+ try {
7473
+ await fs13.mkdir(templatesDir, { recursive: true });
7474
+ console.log(chalk15.cyan("\n\u{1F4C1} Creating custom template directory...\n"));
7475
+ const exampleController = `// Custom controller template
7476
+ // Available variables: name, pascalName, camelName, pluralName
7477
+ export function controllerTemplate(name: string, pascalName: string, camelName: string): string {
7478
+ return \`import type { FastifyRequest, FastifyReply } from 'fastify';
7479
+ import type { \${pascalName}Service } from './\${name}.service.js';
7480
+
7481
+ export class \${pascalName}Controller {
7482
+ constructor(private \${camelName}Service: \${pascalName}Service) {}
7483
+
7484
+ // Add your custom controller methods here
7485
+ async getAll(request: FastifyRequest, reply: FastifyReply) {
7486
+ const data = await this.\${camelName}Service.getAll();
7487
+ return reply.send({ data });
7488
+ }
7489
+ }
7490
+ \`;
7491
+ }
7492
+ `;
7493
+ await fs13.writeFile(
7494
+ path14.join(templatesDir, "controller.example.ts"),
7495
+ exampleController,
7496
+ "utf-8"
7497
+ );
7498
+ console.log(chalk15.green("\u2714 Created template directory: .servcraft/templates/"));
7499
+ console.log(chalk15.green("\u2714 Created example template: controller.example.ts\n"));
7500
+ console.log(chalk15.bold("\u{1F4CB} Available template types:\n"));
7501
+ TEMPLATE_TYPES.forEach((type) => {
7502
+ console.log(chalk15.gray(` \u2022 ${type}.ts`));
7503
+ });
7504
+ console.log(chalk15.yellow("\n\u{1F4A1} To customize a template:"));
7505
+ console.log(chalk15.gray(" 1. Copy the example template"));
7506
+ console.log(chalk15.gray(" 2. Rename it (remove .example)"));
7507
+ console.log(chalk15.gray(" 3. Modify the template code"));
7508
+ console.log(chalk15.gray(" 4. Use --template flag when generating\n"));
7509
+ } catch (error2) {
7510
+ if (error2 instanceof Error) {
7511
+ console.error(chalk15.red(`
7512
+ \u2717 Failed to initialize templates: ${error2.message}
7513
+ `));
7514
+ }
7515
+ }
7516
+ }
7517
+ async function listTemplates() {
7518
+ const projectError = validateProject();
7519
+ if (projectError) {
7520
+ displayError(projectError);
7521
+ return;
7522
+ }
7523
+ const projectRoot = getProjectRoot();
7524
+ const projectTemplatesDir = path14.join(projectRoot, ".servcraft", "templates");
7525
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
7526
+ const userTemplatesDir = path14.join(homeDir, ".servcraft", "templates");
7527
+ console.log(chalk15.bold.cyan("\n\u{1F4CB} Available Templates\n"));
7528
+ console.log(chalk15.bold("Project templates (.servcraft/templates/):"));
7529
+ try {
7530
+ const files = await fs13.readdir(projectTemplatesDir);
7531
+ const templates = files.filter((f) => f.endsWith(".ts") && !f.endsWith(".example.ts"));
7532
+ if (templates.length > 0) {
7533
+ templates.forEach((t) => {
7534
+ console.log(chalk15.green(` \u2713 ${t}`));
7535
+ });
7536
+ } else {
7537
+ console.log(chalk15.gray(" (none)"));
7538
+ }
7539
+ } catch {
7540
+ console.log(chalk15.gray(" (directory not found)"));
7541
+ }
7542
+ console.log(chalk15.bold("\nUser templates (~/.servcraft/templates/):"));
7543
+ try {
7544
+ const files = await fs13.readdir(userTemplatesDir);
7545
+ const templates = files.filter((f) => f.endsWith(".ts") && !f.endsWith(".example.ts"));
7546
+ if (templates.length > 0) {
7547
+ templates.forEach((t) => {
7548
+ console.log(chalk15.green(` \u2713 ${t}`));
7549
+ });
7550
+ } else {
7551
+ console.log(chalk15.gray(" (none)"));
7552
+ }
7553
+ } catch {
7554
+ console.log(chalk15.gray(" (directory not found)"));
7555
+ }
7556
+ console.log(chalk15.bold("\nBuilt-in templates:"));
7557
+ TEMPLATE_TYPES.forEach((t) => {
7558
+ console.log(chalk15.cyan(` \u2022 ${t}.ts`));
7559
+ });
7560
+ console.log(chalk15.gray('\n\u{1F4A1} Run "servcraft templates init" to create custom templates\n'));
7561
+ }
7562
+ var templatesCommand = new Command12("templates").description("Manage code generation templates").addCommand(
7563
+ new Command12("init").description("Initialize custom templates directory").action(initTemplates)
7564
+ ).addCommand(new Command12("list").description("List available templates").action(listTemplates));
7565
+
6661
7566
  // src/cli/index.ts
6662
- var program = new Command9();
7567
+ var program = new Command13();
6663
7568
  program.name("servcraft").description("Servcraft - A modular Node.js backend framework CLI").version("0.1.0");
6664
7569
  program.addCommand(initCommand);
6665
7570
  program.addCommand(generateCommand);
@@ -6669,5 +7574,9 @@ program.addCommand(docsCommand);
6669
7574
  program.addCommand(listCommand);
6670
7575
  program.addCommand(removeCommand);
6671
7576
  program.addCommand(doctorCommand);
7577
+ program.addCommand(updateCommand);
7578
+ program.addCommand(completionCommand);
7579
+ program.addCommand(scaffoldCommand);
7580
+ program.addCommand(templatesCommand);
6672
7581
  program.parse();
6673
7582
  //# sourceMappingURL=index.js.map