speedrun-cli 2.6.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.
Files changed (159) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +21 -0
  3. package/README.md +620 -0
  4. package/bin/cli.js +224 -0
  5. package/index.js +12 -0
  6. package/package.json +74 -0
  7. package/src/constants.js +65 -0
  8. package/src/generator.js +271 -0
  9. package/src/index.js +13 -0
  10. package/src/moduleGenerator.js +586 -0
  11. package/src/postSetup.js +365 -0
  12. package/src/prompts.js +189 -0
  13. package/src/utils.js +112 -0
  14. package/templates/README.md +81 -0
  15. package/templates/base/eslint.config.mjs +34 -0
  16. package/templates/base/gitignore +78 -0
  17. package/templates/base/nest-cli.json +8 -0
  18. package/templates/base/src/common/constants/cookie.config.ts +20 -0
  19. package/templates/base/src/common/decorators/get-user.decorator.ts +13 -0
  20. package/templates/base/src/common/decorators/public.decorator.ts +4 -0
  21. package/templates/base/src/common/decorators/roles.decorator.ts +4 -0
  22. package/templates/base/src/common/dtos/pagination.dto.ts +29 -0
  23. package/templates/base/src/common/filters/http-exception.filter.ts +108 -0
  24. package/templates/base/src/common/guards/auth.guard.ts +51 -0
  25. package/templates/base/src/common/guards/refresh-token.guard.ts +39 -0
  26. package/templates/base/src/common/guards/roles.guard.ts +45 -0
  27. package/templates/base/src/common/interceptors/response.interceptor.ts +55 -0
  28. package/templates/base/src/common/interfaces/api-response.interface.ts +26 -0
  29. package/templates/base/src/common/middleware/correlation-id.middleware.ts +20 -0
  30. package/templates/base/src/common/validators/password.validator.ts +37 -0
  31. package/templates/base/src/config/config.module.ts +14 -0
  32. package/templates/base/src/config/logger.config.ts +109 -0
  33. package/templates/base/src/main.ts +84 -0
  34. package/templates/base/src/modules/auth/auth.controller.ts +133 -0
  35. package/templates/base/src/modules/auth/dtos/login.dto.ts +11 -0
  36. package/templates/base/src/modules/auth/dtos/signup.dto.ts +8 -0
  37. package/templates/base/src/modules/health/health.controller.ts +20 -0
  38. package/templates/base/src/modules/health/health.module.ts +7 -0
  39. package/templates/base/src/modules/users/dtos/update-profile.dto.ts +12 -0
  40. package/templates/base/src/modules/users/dtos/update-user.dto.ts +13 -0
  41. package/templates/base/src/modules/users/users.controller.ts +65 -0
  42. package/templates/base/test/app.e2e-spec.ts +24 -0
  43. package/templates/base/test/jest-e2e.json +9 -0
  44. package/templates/base/tsconfig.build.json +4 -0
  45. package/templates/base/tsconfig.json +24 -0
  46. package/templates/base-crud/CRUD_README.md +385 -0
  47. package/templates/base-crud/src/common/base/base.controller.ts +321 -0
  48. package/templates/base-crud/src/common/base/base.service.ts +192 -0
  49. package/templates/base-crud/src/common/base/index.ts +20 -0
  50. package/templates/base-crud/src/common/base/swagger/api-response.dto.ts +82 -0
  51. package/templates/base-crud/src/common/base/swagger/paginated.dto.ts +102 -0
  52. package/templates/base-crud/src/modules/products/dto/create-product.dto.ts +44 -0
  53. package/templates/base-crud/src/modules/products/dto/product.dto.ts +38 -0
  54. package/templates/base-crud/src/modules/products/dto/update-product.dto.ts +12 -0
  55. package/templates/base-crud/src/modules/products/products.controller.ts +94 -0
  56. package/templates/base-crud-drizzle/src/modules/products/products.controller.ts +79 -0
  57. package/templates/base-crud-drizzle/src/modules/products/products.module.ts +24 -0
  58. package/templates/base-crud-drizzle/src/modules/products/products.service.ts +140 -0
  59. package/templates/base-crud-drizzle/src/modules/products/schema/products.schema.ts +39 -0
  60. package/templates/base-crud-mongoose/src/modules/products/products.controller.ts +79 -0
  61. package/templates/base-crud-mongoose/src/modules/products/products.module.ts +26 -0
  62. package/templates/base-crud-mongoose/src/modules/products/products.service.ts +133 -0
  63. package/templates/base-crud-mongoose/src/modules/products/schemas/product.schema.ts +67 -0
  64. package/templates/base-crud-prisma/src/modules/products/products.module.ts +12 -0
  65. package/templates/base-crud-prisma/src/modules/products/products.service.ts +100 -0
  66. package/templates/base-crud-typeorm/src/modules/products/entities/product.entity.ts +58 -0
  67. package/templates/base-crud-typeorm/src/modules/products/products.controller.ts +79 -0
  68. package/templates/base-crud-typeorm/src/modules/products/products.module.ts +25 -0
  69. package/templates/base-crud-typeorm/src/modules/products/products.service.ts +102 -0
  70. package/templates/database/mongodb/.env.example +22 -0
  71. package/templates/database/mysql/.env.example +24 -0
  72. package/templates/database/mysql/drizzle.config.ts +13 -0
  73. package/templates/database/mysql/package.json +5 -0
  74. package/templates/database/mysql/prisma/schema.prisma +55 -0
  75. package/templates/database/mysql/src/database/drizzle.ts +13 -0
  76. package/templates/database/mysql/src/database/schema.ts +58 -0
  77. package/templates/database/postgres/.env.example +24 -0
  78. package/templates/database/postgres/drizzle.config.ts +13 -0
  79. package/templates/database/postgres/package.json +8 -0
  80. package/templates/database/postgres/prisma/schema.prisma +55 -0
  81. package/templates/database/sqlite/.env.example +24 -0
  82. package/templates/database/sqlite/drizzle.config.ts +13 -0
  83. package/templates/database/sqlite/package.json +8 -0
  84. package/templates/database/sqlite/prisma/schema.prisma +48 -0
  85. package/templates/database/sqlite/src/database/drizzle.ts +11 -0
  86. package/templates/database/sqlite/src/database/schema.ts +52 -0
  87. package/templates/orm/drizzle/drizzle.config.ts +13 -0
  88. package/templates/orm/drizzle/package.json +90 -0
  89. package/templates/orm/drizzle/src/app.module.ts +73 -0
  90. package/templates/orm/drizzle/src/config/env.validation.ts +55 -0
  91. package/templates/orm/drizzle/src/database/database.module.ts +25 -0
  92. package/templates/orm/drizzle/src/database/drizzle.ts +13 -0
  93. package/templates/orm/drizzle/src/database/schema.ts +60 -0
  94. package/templates/orm/drizzle/src/database/seed.ts +87 -0
  95. package/templates/orm/drizzle/src/modules/auth/auth.module.ts +12 -0
  96. package/templates/orm/drizzle/src/modules/auth/auth.service.ts +298 -0
  97. package/templates/orm/drizzle/src/modules/health/health.controller.ts +34 -0
  98. package/templates/orm/drizzle/src/modules/health/health.module.ts +7 -0
  99. package/templates/orm/drizzle/src/modules/users/users.module.ts +10 -0
  100. package/templates/orm/drizzle/src/modules/users/users.service.ts +152 -0
  101. package/templates/orm/mongoose/.env.example +22 -0
  102. package/templates/orm/mongoose/package.json +86 -0
  103. package/templates/orm/mongoose/src/app.module.ts +73 -0
  104. package/templates/orm/mongoose/src/config/env.validation.ts +55 -0
  105. package/templates/orm/mongoose/src/database/database.module.ts +19 -0
  106. package/templates/orm/mongoose/src/database/seed.ts +107 -0
  107. package/templates/orm/mongoose/src/modules/auth/auth.module.ts +21 -0
  108. package/templates/orm/mongoose/src/modules/auth/auth.service.ts +272 -0
  109. package/templates/orm/mongoose/src/modules/auth/dtos/login.dto.ts +10 -0
  110. package/templates/orm/mongoose/src/modules/auth/dtos/signup.dto.ts +8 -0
  111. package/templates/orm/mongoose/src/modules/health/health.controller.ts +26 -0
  112. package/templates/orm/mongoose/src/modules/health/health.module.ts +7 -0
  113. package/templates/orm/mongoose/src/modules/users/dtos/update-profile.dto.ts +24 -0
  114. package/templates/orm/mongoose/src/modules/users/dtos/update-user.dto.ts +13 -0
  115. package/templates/orm/mongoose/src/modules/users/users.module.ts +19 -0
  116. package/templates/orm/mongoose/src/modules/users/users.service.ts +179 -0
  117. package/templates/orm/mongoose/src/schemas/index.ts +2 -0
  118. package/templates/orm/mongoose/src/schemas/refresh-token.schema.ts +55 -0
  119. package/templates/orm/mongoose/src/schemas/user.schema.ts +53 -0
  120. package/templates/orm/prisma/package.json +102 -0
  121. package/templates/orm/prisma/prisma/schema.prisma +60 -0
  122. package/templates/orm/prisma/prisma/seed.ts +69 -0
  123. package/templates/orm/prisma/src/app.module.ts +55 -0
  124. package/templates/orm/prisma/src/config/env.validation.ts +56 -0
  125. package/templates/orm/prisma/src/modules/auth/auth.module.ts +17 -0
  126. package/templates/orm/prisma/src/modules/auth/auth.service.ts +308 -0
  127. package/templates/orm/prisma/src/modules/health/health.controller.ts +26 -0
  128. package/templates/orm/prisma/src/modules/health/health.module.ts +10 -0
  129. package/templates/orm/prisma/src/modules/users/users.module.ts +11 -0
  130. package/templates/orm/prisma/src/modules/users/users.service.ts +105 -0
  131. package/templates/orm/prisma/src/prisma/prisma.module.ts +9 -0
  132. package/templates/orm/prisma/src/prisma/prisma.service.ts +16 -0
  133. package/templates/orm/typeorm/package.json +100 -0
  134. package/templates/orm/typeorm/src/app.module.ts +55 -0
  135. package/templates/orm/typeorm/src/config/env.validation.ts +58 -0
  136. package/templates/orm/typeorm/src/database/data-source.ts +19 -0
  137. package/templates/orm/typeorm/src/database/database.module.ts +27 -0
  138. package/templates/orm/typeorm/src/database/seed.ts +76 -0
  139. package/templates/orm/typeorm/src/entities/index.ts +2 -0
  140. package/templates/orm/typeorm/src/entities/refresh-token.entity.ts +45 -0
  141. package/templates/orm/typeorm/src/entities/user.entity.ts +51 -0
  142. package/templates/orm/typeorm/src/modules/auth/auth.module.ts +19 -0
  143. package/templates/orm/typeorm/src/modules/auth/auth.service.ts +286 -0
  144. package/templates/orm/typeorm/src/modules/health/health.controller.ts +24 -0
  145. package/templates/orm/typeorm/src/modules/health/health.module.ts +9 -0
  146. package/templates/orm/typeorm/src/modules/users/users.module.ts +13 -0
  147. package/templates/orm/typeorm/src/modules/users/users.service.ts +108 -0
  148. package/templates/swagger/src/common/dtos/pagination.dto.ts +43 -0
  149. package/templates/swagger/src/main.ts +116 -0
  150. package/templates/swagger/src/modules/auth/auth.controller.ts +235 -0
  151. package/templates/swagger/src/modules/auth/dtos/login.dto.ts +20 -0
  152. package/templates/swagger/src/modules/auth/dtos/signup.dto.ts +14 -0
  153. package/templates/swagger/src/modules/users/dtos/update-profile.dto.ts +19 -0
  154. package/templates/swagger/src/modules/users/dtos/update-user.dto.ts +23 -0
  155. package/templates/swagger/src/modules/users/users.controller.ts +214 -0
  156. package/templates/swagger-mongoose/src/modules/auth/dtos/login.dto.ts +20 -0
  157. package/templates/swagger-mongoose/src/modules/auth/dtos/signup.dto.ts +14 -0
  158. package/templates/swagger-mongoose/src/modules/users/dtos/update-profile.dto.ts +40 -0
  159. package/templates/swagger-mongoose/src/modules/users/dtos/update-user.dto.ts +23 -0
@@ -0,0 +1,365 @@
1
+ /**
2
+ * Post-Setup Interactive Handlers
3
+ * @module postSetup
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+ const { execSync } = require('child_process');
9
+ const chalk = require('chalk');
10
+ const inquirer = require('inquirer');
11
+ const { ORM_OPTIONS, DATABASE_OPTIONS } = require('./constants');
12
+ const { generateJWTSecret, getRunPrefix } = require('./utils');
13
+
14
+ /**
15
+ * Handles interactive post-setup configuration
16
+ * @param {string} targetDir - Project directory
17
+ * @param {string} appName - Application name
18
+ * @param {object} options - Configuration options
19
+ * @returns {Promise<boolean>} Whether interactive setup completed
20
+ */
21
+ async function handlePostSetup(targetDir, appName, options) {
22
+ const { packageManager, orm, database, swagger, baseCrud, yes: isYesMode, skipInstall } = options;
23
+
24
+ if (isYesMode || skipInstall) {
25
+ return false;
26
+ }
27
+
28
+ printSuccessHeader(appName, orm, database, swagger, baseCrud);
29
+
30
+ const { continueSetup } = await inquirer.prompt([{
31
+ type: 'confirm',
32
+ name: 'continueSetup',
33
+ message: 'Would you like to complete the setup now? (JWT secrets, database, etc.)',
34
+ default: true,
35
+ }]);
36
+
37
+ if (!continueSetup) {
38
+ return false;
39
+ }
40
+
41
+ // Configure JWT secrets and database URL
42
+ await configureEnvironment(targetDir, database);
43
+
44
+ // ORM-specific database setup
45
+ await setupDatabase(targetDir, orm, packageManager);
46
+
47
+ // Optionally start dev server
48
+ await promptDevServer(targetDir, packageManager);
49
+
50
+ return true;
51
+ }
52
+
53
+ /**
54
+ * Prints the success header
55
+ */
56
+ function printSuccessHeader(appName, orm, database, swagger, baseCrud) {
57
+ console.log(chalk.green('\n✅ Success! Created ' + chalk.bold(appName)));
58
+ console.log(chalk.gray(` ORM: ${ORM_OPTIONS[orm]?.name || orm}`));
59
+ console.log(chalk.gray(` Database: ${DATABASE_OPTIONS[database]?.name || database}`));
60
+ if (swagger) {
61
+ console.log(chalk.gray(` Swagger: ${chalk.green('Enabled')}`));
62
+ }
63
+ if (baseCrud) {
64
+ console.log(chalk.gray(` Base CRUD Architecture: ${chalk.green('Enabled')} — src/common/base/`));
65
+ }
66
+ console.log(chalk.white('\n🎉 Your project is ready!\n'));
67
+ }
68
+
69
+ /**
70
+ * Configures environment variables
71
+ */
72
+ async function configureEnvironment(targetDir, database) {
73
+ console.log(chalk.yellow('\n🔑 Generating JWT secrets...\n'));
74
+ const accessSecret = generateJWTSecret();
75
+ const refreshSecret = generateJWTSecret();
76
+
77
+ console.log(chalk.gray(' Generated JWT_ACCESS_SECRET'));
78
+ console.log(chalk.gray(' Generated JWT_REFRESH_SECRET\n'));
79
+
80
+ const dbInfo = DATABASE_OPTIONS[database];
81
+ const { databaseUrl } = await inquirer.prompt([{
82
+ type: 'input',
83
+ name: 'databaseUrl',
84
+ message: `Enter your ${dbInfo.name} database URL:`,
85
+ default: dbInfo.urlTemplate,
86
+ validate: (input) => {
87
+ if (!input || input.trim() === '') {
88
+ return 'Database URL is required';
89
+ }
90
+ const validPrefix = dbInfo.urlPrefix.some((prefix) => input.startsWith(prefix));
91
+ if (!validPrefix) {
92
+ return `Database URL must start with ${dbInfo.urlPrefix.join(' or ')}`;
93
+ }
94
+ return true;
95
+ },
96
+ }]);
97
+
98
+ // Update .env file
99
+ console.log(chalk.gray('\n Updating .env file...'));
100
+ const envPath = path.join(targetDir, '.env');
101
+
102
+ try {
103
+ let envContent = await fs.readFile(envPath, 'utf8');
104
+ envContent = envContent.replace(/DATABASE_URL=.*/, `DATABASE_URL="${databaseUrl}"`);
105
+ envContent = envContent.replace(/JWT_ACCESS_SECRET=.*/, `JWT_ACCESS_SECRET="${accessSecret}"`);
106
+ envContent = envContent.replace(/JWT_REFRESH_SECRET=.*/, `JWT_REFRESH_SECRET="${refreshSecret}"`);
107
+ await fs.writeFile(envPath, envContent);
108
+ console.log(chalk.green(' ✓ Environment variables configured\n'));
109
+ } catch {
110
+ console.error(chalk.red(' ✗ Failed to update .env file'));
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Sets up the database based on selected ORM
116
+ */
117
+ async function setupDatabase(targetDir, orm, packageManager) {
118
+ const setupHandlers = {
119
+ prisma: setupPrisma,
120
+ typeorm: setupTypeOrm,
121
+ mongoose: setupMongoose,
122
+ drizzle: setupDrizzle,
123
+ };
124
+
125
+ const handler = setupHandlers[orm];
126
+ if (handler) {
127
+ await handler(targetDir, packageManager);
128
+ }
129
+ }
130
+
131
+ async function setupPrisma(targetDir, packageManager) {
132
+ const { setupDatabase } = await inquirer.prompt([{
133
+ type: 'confirm',
134
+ name: 'setupDatabase',
135
+ message: 'Set up the database now? (generate Prisma client, run migrations, seed)',
136
+ default: true,
137
+ }]);
138
+
139
+ if (!setupDatabase) return;
140
+
141
+ // Prompt for migration name before running prisma commands
142
+ const { migrationName } = await inquirer.prompt([{
143
+ type: 'input',
144
+ name: 'migrationName',
145
+ message: 'Enter migration name:',
146
+ default: 'init',
147
+ validate: (input) => {
148
+ if (!input || input.trim() === '') {
149
+ return 'Migration name is required';
150
+ }
151
+ if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
152
+ return 'Migration name can only contain letters, numbers, underscores, and hyphens';
153
+ }
154
+ return true;
155
+ },
156
+ }]);
157
+
158
+ console.log(chalk.yellow('\n📦 Setting up database...\n'));
159
+ const pmPrefix = getRunPrefix(packageManager);
160
+
161
+ try {
162
+ console.log(chalk.gray(' Generating Prisma client...'));
163
+ execSync(`${pmPrefix} prisma:generate`, { cwd: targetDir, stdio: 'inherit' });
164
+
165
+ console.log(chalk.gray('\n Running database migrations...'));
166
+ // Run prisma directly with --name to avoid double prompt
167
+ execSync(`npx prisma migrate dev --name ${migrationName}`, { cwd: targetDir, stdio: 'inherit' });
168
+
169
+ console.log(chalk.gray('\n Seeding database...'));
170
+ execSync(`${pmPrefix} prisma:seed`, { cwd: targetDir, stdio: 'inherit' });
171
+
172
+ printCredentials();
173
+ } catch {
174
+ console.error(chalk.red('\n ✗ Database setup failed'));
175
+ console.error(chalk.yellow(' Run these commands manually:'));
176
+ console.error(chalk.gray(` ${pmPrefix} prisma:generate`));
177
+ console.error(chalk.gray(` ${pmPrefix} prisma:migrate`));
178
+ console.error(chalk.gray(` ${pmPrefix} prisma:seed\n`));
179
+ }
180
+ }
181
+
182
+ async function setupTypeOrm(targetDir, packageManager) {
183
+ const { setupDatabase } = await inquirer.prompt([{
184
+ type: 'confirm',
185
+ name: 'setupDatabase',
186
+ message: 'Set up the database now? (sync schema, seed)',
187
+ default: true,
188
+ }]);
189
+
190
+ if (!setupDatabase) return;
191
+
192
+ console.log(chalk.yellow('\n📦 Setting up database...\n'));
193
+ const pmPrefix = getRunPrefix(packageManager);
194
+
195
+ try {
196
+ console.log(chalk.gray(' Synchronizing database schema...'));
197
+ execSync(`${pmPrefix} schema:sync`, { cwd: targetDir, stdio: 'inherit' });
198
+
199
+ console.log(chalk.gray('\n Seeding database...'));
200
+ execSync(`${pmPrefix} seed`, { cwd: targetDir, stdio: 'inherit' });
201
+
202
+ printCredentials();
203
+ } catch {
204
+ console.error(chalk.red('\n ✗ Database setup failed'));
205
+ console.error(chalk.yellow(' Run these commands manually:'));
206
+ console.error(chalk.gray(` ${pmPrefix} schema:sync`));
207
+ console.error(chalk.gray(` ${pmPrefix} seed\n`));
208
+ }
209
+ }
210
+
211
+ async function setupMongoose(targetDir, packageManager) {
212
+ const { setupDatabase } = await inquirer.prompt([{
213
+ type: 'confirm',
214
+ name: 'setupDatabase',
215
+ message: 'Seed the database now? (create default admin user)',
216
+ default: true,
217
+ }]);
218
+
219
+ if (!setupDatabase) return;
220
+
221
+ console.log(chalk.yellow('\n📦 Setting up database...\n'));
222
+ const pmPrefix = getRunPrefix(packageManager);
223
+
224
+ try {
225
+ console.log(chalk.gray(' Seeding database...'));
226
+ execSync(`${pmPrefix} db:seed`, { cwd: targetDir, stdio: 'inherit' });
227
+
228
+ printCredentials();
229
+ } catch {
230
+ console.error(chalk.red('\n ✗ Database setup failed'));
231
+ console.error(chalk.yellow(' Run this command manually:'));
232
+ console.error(chalk.gray(` ${pmPrefix} db:seed\n`));
233
+ }
234
+ }
235
+
236
+ async function setupDrizzle(targetDir, packageManager) {
237
+ const { setupDatabase } = await inquirer.prompt([{
238
+ type: 'confirm',
239
+ name: 'setupDatabase',
240
+ message: 'Set up the database now? (push schema, seed)',
241
+ default: true,
242
+ }]);
243
+
244
+ if (!setupDatabase) return;
245
+
246
+ console.log(chalk.yellow('\n📦 Setting up database...\n'));
247
+ const pmPrefix = getRunPrefix(packageManager);
248
+
249
+ try {
250
+ console.log(chalk.gray(' Pushing schema to database...'));
251
+ execSync(`${pmPrefix} db:push`, { cwd: targetDir, stdio: 'inherit' });
252
+
253
+ console.log(chalk.gray('\n Seeding database...'));
254
+ execSync(`${pmPrefix} db:seed`, { cwd: targetDir, stdio: 'inherit' });
255
+
256
+ printCredentials();
257
+ } catch {
258
+ console.error(chalk.red('\n ✗ Database setup failed'));
259
+ console.error(chalk.yellow(' Run these commands manually:'));
260
+ console.error(chalk.gray(` ${pmPrefix} db:push`));
261
+ console.error(chalk.gray(` ${pmPrefix} db:seed\n`));
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Prints default admin credentials
267
+ */
268
+ function printCredentials() {
269
+ console.log(chalk.green('\n ✓ Database setup complete!\n'));
270
+ console.log(chalk.cyan(' 📝 Default admin credentials:'));
271
+ console.log(chalk.white(' Email: admin@example.com'));
272
+ console.log(chalk.white(' Password: Admin@123\n'));
273
+ }
274
+
275
+ /**
276
+ * Prompts user to start dev server
277
+ */
278
+ async function promptDevServer(targetDir, packageManager) {
279
+ const { startServer } = await inquirer.prompt([{
280
+ type: 'confirm',
281
+ name: 'startServer',
282
+ message: 'Start the development server now?',
283
+ default: false,
284
+ }]);
285
+
286
+ if (!startServer) return;
287
+
288
+ console.log(chalk.yellow('\n🚀 Starting development server...\n'));
289
+ console.log(chalk.gray(` Your API will be available at: ${chalk.cyan('http://localhost:8080/api/v1')}`));
290
+ console.log(chalk.gray(` Press ${chalk.bold('Ctrl+C')} to stop the server\n`));
291
+
292
+ const pmPrefix = getRunPrefix(packageManager);
293
+
294
+ try {
295
+ execSync(`${pmPrefix} start:dev`, { cwd: targetDir, stdio: 'inherit' });
296
+ } catch {
297
+ console.log(chalk.yellow('\n Server stopped.'));
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Prints manual setup instructions when interactive setup is skipped
303
+ */
304
+ function printManualInstructions(appName, options) {
305
+ const { orm, database, packageManager, installDependencies, swagger, baseCrud } = options;
306
+
307
+ console.log(chalk.green('\n✅ Success! Created ' + chalk.bold(appName)));
308
+ console.log(chalk.gray(` ORM: ${ORM_OPTIONS[orm]?.name || orm}`));
309
+ console.log(chalk.gray(` Database: ${DATABASE_OPTIONS[database]?.name || database}`));
310
+ if (swagger) {
311
+ console.log(chalk.gray(` Swagger: ${chalk.green('Enabled')}`));
312
+ }
313
+ if (baseCrud) {
314
+ console.log(chalk.gray(` Base CRUD Architecture: ${chalk.green('Enabled')} — see CRUD_README.md`));
315
+ }
316
+ console.log(chalk.white('\n📚 Next steps:\n'));
317
+ console.log(chalk.cyan(` cd ${appName}`));
318
+
319
+ if (!installDependencies) {
320
+ const installCmd = require('./utils').getInstallCommand(packageManager);
321
+ console.log(chalk.cyan(` ${installCmd}`));
322
+ }
323
+
324
+ console.log(chalk.cyan('\n # Generate secure JWT secrets (save these!):'));
325
+ console.log(chalk.gray(' openssl rand -base64 32 # For JWT_ACCESS_SECRET'));
326
+ console.log(chalk.gray(' openssl rand -base64 32 # For JWT_REFRESH_SECRET'));
327
+ console.log(chalk.cyan('\n # Edit .env with your database URL and JWT secrets'));
328
+
329
+ const ormCommands = {
330
+ prisma: ['prisma:generate', 'prisma:migrate', 'prisma:seed'],
331
+ typeorm: ['schema:sync', 'seed'],
332
+ mongoose: ['db:seed'],
333
+ drizzle: ['db:push', 'db:seed'],
334
+ };
335
+
336
+ const commands = ormCommands[orm];
337
+ if (commands) {
338
+ console.log(chalk.cyan('\n # Then setup the database:'));
339
+ commands.forEach((cmd) => console.log(chalk.gray(` npm run ${cmd}`)));
340
+ }
341
+
342
+ console.log(chalk.cyan('\n # Start development server:'));
343
+ console.log(chalk.gray(' npm run start:dev'));
344
+
345
+ if (swagger) {
346
+ console.log(chalk.cyan('\n # Swagger API documentation:'));
347
+ console.log(chalk.gray(' http://localhost:8080/api/docs'));
348
+ }
349
+
350
+ if (baseCrud) {
351
+ console.log(chalk.cyan('\n # Base CRUD Architecture:'));
352
+ console.log(chalk.gray(' src/common/base/ — BaseService & BaseController'));
353
+ console.log(chalk.gray(' src/modules/products/ — Concrete example (ProductModule)'));
354
+ console.log(chalk.gray(' CRUD_README.md — Full guide & cheatsheet'));
355
+ }
356
+
357
+ console.log(chalk.white('\n📖 Documentation: https://github.com/masabinhok/create-nestjs-auth'));
358
+ console.log(chalk.white('🐛 Issues: https://github.com/masabinhok/create-nestjs-auth/issues\n'));
359
+ console.log(chalk.magenta('Happy coding! 🎉\n'));
360
+ }
361
+
362
+ module.exports = {
363
+ handlePostSetup,
364
+ printManualInstructions,
365
+ };
package/src/prompts.js ADDED
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Interactive Prompt Handlers
3
+ * @module prompts
4
+ */
5
+
6
+ const inquirer = require('inquirer');
7
+ const { ORM_OPTIONS, DATABASE_OPTIONS, RESERVED_NAMES } = require('./constants');
8
+ const { detectPackageManager } = require('./utils');
9
+
10
+ /**
11
+ * Prompts for project configuration interactively
12
+ * @param {string|undefined} providedAppName - App name from CLI args
13
+ * @param {object} options - CLI options
14
+ * @returns {Promise<object>} Project configuration
15
+ */
16
+ async function promptForProjectDetails(providedAppName, options) {
17
+ const questions = [];
18
+
19
+ // App name prompt
20
+ if (!providedAppName) {
21
+ questions.push({
22
+ type: 'input',
23
+ name: 'appName',
24
+ message: 'What is your project name?',
25
+ default: 'my-nestjs-app',
26
+ validate: validateAppNameInput,
27
+ });
28
+ }
29
+
30
+ // ORM selection
31
+ if (!options.yes) {
32
+ questions.push({
33
+ type: 'list',
34
+ name: 'orm',
35
+ message: 'Which ORM would you like to use?',
36
+ choices: Object.entries(ORM_OPTIONS).map(([key, value]) => ({
37
+ name: `${value.name} - ${value.description}`,
38
+ value: key,
39
+ })),
40
+ default: 'prisma',
41
+ });
42
+ }
43
+
44
+ // Database selection
45
+ if (!options.yes) {
46
+ questions.push({
47
+ type: 'list',
48
+ name: 'database',
49
+ message: 'Which database would you like to use?',
50
+ choices: (answers) => getDatabaseChoices(answers, options),
51
+ default: 'postgres',
52
+ when: (answers) => shouldShowDatabasePrompt(answers, options),
53
+ });
54
+ }
55
+
56
+ // Package manager selection
57
+ if (!options.packageManager && !options.yes) {
58
+ const detected = detectPackageManager();
59
+ questions.push({
60
+ type: 'list',
61
+ name: 'packageManager',
62
+ message: 'Which package manager would you like to use?',
63
+ choices: [
64
+ { name: `npm${detected === 'npm' ? ' (detected)' : ''}`, value: 'npm' },
65
+ { name: `pnpm${detected === 'pnpm' ? ' (detected)' : ''}`, value: 'pnpm' },
66
+ { name: `yarn${detected === 'yarn' ? ' (detected)' : ''}`, value: 'yarn' },
67
+ { name: `bun${detected === 'bun' ? ' (detected)' : ''}`, value: 'bun' },
68
+ ],
69
+ default: detected,
70
+ });
71
+ }
72
+
73
+ // Install dependencies prompt
74
+ if (!options.skipInstall && !options.yes) {
75
+ questions.push({
76
+ type: 'confirm',
77
+ name: 'installDependencies',
78
+ message: 'Install dependencies?',
79
+ default: true,
80
+ });
81
+ }
82
+
83
+ // Git initialization prompt
84
+ if (!options.skipGit && !options.yes) {
85
+ questions.push({
86
+ type: 'confirm',
87
+ name: 'initializeGit',
88
+ message: 'Initialize git repository?',
89
+ default: true,
90
+ });
91
+ }
92
+
93
+ // Swagger documentation prompt
94
+ if (!options.swagger && !options.yes) {
95
+ questions.push({
96
+ type: 'confirm',
97
+ name: 'swagger',
98
+ message: 'Add Swagger (OpenAPI) documentation?',
99
+ default: false,
100
+ });
101
+ }
102
+
103
+ // Base CRUD Architecture prompt
104
+ if (!options.baseCrud && !options.yes) {
105
+ questions.push({
106
+ type: 'confirm',
107
+ name: 'baseCrud',
108
+ message:
109
+ 'Enable Base CRUD Architecture? (generates abstract BaseService, BaseController & Swagger helpers in src/common/base)',
110
+ default: false,
111
+ });
112
+ }
113
+
114
+ const answers = await inquirer.prompt(questions);
115
+
116
+ // Determine ORM and database
117
+ const selectedOrm = options.orm || answers.orm || 'prisma';
118
+ const selectedDatabase = ORM_OPTIONS[selectedOrm]?.fixedDatabase
119
+ ? ORM_OPTIONS[selectedOrm].databases[0]
120
+ : options.database || answers.database || 'postgres';
121
+
122
+ return {
123
+ appName: providedAppName || answers.appName,
124
+ orm: selectedOrm,
125
+ database: selectedDatabase,
126
+ packageManager: options.packageManager || answers.packageManager || detectPackageManager(),
127
+ installDependencies: options.skipInstall ? false : answers.installDependencies !== false,
128
+ initializeGit: options.skipGit ? false : answers.initializeGit !== false,
129
+ swagger: options.swagger || answers.swagger || false,
130
+ baseCrud: options.baseCrud || answers.baseCrud || false,
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Validates app name input from inquirer
136
+ * @param {string} input - User input
137
+ * @returns {boolean|string} True if valid, error message if invalid
138
+ */
139
+ function validateAppNameInput(input) {
140
+ if (!input || input.trim() === '') {
141
+ return 'Project name is required';
142
+ }
143
+ if (!/^[a-z0-9-_@/]+$/i.test(input)) {
144
+ return 'Project name must contain only letters, numbers, hyphens, underscores, @ and /';
145
+ }
146
+ if (input.length > 214) {
147
+ return 'Project name must be less than 214 characters';
148
+ }
149
+ if (RESERVED_NAMES.includes(input.toLowerCase())) {
150
+ return `"${input}" is a reserved name and cannot be used`;
151
+ }
152
+ return true;
153
+ }
154
+
155
+ /**
156
+ * Gets database choices based on selected ORM
157
+ * @param {object} answers - Previous answers
158
+ * @param {object} options - CLI options
159
+ * @returns {Array} Database choices for inquirer
160
+ */
161
+ function getDatabaseChoices(answers, options) {
162
+ const selectedOrm = answers.orm || options.orm || 'prisma';
163
+ const supportedDatabases = ORM_OPTIONS[selectedOrm]?.databases || ['postgres'];
164
+
165
+ return supportedDatabases.map((db) => {
166
+ const dbInfo = DATABASE_OPTIONS[db];
167
+ const suffix = dbInfo.devOnly ? ' (Development only)' : '';
168
+ const recommended = db === 'postgres' ? ' (Recommended)' : '';
169
+ return {
170
+ name: `${dbInfo.name}${recommended}${suffix}`,
171
+ value: db,
172
+ };
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Determines if database prompt should be shown
178
+ * @param {object} answers - Previous answers
179
+ * @param {object} options - CLI options
180
+ * @returns {boolean} Whether to show database prompt
181
+ */
182
+ function shouldShowDatabasePrompt(answers, options) {
183
+ const selectedOrm = answers.orm || options.orm || 'prisma';
184
+ return !ORM_OPTIONS[selectedOrm]?.fixedDatabase;
185
+ }
186
+
187
+ module.exports = {
188
+ promptForProjectDetails,
189
+ };
package/src/utils.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Utility and Validation Functions
3
+ * @module utils
4
+ */
5
+
6
+ const { execSync } = require('child_process');
7
+ const crypto = require('crypto');
8
+ const chalk = require('chalk');
9
+ const { RESERVED_NAMES } = require('./constants');
10
+
11
+ /**
12
+ * Validates the application name against npm naming conventions
13
+ * @param {string} name - The app name to validate
14
+ * @throws {Error} If validation fails
15
+ */
16
+ function validateAppName(name) {
17
+ if (!/^[a-z0-9-_@/]+$/i.test(name)) {
18
+ console.error(chalk.red('❌ App name must contain only letters, numbers, hyphens, underscores, @ and /'));
19
+ console.error(chalk.yellow(' Valid examples: my-app, @myorg/app, my_app_123'));
20
+ process.exit(1);
21
+ }
22
+
23
+ if (name.length > 214) {
24
+ console.error(chalk.red('❌ App name must be less than 214 characters (npm restriction)'));
25
+ process.exit(1);
26
+ }
27
+
28
+ if (RESERVED_NAMES.includes(name.toLowerCase())) {
29
+ console.error(chalk.red(`❌ "${name}" is a reserved name and cannot be used`));
30
+ process.exit(1);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Checks if the Node.js version meets minimum requirements
36
+ * @throws {Error} If Node.js version is below 20.x
37
+ */
38
+ function checkNodeVersion() {
39
+ const currentVersion = process.version;
40
+ const major = parseInt(currentVersion.split('.')[0].slice(1));
41
+
42
+ if (major < 20) {
43
+ console.error(chalk.red(`❌ Node.js ${currentVersion} is not supported`));
44
+ console.error(chalk.yellow(' This template requires Node.js >= 20.x'));
45
+ console.error(chalk.cyan(' Please upgrade: https://nodejs.org/'));
46
+ process.exit(1);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Detects the available package manager in order of preference
52
+ * @returns {string} The detected package manager (bun, pnpm, yarn, or npm)
53
+ */
54
+ function detectPackageManager() {
55
+ const managers = [
56
+ { name: 'bun', cmd: 'bun --version' },
57
+ { name: 'pnpm', cmd: 'pnpm --version' },
58
+ { name: 'yarn', cmd: 'yarn --version' },
59
+ ];
60
+
61
+ for (const { name, cmd } of managers) {
62
+ try {
63
+ execSync(cmd, { stdio: 'ignore' });
64
+ return name;
65
+ } catch {
66
+ // Continue to next package manager
67
+ }
68
+ }
69
+
70
+ return 'npm';
71
+ }
72
+
73
+ /**
74
+ * Returns the install command for the given package manager
75
+ * @param {string} packageManager - The package manager name
76
+ * @returns {string} The install command
77
+ */
78
+ function getInstallCommand(packageManager) {
79
+ const commands = {
80
+ npm: 'npm install',
81
+ yarn: 'yarn install',
82
+ pnpm: 'pnpm install',
83
+ bun: 'bun install',
84
+ };
85
+ return commands[packageManager] || 'npm install';
86
+ }
87
+
88
+ /**
89
+ * Generates a cryptographically secure JWT secret
90
+ * @returns {string} A base64-encoded random string
91
+ */
92
+ function generateJWTSecret() {
93
+ return crypto.randomBytes(32).toString('base64');
94
+ }
95
+
96
+ /**
97
+ * Returns the command prefix for running scripts
98
+ * @param {string} packageManager - The package manager name
99
+ * @returns {string} The command prefix (e.g., 'npm run' or 'pnpm')
100
+ */
101
+ function getRunPrefix(packageManager) {
102
+ return packageManager === 'npm' ? 'npm run' : packageManager;
103
+ }
104
+
105
+ module.exports = {
106
+ validateAppName,
107
+ checkNodeVersion,
108
+ detectPackageManager,
109
+ getInstallCommand,
110
+ generateJWTSecret,
111
+ getRunPrefix,
112
+ };