speedrun-cli 2.7.5 → 2.7.6

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": "speedrun-cli",
3
- "version": "2.7.5",
3
+ "version": "2.7.6",
4
4
  "description": "CLI tool to scaffold a production-ready NestJS authentication system with JWT, refresh tokens, and RBAC",
5
5
  "keywords": [
6
6
  "nestjs",
@@ -79,6 +79,81 @@ async function detectOrm(targetDir) {
79
79
  return 'prisma';
80
80
  }
81
81
 
82
+ /**
83
+ * Ensures src/common/base exists with required abstract classes & DTOs
84
+ */
85
+ async function ensureBaseArchitecture(targetDir) {
86
+ try {
87
+ const commonBaseDir = path.join(targetDir, 'src', 'common', 'base');
88
+
89
+ if (!(await fs.pathExists(commonBaseDir))) {
90
+ await fs.ensureDir(commonBaseDir);
91
+
92
+ // 1. base.controller.ts
93
+ await fs.writeFile(
94
+ path.join(commonBaseDir, 'base.controller.ts'),
95
+ `import { Type } from '@nestjs/common';
96
+
97
+ export abstract class BaseController<T, CreateDto, UpdateDto> {
98
+ constructor(protected readonly service: any) {}
99
+ protected abstract getDtoClass(): Type<T>;
100
+ async create(dto: CreateDto): Promise<any> { return this.service.create(dto); }
101
+ async findAll(query: any): Promise<any> { return this.service.findAll(query); }
102
+ async findOne(id: string): Promise<any> { return this.service.findOne(id); }
103
+ async update(id: string, dto: UpdateDto): Promise<any> { return this.service.update(id, dto); }
104
+ async remove(id: string): Promise<any> { return this.service.remove(id); }
105
+ }
106
+ `
107
+ );
108
+
109
+ // 2. base.service.ts
110
+ await fs.writeFile(
111
+ path.join(commonBaseDir, 'base.service.ts'),
112
+ `import { Injectable } from '@nestjs/common';
113
+
114
+ export interface PaginationQueryDto { page?: number; limit?: number; }
115
+ export interface IBaseRepository<T, CreateDto, UpdateDto> {
116
+ create(dto: CreateDto): Promise<T>;
117
+ findAll(pagination: PaginationQueryDto): Promise<{ data: T[]; total: number }>;
118
+ findOne(id: string): Promise<T | null>;
119
+ update(id: string, dto: UpdateDto): Promise<T>;
120
+ remove(id: string): Promise<T>;
121
+ }
122
+
123
+ @Injectable()
124
+ export abstract class BaseService<T, CreateDto, UpdateDto> {
125
+ protected abstract getRepository(): IBaseRepository<T, CreateDto, UpdateDto>;
126
+ async create(dto: CreateDto): Promise<T> { return this.getRepository().create(dto); }
127
+ async findAll(pagination: PaginationQueryDto): Promise<{ data: T[]; total: number }> { return this.getRepository().findAll(pagination); }
128
+ async findOne(id: string): Promise<T | null> { return this.getRepository().findOne(id); }
129
+ async update(id: string, dto: UpdateDto): Promise<T> { return this.getRepository().update(id, dto); }
130
+ async remove(id: string): Promise<T> { return this.getRepository().remove(id); }
131
+ }
132
+ `
133
+ );
134
+
135
+ // 3. index.ts (Re-exports & DTO helpers)
136
+ await fs.writeFile(
137
+ path.join(commonBaseDir, 'index.ts'),
138
+ `export * from './base.controller';
139
+ export * from './base.service';
140
+
141
+ export class ApiResponseDto<T> { statusCode: number; message: string; data: T; }
142
+ export class PaginatedResponseDto<T> { statusCode: number; message: string; data: T[]; total: number; page: number; limit: number; }
143
+ export class PaginationQueryDto { page?: number; limit?: number; }
144
+
145
+ export function ApiResponseSchema(dto: any): any { return {}; }
146
+ export function PaginatedResponseSchema(dto: any): any { return {}; }
147
+ `
148
+ );
149
+
150
+ console.log(chalk.green(' ✓ Auto-generated missing src/common/base architecture'));
151
+ }
152
+ } catch (error) {
153
+ console.warn(chalk.yellow(` ⚠️ Could not verify/create base architecture: ${error.message}`));
154
+ }
155
+ }
156
+
82
157
  /**
83
158
  * Interactive prompt for module options (Name, CRUD Mode, Fields)
84
159
  */
@@ -284,9 +359,9 @@ ${fieldLines.join('\n')}
284
359
 
285
360
  content += modelDefinition;
286
361
  await fs.writeFile(schemaPath, content, 'utf8');
287
- console.log(chalk.green(` ✓ Updated prisma/schema.prisma with model ${singularPascal}`));
362
+ console.log(chalk.green(` ✓ Updated prisma/schema.prisma with model ${singularPascal}`));
288
363
  } catch (error) {
289
- console.warn(chalk.yellow(` ⚠️ Could not sync prisma/schema.prisma: ${error.message}`));
364
+ console.warn(chalk.yellow(` ⚠️ Could not sync prisma/schema.prisma: ${error.message}`));
290
365
  }
291
366
  }
292
367
 
@@ -341,9 +416,9 @@ ${fieldLines.join('\n\n')}
341
416
  `;
342
417
 
343
418
  await fs.writeFile(entityPath, entityContent, 'utf8');
344
- console.log(chalk.green(` ✓ Generated TypeORM entity at src/modules/${kebabName}/entities/${toSingularKebab(kebabName)}.entity.ts`));
419
+ console.log(chalk.green(` ✓ Generated TypeORM entity at src/modules/${kebabName}/entities/${toSingularKebab(kebabName)}.entity.ts`));
345
420
  } catch (error) {
346
- console.warn(chalk.yellow(` ⚠️ Could not generate TypeORM entity: ${error.message}`));
421
+ console.warn(chalk.yellow(` ⚠️ Could not generate TypeORM entity: ${error.message}`));
347
422
  }
348
423
  }
349
424
 
@@ -394,9 +469,9 @@ export const ${singularPascal}Schema = SchemaFactory.createForClass(${singularPa
394
469
  `;
395
470
 
396
471
  await fs.writeFile(schemaPath, schemaContent, 'utf8');
397
- console.log(chalk.green(` ✓ Generated Mongoose schema at src/modules/${kebabName}/schemas/${toSingularKebab(kebabName)}.schema.ts`));
472
+ console.log(chalk.green(` ✓ Generated Mongoose schema at src/modules/${kebabName}/schemas/${toSingularKebab(kebabName)}.schema.ts`));
398
473
  } catch (error) {
399
- console.warn(chalk.yellow(` ⚠️ Could not generate Mongoose schema: ${error.message}`));
474
+ console.warn(chalk.yellow(` ⚠️ Could not generate Mongoose schema: ${error.message}`));
400
475
  }
401
476
  }
402
477
 
@@ -442,9 +517,9 @@ export type New${singularPascal} = typeof ${toCamelCase(kebabName)}s.$inferInser
442
517
  `;
443
518
 
444
519
  await fs.writeFile(schemaPath, schemaContent, 'utf8');
445
- console.log(chalk.green(` ✓ Generated Drizzle schema at src/modules/${kebabName}/schema/${kebabName}.schema.ts`));
520
+ console.log(chalk.green(` ✓ Generated Drizzle schema at src/modules/${kebabName}/schema/${kebabName}.schema.ts`));
446
521
  } catch (error) {
447
- console.warn(chalk.yellow(` ⚠️ Could not generate Drizzle schema: ${error.message}`));
522
+ console.warn(chalk.yellow(` ⚠️ Could not generate Drizzle schema: ${error.message}`));
448
523
  }
449
524
  }
450
525
 
@@ -478,7 +553,7 @@ ${dummyObjFields}
478
553
  }
479
554
  `;
480
555
  await fs.writeFile(seedPath, seedContent, 'utf8');
481
- console.log(chalk.green(` ✓ Generated Prisma seed template at prisma/seeds/${kebabName}.seed.ts`));
556
+ console.log(chalk.green(` ✓ Generated Prisma seed template at prisma/seeds/${kebabName}.seed.ts`));
482
557
  } else {
483
558
  const seedsDir = path.join(targetDir, 'src', 'database', 'seeds');
484
559
  await fs.ensureDir(seedsDir);
@@ -502,10 +577,10 @@ ${dummyObjFields}
502
577
  }
503
578
  `;
504
579
  await fs.writeFile(seedPath, seedContent, 'utf8');
505
- console.log(chalk.green(` ✓ Generated seed template at src/database/seeds/${kebabName}.seed.ts`));
580
+ console.log(chalk.green(` ✓ Generated seed template at src/database/seeds/${kebabName}.seed.ts`));
506
581
  }
507
582
  } catch (error) {
508
- console.warn(chalk.yellow(` ⚠️ Could not generate seed template: ${error.message}`));
583
+ console.warn(chalk.yellow(` ⚠️ Could not generate seed template: ${error.message}`));
509
584
  }
510
585
  }
511
586
 
@@ -843,6 +918,9 @@ async function registerInAppModule(targetDir, pascalName, kebabName) {
843
918
  */
844
919
  async function generateModule(providedModuleName, targetDir = process.cwd(), specifiedOrm = null) {
845
920
  try {
921
+ // Ensure src/common/base exists before generating any module components
922
+ await ensureBaseArchitecture(targetDir);
923
+
846
924
  const options = await promptForModuleOptions(providedModuleName);
847
925
  const kebabName = toKebabCase(options.moduleName);
848
926
  const pascalName = toPascalCase(options.moduleName);
@@ -991,11 +1069,10 @@ ${responseFieldsText}
991
1069
  );
992
1070
  await fs.writeFile(path.join(moduleDir, `${kebabName}.service.ts`), serviceContent);
993
1071
 
994
- // 4. Generate Controller
1072
+ // 4. Generate Controller (Fixed single clean import path from common/base)
995
1073
  const controllerContent = `import { Controller${ops.findAll ? ', Query' : ''}${ops.findOne || ops.update || ops.remove ? ', Param, ParseUUIDPipe, HttpStatus' : ''}${ops.create ? ', Post, Body' : ''}${ops.findAll || ops.findOne ? ', Get' : ''}${ops.update ? ', Put' : ''}${ops.remove ? ', Delete' : ''}, Type } from '@nestjs/common';
996
1074
  import { ApiTags, ApiBearerAuth, ApiExtraModels, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
997
- import { BaseController } from '../../common/base/base.controller';
998
- import { ApiResponseDto, ApiResponseSchema, PaginatedResponseDto, PaginatedResponseSchema, PaginationQueryDto } from '../../common/base';
1075
+ import { BaseController, ApiResponseDto, ApiResponseSchema, PaginatedResponseDto, PaginatedResponseSchema, PaginationQueryDto } from '../../common/base';
999
1076
  import { ${pascalName}Service } from './${kebabName}.service';
1000
1077
  ${ops.create || ops.update ? `import { ${createDtoName} } from './dto/create-${kebabName}.dto';\nimport { ${updateDtoName} } from './dto/update-${kebabName}.dto';` : `type ${createDtoName} = any;\ntype ${updateDtoName} = any;`}
1001
1078
  import { ${responseDtoName} } from './dto/${kebabName}.dto';
@@ -1074,7 +1151,7 @@ ${ops.create ? `
1074
1151
  await generateSeedFileTemplate(targetDir, orm, singularPascal, kebabName, options.fields);
1075
1152
 
1076
1153
  console.log(chalk.green(`\n✅ Module "${kebabName}" successfully generated in ${path.relative(process.cwd(), moduleDir)}`));
1077
- console.log(chalk.gray(` Detected ORM: ${orm}`));
1154
+ console.log(chalk.gray(` Detected ORM: ${orm}`));
1078
1155
 
1079
1156
  if (!isAutoRegistered) {
1080
1157
  console.log(chalk.yellow(`\n⚠️ Please manually register ${pascalName}Module in src/app.module.ts:`));