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.
- package/CHANGELOG.md +96 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/cli.js +224 -0
- package/index.js +12 -0
- package/package.json +74 -0
- package/src/constants.js +65 -0
- package/src/generator.js +271 -0
- package/src/index.js +13 -0
- package/src/moduleGenerator.js +586 -0
- package/src/postSetup.js +365 -0
- package/src/prompts.js +189 -0
- package/src/utils.js +112 -0
- package/templates/README.md +81 -0
- package/templates/base/eslint.config.mjs +34 -0
- package/templates/base/gitignore +78 -0
- package/templates/base/nest-cli.json +8 -0
- package/templates/base/src/common/constants/cookie.config.ts +20 -0
- package/templates/base/src/common/decorators/get-user.decorator.ts +13 -0
- package/templates/base/src/common/decorators/public.decorator.ts +4 -0
- package/templates/base/src/common/decorators/roles.decorator.ts +4 -0
- package/templates/base/src/common/dtos/pagination.dto.ts +29 -0
- package/templates/base/src/common/filters/http-exception.filter.ts +108 -0
- package/templates/base/src/common/guards/auth.guard.ts +51 -0
- package/templates/base/src/common/guards/refresh-token.guard.ts +39 -0
- package/templates/base/src/common/guards/roles.guard.ts +45 -0
- package/templates/base/src/common/interceptors/response.interceptor.ts +55 -0
- package/templates/base/src/common/interfaces/api-response.interface.ts +26 -0
- package/templates/base/src/common/middleware/correlation-id.middleware.ts +20 -0
- package/templates/base/src/common/validators/password.validator.ts +37 -0
- package/templates/base/src/config/config.module.ts +14 -0
- package/templates/base/src/config/logger.config.ts +109 -0
- package/templates/base/src/main.ts +84 -0
- package/templates/base/src/modules/auth/auth.controller.ts +133 -0
- package/templates/base/src/modules/auth/dtos/login.dto.ts +11 -0
- package/templates/base/src/modules/auth/dtos/signup.dto.ts +8 -0
- package/templates/base/src/modules/health/health.controller.ts +20 -0
- package/templates/base/src/modules/health/health.module.ts +7 -0
- package/templates/base/src/modules/users/dtos/update-profile.dto.ts +12 -0
- package/templates/base/src/modules/users/dtos/update-user.dto.ts +13 -0
- package/templates/base/src/modules/users/users.controller.ts +65 -0
- package/templates/base/test/app.e2e-spec.ts +24 -0
- package/templates/base/test/jest-e2e.json +9 -0
- package/templates/base/tsconfig.build.json +4 -0
- package/templates/base/tsconfig.json +24 -0
- package/templates/base-crud/CRUD_README.md +385 -0
- package/templates/base-crud/src/common/base/base.controller.ts +321 -0
- package/templates/base-crud/src/common/base/base.service.ts +192 -0
- package/templates/base-crud/src/common/base/index.ts +20 -0
- package/templates/base-crud/src/common/base/swagger/api-response.dto.ts +82 -0
- package/templates/base-crud/src/common/base/swagger/paginated.dto.ts +102 -0
- package/templates/base-crud/src/modules/products/dto/create-product.dto.ts +44 -0
- package/templates/base-crud/src/modules/products/dto/product.dto.ts +38 -0
- package/templates/base-crud/src/modules/products/dto/update-product.dto.ts +12 -0
- package/templates/base-crud/src/modules/products/products.controller.ts +94 -0
- package/templates/base-crud-drizzle/src/modules/products/products.controller.ts +79 -0
- package/templates/base-crud-drizzle/src/modules/products/products.module.ts +24 -0
- package/templates/base-crud-drizzle/src/modules/products/products.service.ts +140 -0
- package/templates/base-crud-drizzle/src/modules/products/schema/products.schema.ts +39 -0
- package/templates/base-crud-mongoose/src/modules/products/products.controller.ts +79 -0
- package/templates/base-crud-mongoose/src/modules/products/products.module.ts +26 -0
- package/templates/base-crud-mongoose/src/modules/products/products.service.ts +133 -0
- package/templates/base-crud-mongoose/src/modules/products/schemas/product.schema.ts +67 -0
- package/templates/base-crud-prisma/src/modules/products/products.module.ts +12 -0
- package/templates/base-crud-prisma/src/modules/products/products.service.ts +100 -0
- package/templates/base-crud-typeorm/src/modules/products/entities/product.entity.ts +58 -0
- package/templates/base-crud-typeorm/src/modules/products/products.controller.ts +79 -0
- package/templates/base-crud-typeorm/src/modules/products/products.module.ts +25 -0
- package/templates/base-crud-typeorm/src/modules/products/products.service.ts +102 -0
- package/templates/database/mongodb/.env.example +22 -0
- package/templates/database/mysql/.env.example +24 -0
- package/templates/database/mysql/drizzle.config.ts +13 -0
- package/templates/database/mysql/package.json +5 -0
- package/templates/database/mysql/prisma/schema.prisma +55 -0
- package/templates/database/mysql/src/database/drizzle.ts +13 -0
- package/templates/database/mysql/src/database/schema.ts +58 -0
- package/templates/database/postgres/.env.example +24 -0
- package/templates/database/postgres/drizzle.config.ts +13 -0
- package/templates/database/postgres/package.json +8 -0
- package/templates/database/postgres/prisma/schema.prisma +55 -0
- package/templates/database/sqlite/.env.example +24 -0
- package/templates/database/sqlite/drizzle.config.ts +13 -0
- package/templates/database/sqlite/package.json +8 -0
- package/templates/database/sqlite/prisma/schema.prisma +48 -0
- package/templates/database/sqlite/src/database/drizzle.ts +11 -0
- package/templates/database/sqlite/src/database/schema.ts +52 -0
- package/templates/orm/drizzle/drizzle.config.ts +13 -0
- package/templates/orm/drizzle/package.json +90 -0
- package/templates/orm/drizzle/src/app.module.ts +73 -0
- package/templates/orm/drizzle/src/config/env.validation.ts +55 -0
- package/templates/orm/drizzle/src/database/database.module.ts +25 -0
- package/templates/orm/drizzle/src/database/drizzle.ts +13 -0
- package/templates/orm/drizzle/src/database/schema.ts +60 -0
- package/templates/orm/drizzle/src/database/seed.ts +87 -0
- package/templates/orm/drizzle/src/modules/auth/auth.module.ts +12 -0
- package/templates/orm/drizzle/src/modules/auth/auth.service.ts +298 -0
- package/templates/orm/drizzle/src/modules/health/health.controller.ts +34 -0
- package/templates/orm/drizzle/src/modules/health/health.module.ts +7 -0
- package/templates/orm/drizzle/src/modules/users/users.module.ts +10 -0
- package/templates/orm/drizzle/src/modules/users/users.service.ts +152 -0
- package/templates/orm/mongoose/.env.example +22 -0
- package/templates/orm/mongoose/package.json +86 -0
- package/templates/orm/mongoose/src/app.module.ts +73 -0
- package/templates/orm/mongoose/src/config/env.validation.ts +55 -0
- package/templates/orm/mongoose/src/database/database.module.ts +19 -0
- package/templates/orm/mongoose/src/database/seed.ts +107 -0
- package/templates/orm/mongoose/src/modules/auth/auth.module.ts +21 -0
- package/templates/orm/mongoose/src/modules/auth/auth.service.ts +272 -0
- package/templates/orm/mongoose/src/modules/auth/dtos/login.dto.ts +10 -0
- package/templates/orm/mongoose/src/modules/auth/dtos/signup.dto.ts +8 -0
- package/templates/orm/mongoose/src/modules/health/health.controller.ts +26 -0
- package/templates/orm/mongoose/src/modules/health/health.module.ts +7 -0
- package/templates/orm/mongoose/src/modules/users/dtos/update-profile.dto.ts +24 -0
- package/templates/orm/mongoose/src/modules/users/dtos/update-user.dto.ts +13 -0
- package/templates/orm/mongoose/src/modules/users/users.module.ts +19 -0
- package/templates/orm/mongoose/src/modules/users/users.service.ts +179 -0
- package/templates/orm/mongoose/src/schemas/index.ts +2 -0
- package/templates/orm/mongoose/src/schemas/refresh-token.schema.ts +55 -0
- package/templates/orm/mongoose/src/schemas/user.schema.ts +53 -0
- package/templates/orm/prisma/package.json +102 -0
- package/templates/orm/prisma/prisma/schema.prisma +60 -0
- package/templates/orm/prisma/prisma/seed.ts +69 -0
- package/templates/orm/prisma/src/app.module.ts +55 -0
- package/templates/orm/prisma/src/config/env.validation.ts +56 -0
- package/templates/orm/prisma/src/modules/auth/auth.module.ts +17 -0
- package/templates/orm/prisma/src/modules/auth/auth.service.ts +308 -0
- package/templates/orm/prisma/src/modules/health/health.controller.ts +26 -0
- package/templates/orm/prisma/src/modules/health/health.module.ts +10 -0
- package/templates/orm/prisma/src/modules/users/users.module.ts +11 -0
- package/templates/orm/prisma/src/modules/users/users.service.ts +105 -0
- package/templates/orm/prisma/src/prisma/prisma.module.ts +9 -0
- package/templates/orm/prisma/src/prisma/prisma.service.ts +16 -0
- package/templates/orm/typeorm/package.json +100 -0
- package/templates/orm/typeorm/src/app.module.ts +55 -0
- package/templates/orm/typeorm/src/config/env.validation.ts +58 -0
- package/templates/orm/typeorm/src/database/data-source.ts +19 -0
- package/templates/orm/typeorm/src/database/database.module.ts +27 -0
- package/templates/orm/typeorm/src/database/seed.ts +76 -0
- package/templates/orm/typeorm/src/entities/index.ts +2 -0
- package/templates/orm/typeorm/src/entities/refresh-token.entity.ts +45 -0
- package/templates/orm/typeorm/src/entities/user.entity.ts +51 -0
- package/templates/orm/typeorm/src/modules/auth/auth.module.ts +19 -0
- package/templates/orm/typeorm/src/modules/auth/auth.service.ts +286 -0
- package/templates/orm/typeorm/src/modules/health/health.controller.ts +24 -0
- package/templates/orm/typeorm/src/modules/health/health.module.ts +9 -0
- package/templates/orm/typeorm/src/modules/users/users.module.ts +13 -0
- package/templates/orm/typeorm/src/modules/users/users.service.ts +108 -0
- package/templates/swagger/src/common/dtos/pagination.dto.ts +43 -0
- package/templates/swagger/src/main.ts +116 -0
- package/templates/swagger/src/modules/auth/auth.controller.ts +235 -0
- package/templates/swagger/src/modules/auth/dtos/login.dto.ts +20 -0
- package/templates/swagger/src/modules/auth/dtos/signup.dto.ts +14 -0
- package/templates/swagger/src/modules/users/dtos/update-profile.dto.ts +19 -0
- package/templates/swagger/src/modules/users/dtos/update-user.dto.ts +23 -0
- package/templates/swagger/src/modules/users/users.controller.ts +214 -0
- package/templates/swagger-mongoose/src/modules/auth/dtos/login.dto.ts +20 -0
- package/templates/swagger-mongoose/src/modules/auth/dtos/signup.dto.ts +14 -0
- package/templates/swagger-mongoose/src/modules/users/dtos/update-profile.dto.ts +40 -0
- package/templates/swagger-mongoose/src/modules/users/dtos/update-user.dto.ts +23 -0
package/src/postSetup.js
ADDED
|
@@ -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
|
+
};
|