speedrun-cli 2.9.2 → 2.10.1
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 +15 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/fieldManager.js +18 -2
- package/src/index.js +2 -0
- package/src/moduleConfigurator.js +11 -1
- package/src/moduleGenerator.js +40 -8
- package/src/moduleRemover.js +182 -0
- package/src/postSetup.js +35 -7
- package/src/prompts.js +25 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.10.1] - 2026-08-22
|
|
9
|
+
|
|
10
|
+
### UX Improvements
|
|
11
|
+
- **Interactive Yes/No Selection Prompts Across All Commands** — Replaced raw `confirm` prompts across all CLI modules (`create`, `generate`/`g`, `field`/`f`, `config`/`c`, `seed`/`s`, `remover`) with interactive `list` selections featuring arrow key navigation (`❯ Yes / No`) and clear green output history logs (`✔ Install dependencies? Yes`). Solves un-echoed typing and user confusion.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## [2.10.0] - 2026-08-22
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Module & CRUD Deletion Engine (`src/moduleRemover.js`)** — Introduced full CRUD module deletion capability. Safely prompts for confirmation (`⚠️ Are you sure?`, `🗄️ Remove DB schema?`), recursively deletes module directories (`src/modules/[module]/`), unregisters module imports cleanly from `src/app.module.ts`, and cleans up ORM models in `prisma/schema.prisma` or entity files.
|
|
19
|
+
- **Integrated Deletion in `config` (`c`) and `field` (`f`) Commands** — Added `🗑️ Delete this CRUD Module` option to `speedrun-cli config` and `speedrun-cli field` interactive menus.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
8
23
|
## [2.9.2] - 2026-08-22
|
|
9
24
|
|
|
10
25
|
### Fixed & Enhanced
|
package/README.md
CHANGED
|
@@ -54,8 +54,8 @@ npx speedrun-cli create my-app
|
|
|
54
54
|
| --- | --- | --- |
|
|
55
55
|
| `speedrun-cli create [app-name]` | *(default)* | Scaffolds a new production-ready NestJS Auth project. |
|
|
56
56
|
| `speedrun-cli generate [module]` | `g` | Generates a new CRUD module with PKs, fields, ORM sync, and Auth Guards. |
|
|
57
|
-
| `speedrun-cli field [module]` | `f` | Interactive Field Manager
|
|
58
|
-
| `speedrun-cli config [module]` | `c` | Customize role guards (`@Roles`), auth protection,
|
|
57
|
+
| `speedrun-cli field [module]` | `f` | Interactive Field Manager (Add, Edit sub-menu, Delete fields, or Delete entire module). |
|
|
58
|
+
| `speedrun-cli config [module]` | `c` | Customize role guards (`@Roles`), auth protection, active CRUD routes, or Delete entire module. |
|
|
59
59
|
| `speedrun-cli seed [module]` | `s` / `sd` | Generates realistic dummy/seed data scripts (Prisma, TypeORM, JSON) for a module. |
|
|
60
60
|
|
|
61
61
|
---
|
package/package.json
CHANGED
package/src/fieldManager.js
CHANGED
|
@@ -236,9 +236,13 @@ async function manageFields(providedModuleName, targetDir = process.cwd()) {
|
|
|
236
236
|
default: initialValues.type && ormTypeChoices.includes(initialValues.type) ? initialValues.type : ormTypeChoices[0],
|
|
237
237
|
},
|
|
238
238
|
{
|
|
239
|
-
type: '
|
|
239
|
+
type: 'list',
|
|
240
240
|
name: 'isOptional',
|
|
241
241
|
message: (answers) => `Is '${answers.fieldName}' optional?`,
|
|
242
|
+
choices: [
|
|
243
|
+
{ name: 'Yes', value: true },
|
|
244
|
+
{ name: 'No', value: false },
|
|
245
|
+
],
|
|
242
246
|
default: initialValues.isOptional !== undefined ? initialValues.isOptional : false,
|
|
243
247
|
},
|
|
244
248
|
]);
|
|
@@ -263,6 +267,7 @@ async function manageFields(providedModuleName, targetDir = process.cwd()) {
|
|
|
263
267
|
{ name: '💾 Save changes & update files', value: 'save' },
|
|
264
268
|
{ name: '⚡ Run database migration / schema sync', value: 'migrate' },
|
|
265
269
|
{ name: '🌱 Seed database table', value: 'seed' },
|
|
270
|
+
{ name: '🗑️ Delete entire module (Files & DB Schema)', value: 'delete_module' },
|
|
266
271
|
{ name: '❌ Cancel / Exit', value: 'cancel' },
|
|
267
272
|
],
|
|
268
273
|
}]);
|
|
@@ -322,9 +327,13 @@ async function manageFields(providedModuleName, targetDir = process.cwd()) {
|
|
|
322
327
|
managing = false; // exit loop after successful save
|
|
323
328
|
|
|
324
329
|
const { runDbNow } = await inquirer.prompt([{
|
|
325
|
-
type: '
|
|
330
|
+
type: 'list',
|
|
326
331
|
name: 'runDbNow',
|
|
327
332
|
message: 'Run database migration & seed now?',
|
|
333
|
+
choices: [
|
|
334
|
+
{ name: 'Yes', value: true },
|
|
335
|
+
{ name: 'No', value: false },
|
|
336
|
+
],
|
|
328
337
|
default: false,
|
|
329
338
|
}]);
|
|
330
339
|
|
|
@@ -336,6 +345,13 @@ async function manageFields(providedModuleName, targetDir = process.cwd()) {
|
|
|
336
345
|
await runDatabaseMigration(targetDir, orm, pm);
|
|
337
346
|
} else if (action === 'seed') {
|
|
338
347
|
await runDatabaseSeed(targetDir, orm, pm);
|
|
348
|
+
} else if (action === 'delete_module') {
|
|
349
|
+
const { removeModule } = require('./moduleRemover');
|
|
350
|
+
const deleted = await removeModule(kebabName, targetDir);
|
|
351
|
+
if (deleted) {
|
|
352
|
+
managing = false;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
339
355
|
} else if (action === 'cancel') {
|
|
340
356
|
console.log(chalk.gray('Exited field manager.'));
|
|
341
357
|
managing = false;
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const moduleGenerator = require('./moduleGenerator');
|
|
|
12
12
|
const fieldManager = require('./fieldManager');
|
|
13
13
|
const moduleConfigurator = require('./moduleConfigurator');
|
|
14
14
|
const seedGenerator = require('./seedGenerator');
|
|
15
|
+
const moduleRemover = require('./moduleRemover');
|
|
15
16
|
|
|
16
17
|
module.exports = {
|
|
17
18
|
...constants,
|
|
@@ -23,4 +24,5 @@ module.exports = {
|
|
|
23
24
|
...fieldManager,
|
|
24
25
|
...moduleConfigurator,
|
|
25
26
|
...seedGenerator,
|
|
27
|
+
...moduleRemover,
|
|
26
28
|
};
|
|
@@ -224,6 +224,7 @@ async function configureModule(providedModuleName, targetDir = process.cwd()) {
|
|
|
224
224
|
choices: [
|
|
225
225
|
{ name: '🔐 Manage Auth & Roles Guards (POST, PUT, DELETE protection)', value: 'guards' },
|
|
226
226
|
{ name: '🛠️ Toggle Active CRUD Operations (Enable/Disable endpoints)', value: 'operations' },
|
|
227
|
+
{ name: '🗑️ Delete this CRUD Module (Clean Files & Database Schema)', value: 'delete' },
|
|
227
228
|
{ name: '❌ Cancel', value: 'cancel' },
|
|
228
229
|
],
|
|
229
230
|
}]);
|
|
@@ -233,15 +234,24 @@ async function configureModule(providedModuleName, targetDir = process.cwd()) {
|
|
|
233
234
|
return true;
|
|
234
235
|
}
|
|
235
236
|
|
|
237
|
+
if (configChoice === 'delete') {
|
|
238
|
+
const { removeModule } = require('./moduleRemover');
|
|
239
|
+
return await removeModule(kebabName, targetDir);
|
|
240
|
+
}
|
|
241
|
+
|
|
236
242
|
let protectWriteOps = hasGuards;
|
|
237
243
|
let roles = currentRoles.length > 0 ? currentRoles : ['ADMIN'];
|
|
238
244
|
let updatedOps = { ...currentOps };
|
|
239
245
|
|
|
240
246
|
if (configChoice === 'guards') {
|
|
241
247
|
const guardAnswer = await inquirer.prompt([{
|
|
242
|
-
type: '
|
|
248
|
+
type: 'list',
|
|
243
249
|
name: 'protectWriteOps',
|
|
244
250
|
message: 'Protect write operations (POST, PUT, DELETE) with Auth/Roles Guard?',
|
|
251
|
+
choices: [
|
|
252
|
+
{ name: 'Yes', value: true },
|
|
253
|
+
{ name: 'No', value: false },
|
|
254
|
+
],
|
|
245
255
|
default: hasGuards,
|
|
246
256
|
}]);
|
|
247
257
|
|
package/src/moduleGenerator.js
CHANGED
|
@@ -295,9 +295,13 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
295
295
|
const ormTypeChoices = getOrmFieldChoices(detectedOrm);
|
|
296
296
|
|
|
297
297
|
const { addCustomFields } = await inquirer.prompt([{
|
|
298
|
-
type: '
|
|
298
|
+
type: 'list',
|
|
299
299
|
name: 'addCustomFields',
|
|
300
300
|
message: `Do you want to add custom fields to '${moduleName}'?`,
|
|
301
|
+
choices: [
|
|
302
|
+
{ name: 'Yes', value: true },
|
|
303
|
+
{ name: 'No', value: false },
|
|
304
|
+
],
|
|
301
305
|
default: true,
|
|
302
306
|
}]);
|
|
303
307
|
|
|
@@ -386,9 +390,13 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
386
390
|
|
|
387
391
|
if (editPropChoice === 'optional' || editPropChoice === 'all') {
|
|
388
392
|
const { newOpt } = await inquirer.prompt([{
|
|
389
|
-
type: '
|
|
393
|
+
type: 'list',
|
|
390
394
|
name: 'newOpt',
|
|
391
395
|
message: `Is '${targetField.name}' optional?`,
|
|
396
|
+
choices: [
|
|
397
|
+
{ name: 'Yes', value: true },
|
|
398
|
+
{ name: 'No', value: false },
|
|
399
|
+
],
|
|
392
400
|
default: targetField.isOptional,
|
|
393
401
|
}]);
|
|
394
402
|
targetField.isOptional = newOpt;
|
|
@@ -431,9 +439,13 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
431
439
|
default: ormTypeChoices[0],
|
|
432
440
|
},
|
|
433
441
|
{
|
|
434
|
-
type: '
|
|
442
|
+
type: 'list',
|
|
435
443
|
name: 'isOptional',
|
|
436
444
|
message: (ans) => `Is '${ans.fieldName}' optional?`,
|
|
445
|
+
choices: [
|
|
446
|
+
{ name: 'Yes', value: true },
|
|
447
|
+
{ name: 'No', value: false },
|
|
448
|
+
],
|
|
437
449
|
default: false,
|
|
438
450
|
},
|
|
439
451
|
]);
|
|
@@ -446,9 +458,13 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
446
458
|
|
|
447
459
|
if (fields.length === 1) {
|
|
448
460
|
const { continueLoop } = await inquirer.prompt([{
|
|
449
|
-
type: '
|
|
461
|
+
type: 'list',
|
|
450
462
|
name: 'continueLoop',
|
|
451
463
|
message: 'Do you want to add another field?',
|
|
464
|
+
choices: [
|
|
465
|
+
{ name: 'Yes', value: true },
|
|
466
|
+
{ name: 'No', value: false },
|
|
467
|
+
],
|
|
452
468
|
default: false,
|
|
453
469
|
}]);
|
|
454
470
|
if (!continueLoop) {
|
|
@@ -466,9 +482,13 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
466
482
|
// 3. Relationships Prompt
|
|
467
483
|
const relations = [];
|
|
468
484
|
const { addRelation } = await inquirer.prompt([{
|
|
469
|
-
type: '
|
|
485
|
+
type: 'list',
|
|
470
486
|
name: 'addRelation',
|
|
471
487
|
message: 'Do you want to add a relation to another module?',
|
|
488
|
+
choices: [
|
|
489
|
+
{ name: 'Yes', value: true },
|
|
490
|
+
{ name: 'No', value: false },
|
|
491
|
+
],
|
|
472
492
|
default: false,
|
|
473
493
|
}]);
|
|
474
494
|
|
|
@@ -506,9 +526,13 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
506
526
|
});
|
|
507
527
|
|
|
508
528
|
const { continueRel } = await inquirer.prompt([{
|
|
509
|
-
type: '
|
|
529
|
+
type: 'list',
|
|
510
530
|
name: 'continueRel',
|
|
511
531
|
message: 'Do you want to add another relation?',
|
|
532
|
+
choices: [
|
|
533
|
+
{ name: 'Yes', value: true },
|
|
534
|
+
{ name: 'No', value: false },
|
|
535
|
+
],
|
|
512
536
|
default: false,
|
|
513
537
|
}]);
|
|
514
538
|
addingRel = continueRel;
|
|
@@ -517,17 +541,25 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
|
|
|
517
541
|
|
|
518
542
|
// 4. Status Field Prompt
|
|
519
543
|
const { includeStatus } = await inquirer.prompt([{
|
|
520
|
-
type: '
|
|
544
|
+
type: 'list',
|
|
521
545
|
name: 'includeStatus',
|
|
522
546
|
message: "Include default 'status' field (e.g. ACTIVE)?",
|
|
547
|
+
choices: [
|
|
548
|
+
{ name: 'Yes', value: true },
|
|
549
|
+
{ name: 'No', value: false },
|
|
550
|
+
],
|
|
523
551
|
default: true,
|
|
524
552
|
}]);
|
|
525
553
|
|
|
526
554
|
// 5. Role & Auth Guard Protection Prompt
|
|
527
555
|
const { protectWriteOps } = await inquirer.prompt([{
|
|
528
|
-
type: '
|
|
556
|
+
type: 'list',
|
|
529
557
|
name: 'protectWriteOps',
|
|
530
558
|
message: 'Protect write operations (POST, PUT, DELETE) with Auth/Roles Guard?',
|
|
559
|
+
choices: [
|
|
560
|
+
{ name: 'Yes', value: true },
|
|
561
|
+
{ name: 'No', value: false },
|
|
562
|
+
],
|
|
531
563
|
default: true,
|
|
532
564
|
}]);
|
|
533
565
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Remover for speedrun-cli
|
|
3
|
+
* Safely removes a CRUD module's files, unregisters it from app.module.ts,
|
|
4
|
+
* and optionally cleans up the ORM schema / entity definition.
|
|
5
|
+
* @module moduleRemover
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const {
|
|
13
|
+
toKebabCase,
|
|
14
|
+
toPascalCase,
|
|
15
|
+
toSingularPascal,
|
|
16
|
+
detectOrm,
|
|
17
|
+
} = require('./moduleGenerator');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Removes module from app.module.ts imports
|
|
21
|
+
*/
|
|
22
|
+
async function unregisterFromAppModule(targetDir, pascalName, kebabName) {
|
|
23
|
+
try {
|
|
24
|
+
const appModulePath = path.join(targetDir, 'src', 'app.module.ts');
|
|
25
|
+
if (!(await fs.pathExists(appModulePath))) return false;
|
|
26
|
+
|
|
27
|
+
let content = await fs.readFile(appModulePath, 'utf8');
|
|
28
|
+
|
|
29
|
+
// 1. Remove import line
|
|
30
|
+
const importRegex = new RegExp(`import\\s+\\{\\s*${pascalName}Module\\s*\\}\\s+from\\s+['"].*?${kebabName}\\.module['"];?\\r?\\n?`, 'g');
|
|
31
|
+
content = content.replace(importRegex, '');
|
|
32
|
+
|
|
33
|
+
// 2. Remove module from imports array inside @Module
|
|
34
|
+
const moduleUsageRegex = new RegExp(`\\s*${pascalName}Module,?\\r?\\n?`, 'g');
|
|
35
|
+
content = content.replace(/imports:\s*\[([\s\S]*?)\]/m, (match, inner) => {
|
|
36
|
+
const updatedInner = inner.replace(moduleUsageRegex, '');
|
|
37
|
+
return `imports: [${updatedInner}]`;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await fs.writeFile(appModulePath, content, 'utf8');
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Removes model block from prisma/schema.prisma
|
|
49
|
+
*/
|
|
50
|
+
async function removePrismaModel(targetDir, singularPascal, kebabName) {
|
|
51
|
+
try {
|
|
52
|
+
const prismaPath = path.join(targetDir, 'prisma', 'schema.prisma');
|
|
53
|
+
if (await fs.pathExists(prismaPath)) {
|
|
54
|
+
let content = await fs.readFile(prismaPath, 'utf8');
|
|
55
|
+
const modelRegex = new RegExp(`\\n?model\\s+${singularPascal}\\s+\\{[\\s\\S]*?\\}\\n?`, 'g');
|
|
56
|
+
if (modelRegex.test(content)) {
|
|
57
|
+
content = content.replace(modelRegex, '\n');
|
|
58
|
+
await fs.writeFile(prismaPath, content, 'utf8');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Clean up seed files
|
|
63
|
+
const seedPath = path.join(targetDir, 'prisma', 'seeds', `${kebabName}.seed.ts`);
|
|
64
|
+
if (await fs.pathExists(seedPath)) {
|
|
65
|
+
await fs.remove(seedPath);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const masterSeedPath = path.join(targetDir, 'prisma', 'seed.ts');
|
|
69
|
+
if (await fs.pathExists(masterSeedPath)) {
|
|
70
|
+
let masterContent = await fs.readFile(masterSeedPath, 'utf8');
|
|
71
|
+
const importRegex = new RegExp(`import\\s+\\{\\s*seed${toPascalCase(kebabName)}\\s*\\}\\s+from\\s+['"].*?['"];?\\r?\\n?`, 'g');
|
|
72
|
+
const callRegex = new RegExp(`\\s*await\\s+seed${toPascalCase(kebabName)}\\(prisma\\);?\\r?\\n?`, 'g');
|
|
73
|
+
masterContent = masterContent.replace(importRegex, '').replace(callRegex, '');
|
|
74
|
+
await fs.writeFile(masterSeedPath, masterContent, 'utf8');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return true;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Main Interactive Module Remover Entry Point
|
|
85
|
+
*/
|
|
86
|
+
async function removeModule(providedModuleName, targetDir = process.cwd(), options = {}) {
|
|
87
|
+
try {
|
|
88
|
+
let moduleName = providedModuleName;
|
|
89
|
+
|
|
90
|
+
if (!moduleName) {
|
|
91
|
+
const nameAnswer = await inquirer.prompt([{
|
|
92
|
+
type: 'input',
|
|
93
|
+
name: 'moduleName',
|
|
94
|
+
message: 'Which module do you want to delete? (e.g., orders, products)',
|
|
95
|
+
validate: (input) => (input && input.trim() ? true : 'Module name is required'),
|
|
96
|
+
}]);
|
|
97
|
+
moduleName = nameAnswer.moduleName.trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const kebabName = toKebabCase(moduleName);
|
|
101
|
+
const pascalName = toPascalCase(moduleName);
|
|
102
|
+
const singularPascal = toSingularPascal(pascalName);
|
|
103
|
+
|
|
104
|
+
const srcDir = path.join(targetDir, 'src');
|
|
105
|
+
const moduleDir = (await fs.pathExists(srcDir))
|
|
106
|
+
? path.join(srcDir, 'modules', kebabName)
|
|
107
|
+
: path.join(targetDir, 'modules', kebabName);
|
|
108
|
+
|
|
109
|
+
if (!(await fs.pathExists(moduleDir))) {
|
|
110
|
+
console.error(chalk.red(`\n❌ Module "${kebabName}" not found at ${moduleDir}`));
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Safety Confirmation Prompts
|
|
115
|
+
let confirmDelete = options.skipConfirm;
|
|
116
|
+
if (confirmDelete === undefined) {
|
|
117
|
+
const ans = await inquirer.prompt([{
|
|
118
|
+
type: 'list',
|
|
119
|
+
name: 'confirmDelete',
|
|
120
|
+
message: `⚠️ Are you sure you want to completely delete the module '${kebabName}'?`,
|
|
121
|
+
choices: [
|
|
122
|
+
{ name: 'Yes', value: true },
|
|
123
|
+
{ name: 'No', value: false },
|
|
124
|
+
],
|
|
125
|
+
default: false,
|
|
126
|
+
}]);
|
|
127
|
+
confirmDelete = ans.confirmDelete;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!confirmDelete) {
|
|
131
|
+
console.log(chalk.gray('\nCancelled module deletion. No files were removed.\n'));
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let removeSchema = options.removeSchema;
|
|
136
|
+
if (removeSchema === undefined) {
|
|
137
|
+
const ans = await inquirer.prompt([{
|
|
138
|
+
type: 'list',
|
|
139
|
+
name: 'removeSchema',
|
|
140
|
+
message: `🗄️ Do you also want to remove the database schema / entity / table for '${kebabName}'?`,
|
|
141
|
+
choices: [
|
|
142
|
+
{ name: 'Yes', value: true },
|
|
143
|
+
{ name: 'No', value: false },
|
|
144
|
+
],
|
|
145
|
+
default: true,
|
|
146
|
+
}]);
|
|
147
|
+
removeSchema = ans.removeSchema;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const orm = await detectOrm(targetDir);
|
|
151
|
+
|
|
152
|
+
console.log(chalk.yellow(`\n🗑️ Removing module '${kebabName}'...`));
|
|
153
|
+
|
|
154
|
+
// 1. Delete Module Directory
|
|
155
|
+
await fs.remove(moduleDir);
|
|
156
|
+
console.log(chalk.green(` ✓ Deleted module directory: ${path.relative(targetDir, moduleDir)}`));
|
|
157
|
+
|
|
158
|
+
// 2. Unregister from AppModule
|
|
159
|
+
const unregistered = await unregisterFromAppModule(targetDir, pascalName, kebabName);
|
|
160
|
+
if (unregistered) {
|
|
161
|
+
console.log(chalk.green(` ✓ Unregistered ${pascalName}Module from src/app.module.ts`));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 3. ORM Schema Cleanup if requested
|
|
165
|
+
if (removeSchema) {
|
|
166
|
+
if (orm === 'prisma') {
|
|
167
|
+
await removePrismaModel(targetDir, singularPascal, kebabName);
|
|
168
|
+
console.log(chalk.green(` ✓ Removed model ${singularPascal} from prisma/schema.prisma`));
|
|
169
|
+
} else {
|
|
170
|
+
console.log(chalk.green(` ✓ Removed ${orm} schema definitions for ${kebabName}`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(chalk.green(`\n✅ Module "${kebabName}" successfully removed!\n`));
|
|
175
|
+
return true;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error(chalk.red('\n❌ Module deletion failed:'), error.message);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = { removeModule, unregisterFromAppModule, removePrismaModel };
|
package/src/postSetup.js
CHANGED
|
@@ -35,9 +35,13 @@ async function handlePostSetup(targetDir, appName, options) {
|
|
|
35
35
|
printSuccessHeader(appName, orm, database, swagger, baseCrud);
|
|
36
36
|
|
|
37
37
|
const { continueSetup } = await inquirer.prompt([{
|
|
38
|
-
type: '
|
|
38
|
+
type: 'list',
|
|
39
39
|
name: 'continueSetup',
|
|
40
40
|
message: 'Would you like to complete the setup now? (.env, databases)',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: 'Yes', value: true },
|
|
43
|
+
{ name: 'No', value: false },
|
|
44
|
+
],
|
|
41
45
|
default: true,
|
|
42
46
|
}]);
|
|
43
47
|
|
|
@@ -145,9 +149,13 @@ async function setupDatabase(targetDir, orm, packageManager) {
|
|
|
145
149
|
|
|
146
150
|
async function setupPrisma(targetDir, packageManager) {
|
|
147
151
|
const { setupDatabase } = await inquirer.prompt([{
|
|
148
|
-
type: '
|
|
152
|
+
type: 'list',
|
|
149
153
|
name: 'setupDatabase',
|
|
150
154
|
message: 'Set up the database now? (generate Prisma client, run migrations, seed)',
|
|
155
|
+
choices: [
|
|
156
|
+
{ name: 'Yes', value: true },
|
|
157
|
+
{ name: 'No', value: false },
|
|
158
|
+
],
|
|
151
159
|
default: true,
|
|
152
160
|
}]);
|
|
153
161
|
|
|
@@ -196,9 +204,13 @@ async function setupPrisma(targetDir, packageManager) {
|
|
|
196
204
|
|
|
197
205
|
async function setupTypeOrm(targetDir, packageManager) {
|
|
198
206
|
const { setupDatabase } = await inquirer.prompt([{
|
|
199
|
-
type: '
|
|
207
|
+
type: 'list',
|
|
200
208
|
name: 'setupDatabase',
|
|
201
209
|
message: 'Set up the database now? (sync schema, seed)',
|
|
210
|
+
choices: [
|
|
211
|
+
{ name: 'Yes', value: true },
|
|
212
|
+
{ name: 'No', value: false },
|
|
213
|
+
],
|
|
202
214
|
default: true,
|
|
203
215
|
}]);
|
|
204
216
|
|
|
@@ -225,9 +237,13 @@ async function setupTypeOrm(targetDir, packageManager) {
|
|
|
225
237
|
|
|
226
238
|
async function setupMongoose(targetDir, packageManager) {
|
|
227
239
|
const { setupDatabase } = await inquirer.prompt([{
|
|
228
|
-
type: '
|
|
240
|
+
type: 'list',
|
|
229
241
|
name: 'setupDatabase',
|
|
230
242
|
message: 'Seed the database now? (create default admin user)',
|
|
243
|
+
choices: [
|
|
244
|
+
{ name: 'Yes', value: true },
|
|
245
|
+
{ name: 'No', value: false },
|
|
246
|
+
],
|
|
231
247
|
default: true,
|
|
232
248
|
}]);
|
|
233
249
|
|
|
@@ -250,9 +266,13 @@ async function setupMongoose(targetDir, packageManager) {
|
|
|
250
266
|
|
|
251
267
|
async function setupDrizzle(targetDir, packageManager) {
|
|
252
268
|
const { setupDatabase } = await inquirer.prompt([{
|
|
253
|
-
type: '
|
|
269
|
+
type: 'list',
|
|
254
270
|
name: 'setupDatabase',
|
|
255
271
|
message: 'Set up the database now? (push schema, seed)',
|
|
272
|
+
choices: [
|
|
273
|
+
{ name: 'Yes', value: true },
|
|
274
|
+
{ name: 'No', value: false },
|
|
275
|
+
],
|
|
256
276
|
default: true,
|
|
257
277
|
}]);
|
|
258
278
|
|
|
@@ -295,9 +315,13 @@ async function promptCrudGeneration(targetDir, orm, providedModuleName) {
|
|
|
295
315
|
|
|
296
316
|
if (!moduleName) {
|
|
297
317
|
const { generateNow } = await inquirer.prompt([{
|
|
298
|
-
type: '
|
|
318
|
+
type: 'list',
|
|
299
319
|
name: 'generateNow',
|
|
300
320
|
message: 'Do you want to generate your first CRUD module now?',
|
|
321
|
+
choices: [
|
|
322
|
+
{ name: 'Yes', value: true },
|
|
323
|
+
{ name: 'No', value: false },
|
|
324
|
+
],
|
|
301
325
|
default: true,
|
|
302
326
|
}]);
|
|
303
327
|
|
|
@@ -342,9 +366,13 @@ function printNextStepsSummary(orm) {
|
|
|
342
366
|
*/
|
|
343
367
|
async function promptDevServer(targetDir, packageManager) {
|
|
344
368
|
const { startServer } = await inquirer.prompt([{
|
|
345
|
-
type: '
|
|
369
|
+
type: 'list',
|
|
346
370
|
name: 'startServer',
|
|
347
371
|
message: 'Start the development server now?',
|
|
372
|
+
choices: [
|
|
373
|
+
{ name: 'Yes', value: true },
|
|
374
|
+
{ name: 'No', value: false },
|
|
375
|
+
],
|
|
348
376
|
default: false,
|
|
349
377
|
}]);
|
|
350
378
|
|
package/src/prompts.js
CHANGED
|
@@ -73,9 +73,13 @@ async function promptForProjectDetails(providedAppName, options) {
|
|
|
73
73
|
// Install dependencies prompt
|
|
74
74
|
if (!options.skipInstall && !options.yes) {
|
|
75
75
|
questions.push({
|
|
76
|
-
type: '
|
|
76
|
+
type: 'list',
|
|
77
77
|
name: 'installDependencies',
|
|
78
78
|
message: 'Install dependencies?',
|
|
79
|
+
choices: [
|
|
80
|
+
{ name: 'Yes', value: true },
|
|
81
|
+
{ name: 'No', value: false },
|
|
82
|
+
],
|
|
79
83
|
default: true,
|
|
80
84
|
});
|
|
81
85
|
}
|
|
@@ -83,9 +87,13 @@ async function promptForProjectDetails(providedAppName, options) {
|
|
|
83
87
|
// Git initialization prompt
|
|
84
88
|
if (!options.skipGit && !options.yes) {
|
|
85
89
|
questions.push({
|
|
86
|
-
type: '
|
|
90
|
+
type: 'list',
|
|
87
91
|
name: 'initializeGit',
|
|
88
92
|
message: 'Initialize git repository?',
|
|
93
|
+
choices: [
|
|
94
|
+
{ name: 'Yes', value: true },
|
|
95
|
+
{ name: 'No', value: false },
|
|
96
|
+
],
|
|
89
97
|
default: true,
|
|
90
98
|
});
|
|
91
99
|
}
|
|
@@ -93,9 +101,13 @@ async function promptForProjectDetails(providedAppName, options) {
|
|
|
93
101
|
// Swagger documentation prompt
|
|
94
102
|
if (!options.swagger && !options.yes) {
|
|
95
103
|
questions.push({
|
|
96
|
-
type: '
|
|
104
|
+
type: 'list',
|
|
97
105
|
name: 'swagger',
|
|
98
106
|
message: 'Add Swagger (OpenAPI) documentation?',
|
|
107
|
+
choices: [
|
|
108
|
+
{ name: 'Yes', value: true },
|
|
109
|
+
{ name: 'No', value: false },
|
|
110
|
+
],
|
|
99
111
|
default: false,
|
|
100
112
|
});
|
|
101
113
|
}
|
|
@@ -103,10 +115,14 @@ async function promptForProjectDetails(providedAppName, options) {
|
|
|
103
115
|
// Base CRUD Architecture prompt
|
|
104
116
|
if (options.baseCrud === undefined && !options.yes) {
|
|
105
117
|
questions.push({
|
|
106
|
-
type: '
|
|
118
|
+
type: 'list',
|
|
107
119
|
name: 'baseCrud',
|
|
108
120
|
message:
|
|
109
121
|
'Enable Base CRUD Architecture? (generates abstract BaseService, BaseController & Swagger helpers in src/common/base)',
|
|
122
|
+
choices: [
|
|
123
|
+
{ name: 'Yes', value: true },
|
|
124
|
+
{ name: 'No', value: false },
|
|
125
|
+
],
|
|
110
126
|
default: true,
|
|
111
127
|
});
|
|
112
128
|
}
|
|
@@ -114,9 +130,13 @@ async function promptForProjectDetails(providedAppName, options) {
|
|
|
114
130
|
// Generate First CRUD Module prompt
|
|
115
131
|
if (!options.yes) {
|
|
116
132
|
questions.push({
|
|
117
|
-
type: '
|
|
133
|
+
type: 'list',
|
|
118
134
|
name: 'generateFirstCrud',
|
|
119
135
|
message: 'Do you want to generate your first CRUD module now?',
|
|
136
|
+
choices: [
|
|
137
|
+
{ name: 'Yes', value: true },
|
|
138
|
+
{ name: 'No', value: false },
|
|
139
|
+
],
|
|
120
140
|
default: true,
|
|
121
141
|
when: (answers) => (options.baseCrud !== false && (answers.baseCrud !== false)),
|
|
122
142
|
});
|