speedrun-cli 2.10.0 → 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 CHANGED
@@ -5,6 +5,13 @@ 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
+
8
15
  ## [2.10.0] - 2026-08-22
9
16
 
10
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speedrun-cli",
3
- "version": "2.10.0",
3
+ "version": "2.10.1",
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",
@@ -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: 'confirm',
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
  ]);
@@ -323,9 +327,13 @@ async function manageFields(providedModuleName, targetDir = process.cwd()) {
323
327
  managing = false; // exit loop after successful save
324
328
 
325
329
  const { runDbNow } = await inquirer.prompt([{
326
- type: 'confirm',
330
+ type: 'list',
327
331
  name: 'runDbNow',
328
332
  message: 'Run database migration & seed now?',
333
+ choices: [
334
+ { name: 'Yes', value: true },
335
+ { name: 'No', value: false },
336
+ ],
329
337
  default: false,
330
338
  }]);
331
339
 
@@ -245,9 +245,13 @@ async function configureModule(providedModuleName, targetDir = process.cwd()) {
245
245
 
246
246
  if (configChoice === 'guards') {
247
247
  const guardAnswer = await inquirer.prompt([{
248
- type: 'confirm',
248
+ type: 'list',
249
249
  name: 'protectWriteOps',
250
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
+ ],
251
255
  default: hasGuards,
252
256
  }]);
253
257
 
@@ -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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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
 
@@ -115,9 +115,13 @@ async function removeModule(providedModuleName, targetDir = process.cwd(), optio
115
115
  let confirmDelete = options.skipConfirm;
116
116
  if (confirmDelete === undefined) {
117
117
  const ans = await inquirer.prompt([{
118
- type: 'confirm',
118
+ type: 'list',
119
119
  name: 'confirmDelete',
120
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
+ ],
121
125
  default: false,
122
126
  }]);
123
127
  confirmDelete = ans.confirmDelete;
@@ -131,9 +135,13 @@ async function removeModule(providedModuleName, targetDir = process.cwd(), optio
131
135
  let removeSchema = options.removeSchema;
132
136
  if (removeSchema === undefined) {
133
137
  const ans = await inquirer.prompt([{
134
- type: 'confirm',
138
+ type: 'list',
135
139
  name: 'removeSchema',
136
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
+ ],
137
145
  default: true,
138
146
  }]);
139
147
  removeSchema = ans.removeSchema;
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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: 'confirm',
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
  });