speedrun-cli 2.8.0 → 2.9.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/CHANGELOG.md +22 -0
- package/README.md +8 -0
- package/bin/cli.js +16 -0
- package/package.json +1 -1
- package/src/fieldManager.js +1 -1
- package/src/index.js +2 -0
- package/src/moduleConfigurator.js +3 -0
- package/src/moduleGenerator.js +84 -3
- package/src/seedGenerator.js +236 -0
- package/templates/base-crud/src/common/base/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to create-nestjs-auth will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.9.0] - 2026-08-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Realistic Seed Generator (`speedrun-cli seed [module]` / alias `s` or `sd`)** — Introduced a new core CLI command and generator module (`src/seedGenerator.js`) to interactively generate realistic dummy data tailored to a module's active fields (emails, titles, prices, dates, UUIDs) targeting Prisma seed scripts (`prisma/seeds/[module].seed.ts` & master `prisma/seed.ts`), TypeORM custom scripts (`src/database/seeds/[module].seed.ts`), or raw JSON mock files (`src/modules/[module]/mock-data.json`).
|
|
12
|
+
- **Interconnected CLI Ecosystem** — Integrated `seed` (`s`) with existing module parser (`parseExistingFields`), ORM detector (`detectOrm`), primary key resolver (`detectModulePrimaryKey`), and package manager runner.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## [2.8.2] - 2026-08-22
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **Duplicate Module Validation & Overwrite Guard** — When running `speedrun-cli g [module]`, the CLI now checks if the module directory (e.g. `src/modules/orange`) already exists. If detected, it displays a warning with hints to use `speedrun-cli field` (`f`) or `speedrun-cli config` (`c`), and prompts for explicit confirmation before proceeding (`default: false`), preventing accidental overwrites.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## [2.8.1] - 2026-08-22
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **Fixed TS2307: Cannot find module `../../common/guards/jwt-auth.guard`** — Enhanced `ensureBaseArchitecture(targetDir)` in `src/moduleGenerator.js` to automatically verify and scaffold placeholder guards (`src/common/guards/jwt-auth.guard.ts`, `src/common/guards/roles.guard.ts`) and decorators (`src/common/decorators/roles.decorator.ts`) whenever generating (`speedrun-cli g`) or configuring (`speedrun-cli c`) protected CRUD modules.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
8
30
|
## [2.8.0] - 2026-08-22
|
|
9
31
|
|
|
10
32
|
### Added
|
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ npx speedrun-cli create my-app
|
|
|
42
42
|
* 🔗 **Relationship Builder:** Add `Many-to-One` or `One-to-Many` relationships to other modules directly from the terminal with automatic foreign key wiring.
|
|
43
43
|
* ✏️ **Sub-Menu Interactive Field Manager (`speedrun-cli field` / `f`):** Modify existing generated modules on the fly. Selectively edit specific field attributes (Name, Type, or Optional status), add new fields, or delete fields with automated DTO and ORM schema re-sync.
|
|
44
44
|
* ⚙️ **Dynamic Module Configurator (`speedrun-cli config` / `c`):** Customize Auth Guards, change role permissions (`@Roles('ADMIN', 'SUPERADMIN')`), and enable/disable active CRUD endpoints on existing controllers without rewriting code.
|
|
45
|
+
* 🌱 **Realistic Seed Generator (`speedrun-cli seed` / `s` / `sd`):** Interactively generate dummy/seed data tailored to module fields (emails, titles, prices, dates, UUIDs) targeting Prisma seed scripts, TypeORM seed scripts, or raw JSON mock files.
|
|
45
46
|
* 🏗️ **Automated Base Architecture:** Ensures `src/common/base` (`BaseController`, `BaseService`, and Swagger helpers) exists to eliminate missing import compilation errors (`TS2307`/`TS4112`).
|
|
46
47
|
* 🔄 **Auto AppModule Registration:** Automatically injects generated modules into `src/app.module.ts`.
|
|
47
48
|
|
|
@@ -55,6 +56,7 @@ npx speedrun-cli create my-app
|
|
|
55
56
|
| `speedrun-cli generate [module]` | `g` | Generates a new CRUD module with PKs, fields, ORM sync, and Auth Guards. |
|
|
56
57
|
| `speedrun-cli field [module]` | `f` | Interactive Field Manager to Add, Edit (sub-menu), or Delete fields on existing modules. |
|
|
57
58
|
| `speedrun-cli config [module]` | `c` | Customize role guards (`@Roles`), auth protection, and active CRUD operations. |
|
|
59
|
+
| `speedrun-cli seed [module]` | `s` / `sd` | Generates realistic dummy/seed data scripts (Prisma, TypeORM, JSON) for a module. |
|
|
58
60
|
|
|
59
61
|
---
|
|
60
62
|
|
|
@@ -89,6 +91,12 @@ npx speedrun-cli f orders
|
|
|
89
91
|
npx speedrun-cli c orders
|
|
90
92
|
```
|
|
91
93
|
|
|
94
|
+
### 5. Generate Seed & Dummy Data
|
|
95
|
+
```bash
|
|
96
|
+
# Generate realistic seed data for Prisma, TypeORM, or raw JSON mock files
|
|
97
|
+
npx speedrun-cli s orders
|
|
98
|
+
```
|
|
99
|
+
|
|
92
100
|
---
|
|
93
101
|
|
|
94
102
|
## 💡 Why This Exists
|
package/bin/cli.js
CHANGED
|
@@ -30,6 +30,7 @@ const {
|
|
|
30
30
|
generateModule,
|
|
31
31
|
manageFields,
|
|
32
32
|
configureModule,
|
|
33
|
+
generateSeed,
|
|
33
34
|
} = require(path.join(packageRoot, 'src'));
|
|
34
35
|
|
|
35
36
|
program
|
|
@@ -81,6 +82,21 @@ program
|
|
|
81
82
|
}
|
|
82
83
|
});
|
|
83
84
|
|
|
85
|
+
// ==================== SEED GENERATOR COMMAND ====================
|
|
86
|
+
program
|
|
87
|
+
.command('seed [module-name]')
|
|
88
|
+
.alias('s')
|
|
89
|
+
.alias('sd')
|
|
90
|
+
.description('Generate realistic seed/dummy data for a module')
|
|
91
|
+
.action(async (moduleName) => {
|
|
92
|
+
try {
|
|
93
|
+
console.log(chalk.cyan(`\n😱🤯🤯 speedrun-cli v${CLI_VERSION} seed generator 🤧🥶🥶🥶 (real)\n`));
|
|
94
|
+
await generateSeed(moduleName, process.cwd());
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error(chalk.red('\n❌ Seed generation failed:'), error);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
84
100
|
// ==================== MAIN SCAFFOLD COMMAND (DEFAULT) ====================
|
|
85
101
|
program
|
|
86
102
|
.command('create [app-name]', { isDefault: true })
|
package/package.json
CHANGED
package/src/fieldManager.js
CHANGED
package/src/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const postSetup = require('./postSetup');
|
|
|
11
11
|
const moduleGenerator = require('./moduleGenerator');
|
|
12
12
|
const fieldManager = require('./fieldManager');
|
|
13
13
|
const moduleConfigurator = require('./moduleConfigurator');
|
|
14
|
+
const seedGenerator = require('./seedGenerator');
|
|
14
15
|
|
|
15
16
|
module.exports = {
|
|
16
17
|
...constants,
|
|
@@ -21,4 +22,5 @@ module.exports = {
|
|
|
21
22
|
...moduleGenerator,
|
|
22
23
|
...fieldManager,
|
|
23
24
|
...moduleConfigurator,
|
|
25
|
+
...seedGenerator,
|
|
24
26
|
};
|
|
@@ -176,6 +176,9 @@ ${ops.create ? `
|
|
|
176
176
|
*/
|
|
177
177
|
async function configureModule(providedModuleName, targetDir = process.cwd()) {
|
|
178
178
|
try {
|
|
179
|
+
const { ensureBaseArchitecture } = require('./moduleGenerator');
|
|
180
|
+
await ensureBaseArchitecture(targetDir);
|
|
181
|
+
|
|
179
182
|
let moduleName = providedModuleName;
|
|
180
183
|
|
|
181
184
|
if (!moduleName) {
|
package/src/moduleGenerator.js
CHANGED
|
@@ -80,11 +80,13 @@ async function detectOrm(targetDir) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Ensures src/common/base
|
|
83
|
+
* Ensures src/common/base, src/common/guards, and src/common/decorators exist with required abstract classes, DTOs, guards & decorators
|
|
84
84
|
*/
|
|
85
85
|
async function ensureBaseArchitecture(targetDir) {
|
|
86
86
|
try {
|
|
87
87
|
const commonBaseDir = path.join(targetDir, 'src', 'common', 'base');
|
|
88
|
+
const commonGuardsDir = path.join(targetDir, 'src', 'common', 'guards');
|
|
89
|
+
const commonDecoratorsDir = path.join(targetDir, 'src', 'common', 'decorators');
|
|
88
90
|
|
|
89
91
|
if (!(await fs.pathExists(commonBaseDir))) {
|
|
90
92
|
const templateBaseDir = path.join(__dirname, '..', 'templates', 'base-crud', 'src', 'common', 'base');
|
|
@@ -93,6 +95,54 @@ async function ensureBaseArchitecture(targetDir) {
|
|
|
93
95
|
console.log(chalk.green(' ✓ Scaffolded Base CRUD architecture at src/common/base'));
|
|
94
96
|
}
|
|
95
97
|
}
|
|
98
|
+
|
|
99
|
+
// Scaffold Guards if missing
|
|
100
|
+
await fs.ensureDir(commonGuardsDir);
|
|
101
|
+
const jwtGuardPath = path.join(commonGuardsDir, 'jwt-auth.guard.ts');
|
|
102
|
+
const authGuardPath = path.join(commonGuardsDir, 'auth.guard.ts');
|
|
103
|
+
const rolesGuardPath = path.join(commonGuardsDir, 'roles.guard.ts');
|
|
104
|
+
|
|
105
|
+
if (!(await fs.pathExists(jwtGuardPath)) && !(await fs.pathExists(authGuardPath))) {
|
|
106
|
+
const jwtGuardContent = `import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
107
|
+
|
|
108
|
+
@Injectable()
|
|
109
|
+
export class JwtAuthGuard implements CanActivate {
|
|
110
|
+
canActivate(context: ExecutionContext): boolean {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
await fs.writeFile(jwtGuardPath, jwtGuardContent, 'utf8');
|
|
116
|
+
console.log(chalk.green(' ✓ Scaffolded JwtAuthGuard at src/common/guards/jwt-auth.guard.ts'));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!(await fs.pathExists(rolesGuardPath))) {
|
|
120
|
+
const rolesGuardContent = `import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
121
|
+
|
|
122
|
+
@Injectable()
|
|
123
|
+
export class RolesGuard implements CanActivate {
|
|
124
|
+
canActivate(context: ExecutionContext): boolean {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
await fs.writeFile(rolesGuardPath, rolesGuardContent, 'utf8');
|
|
130
|
+
console.log(chalk.green(' ✓ Scaffolded RolesGuard at src/common/guards/roles.guard.ts'));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Scaffold Decorators if missing
|
|
134
|
+
await fs.ensureDir(commonDecoratorsDir);
|
|
135
|
+
const rolesDecoratorPath = path.join(commonDecoratorsDir, 'roles.decorator.ts');
|
|
136
|
+
|
|
137
|
+
if (!(await fs.pathExists(rolesDecoratorPath))) {
|
|
138
|
+
const rolesDecoratorContent = `import { SetMetadata } from '@nestjs/common';
|
|
139
|
+
|
|
140
|
+
export const ROLES_KEY = 'roles';
|
|
141
|
+
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
|
|
142
|
+
`;
|
|
143
|
+
await fs.writeFile(rolesDecoratorPath, rolesDecoratorContent, 'utf8');
|
|
144
|
+
console.log(chalk.green(' ✓ Scaffolded Roles decorator at src/common/decorators/roles.decorator.ts'));
|
|
145
|
+
}
|
|
96
146
|
} catch (error) {
|
|
97
147
|
console.warn(chalk.yellow(` ⚠️ Could not scaffold Base CRUD architecture: ${error.message}`));
|
|
98
148
|
}
|
|
@@ -152,7 +202,7 @@ function getFieldDetails(fieldType) {
|
|
|
152
202
|
/**
|
|
153
203
|
* Interactive prompt for module options (Name, CRUD Mode, PK, Fields, Relations, Auth Guards)
|
|
154
204
|
*/
|
|
155
|
-
async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma') {
|
|
205
|
+
async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma', targetDir = process.cwd()) {
|
|
156
206
|
let moduleName = providedModuleName;
|
|
157
207
|
|
|
158
208
|
if (!moduleName) {
|
|
@@ -166,6 +216,30 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
166
216
|
}
|
|
167
217
|
|
|
168
218
|
const kebabName = toKebabCase(moduleName);
|
|
219
|
+
const srcDir = path.join(targetDir, 'src');
|
|
220
|
+
const moduleDir = (await fs.pathExists(srcDir))
|
|
221
|
+
? path.join(srcDir, 'modules', kebabName)
|
|
222
|
+
: path.join(targetDir, 'modules', kebabName);
|
|
223
|
+
|
|
224
|
+
if (await fs.pathExists(moduleDir)) {
|
|
225
|
+
const relPath = path.relative(targetDir, moduleDir);
|
|
226
|
+
console.log(chalk.yellow(`\n⚠️ Module "${kebabName}" already exists at ${relPath}`));
|
|
227
|
+
console.log(chalk.gray(` - Use "speedrun-cli field ${kebabName}" (alias: f) to add or edit fields.`));
|
|
228
|
+
console.log(chalk.gray(` - Use "speedrun-cli config ${kebabName}" (alias: c) to configure guards & routes.\n`));
|
|
229
|
+
|
|
230
|
+
const { overwrite } = await inquirer.prompt([{
|
|
231
|
+
type: 'confirm',
|
|
232
|
+
name: 'overwrite',
|
|
233
|
+
message: `Do you still want to overwrite existing module "${kebabName}"?`,
|
|
234
|
+
default: false,
|
|
235
|
+
}]);
|
|
236
|
+
|
|
237
|
+
if (!overwrite) {
|
|
238
|
+
console.log(chalk.gray('Cancelled module generation. Existing module files preserved.\n'));
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
169
243
|
const pascalName = toPascalCase(moduleName);
|
|
170
244
|
const singularPascal = toSingularPascal(pascalName);
|
|
171
245
|
const singularSnake = toSnakeCase(singularPascal);
|
|
@@ -1152,7 +1226,9 @@ async function generateModule(providedModuleName, targetDir = process.cwd(), spe
|
|
|
1152
1226
|
// Detect ORM first so prompt choices match
|
|
1153
1227
|
const orm = specifiedOrm || (await detectOrm(targetDir));
|
|
1154
1228
|
|
|
1155
|
-
const options = await promptForModuleOptions(providedModuleName, orm);
|
|
1229
|
+
const options = await promptForModuleOptions(providedModuleName, orm, targetDir);
|
|
1230
|
+
if (!options) return false;
|
|
1231
|
+
|
|
1156
1232
|
const kebabName = toKebabCase(options.moduleName);
|
|
1157
1233
|
const pascalName = toPascalCase(options.moduleName);
|
|
1158
1234
|
const camelName = toCamelCase(options.moduleName);
|
|
@@ -1526,6 +1602,11 @@ module.exports = {
|
|
|
1526
1602
|
detectModulePrimaryKey,
|
|
1527
1603
|
getOrmFieldChoices,
|
|
1528
1604
|
toKebabCase,
|
|
1605
|
+
toCamelCase,
|
|
1606
|
+
toPascalCase,
|
|
1607
|
+
toSingularPascal,
|
|
1608
|
+
toSingularCamel,
|
|
1609
|
+
toSnakeCase,
|
|
1529
1610
|
registerInAppModule,
|
|
1530
1611
|
ensureBaseArchitecture,
|
|
1531
1612
|
regenerateModuleComponents,
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed Generator for speedrun-cli
|
|
3
|
+
* Generates realistic dummy/seed data for a target CRUD module based on its DTOs / ORM Schema.
|
|
4
|
+
* @module seedGenerator
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const fs = require('fs-extra');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
const {
|
|
13
|
+
detectOrm,
|
|
14
|
+
detectModulePrimaryKey,
|
|
15
|
+
toKebabCase,
|
|
16
|
+
toPascalCase,
|
|
17
|
+
toSingularPascal,
|
|
18
|
+
toSingularCamel,
|
|
19
|
+
} = require('./moduleGenerator');
|
|
20
|
+
const { parseExistingFields } = require('./fieldManager');
|
|
21
|
+
const { detectPackageManager, getRunPrefix } = require('./utils');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generates realistic mock values based on field name and field type.
|
|
25
|
+
*/
|
|
26
|
+
function generateMockValue(fieldName, fieldType, index = 1) {
|
|
27
|
+
const lowerName = fieldName.toLowerCase();
|
|
28
|
+
|
|
29
|
+
if (lowerName.includes('email')) {
|
|
30
|
+
return `user${index}@example.com`;
|
|
31
|
+
}
|
|
32
|
+
if (lowerName.includes('phone') || lowerName.includes('mobile')) {
|
|
33
|
+
return `+1555010${(index % 100).toString().padStart(4, '0')}`;
|
|
34
|
+
}
|
|
35
|
+
if (lowerName.includes('name') || lowerName.includes('title')) {
|
|
36
|
+
return `Sample ${fieldName} ${index}`;
|
|
37
|
+
}
|
|
38
|
+
if (lowerName.includes('price') || lowerName.includes('amount') || lowerName.includes('cost') || lowerName.includes('total')) {
|
|
39
|
+
return Number((10 + index * 5.5).toFixed(2));
|
|
40
|
+
}
|
|
41
|
+
if (lowerName.includes('count') || lowerName.includes('quantity') || lowerName.includes('stock')) {
|
|
42
|
+
return 10 * index;
|
|
43
|
+
}
|
|
44
|
+
if (lowerName.includes('url') || lowerName.includes('image') || lowerName.includes('avatar')) {
|
|
45
|
+
return `https://example.com/assets/${lowerName}-${index}.jpg`;
|
|
46
|
+
}
|
|
47
|
+
if (lowerName.includes('description') || lowerName.includes('note') || lowerName.includes('remark') || lowerName.includes('comment')) {
|
|
48
|
+
return `This is a sample ${fieldName} content for item #${index}.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Type fallbacks
|
|
52
|
+
const ft = (fieldType || '').toLowerCase();
|
|
53
|
+
if (['int', 'integer', 'number'].includes(ft)) {
|
|
54
|
+
return index * 10;
|
|
55
|
+
}
|
|
56
|
+
if (['float', 'decimal', 'numeric'].includes(ft)) {
|
|
57
|
+
return Number((9.99 + index).toFixed(2));
|
|
58
|
+
}
|
|
59
|
+
if (['boolean'].includes(ft)) {
|
|
60
|
+
return index % 2 === 0;
|
|
61
|
+
}
|
|
62
|
+
if (['datetime', 'date', 'timestamp'].includes(ft)) {
|
|
63
|
+
return new Date(Date.now() - index * 86400000).toISOString();
|
|
64
|
+
}
|
|
65
|
+
if (['json', 'object'].includes(ft)) {
|
|
66
|
+
return { key: `value_${index}` };
|
|
67
|
+
}
|
|
68
|
+
if (ft === 'array') {
|
|
69
|
+
return [`item_${index}_a`, `item_${index}_b`];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return `Sample ${fieldName} ${index}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Main Interactive Seed Generator Entry Point
|
|
77
|
+
*/
|
|
78
|
+
async function generateSeed(providedModuleName, targetDir = process.cwd()) {
|
|
79
|
+
try {
|
|
80
|
+
let moduleName = providedModuleName;
|
|
81
|
+
|
|
82
|
+
if (!moduleName) {
|
|
83
|
+
const nameAnswer = await inquirer.prompt([{
|
|
84
|
+
type: 'input',
|
|
85
|
+
name: 'moduleName',
|
|
86
|
+
message: 'Which module do you want to generate seed data for? (e.g., orders, products)',
|
|
87
|
+
validate: (input) => (input && input.trim() ? true : 'Module name is required'),
|
|
88
|
+
}]);
|
|
89
|
+
moduleName = nameAnswer.moduleName.trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const kebabName = toKebabCase(moduleName);
|
|
93
|
+
const pascalName = toPascalCase(moduleName);
|
|
94
|
+
const singularPascal = toSingularPascal(pascalName);
|
|
95
|
+
const singularCamel = toSingularCamel(kebabName);
|
|
96
|
+
|
|
97
|
+
const srcDir = path.join(targetDir, 'src');
|
|
98
|
+
const moduleDir = (await fs.pathExists(srcDir))
|
|
99
|
+
? path.join(srcDir, 'modules', kebabName)
|
|
100
|
+
: path.join(targetDir, 'modules', kebabName);
|
|
101
|
+
|
|
102
|
+
if (!(await fs.pathExists(moduleDir))) {
|
|
103
|
+
console.error(chalk.red(`\n❌ Module "${kebabName}" not found at ${moduleDir}`));
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const orm = await detectOrm(targetDir);
|
|
108
|
+
const primaryKey = await detectModulePrimaryKey(moduleDir, kebabName);
|
|
109
|
+
const fields = await parseExistingFields(moduleDir, kebabName, orm);
|
|
110
|
+
const pm = detectPackageManager();
|
|
111
|
+
const runPrefix = getRunPrefix(pm);
|
|
112
|
+
|
|
113
|
+
console.log(chalk.cyan(`\n🌱 Generating seed data for module: ${chalk.bold(kebabName)} (Detected ORM: ${orm})`));
|
|
114
|
+
|
|
115
|
+
// 1. Prompt for count
|
|
116
|
+
const { countStr } = await inquirer.prompt([{
|
|
117
|
+
type: 'input',
|
|
118
|
+
name: 'countStr',
|
|
119
|
+
message: 'How many seed records do you want to generate?',
|
|
120
|
+
default: '10',
|
|
121
|
+
validate: (input) => {
|
|
122
|
+
const num = parseInt(input, 10);
|
|
123
|
+
return !isNaN(num) && num > 0 ? true : 'Please enter a positive integer';
|
|
124
|
+
},
|
|
125
|
+
}]);
|
|
126
|
+
const count = parseInt(countStr, 10);
|
|
127
|
+
|
|
128
|
+
// 2. Prompt for seed strategy
|
|
129
|
+
const { seedStrategy } = await inquirer.prompt([{
|
|
130
|
+
type: 'list',
|
|
131
|
+
name: 'seedStrategy',
|
|
132
|
+
message: 'Select target output format / seeding strategy:',
|
|
133
|
+
choices: [
|
|
134
|
+
{ name: 'Prisma Seed Script (prisma/seeds/[module].seed.ts integration)', value: 'prisma' },
|
|
135
|
+
{ name: 'TypeORM / Custom Script (src/database/seeds/[module].seed.ts)', value: 'typeorm' },
|
|
136
|
+
{ name: 'Raw JSON Mock File (src/modules/[module]/mock-data.json)', value: 'json' },
|
|
137
|
+
],
|
|
138
|
+
default: orm === 'prisma' ? 'prisma' : 'typeorm',
|
|
139
|
+
}]);
|
|
140
|
+
|
|
141
|
+
// 3. Generate Records
|
|
142
|
+
const records = [];
|
|
143
|
+
for (let i = 1; i <= count; i++) {
|
|
144
|
+
const uuidVal = crypto.randomUUID ? crypto.randomUUID() : `123e4567-e89b-12d3-a456-${(100000000000 + i).toString()}`;
|
|
145
|
+
const rec = {
|
|
146
|
+
[primaryKey]: uuidVal,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
fields.forEach((f) => {
|
|
150
|
+
rec[f.name] = generateMockValue(f.name, f.type, i);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
rec['status'] = 'ACTIVE';
|
|
154
|
+
records.push(rec);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 4. Output according to selected strategy
|
|
158
|
+
if (seedStrategy === 'prisma') {
|
|
159
|
+
const prismaSeedsDir = path.join(targetDir, 'prisma', 'seeds');
|
|
160
|
+
await fs.ensureDir(prismaSeedsDir);
|
|
161
|
+
const moduleSeedPath = path.join(prismaSeedsDir, `${kebabName}.seed.ts`);
|
|
162
|
+
|
|
163
|
+
const seedScriptContent = `import { PrismaClient } from '@prisma/client';
|
|
164
|
+
|
|
165
|
+
export const ${singularCamel}SeedData = ${JSON.stringify(records, null, 2)};
|
|
166
|
+
|
|
167
|
+
export async function seed${pascalName}(prisma: PrismaClient) {
|
|
168
|
+
console.log('🌱 Seeding ${kebabName}...');
|
|
169
|
+
for (const item of ${singularCamel}SeedData) {
|
|
170
|
+
await (prisma as any).${singularCamel}.upsert({
|
|
171
|
+
where: { ${primaryKey}: item.${primaryKey} },
|
|
172
|
+
update: {},
|
|
173
|
+
create: item,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
console.log(' ✓ Seeded ${count} ${kebabName} records');
|
|
177
|
+
}
|
|
178
|
+
`;
|
|
179
|
+
await fs.writeFile(moduleSeedPath, seedScriptContent, 'utf8');
|
|
180
|
+
console.log(chalk.green(`\n⚡ Generated Prisma seed script at: ${path.relative(targetDir, moduleSeedPath)}`));
|
|
181
|
+
|
|
182
|
+
// Check master prisma/seed.ts
|
|
183
|
+
const masterSeedPath = path.join(targetDir, 'prisma', 'seed.ts');
|
|
184
|
+
if (await fs.pathExists(masterSeedPath)) {
|
|
185
|
+
let masterContent = await fs.readFile(masterSeedPath, 'utf8');
|
|
186
|
+
const importLine = `import { seed${pascalName} } from './seeds/${kebabName}.seed';`;
|
|
187
|
+
const callLine = `await seed${pascalName}(prisma);`;
|
|
188
|
+
|
|
189
|
+
if (!masterContent.includes(importLine)) {
|
|
190
|
+
masterContent = `${importLine}\n` + masterContent;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!masterContent.includes(callLine)) {
|
|
194
|
+
masterContent = masterContent.replace(/async function main\(\) \{/, `async function main() {\n ${callLine}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
await fs.writeFile(masterSeedPath, masterContent, 'utf8');
|
|
198
|
+
console.log(chalk.green(`✨ Integrated seed${pascalName} into prisma/seed.ts`));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log(chalk.cyan(`\n💡 To execute this seed script, run:`));
|
|
202
|
+
console.log(chalk.bold(` ${runPrefix} prisma:seed (or npx prisma db seed)\n`));
|
|
203
|
+
} else if (seedStrategy === 'typeorm') {
|
|
204
|
+
const seedsDir = path.join(targetDir, 'src', 'database', 'seeds');
|
|
205
|
+
await fs.ensureDir(seedsDir);
|
|
206
|
+
const customSeedPath = path.join(seedsDir, `${kebabName}.seed.ts`);
|
|
207
|
+
|
|
208
|
+
const customSeedContent = `/**
|
|
209
|
+
* Seed data for module "${kebabName}"
|
|
210
|
+
*/
|
|
211
|
+
export const ${singularCamel}SeedData = ${JSON.stringify(records, null, 2)};
|
|
212
|
+
|
|
213
|
+
export async function seed${pascalName}() {
|
|
214
|
+
console.log('🌱 Seeding ${kebabName} with ${count} records...');
|
|
215
|
+
return ${singularCamel}SeedData;
|
|
216
|
+
}
|
|
217
|
+
`;
|
|
218
|
+
await fs.writeFile(customSeedPath, customSeedContent, 'utf8');
|
|
219
|
+
console.log(chalk.green(`\n⚡ Generated seed script at: ${path.relative(targetDir, customSeedPath)}`));
|
|
220
|
+
console.log(chalk.cyan(`\n💡 To execute custom seeds, run:`));
|
|
221
|
+
console.log(chalk.bold(` ${runPrefix} db:seed (or ${runPrefix} seed)\n`));
|
|
222
|
+
} else if (seedStrategy === 'json') {
|
|
223
|
+
const jsonMockPath = path.join(moduleDir, 'mock-data.json');
|
|
224
|
+
await fs.writeFile(jsonMockPath, JSON.stringify(records, null, 2), 'utf8');
|
|
225
|
+
console.log(chalk.green(`\n⚡ Generated raw JSON mock data file at: ${path.relative(targetDir, jsonMockPath)}`));
|
|
226
|
+
console.log(chalk.gray(` Contains ${count} mock records.\n`));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return true;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error(chalk.red('\n❌ Seed generation failed:'), error.message);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = { generateSeed, generateMockValue };
|