speedrun-cli 2.8.1 → 2.9.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/CHANGELOG.md +30 -0
- package/README.md +8 -0
- package/bin/cli.js +16 -0
- package/package.json +1 -1
- package/src/fieldManager.js +12 -2
- package/src/index.js +2 -0
- package/src/moduleGenerator.js +32 -4
- package/src/seedGenerator.js +236 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ 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.2] - 2026-08-22
|
|
9
|
+
|
|
10
|
+
### Fixed & Enhanced
|
|
11
|
+
- **Strict Module & Field Exists Validation** — When running `speedrun-cli g [module]` and an existing module is detected (e.g. `orange`), the CLI displays a clear red error message detailing `field` & `config` hints and immediately cancels execution to prevent accidental overwrites.
|
|
12
|
+
- **Duplicate & System Field Validation** — Added prompt validation in both `speedrun-cli g` and `speedrun-cli f` to prevent adding duplicate fields or reserved system fields (`id`, `status`, `createdAt`, `updatedAt`, `deletedAt`, primary key), displaying `❌ Field "[name]" already exists in module "[module]"!`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## [2.9.1] - 2026-08-22
|
|
17
|
+
|
|
18
|
+
### Enhanced
|
|
19
|
+
- **Interactive Handoff Menu for Existing Modules** — When running `speedrun-cli g [module]` and an existing module is detected (e.g. `orders`), the CLI now prompts with an interactive action menu to seamlessly transition to Field Manager (`field`), Configurator (`config`), Seed Generator (`seed`), Overwrite (`overwrite`), or Cancel (`cancel`).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## [2.9.0] - 2026-08-22
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **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`).
|
|
27
|
+
- **Interconnected CLI Ecosystem** — Integrated `seed` (`s`) with existing module parser (`parseExistingFields`), ORM detector (`detectOrm`), primary key resolver (`detectModulePrimaryKey`), and package manager runner.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## [2.8.2] - 2026-08-22
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- **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.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
8
38
|
## [2.8.1] - 2026-08-22
|
|
9
39
|
|
|
10
40
|
### Fixed
|
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
|
@@ -212,9 +212,19 @@ async function manageFields(providedModuleName, targetDir = process.cwd()) {
|
|
|
212
212
|
default: initialValues.name,
|
|
213
213
|
validate: (input) => {
|
|
214
214
|
if (!input || !input.trim()) return 'Field name is required';
|
|
215
|
-
|
|
215
|
+
const name = input.trim();
|
|
216
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(name)) {
|
|
216
217
|
return 'Field name must be a valid identifier';
|
|
217
218
|
}
|
|
219
|
+
if (initialValues.name && initialValues.name.toLowerCase() === name.toLowerCase()) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
if (['id', 'status', 'createdAt', 'updatedAt', 'deletedAt'].includes(name)) {
|
|
223
|
+
return `Field '${name}' is a system field. Please choose another field name.`;
|
|
224
|
+
}
|
|
225
|
+
if (fields.some((f) => f.name.toLowerCase() === name.toLowerCase())) {
|
|
226
|
+
return `Field '${name}' already exists in module '${kebabName}'. Please choose a different name.`;
|
|
227
|
+
}
|
|
218
228
|
return true;
|
|
219
229
|
},
|
|
220
230
|
},
|
|
@@ -336,4 +346,4 @@ async function manageFields(providedModuleName, targetDir = process.cwd()) {
|
|
|
336
346
|
}
|
|
337
347
|
}
|
|
338
348
|
|
|
339
|
-
module.exports = { manageFields };
|
|
349
|
+
module.exports = { manageFields, parseExistingFields };
|
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
|
};
|
package/src/moduleGenerator.js
CHANGED
|
@@ -202,7 +202,7 @@ function getFieldDetails(fieldType) {
|
|
|
202
202
|
/**
|
|
203
203
|
* Interactive prompt for module options (Name, CRUD Mode, PK, Fields, Relations, Auth Guards)
|
|
204
204
|
*/
|
|
205
|
-
async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma') {
|
|
205
|
+
async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma', targetDir = process.cwd()) {
|
|
206
206
|
let moduleName = providedModuleName;
|
|
207
207
|
|
|
208
208
|
if (!moduleName) {
|
|
@@ -216,6 +216,20 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
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.red(`\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
|
+
console.log(chalk.yellow(`Module generation cancelled because "${kebabName}" already exists.\n`));
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
219
233
|
const pascalName = toPascalCase(moduleName);
|
|
220
234
|
const singularPascal = toSingularPascal(pascalName);
|
|
221
235
|
const singularSnake = toSnakeCase(singularPascal);
|
|
@@ -393,12 +407,19 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
393
407
|
message: 'Enter field name (e.g., totalAmount, title):',
|
|
394
408
|
validate: (input) => {
|
|
395
409
|
if (!input || !input.trim()) return 'Field name is required';
|
|
396
|
-
|
|
410
|
+
const name = input.trim();
|
|
411
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(name)) {
|
|
397
412
|
return 'Field name must be a valid identifier (e.g., totalAmount)';
|
|
398
413
|
}
|
|
399
|
-
if (
|
|
414
|
+
if (name === primaryKey) {
|
|
400
415
|
return `Primary key '${primaryKey}' is already defined. Please choose another field name.`;
|
|
401
416
|
}
|
|
417
|
+
if (['id', 'status', 'createdAt', 'updatedAt', 'deletedAt'].includes(name)) {
|
|
418
|
+
return `Field '${name}' is a system field. Please choose another field name.`;
|
|
419
|
+
}
|
|
420
|
+
if (fields.some((f) => f.name.toLowerCase() === name.toLowerCase())) {
|
|
421
|
+
return `Field '${name}' already exists in module '${moduleName}'. Please choose a different name.`;
|
|
422
|
+
}
|
|
402
423
|
return true;
|
|
403
424
|
},
|
|
404
425
|
},
|
|
@@ -1202,7 +1223,9 @@ async function generateModule(providedModuleName, targetDir = process.cwd(), spe
|
|
|
1202
1223
|
// Detect ORM first so prompt choices match
|
|
1203
1224
|
const orm = specifiedOrm || (await detectOrm(targetDir));
|
|
1204
1225
|
|
|
1205
|
-
const options = await promptForModuleOptions(providedModuleName, orm);
|
|
1226
|
+
const options = await promptForModuleOptions(providedModuleName, orm, targetDir);
|
|
1227
|
+
if (!options) return false;
|
|
1228
|
+
|
|
1206
1229
|
const kebabName = toKebabCase(options.moduleName);
|
|
1207
1230
|
const pascalName = toPascalCase(options.moduleName);
|
|
1208
1231
|
const camelName = toCamelCase(options.moduleName);
|
|
@@ -1576,6 +1599,11 @@ module.exports = {
|
|
|
1576
1599
|
detectModulePrimaryKey,
|
|
1577
1600
|
getOrmFieldChoices,
|
|
1578
1601
|
toKebabCase,
|
|
1602
|
+
toCamelCase,
|
|
1603
|
+
toPascalCase,
|
|
1604
|
+
toSingularPascal,
|
|
1605
|
+
toSingularCamel,
|
|
1606
|
+
toSnakeCase,
|
|
1579
1607
|
registerInAppModule,
|
|
1580
1608
|
ensureBaseArchitecture,
|
|
1581
1609
|
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 };
|