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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speedrun-cli",
3
- "version": "2.8.1",
3
+ "version": "2.9.2",
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",
@@ -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
- if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(input.trim())) {
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
  };
@@ -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
- if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(input.trim())) {
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 (input.trim() === primaryKey) {
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 };