speedrun-cli 2.7.3 → 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 +1 -1
- package/src/moduleGenerator.js +92 -15
- package/src/utils.js +9 -5
package/package.json
CHANGED
package/src/moduleGenerator.js
CHANGED
|
@@ -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(`
|
|
362
|
+
console.log(chalk.green(` ✓ Updated prisma/schema.prisma with model ${singularPascal}`));
|
|
288
363
|
} catch (error) {
|
|
289
|
-
console.warn(chalk.yellow(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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
|
|
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(`
|
|
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:`));
|
package/src/utils.js
CHANGED
|
@@ -115,9 +115,13 @@ function registerStrictConfirmPrompt(inquirerInstance = require('inquirer')) {
|
|
|
115
115
|
class StrictConfirmPrompt extends InputPrompt {
|
|
116
116
|
constructor(questions, rl, answers) {
|
|
117
117
|
super(questions, rl, answers);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
this.defaultBool = this.opt.default !== false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getQuestion() {
|
|
122
|
+
let message = super.getQuestion();
|
|
123
|
+
// Remove auto-appended (true) or (false) text from InputPrompt
|
|
124
|
+
return message.replace(/\s*\((true|false)\)\s*/g, ' ');
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
render(answer) {
|
|
@@ -126,7 +130,7 @@ function registerStrictConfirmPrompt(inquirerInstance = require('inquirer')) {
|
|
|
126
130
|
if (this.status === 'answered') {
|
|
127
131
|
message += chalk.cyan(answer !== undefined ? (answer ? 'Yes' : 'No') : (this.answer ? 'Yes' : 'No'));
|
|
128
132
|
} else {
|
|
129
|
-
message += chalk.dim(this.
|
|
133
|
+
message += chalk.dim(this.defaultBool ? ' (Y/n)' : ' (y/N)');
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
let bottomContent = '';
|
|
@@ -150,7 +154,7 @@ function registerStrictConfirmPrompt(inquirerInstance = require('inquirer')) {
|
|
|
150
154
|
|
|
151
155
|
filterInput(input) {
|
|
152
156
|
if (input === undefined || input === null || input.toString().trim() === '') {
|
|
153
|
-
return this.
|
|
157
|
+
return this.defaultBool;
|
|
154
158
|
}
|
|
155
159
|
const val = input.toString().trim().toLowerCase();
|
|
156
160
|
return ['y', 'yes'].includes(val);
|