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/bin/cli.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-nestjs-auth CLI
|
|
4
|
+
* Scaffold production-ready NestJS authentication projects
|
|
5
|
+
*
|
|
6
|
+
* @author Sabin Shrestha <sabin.shrestha.er@gmail.com>
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { program } = require('commander');
|
|
11
|
+
const fs = require('fs-extra');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
const chalk = require('chalk');
|
|
15
|
+
|
|
16
|
+
// Resolve paths relative to the package root
|
|
17
|
+
const packageRoot = path.join(__dirname, '..');
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
CLI_VERSION,
|
|
21
|
+
ORM_OPTIONS,
|
|
22
|
+
DATABASE_OPTIONS,
|
|
23
|
+
validateAppName,
|
|
24
|
+
checkNodeVersion,
|
|
25
|
+
getInstallCommand,
|
|
26
|
+
promptForProjectDetails,
|
|
27
|
+
generateProject,
|
|
28
|
+
handlePostSetup,
|
|
29
|
+
printManualInstructions,
|
|
30
|
+
generateModule,
|
|
31
|
+
} = require(path.join(packageRoot, 'src'));
|
|
32
|
+
|
|
33
|
+
program
|
|
34
|
+
.name('speedrun-cli')
|
|
35
|
+
.version(CLI_VERSION)
|
|
36
|
+
.description('Create a production-ready NestJS authentication system with your choice of ORM and database');
|
|
37
|
+
|
|
38
|
+
// ==================== GENERATE COMMAND ====================
|
|
39
|
+
program
|
|
40
|
+
.command('generate [module-name]')
|
|
41
|
+
.alias('g')
|
|
42
|
+
.description('Generate a new CRUD module')
|
|
43
|
+
.action(async (moduleName) => {
|
|
44
|
+
try {
|
|
45
|
+
console.log(chalk.cyan(`\n⚡️ create-nestjs-auth v${CLI_VERSION} Module Generator\n`));
|
|
46
|
+
await generateModule(moduleName, process.cwd());
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(chalk.red('\n❌ Module generation failed:'));
|
|
49
|
+
console.error(error);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ==================== MAIN SCAFFOLD COMMAND (DEFAULT) ====================
|
|
54
|
+
program
|
|
55
|
+
.command('create [app-name]', { isDefault: true })
|
|
56
|
+
.description('Create a new NestJS Auth project')
|
|
57
|
+
.option('--skip-install', 'Skip automatic dependency installation')
|
|
58
|
+
.option('--package-manager <pm>', 'Package manager to use (npm|pnpm|yarn|bun)')
|
|
59
|
+
.option('--skip-git', 'Skip git repository initialization')
|
|
60
|
+
.option('--orm <orm>', 'ORM to use (prisma|typeorm|drizzle|mongoose)')
|
|
61
|
+
.option('--database <db>', 'Database to use (postgres|mysql|sqlite|mongodb)')
|
|
62
|
+
.option('--swagger', 'Add Swagger API documentation')
|
|
63
|
+
.option('--base-crud', 'Generate Base CRUD Architecture (BaseService, BaseController & Swagger helpers in src/common/base)')
|
|
64
|
+
.option('--yes', 'Skip all prompts and use defaults')
|
|
65
|
+
.action(async (appName, options) => {
|
|
66
|
+
try {
|
|
67
|
+
console.log(chalk.cyan(`\n⚡️ create-nestjs-auth v${CLI_VERSION}\n`));
|
|
68
|
+
console.log(chalk.gray('Production-ready NestJS authentication - Now with ORM & Database choices!\n'));
|
|
69
|
+
|
|
70
|
+
// Check Node.js version
|
|
71
|
+
checkNodeVersion();
|
|
72
|
+
|
|
73
|
+
// Interactive mode - prompt for missing information
|
|
74
|
+
const projectOptions = await promptForProjectDetails(appName, options);
|
|
75
|
+
appName = projectOptions.appName;
|
|
76
|
+
|
|
77
|
+
// Validate app name
|
|
78
|
+
validateAppName(appName);
|
|
79
|
+
|
|
80
|
+
const targetDir = path.join(process.cwd(), appName);
|
|
81
|
+
|
|
82
|
+
// Check if directory exists
|
|
83
|
+
if (await fs.pathExists(targetDir)) {
|
|
84
|
+
console.error(chalk.red(`❌ Directory "${appName}" already exists`));
|
|
85
|
+
console.error(chalk.yellow(' Please choose a different name or remove the existing directory'));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(chalk.blue(`\n🚀 Creating ${chalk.bold(appName)}...`));
|
|
90
|
+
console.log(chalk.gray(` ORM: ${ORM_OPTIONS[projectOptions.orm]?.name || projectOptions.orm}`));
|
|
91
|
+
console.log(chalk.gray(` Database: ${DATABASE_OPTIONS[projectOptions.database]?.name || projectOptions.database}`));
|
|
92
|
+
if (projectOptions.swagger) {
|
|
93
|
+
console.log(chalk.gray(` Swagger: ${chalk.green('Enabled')}`));
|
|
94
|
+
}
|
|
95
|
+
if (projectOptions.baseCrud) {
|
|
96
|
+
console.log(chalk.gray(` Base CRUD: ${chalk.green('Enabled')}`));
|
|
97
|
+
}
|
|
98
|
+
console.log('');
|
|
99
|
+
|
|
100
|
+
// Generate the project
|
|
101
|
+
await generateProject(targetDir, projectOptions);
|
|
102
|
+
|
|
103
|
+
// Update package.json
|
|
104
|
+
console.log(chalk.gray(' Updating package.json...'));
|
|
105
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
106
|
+
|
|
107
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
108
|
+
const packageJson = await fs.readJSON(packageJsonPath);
|
|
109
|
+
packageJson.name = appName;
|
|
110
|
+
packageJson.version = '0.0.1';
|
|
111
|
+
delete packageJson.private;
|
|
112
|
+
await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Create .env from .env.example
|
|
116
|
+
console.log(chalk.gray(' Setting up environment variables...'));
|
|
117
|
+
const envExamplePath = path.join(targetDir, '.env.example');
|
|
118
|
+
const envPath = path.join(targetDir, '.env');
|
|
119
|
+
|
|
120
|
+
if (await fs.pathExists(envExamplePath)) {
|
|
121
|
+
const envExample = await fs.readFile(envExamplePath, 'utf8');
|
|
122
|
+
await fs.writeFile(envPath, envExample);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Install dependencies
|
|
126
|
+
if (projectOptions.installDependencies) {
|
|
127
|
+
const pm = projectOptions.packageManager;
|
|
128
|
+
const installCmd = getInstallCommand(pm);
|
|
129
|
+
|
|
130
|
+
console.log(chalk.yellow(`\n📦 Installing dependencies with ${chalk.bold(pm)}...`));
|
|
131
|
+
console.log(chalk.gray(` Running: ${installCmd}\n`));
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
execSync(installCmd, {
|
|
135
|
+
cwd: targetDir,
|
|
136
|
+
stdio: 'inherit',
|
|
137
|
+
timeout: 300000,
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(chalk.red('\n❌ Dependency installation failed'));
|
|
141
|
+
console.error(chalk.yellow(' You can try installing manually:'));
|
|
142
|
+
console.error(chalk.cyan(` cd ${appName}`));
|
|
143
|
+
console.error(chalk.cyan(` ${installCmd}`));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
console.log(chalk.gray('\n Skipping dependency installation (--skip-install)'));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Initialize git repository
|
|
151
|
+
if (projectOptions.initializeGit) {
|
|
152
|
+
console.log(chalk.yellow('\n🔧 Initializing git repository...'));
|
|
153
|
+
try {
|
|
154
|
+
execSync('git init', { cwd: targetDir, stdio: 'ignore' });
|
|
155
|
+
execSync('git add -A', { cwd: targetDir, stdio: 'ignore' });
|
|
156
|
+
execSync('git commit -m "Initial commit from create-nestjs-auth"', {
|
|
157
|
+
cwd: targetDir,
|
|
158
|
+
stdio: 'ignore',
|
|
159
|
+
});
|
|
160
|
+
console.log(chalk.gray(' Git repository initialized with initial commit'));
|
|
161
|
+
} catch {
|
|
162
|
+
console.warn(chalk.yellow(' ⚠️ Git initialization failed (git may not be installed)'));
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
console.log(chalk.gray('\n Skipping git initialization (--skip-git)'));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Post-setup interactive prompts
|
|
169
|
+
const completedInteractiveSetup = await handlePostSetup(
|
|
170
|
+
targetDir,
|
|
171
|
+
appName,
|
|
172
|
+
{
|
|
173
|
+
...projectOptions,
|
|
174
|
+
skipInstall: !projectOptions.installDependencies,
|
|
175
|
+
yes: options.yes,
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Show manual instructions if interactive setup was skipped
|
|
180
|
+
if (!completedInteractiveSetup) {
|
|
181
|
+
printManualInstructions(appName, projectOptions);
|
|
182
|
+
} else {
|
|
183
|
+
console.log(chalk.white('\n📖 Documentation: https://github.com/masabinhok/create-nestjs-auth'));
|
|
184
|
+
console.log(chalk.white('🐛 Issues: https://github.com/masabinhok/create-nestjs-auth/issues'));
|
|
185
|
+
if (projectOptions.swagger) {
|
|
186
|
+
console.log(chalk.cyan('📄 Swagger docs: http://localhost:8080/api/docs'));
|
|
187
|
+
}
|
|
188
|
+
if (projectOptions.baseCrud) {
|
|
189
|
+
console.log(chalk.cyan('🏗️ Base CRUD: src/common/base/ — see CRUD_README.md'));
|
|
190
|
+
}
|
|
191
|
+
console.log(chalk.magenta('\nHappy coding! 🎉\n'));
|
|
192
|
+
|
|
193
|
+
// Post-setup module generation hook
|
|
194
|
+
if (projectOptions.baseCrud) {
|
|
195
|
+
const inquirer = require('inquirer');
|
|
196
|
+
const { generateNow } = await inquirer.prompt([{
|
|
197
|
+
type: 'confirm',
|
|
198
|
+
name: 'generateNow',
|
|
199
|
+
message: 'Do you want to generate your first CRUD module now?',
|
|
200
|
+
default: true
|
|
201
|
+
}]);
|
|
202
|
+
|
|
203
|
+
if (generateNow) {
|
|
204
|
+
await generateModule(undefined, targetDir, projectOptions.orm);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error(chalk.red('\n❌ An unexpected error occurred:'));
|
|
211
|
+
console.error(chalk.red(` ${error.message}`));
|
|
212
|
+
|
|
213
|
+
if (error.stack && process.env.DEBUG) {
|
|
214
|
+
console.error(chalk.gray('\n Stack trace:'));
|
|
215
|
+
console.error(chalk.gray(error.stack));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.error(chalk.yellow('\n If this persists, please report it:'));
|
|
219
|
+
console.error(chalk.cyan(' https://github.com/masabinhok/create-nestjs-auth/issues\n'));
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
program.parse(process.argv);
|
package/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-nestjs-auth CLI
|
|
4
|
+
*
|
|
5
|
+
* Entry point for npx/npm execution.
|
|
6
|
+
* All logic has been modularized into src/ directory.
|
|
7
|
+
*
|
|
8
|
+
* @see ./bin/cli.js for the main CLI implementation
|
|
9
|
+
* @see ./src/ for modular source code
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
require('./bin/cli');
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "speedrun-cli",
|
|
3
|
+
"version": "2.6.1",
|
|
4
|
+
"description": "CLI tool to scaffold a production-ready NestJS authentication system with JWT, refresh tokens, and RBAC",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nestjs",
|
|
7
|
+
"authentication",
|
|
8
|
+
"jwt",
|
|
9
|
+
"auth",
|
|
10
|
+
"boilerplate",
|
|
11
|
+
"starter",
|
|
12
|
+
"template",
|
|
13
|
+
"rbac",
|
|
14
|
+
"refresh-token",
|
|
15
|
+
"prisma",
|
|
16
|
+
"drizzle",
|
|
17
|
+
"typeorm",
|
|
18
|
+
"mongoose",
|
|
19
|
+
"postgresql",
|
|
20
|
+
"mysql",
|
|
21
|
+
"mongodb",
|
|
22
|
+
"sqlite",
|
|
23
|
+
"cli",
|
|
24
|
+
"scaffold",
|
|
25
|
+
"generator"
|
|
26
|
+
],
|
|
27
|
+
"author": {
|
|
28
|
+
"name": "Sabin Shrestha",
|
|
29
|
+
"email": "sabin.shrestha.er@gmail.com",
|
|
30
|
+
"url": "https://sabinshrestha69.com.np"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"homepage": "https://github.com/masabinhok/create-nestjs-auth#readme",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/masabinhok/create-nestjs-auth.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/masabinhok/create-nestjs-auth/issues"
|
|
40
|
+
},
|
|
41
|
+
"bin": {
|
|
42
|
+
"create-nestjs-auth": "./index.js"
|
|
43
|
+
},
|
|
44
|
+
"main": "./index.js",
|
|
45
|
+
"type": "commonjs",
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20.0.0"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "node index.js test-output --skip-install --skip-git --yes",
|
|
51
|
+
"test:full": "node index.js test-output",
|
|
52
|
+
"test:prisma": "node index.js test-prisma --orm prisma --database postgres --skip-install --skip-git --yes",
|
|
53
|
+
"test:drizzle": "node index.js test-drizzle --orm drizzle --database postgres --skip-install --skip-git --yes",
|
|
54
|
+
"test:typeorm": "node index.js test-typeorm --orm typeorm --database postgres --skip-install --skip-git --yes",
|
|
55
|
+
"test:mongoose": "node index.js test-mongoose --orm mongoose --skip-install --skip-git --yes",
|
|
56
|
+
"clean": "node -e \"require('fs-extra').removeSync('test-output'); require('fs-extra').removeSync('test-prisma'); require('fs-extra').removeSync('test-drizzle'); require('fs-extra').removeSync('test-typeorm'); require('fs-extra').removeSync('test-mongoose');\"",
|
|
57
|
+
"prepublishOnly": "npm run clean && echo 'Ready to publish create-nestjs-auth'"
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"index.js",
|
|
61
|
+
"bin/",
|
|
62
|
+
"src/",
|
|
63
|
+
"templates/",
|
|
64
|
+
"README.md",
|
|
65
|
+
"LICENSE",
|
|
66
|
+
"CHANGELOG.md"
|
|
67
|
+
],
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"chalk": "^4.1.2",
|
|
70
|
+
"commander": "^14.0.0",
|
|
71
|
+
"fs-extra": "^11.2.0",
|
|
72
|
+
"inquirer": "^8.2.6"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Constants and Configuration
|
|
3
|
+
* @module constants
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ORM_OPTIONS = {
|
|
7
|
+
prisma: {
|
|
8
|
+
name: 'Prisma',
|
|
9
|
+
description: 'Next-generation ORM with type safety',
|
|
10
|
+
databases: ['postgres', 'mysql', 'sqlite'],
|
|
11
|
+
},
|
|
12
|
+
typeorm: {
|
|
13
|
+
name: 'TypeORM',
|
|
14
|
+
description: 'Traditional ORM with decorators',
|
|
15
|
+
databases: ['postgres', 'mysql', 'sqlite'],
|
|
16
|
+
},
|
|
17
|
+
drizzle: {
|
|
18
|
+
name: 'Drizzle',
|
|
19
|
+
description: 'Lightweight TypeScript ORM',
|
|
20
|
+
databases: ['postgres', 'mysql', 'sqlite'],
|
|
21
|
+
},
|
|
22
|
+
mongoose: {
|
|
23
|
+
name: 'Mongoose',
|
|
24
|
+
description: 'MongoDB ODM (MongoDB only)',
|
|
25
|
+
databases: ['mongodb'],
|
|
26
|
+
fixedDatabase: true,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const DATABASE_OPTIONS = {
|
|
31
|
+
postgres: {
|
|
32
|
+
name: 'PostgreSQL',
|
|
33
|
+
urlTemplate: 'postgresql://user:password@localhost:5432/database_name',
|
|
34
|
+
urlPrefix: ['postgresql://', 'postgres://'],
|
|
35
|
+
},
|
|
36
|
+
mysql: {
|
|
37
|
+
name: 'MySQL',
|
|
38
|
+
urlTemplate: 'mysql://user:password@localhost:3306/database_name',
|
|
39
|
+
urlPrefix: ['mysql://'],
|
|
40
|
+
},
|
|
41
|
+
sqlite: {
|
|
42
|
+
name: 'SQLite',
|
|
43
|
+
urlTemplate: 'file:./dev.db',
|
|
44
|
+
urlPrefix: ['file:'],
|
|
45
|
+
devOnly: true,
|
|
46
|
+
},
|
|
47
|
+
mongodb: {
|
|
48
|
+
name: 'MongoDB',
|
|
49
|
+
urlTemplate: 'mongodb://user:password@localhost:27017/database_name',
|
|
50
|
+
urlPrefix: ['mongodb://', 'mongodb+srv://'],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const RESERVED_NAMES = ['node_modules', 'favicon.ico'];
|
|
55
|
+
|
|
56
|
+
const path = require('path');
|
|
57
|
+
const packageJson = require(path.join(__dirname, '..', 'package.json'));
|
|
58
|
+
const CLI_VERSION = packageJson.version;
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
ORM_OPTIONS,
|
|
62
|
+
DATABASE_OPTIONS,
|
|
63
|
+
RESERVED_NAMES,
|
|
64
|
+
CLI_VERSION,
|
|
65
|
+
};
|
package/src/generator.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Generation Logic
|
|
3
|
+
* @module generator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const { ORM_OPTIONS } = require('./constants');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generates the project by merging base, ORM, and database templates
|
|
13
|
+
* @param {string} targetDir - Target directory for the project
|
|
14
|
+
* @param {object} options - Generation options
|
|
15
|
+
*/
|
|
16
|
+
async function generateProject(targetDir, options) {
|
|
17
|
+
const { orm, database, swagger, baseCrud } = options;
|
|
18
|
+
const templatesDir = path.join(__dirname, '..', 'templates');
|
|
19
|
+
|
|
20
|
+
const baseDir = path.join(templatesDir, 'base');
|
|
21
|
+
const ormDir = path.join(templatesDir, 'orm', orm);
|
|
22
|
+
const dbDir = path.join(templatesDir, 'database', database);
|
|
23
|
+
const swaggerDir = path.join(templatesDir, 'swagger');
|
|
24
|
+
const swaggerMongooseDir = path.join(templatesDir, 'swagger-mongoose');
|
|
25
|
+
|
|
26
|
+
const useNewStructure = await fs.pathExists(baseDir);
|
|
27
|
+
|
|
28
|
+
if (useNewStructure) {
|
|
29
|
+
await generateFromModularTemplates(targetDir, { baseDir, ormDir, dbDir, orm, swagger, swaggerDir, swaggerMongooseDir, baseCrud });
|
|
30
|
+
} else {
|
|
31
|
+
await generateFromLegacyTemplate(targetDir);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generates project from modular template structure
|
|
37
|
+
* @param {string} targetDir - Target directory
|
|
38
|
+
* @param {object} dirs - Directory paths
|
|
39
|
+
*/
|
|
40
|
+
async function generateFromModularTemplates(targetDir, { baseDir, ormDir, dbDir, orm, swagger, swaggerDir, swaggerMongooseDir, baseCrud }) {
|
|
41
|
+
console.log(chalk.gray(' Using modular template structure...'));
|
|
42
|
+
|
|
43
|
+
// Ensure target directory exists
|
|
44
|
+
await fs.ensureDir(targetDir);
|
|
45
|
+
|
|
46
|
+
// Step 1: Copy base template
|
|
47
|
+
console.log(chalk.gray(' Copying base template...'));
|
|
48
|
+
await fs.copy(baseDir, targetDir, {
|
|
49
|
+
filter: createCopyFilter(baseDir),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Step 2: Rename gitignore to .gitignore (npm excludes .gitignore files from packages)
|
|
53
|
+
const gitignoreSrc = path.join(targetDir, 'gitignore');
|
|
54
|
+
const gitignoreDest = path.join(targetDir, '.gitignore');
|
|
55
|
+
if (await fs.pathExists(gitignoreSrc)) {
|
|
56
|
+
await fs.rename(gitignoreSrc, gitignoreDest);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Step 3: Apply ORM adapter
|
|
60
|
+
if (await fs.pathExists(ormDir)) {
|
|
61
|
+
console.log(chalk.gray(` Applying ${ORM_OPTIONS[orm]?.name || orm} adapter...`));
|
|
62
|
+
await fs.copy(ormDir, targetDir, {
|
|
63
|
+
overwrite: true,
|
|
64
|
+
filter: createCopyFilter(ormDir, ['package.json']),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await mergePackageJson(ormDir, targetDir);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Step 4: Apply database configuration
|
|
71
|
+
if (await fs.pathExists(dbDir)) {
|
|
72
|
+
console.log(chalk.gray(` Configuring database...`));
|
|
73
|
+
await fs.copy(dbDir, targetDir, {
|
|
74
|
+
overwrite: true,
|
|
75
|
+
filter: createOrmSpecificFilter(orm, dbDir),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await mergePackageJson(dbDir, targetDir, true);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Step 5: Apply Swagger documentation overlay
|
|
82
|
+
if (swagger) {
|
|
83
|
+
console.log(chalk.gray(' Adding Swagger documentation...'));
|
|
84
|
+
if (await fs.pathExists(swaggerDir)) {
|
|
85
|
+
await fs.copy(swaggerDir, targetDir, {
|
|
86
|
+
overwrite: true,
|
|
87
|
+
filter: createCopyFilter(swaggerDir),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Apply ORM-specific Swagger overrides (e.g., Mongoose DTOs)
|
|
92
|
+
if (orm === 'mongoose' && await fs.pathExists(swaggerMongooseDir)) {
|
|
93
|
+
await fs.copy(swaggerMongooseDir, targetDir, {
|
|
94
|
+
overwrite: true,
|
|
95
|
+
filter: createCopyFilter(swaggerMongooseDir),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Add Swagger dependencies to package.json
|
|
100
|
+
await addSwaggerDependencies(targetDir);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Step 6: Apply Base CRUD Architecture overlay
|
|
104
|
+
if (baseCrud) {
|
|
105
|
+
// 6a. Shared layer: abstract base + shared DTOs + shared controller
|
|
106
|
+
const baseCrudDir = path.join(__dirname, '..', 'templates', 'base-crud');
|
|
107
|
+
if (await fs.pathExists(baseCrudDir)) {
|
|
108
|
+
console.log(chalk.gray(' Adding Base CRUD Architecture (abstract layer + shared DTOs)...'));
|
|
109
|
+
await fs.copy(baseCrudDir, targetDir, {
|
|
110
|
+
overwrite: true,
|
|
111
|
+
filter: createCopyFilter(baseCrudDir),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 6b. ORM-specific ProductModule example (overrides products.service.ts + module)
|
|
116
|
+
const baseCrudOrmDir = path.join(__dirname, '..', 'templates', `base-crud-${orm}`);
|
|
117
|
+
if (await fs.pathExists(baseCrudOrmDir)) {
|
|
118
|
+
console.log(chalk.gray(` Adding ${ORM_OPTIONS[orm]?.name || orm} ProductModule example...`));
|
|
119
|
+
await fs.copy(baseCrudOrmDir, targetDir, {
|
|
120
|
+
overwrite: true,
|
|
121
|
+
filter: createCopyFilter(baseCrudOrmDir),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Generates project from legacy single-template structure
|
|
129
|
+
* @param {string} targetDir - Target directory
|
|
130
|
+
*/
|
|
131
|
+
async function generateFromLegacyTemplate(targetDir) {
|
|
132
|
+
console.log(chalk.gray(' Using legacy template structure...'));
|
|
133
|
+
const templateDir = path.join(__dirname, '..', 'template');
|
|
134
|
+
|
|
135
|
+
if (!(await fs.pathExists(templateDir))) {
|
|
136
|
+
console.error(chalk.red('❌ Template directory not found'));
|
|
137
|
+
console.error(chalk.yellow(' Please reinstall create-nestjs-auth: npm install -g create-nestjs-auth'));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await fs.copy(templateDir, targetDir, {
|
|
142
|
+
filter: (src) => {
|
|
143
|
+
const relativePath = path.relative(templateDir, src);
|
|
144
|
+
const basename = path.basename(src);
|
|
145
|
+
if (basename === '.gitignore') return true;
|
|
146
|
+
return !relativePath.startsWith('.git' + path.sep) &&
|
|
147
|
+
relativePath !== '.git' &&
|
|
148
|
+
!relativePath.includes('node_modules') &&
|
|
149
|
+
!relativePath.includes('dist');
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates a copy filter function
|
|
156
|
+
* @param {string} baseDir - The base directory being copied from
|
|
157
|
+
* @param {Array<string>} excludeFiles - Additional files to exclude
|
|
158
|
+
* @returns {function} Filter function
|
|
159
|
+
*/
|
|
160
|
+
function createCopyFilter(baseDir, excludeFiles = []) {
|
|
161
|
+
return (src) => {
|
|
162
|
+
const basename = path.basename(src);
|
|
163
|
+
const relativePath = path.relative(baseDir, src);
|
|
164
|
+
|
|
165
|
+
// Allow gitignore (renamed from .gitignore for npm compatibility)
|
|
166
|
+
if (basename === 'gitignore' || basename === '.gitignore') return true;
|
|
167
|
+
if (excludeFiles.includes(basename)) return false;
|
|
168
|
+
|
|
169
|
+
// Check for node_modules and .git only within the template directory
|
|
170
|
+
return !relativePath.includes('node_modules') &&
|
|
171
|
+
!relativePath.includes(path.sep + '.git') &&
|
|
172
|
+
basename !== '.git';
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Creates ORM-specific filter for database templates
|
|
178
|
+
* @param {string} orm - Selected ORM
|
|
179
|
+
* @param {string} dbDir - Database directory path
|
|
180
|
+
* @returns {function} Filter function
|
|
181
|
+
*/
|
|
182
|
+
function createOrmSpecificFilter(orm, dbDir) {
|
|
183
|
+
const drizzleFiles = ['drizzle.config.ts', 'drizzle'];
|
|
184
|
+
const prismaFiles = ['prisma'];
|
|
185
|
+
|
|
186
|
+
return (src) => {
|
|
187
|
+
const basename = path.basename(src);
|
|
188
|
+
const relativePath = path.relative(dbDir, src);
|
|
189
|
+
|
|
190
|
+
// Allow gitignore (renamed from .gitignore for npm compatibility)
|
|
191
|
+
if (basename === 'gitignore' || basename === '.gitignore') return true;
|
|
192
|
+
// Check for node_modules only within the template directory (not in the CLI install path)
|
|
193
|
+
if (relativePath.includes('node_modules') || basename === 'package.json') return false;
|
|
194
|
+
|
|
195
|
+
// Filter based on ORM
|
|
196
|
+
if (orm === 'prisma') {
|
|
197
|
+
if (drizzleFiles.some((f) => relativePath.startsWith(f) || basename === f)) return false;
|
|
198
|
+
if (relativePath.includes('src' + path.sep + 'database')) return false;
|
|
199
|
+
} else if (orm === 'drizzle') {
|
|
200
|
+
if (prismaFiles.some((f) => relativePath.startsWith(f) || basename === f)) return false;
|
|
201
|
+
} else if (orm === 'typeorm' || orm === 'mongoose') {
|
|
202
|
+
if (drizzleFiles.some((f) => relativePath.startsWith(f) || basename === f)) return false;
|
|
203
|
+
if (prismaFiles.some((f) => relativePath.startsWith(f) || basename === f)) return false;
|
|
204
|
+
if (relativePath.includes('src' + path.sep + 'database')) return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return true;
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Merges package.json files
|
|
213
|
+
* @param {string} sourceDir - Source directory
|
|
214
|
+
* @param {string} targetDir - Target directory
|
|
215
|
+
* @param {boolean} dependenciesOnly - Only merge dependencies
|
|
216
|
+
*/
|
|
217
|
+
async function mergePackageJson(sourceDir, targetDir, dependenciesOnly = false) {
|
|
218
|
+
const sourcePackageJsonPath = path.join(sourceDir, 'package.json');
|
|
219
|
+
const targetPackageJsonPath = path.join(targetDir, 'package.json');
|
|
220
|
+
|
|
221
|
+
if (!(await fs.pathExists(sourcePackageJsonPath))) return;
|
|
222
|
+
|
|
223
|
+
const sourcePackageJson = await fs.readJSON(sourcePackageJsonPath);
|
|
224
|
+
let targetPackageJson = {};
|
|
225
|
+
|
|
226
|
+
// Ensure target directory exists before writing
|
|
227
|
+
await fs.ensureDir(targetDir);
|
|
228
|
+
|
|
229
|
+
if (await fs.pathExists(targetPackageJsonPath)) {
|
|
230
|
+
targetPackageJson = await fs.readJSON(targetPackageJsonPath);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (dependenciesOnly) {
|
|
234
|
+
targetPackageJson = {
|
|
235
|
+
...targetPackageJson,
|
|
236
|
+
dependencies: { ...targetPackageJson.dependencies, ...sourcePackageJson.dependencies },
|
|
237
|
+
devDependencies: { ...targetPackageJson.devDependencies, ...sourcePackageJson.devDependencies },
|
|
238
|
+
};
|
|
239
|
+
} else {
|
|
240
|
+
targetPackageJson = {
|
|
241
|
+
...targetPackageJson,
|
|
242
|
+
...sourcePackageJson,
|
|
243
|
+
dependencies: { ...targetPackageJson.dependencies, ...sourcePackageJson.dependencies },
|
|
244
|
+
devDependencies: { ...targetPackageJson.devDependencies, ...sourcePackageJson.devDependencies },
|
|
245
|
+
scripts: { ...targetPackageJson.scripts, ...sourcePackageJson.scripts },
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await fs.writeJSON(targetPackageJsonPath, targetPackageJson, { spaces: 2 });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Adds Swagger dependencies to the project's package.json
|
|
254
|
+
* @param {string} targetDir - Target directory
|
|
255
|
+
*/
|
|
256
|
+
async function addSwaggerDependencies(targetDir) {
|
|
257
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
258
|
+
if (!(await fs.pathExists(packageJsonPath))) return;
|
|
259
|
+
|
|
260
|
+
const packageJson = await fs.readJSON(packageJsonPath);
|
|
261
|
+
packageJson.dependencies = {
|
|
262
|
+
...packageJson.dependencies,
|
|
263
|
+
'@nestjs/swagger': '^11.0.0',
|
|
264
|
+
'swagger-ui-express': '^5.0.1',
|
|
265
|
+
};
|
|
266
|
+
await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
module.exports = {
|
|
270
|
+
generateProject,
|
|
271
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module exports for CLI source files
|
|
3
|
+
* @module src
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
...require('./constants'),
|
|
8
|
+
...require('./utils'),
|
|
9
|
+
...require('./prompts'),
|
|
10
|
+
...require('./generator'),
|
|
11
|
+
...require('./postSetup'),
|
|
12
|
+
...require('./moduleGenerator'),
|
|
13
|
+
};
|