servcraft 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "servcraft",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "A modular, production-ready Node.js backend framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -51,7 +51,7 @@
51
51
  "@fastify/cookie": "^9.3.1",
52
52
  "@fastify/cors": "^9.0.1",
53
53
  "@fastify/helmet": "^11.1.1",
54
- "@fastify/jwt": "^10.0.0",
54
+ "@fastify/jwt": "^7.2.4",
55
55
  "@fastify/multipart": "^8.3.0",
56
56
  "@fastify/rate-limit": "^9.1.0",
57
57
  "@fastify/swagger": "^8.15.0",
@@ -16,6 +16,8 @@ import {
16
16
  import { EnvManager } from '../utils/env-manager.js';
17
17
  import { TemplateManager } from '../utils/template-manager.js';
18
18
  import { InteractivePrompt } from '../utils/interactive-prompt.js';
19
+ import { DryRunManager } from '../utils/dry-run.js';
20
+ import { ErrorTypes, displayError, validateProject } from '../utils/error-handler.js';
19
21
 
20
22
  // Pre-built modules that can be added
21
23
  const AVAILABLE_MODULES = {
@@ -160,10 +162,17 @@ export const addModuleCommand = new Command('add')
160
162
  .option('-f, --force', 'Force overwrite existing module')
161
163
  .option('-u, --update', 'Update existing module (smart merge)')
162
164
  .option('--skip-existing', 'Skip if module already exists')
165
+ .option('--dry-run', 'Preview changes without writing files')
163
166
  .action(
164
167
  async (
165
168
  moduleName?: string,
166
- options?: { list?: boolean; force?: boolean; update?: boolean; skipExisting?: boolean }
169
+ options?: {
170
+ list?: boolean;
171
+ force?: boolean;
172
+ update?: boolean;
173
+ skipExisting?: boolean;
174
+ dryRun?: boolean;
175
+ }
167
176
  ) => {
168
177
  if (options?.list || !moduleName) {
169
178
  console.log(chalk.bold('\nšŸ“¦ Available Modules:\n'));
@@ -180,11 +189,24 @@ export const addModuleCommand = new Command('add')
180
189
  return;
181
190
  }
182
191
 
192
+ // Enable dry-run mode if specified
193
+ const dryRun = DryRunManager.getInstance();
194
+ if (options?.dryRun) {
195
+ dryRun.enable();
196
+ console.log(chalk.yellow('\n⚠ DRY RUN MODE - No files will be written\n'));
197
+ }
198
+
183
199
  const module = AVAILABLE_MODULES[moduleName as keyof typeof AVAILABLE_MODULES];
184
200
 
185
201
  if (!module) {
186
- error(`Unknown module: ${moduleName}`);
187
- info('Run "servcraft add --list" to see available modules');
202
+ displayError(ErrorTypes.MODULE_NOT_FOUND(moduleName));
203
+ return;
204
+ }
205
+
206
+ // Validate project structure
207
+ const projectError = validateProject();
208
+ if (projectError) {
209
+ displayError(projectError);
188
210
  return;
189
211
  }
190
212
 
@@ -319,10 +341,17 @@ export const addModuleCommand = new Command('add')
319
341
  }
320
342
  }
321
343
 
322
- console.log('\nšŸ“Œ Next steps:');
323
- info(' 1. Configure environment variables in .env (if needed)');
324
- info(' 2. Register the module in your main app file');
325
- info(' 3. Run database migrations if needed');
344
+ if (!options?.dryRun) {
345
+ console.log('\nšŸ“Œ Next steps:');
346
+ info(' 1. Configure environment variables in .env (if needed)');
347
+ info(' 2. Register the module in your main app file');
348
+ info(' 3. Run database migrations if needed');
349
+ }
350
+
351
+ // Show dry-run summary if enabled
352
+ if (options?.dryRun) {
353
+ dryRun.printSummary();
354
+ }
326
355
  } catch (err) {
327
356
  spinner.fail('Failed to add module');
328
357
  error(err instanceof Error ? err.message : String(err));
@@ -620,34 +649,6 @@ export interface ${name.charAt(0).toUpperCase() + name.slice(1)}Data {
620
649
  }
621
650
  }
622
651
 
623
- /**
624
- * Helper: Get the servcraft package directory (works for both npm installed and local dev)
625
- */
626
- function getServercraftModulesDir(): string {
627
- // Try node_modules first (when installed as dependency)
628
- const nodeModulesPath = path.join(process.cwd(), 'node_modules', 'servcraft', 'src', 'modules');
629
-
630
- // Try global npm path
631
- const globalPath = path.join(
632
- process.env.npm_config_prefix || '/usr/local',
633
- 'lib',
634
- 'node_modules',
635
- 'servcraft',
636
- 'src',
637
- 'modules'
638
- );
639
-
640
- // For CLI execution, use import.meta.url to find package location
641
- const cliPath = path.resolve(
642
- path.dirname(new URL(import.meta.url).pathname),
643
- '..',
644
- '..',
645
- 'modules'
646
- );
647
-
648
- return nodeModulesPath; // Primary path - will check existence in generateModuleFiles
649
- }
650
-
651
652
  /**
652
653
  * Helper: Find servcraft modules source directory
653
654
  */
@@ -683,7 +684,7 @@ async function findServercraftModules(): Promise<string | null> {
683
684
  async function generateModuleFiles(moduleName: string, moduleDir: string): Promise<void> {
684
685
  // Map module names to their directory names in servcraft
685
686
  const moduleNameMap: Record<string, string> = {
686
- 'users': 'user',
687
+ users: 'user',
687
688
  'rate-limit': 'rate-limit',
688
689
  'feature-flag': 'feature-flag',
689
690
  'api-versioning': 'api-versioning',
@@ -0,0 +1,8 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+
4
+ export const doctorCommand = new Command('doctor')
5
+ .description('Diagnose project configuration and dependencies')
6
+ .action(async () => {
7
+ console.log(chalk.bold.cyan('\nServCraft Doctor - Coming soon!\n'));
8
+ });
@@ -14,7 +14,26 @@ import {
14
14
  info,
15
15
  getModulesDir,
16
16
  } from '../utils/helpers.js';
17
+ import { DryRunManager } from '../utils/dry-run.js';
18
+ import chalk from 'chalk';
17
19
  import { parseFields, type FieldDefinition } from '../utils/field-parser.js';
20
+ import { ErrorTypes, displayError } from '../utils/error-handler.js';
21
+
22
+ // Helper to enable dry-run mode
23
+ function enableDryRunIfNeeded(options: { dryRun?: boolean }): void {
24
+ const dryRun = DryRunManager.getInstance();
25
+ if (options.dryRun) {
26
+ dryRun.enable();
27
+ console.log(chalk.yellow('\n⚠ DRY RUN MODE - No files will be written\n'));
28
+ }
29
+ }
30
+
31
+ // Helper to show dry-run summary
32
+ function showDryRunSummary(options: { dryRun?: boolean }): void {
33
+ if (options.dryRun) {
34
+ DryRunManager.getInstance().printSummary();
35
+ }
36
+ }
18
37
  import { controllerTemplate } from '../templates/controller.js';
19
38
  import { serviceTemplate } from '../templates/service.js';
20
39
  import { repositoryTemplate } from '../templates/repository.js';
@@ -43,7 +62,9 @@ generateCommand
43
62
  .option('--prisma', 'Generate Prisma model suggestion')
44
63
  .option('--validator <type>', 'Validator type: zod, joi, yup', 'zod')
45
64
  .option('-i, --interactive', 'Interactive mode to define fields')
65
+ .option('--dry-run', 'Preview changes without writing files')
46
66
  .action(async (name: string, fieldsArgs: string[], options) => {
67
+ enableDryRunIfNeeded(options);
47
68
  let fields: FieldDefinition[] = [];
48
69
 
49
70
  // Parse fields from command line or interactive mode
@@ -161,6 +182,9 @@ generateCommand
161
182
  info(` ${hasFields ? '3' : '4'}. Add the Prisma model to schema.prisma`);
162
183
  info(` ${hasFields ? '4' : '5'}. Run: npm run db:migrate`);
163
184
  }
185
+
186
+ // Show dry-run summary if enabled
187
+ showDryRunSummary(options);
164
188
  } catch (err) {
165
189
  spinner.fail('Failed to generate module');
166
190
  error(err instanceof Error ? err.message : String(err));
@@ -173,7 +197,9 @@ generateCommand
173
197
  .alias('c')
174
198
  .description('Generate a controller')
175
199
  .option('-m, --module <module>', 'Target module name')
200
+ .option('--dry-run', 'Preview changes without writing files')
176
201
  .action(async (name: string, options) => {
202
+ enableDryRunIfNeeded(options);
177
203
  const spinner = ora('Generating controller...').start();
178
204
 
179
205
  try {
@@ -187,7 +213,7 @@ generateCommand
187
213
 
188
214
  if (await fileExists(filePath)) {
189
215
  spinner.stop();
190
- error(`Controller "${kebabName}" already exists`);
216
+ displayError(ErrorTypes.FILE_ALREADY_EXISTS(`${kebabName}.controller.ts`));
191
217
  return;
192
218
  }
193
219
 
@@ -195,6 +221,7 @@ generateCommand
195
221
 
196
222
  spinner.succeed(`Controller "${pascalName}Controller" generated!`);
197
223
  success(` src/modules/${moduleName}/${kebabName}.controller.ts`);
224
+ showDryRunSummary(options);
198
225
  } catch (err) {
199
226
  spinner.fail('Failed to generate controller');
200
227
  error(err instanceof Error ? err.message : String(err));
@@ -207,7 +234,9 @@ generateCommand
207
234
  .alias('s')
208
235
  .description('Generate a service')
209
236
  .option('-m, --module <module>', 'Target module name')
237
+ .option('--dry-run', 'Preview changes without writing files')
210
238
  .action(async (name: string, options) => {
239
+ enableDryRunIfNeeded(options);
211
240
  const spinner = ora('Generating service...').start();
212
241
 
213
242
  try {
@@ -229,6 +258,7 @@ generateCommand
229
258
 
230
259
  spinner.succeed(`Service "${pascalName}Service" generated!`);
231
260
  success(` src/modules/${moduleName}/${kebabName}.service.ts`);
261
+ showDryRunSummary(options);
232
262
  } catch (err) {
233
263
  spinner.fail('Failed to generate service');
234
264
  error(err instanceof Error ? err.message : String(err));
@@ -241,7 +271,9 @@ generateCommand
241
271
  .alias('r')
242
272
  .description('Generate a repository')
243
273
  .option('-m, --module <module>', 'Target module name')
274
+ .option('--dry-run', 'Preview changes without writing files')
244
275
  .action(async (name: string, options) => {
276
+ enableDryRunIfNeeded(options);
245
277
  const spinner = ora('Generating repository...').start();
246
278
 
247
279
  try {
@@ -264,6 +296,7 @@ generateCommand
264
296
 
265
297
  spinner.succeed(`Repository "${pascalName}Repository" generated!`);
266
298
  success(` src/modules/${moduleName}/${kebabName}.repository.ts`);
299
+ showDryRunSummary(options);
267
300
  } catch (err) {
268
301
  spinner.fail('Failed to generate repository');
269
302
  error(err instanceof Error ? err.message : String(err));
@@ -276,7 +309,9 @@ generateCommand
276
309
  .alias('t')
277
310
  .description('Generate types/interfaces')
278
311
  .option('-m, --module <module>', 'Target module name')
312
+ .option('--dry-run', 'Preview changes without writing files')
279
313
  .action(async (name: string, options) => {
314
+ enableDryRunIfNeeded(options);
280
315
  const spinner = ora('Generating types...').start();
281
316
 
282
317
  try {
@@ -297,6 +332,7 @@ generateCommand
297
332
 
298
333
  spinner.succeed(`Types for "${pascalName}" generated!`);
299
334
  success(` src/modules/${moduleName}/${kebabName}.types.ts`);
335
+ showDryRunSummary(options);
300
336
  } catch (err) {
301
337
  spinner.fail('Failed to generate types');
302
338
  error(err instanceof Error ? err.message : String(err));
@@ -309,7 +345,9 @@ generateCommand
309
345
  .alias('v')
310
346
  .description('Generate validation schemas')
311
347
  .option('-m, --module <module>', 'Target module name')
348
+ .option('--dry-run', 'Preview changes without writing files')
312
349
  .action(async (name: string, options) => {
350
+ enableDryRunIfNeeded(options);
313
351
  const spinner = ora('Generating schemas...').start();
314
352
 
315
353
  try {
@@ -331,6 +369,7 @@ generateCommand
331
369
 
332
370
  spinner.succeed(`Schemas for "${pascalName}" generated!`);
333
371
  success(` src/modules/${moduleName}/${kebabName}.schemas.ts`);
372
+ showDryRunSummary(options);
334
373
  } catch (err) {
335
374
  spinner.fail('Failed to generate schemas');
336
375
  error(err instanceof Error ? err.message : String(err));
@@ -342,7 +381,9 @@ generateCommand
342
381
  .command('routes <name>')
343
382
  .description('Generate routes')
344
383
  .option('-m, --module <module>', 'Target module name')
384
+ .option('--dry-run', 'Preview changes without writing files')
345
385
  .action(async (name: string, options) => {
386
+ enableDryRunIfNeeded(options);
346
387
  const spinner = ora('Generating routes...').start();
347
388
 
348
389
  try {
@@ -365,6 +406,7 @@ generateCommand
365
406
 
366
407
  spinner.succeed(`Routes for "${pascalName}" generated!`);
367
408
  success(` src/modules/${moduleName}/${kebabName}.routes.ts`);
409
+ showDryRunSummary(options);
368
410
  } catch (err) {
369
411
  spinner.fail('Failed to generate routes');
370
412
  error(err instanceof Error ? err.message : String(err));