create-wabck 1.0.0

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/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # wabck
2
+
3
+ > **Scaffold production-ready Express.js backends in seconds.**
4
+
5
+ ```bash
6
+ npx wabck my-api
7
+ ```
8
+
9
+ or
10
+
11
+ ```bash
12
+ npm create wabck@latest my-api
13
+ ```
14
+
15
+ ---
16
+
17
+ ## ✨ Features
18
+
19
+ | Feature | Details |
20
+ |---------|---------|
21
+ | 🌐 Language | JavaScript (ESM) or TypeScript |
22
+ | πŸ—„ Database | MongoDB, PostgreSQL (Prisma), MySQL, SQLite, None |
23
+ | πŸ” Auth | JWT, Clerk, Firebase, None |
24
+ | βœ… Validation | Zod, Joi, Express Validator |
25
+ | πŸ”— API Style | REST or GraphQL (Apollo Server 4) |
26
+ | πŸ“– Docs | Swagger / OpenAPI 3 |
27
+ | 🐳 Docker | Multi-stage Dockerfile + docker-compose |
28
+ | ⚑ Package managers | npm, pnpm, yarn, bun |
29
+
30
+ ## 🎨 CLI Experience
31
+
32
+ - **ASCII art banner** with gradient colors (figlet + gradient-string)
33
+ - **Animated spinners** for every step (ora)
34
+ - **Color-coded prompts** (chalk)
35
+ - **Boxed success summary** with numbered next steps
36
+
37
+ ## πŸ— Generated Structure
38
+
39
+ ```
40
+ my-api/
41
+ β”œβ”€β”€ src/
42
+ β”‚ β”œβ”€β”€ config/ db.js + env.js [+ swagger.js]
43
+ β”‚ β”œβ”€β”€ controllers/ user.controller.js
44
+ β”‚ β”œβ”€β”€ middlewares/ error, notFound, rateLimiter, auth
45
+ β”‚ β”œβ”€β”€ models/ user.model.js
46
+ β”‚ β”œβ”€β”€ routes/ index, health, user [+ auth]
47
+ β”‚ β”œβ”€β”€ services/ user.service.js
48
+ β”‚ β”œβ”€β”€ utils/ asyncHandler, ApiError, ApiResponse
49
+ β”‚ β”œβ”€β”€ validators/ user.validator.js
50
+ β”‚ β”œβ”€β”€ constants/ HTTP_STATUS, ROLES, PAGINATION
51
+ β”‚ β”œβ”€β”€ helpers/ token.helper.js (JWT)
52
+ β”‚ β”œβ”€β”€ types/ (TypeScript only)
53
+ β”‚ β”œβ”€β”€ graphql/ (GraphQL only)
54
+ β”‚ β”œβ”€β”€ app.js
55
+ β”‚ └── server.js
56
+ β”œβ”€β”€ .env.example
57
+ β”œβ”€β”€ .prettierrc
58
+ β”œβ”€β”€ eslint.config.js
59
+ β”œβ”€β”€ Dockerfile (optional)
60
+ └── docker-compose.yml (optional)
61
+ ```
62
+
63
+ ## πŸ”— Generated Endpoints
64
+
65
+ | Method | Path | Description |
66
+ |--------|------|-------------|
67
+ | GET | `/api/v1/health` | Health + uptime |
68
+ | GET | `/api/v1/users` | List users |
69
+ | POST | `/api/v1/users` | Create user |
70
+ | GET | `/api/v1/users/:id` | Get user |
71
+ | PUT | `/api/v1/users/:id` | Update user |
72
+ | DELETE | `/api/v1/users/:id` | Delete user |
73
+ | POST | `/api/v1/auth/register` | Register (JWT) |
74
+ | POST | `/api/v1/auth/login` | Login (JWT) |
75
+ | GET | `/api/v1/auth/me` | Profile (JWT) |
76
+
77
+ ## πŸ“œ License
78
+
79
+ MIT
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "create-wabck",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold production-ready Express.js backends in seconds",
5
+ "type": "module",
6
+ "bin": {
7
+ "wabck": "src/index.js",
8
+ "create-wabck": "src/index.js"
9
+ },
10
+ "files": [
11
+ "src"
12
+ ],
13
+ "scripts": {
14
+ "start": "node src/index.js",
15
+ "dev": "node src/index.js"
16
+ },
17
+ "dependencies": {
18
+ "@inquirer/prompts": "^7.1.0",
19
+ "boxen": "^8.0.1",
20
+ "chalk": "^5.3.0",
21
+ "execa": "^9.3.0",
22
+ "figlet": "^1.7.0",
23
+ "fs-extra": "^11.2.0",
24
+ "gradient-string": "^3.0.0",
25
+ "ora": "^8.1.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ },
30
+ "keywords": [
31
+ "express",
32
+ "backend",
33
+ "scaffold",
34
+ "boilerplate",
35
+ "cli",
36
+ "create-app",
37
+ "nodejs",
38
+ "typescript",
39
+ "rest",
40
+ "graphql"
41
+ ],
42
+ "author": "w4simz",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": ""
47
+ }
48
+ }
@@ -0,0 +1,201 @@
1
+ import path from 'path';
2
+ import { writeFile, createDir, pathExists, remove } from './utils/fs.js';
3
+ import { log, spinner } from './utils/logger.js';
4
+ import { buildPackageJson } from './utils/pkg.js';
5
+ import * as T from './templates/index.js';
6
+
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+ // Project Generator
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+
11
+ /**
12
+ * Orchestrates full project scaffolding:
13
+ * 1. Validate that the target directory doesn't exist
14
+ * 2. Create directory structure
15
+ * 3. Generate all files from templates
16
+ *
17
+ * On any failure, rolls back all created directories/files.
18
+ *
19
+ * @param {object} options - Resolved user options from prompts
20
+ * @returns {Promise<string>} - Absolute path to the created project
21
+ */
22
+ export async function generate(options) {
23
+ const { projectName, language, database, auth, validation, apiStyle, docker } = options;
24
+ const isTS = language === 'typescript';
25
+ const ext = isTS ? 'ts' : 'js';
26
+ const hasDB = database !== 'none';
27
+ const hasAuth = auth !== 'none';
28
+ const hasJwt = auth === 'jwt';
29
+ const isGql = apiStyle === 'graphql';
30
+ const hasValidation = validation && validation !== 'none';
31
+
32
+ const projectDir = path.resolve(process.cwd(), projectName);
33
+ const createdDirs = [];
34
+
35
+ // ── Pre-flight: check directory doesn't already exist ─────────────────────
36
+ if (await pathExists(projectDir)) {
37
+ throw new Error(
38
+ `Directory "${projectName}" already exists.\n` +
39
+ ` Please choose a different name or remove the existing directory.`
40
+ );
41
+ }
42
+
43
+ // ── Step 1: Create directory skeleton ─────────────────────────────────────
44
+ const dirSpin = spinner('Creating project structure…').start();
45
+
46
+ try {
47
+ const dirs = [
48
+ '', // root
49
+ 'src',
50
+ 'src/config',
51
+ 'src/controllers',
52
+ 'src/routes',
53
+ 'src/middlewares',
54
+ 'src/services',
55
+ 'src/utils',
56
+ 'src/constants',
57
+ 'src/helpers',
58
+ ];
59
+
60
+ if (hasDB) dirs.push('src/models');
61
+ if (isTS) dirs.push('src/types');
62
+ if (isGql) dirs.push('src/graphql');
63
+ if (hasValidation) dirs.push('src/validators');
64
+ if (database === 'postgresql') dirs.push('prisma');
65
+
66
+ for (const d of dirs) {
67
+ const full = d ? path.join(projectDir, d) : projectDir;
68
+ await createDir(full);
69
+ createdDirs.push(full);
70
+ }
71
+
72
+ dirSpin.succeed('Project structure created');
73
+ } catch (err) {
74
+ dirSpin.fail('Failed to create directory structure');
75
+ await rollback(createdDirs);
76
+ throw err;
77
+ }
78
+
79
+ // ── Step 2: Write all files ────────────────────────────────────────────────
80
+ const fileSpin = spinner('Writing files…').start();
81
+
82
+ try {
83
+ const write = async (relPath, content) =>
84
+ writeFile(path.join(projectDir, relPath), content);
85
+
86
+ // ── package.json ──────────────────────────────────────────────────────
87
+ await write('package.json', JSON.stringify(buildPackageJson(options), null, 2));
88
+
89
+ // ── Root config files ─────────────────────────────────────────────────
90
+ await write('.env', T.envFile(options));
91
+ await write('.env.example', T.envExample(options));
92
+ await write('.gitignore', T.gitignore());
93
+ await write('.prettierrc', T.prettierrc());
94
+ await write('eslint.config.js', T.eslintConfig(options));
95
+ await write('README.md', T.readme(options));
96
+
97
+ // ── TypeScript extras ─────────────────────────────────────────────────
98
+ if (isTS) {
99
+ await write('tsconfig.json', T.tsconfig());
100
+ await write(`src/types/index.ts`, T.tsTypes(options));
101
+ }
102
+
103
+ // ── Core application files ────────────────────────────────────────────
104
+ await write(`src/app.${ext}`, T.app(options));
105
+ await write(`src/server.${ext}`, T.server(options));
106
+
107
+ // ── Config ────────────────────────────────────────────────────────────
108
+ await write(`src/config/db.${ext}`, T.db(options));
109
+ await write(`src/config/env.${ext}`, T.envConfig(options));
110
+
111
+ // ── Routes ────────────────────────────────────────────────────────────
112
+ await write(`src/routes/index.${ext}`, T.routesIndex(options));
113
+ await write(`src/routes/health.route.${ext}`, T.healthRoute(options));
114
+ await write(`src/routes/user.route.${ext}`, T.userRoute(options));
115
+ if (hasJwt) {
116
+ await write(`src/routes/auth.route.${ext}`, T.authRoute(options));
117
+ }
118
+
119
+ // ── Controllers ───────────────────────────────────────────────────────
120
+ await write(`src/controllers/user.controller.${ext}`, T.userController(options));
121
+
122
+ // ── Models ────────────────────────────────────────────────────────────
123
+ if (hasDB) {
124
+ await write(`src/models/user.model.${ext}`, T.userModel(options));
125
+ }
126
+ if (database === 'postgresql') {
127
+ await write('prisma/schema.prisma', T.prismaSchema(options));
128
+ }
129
+
130
+ // ── Middlewares ───────────────────────────────────────────────────────
131
+ await write(`src/middlewares/error.middleware.${ext}`, T.errorMiddleware(options));
132
+ await write(`src/middlewares/notFound.middleware.${ext}`, T.notFoundMiddleware(options));
133
+ await write(`src/middlewares/rateLimiter.middleware.${ext}`, T.rateLimiterMiddleware(options));
134
+ if (hasAuth) {
135
+ await write(`src/middlewares/auth.middleware.${ext}`, T.authMiddleware(options));
136
+ }
137
+
138
+ // ── Services ──────────────────────────────────────────────────────────
139
+ await write(`src/services/user.service.${ext}`, T.userService(options));
140
+
141
+ // ── Utilities ─────────────────────────────────────────────────────────
142
+ await write(`src/utils/asyncHandler.${ext}`, T.asyncHandler(options));
143
+ await write(`src/utils/apiError.${ext}`, T.apiError(options));
144
+ await write(`src/utils/apiResponse.${ext}`, T.apiResponse(options));
145
+
146
+ // ── Validators ────────────────────────────────────────────────────────
147
+ if (hasValidation) {
148
+ await write(`src/validators/user.validator.${ext}`, T.userValidator(options));
149
+ }
150
+
151
+ // ── Constants ─────────────────────────────────────────────────────────
152
+ await write(`src/constants/index.${ext}`, T.constants(options));
153
+
154
+ // ── Helpers ───────────────────────────────────────────────────────────
155
+ if (hasJwt) {
156
+ await write(`src/helpers/token.helper.${ext}`, T.tokenHelper(options));
157
+ }
158
+
159
+ // ── GraphQL ───────────────────────────────────────────────────────────
160
+ if (isGql) {
161
+ await write(`src/graphql/schema.${ext}`, T.graphqlSchema(options));
162
+ await write(`src/graphql/resolvers.${ext}`, T.graphqlResolvers(options));
163
+ await write(`src/graphql/server.${ext}`, T.graphqlServer(options));
164
+ }
165
+
166
+ // ── Docker ────────────────────────────────────────────────────────────
167
+ if (docker) {
168
+ await write('Dockerfile', T.dockerfile(options));
169
+ await write('docker-compose.yml', T.dockerCompose(options));
170
+ await write('.dockerignore', T.dockerignore());
171
+ }
172
+
173
+ fileSpin.succeed('Files written successfully');
174
+ return projectDir;
175
+ } catch (err) {
176
+ fileSpin.fail('Failed to write project files');
177
+ await rollback(createdDirs);
178
+ throw err;
179
+ }
180
+ }
181
+
182
+ // ─────────────────────────────────────────────────────────────────────────────
183
+ // Rollback Helper
184
+ // ─────────────────────────────────────────────────────────────────────────────
185
+
186
+ /**
187
+ * Removes all created directories in reverse order (deepest first).
188
+ * Called when generation fails partway through.
189
+ * @param {string[]} dirs - Array of absolute directory paths to remove
190
+ */
191
+ async function rollback(dirs) {
192
+ log.warn('Rolling back created files…');
193
+ for (const d of [...dirs].reverse()) {
194
+ try {
195
+ await remove(d);
196
+ } catch {
197
+ // Silently continue β€” best-effort cleanup
198
+ }
199
+ }
200
+ log.dim(' Cleanup complete.');
201
+ }
package/src/index.js ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from 'process';
4
+ import path from 'path';
5
+ import chalk from 'chalk';
6
+ import { renderBanner, log, box } from './utils/logger.js';
7
+ import { runPrompts } from './prompts.js';
8
+ import { generate } from './generator.js';
9
+ import { installDependencies, runPrismaGenerate } from './installer.js';
10
+
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+ // Entry Point
13
+ // ─────────────────────────────────────────────────────────────────────────────
14
+
15
+ async function main() {
16
+ // ── 1. Banner ──────────────────────────────────────────────────────────────
17
+ await renderBanner();
18
+
19
+ // ── 2. Parse argv (optional pre-fill for project name) ────────────────────
20
+ const argName = process.argv[2];
21
+
22
+ // ── 3. Interactive prompts ─────────────────────────────────────────────────
23
+ let options;
24
+ try {
25
+ options = await runPrompts(argName);
26
+ } catch (err) {
27
+ // User pressed Ctrl+C or similar
28
+ if (err?.name === 'ExitPromptError' || err?.message?.includes('User force closed')) {
29
+ log.blank();
30
+ log.warn('Scaffold cancelled.');
31
+ process.exit(0);
32
+ }
33
+ throw err;
34
+ }
35
+
36
+ log.blank();
37
+
38
+ // ── 4. Generate project files ──────────────────────────────────────────────
39
+ let projectDir;
40
+ try {
41
+ projectDir = await generate(options);
42
+ } catch (err) {
43
+ log.blank();
44
+ log.error(`Generation failed: ${err.message}`);
45
+ process.exit(1);
46
+ }
47
+
48
+ // ── 5. Install dependencies ────────────────────────────────────────────────
49
+ if (options.installDeps) {
50
+ try {
51
+ await installDependencies(projectDir, options.packageManager);
52
+
53
+ // Run prisma generate after install for PostgreSQL projects
54
+ if (options.database === 'postgresql') {
55
+ await runPrismaGenerate(projectDir, options.packageManager);
56
+ }
57
+ } catch {
58
+ // Installation failure is non-fatal β€” project files are still valid
59
+ log.blank();
60
+ log.warn('Dependencies were not installed automatically. Install them manually:');
61
+ log.dim(` cd ${options.projectName}`);
62
+ log.dim(` ${options.packageManager} ${options.packageManager === 'yarn' ? '' : 'install'}`);
63
+ }
64
+ }
65
+
66
+ // ── 6. Success summary ─────────────────────────────────────────────────────
67
+ log.blank();
68
+ printSuccessSummary(options, projectDir);
69
+ }
70
+
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+ // Success Summary
73
+ // ─────────────────────────────────────────────────────────────────────────────
74
+
75
+ function printSuccessSummary(options, projectDir) {
76
+ const { projectName, language, database, auth, validation, docker, packageManager, installDeps } = options;
77
+ const pm = packageManager;
78
+ const relDir = path.relative(process.cwd(), projectDir) || projectName;
79
+ const devCmd = language === 'typescript' ? `${pm} run dev` : `${pm} run dev`;
80
+
81
+ // Progress recap
82
+ log.success('Project scaffolded successfully!');
83
+ log.blank();
84
+ log.label(' Language', language === 'typescript' ? 'TypeScript' : 'JavaScript');
85
+ log.label(' Database', database === 'none' ? 'None' : database);
86
+ log.label(' Auth', auth === 'none' ? 'None' : auth);
87
+ if (validation && validation !== 'none') log.label(' Validation', validation);
88
+ if (docker) log.label(' Docker', 'Dockerfile + docker-compose.yml');
89
+ log.blank();
90
+
91
+ // Next steps
92
+ const steps = [];
93
+ steps.push(`cd ${relDir}`);
94
+ if (!installDeps) steps.push(`${pm}${pm === 'yarn' ? '' : ' install'}`);
95
+ steps.push('cp .env.example .env');
96
+ if (database === 'postgresql') steps.push('npx prisma migrate dev --name init');
97
+ steps.push(devCmd);
98
+
99
+ const stepsStr = steps.map((s, i) => ` ${chalk.dim(`${i + 1}.`)} ${chalk.cyan(s)}`).join('\n');
100
+
101
+ console.log(
102
+ box(
103
+ chalk.bold.white('πŸš€ Next steps:\n\n') + stepsStr + '\n\n' +
104
+ chalk.dim(' API: ') + chalk.white('http://localhost:5000/api/v1/health'),
105
+ { borderColor: 'green', title: chalk.green('Ready!'), titleAlignment: 'center' }
106
+ )
107
+ );
108
+ }
109
+
110
+ // ─────────────────────────────────────────────────────────────────────────────
111
+ // Run
112
+ // ─────────────────────────────────────────────────────────────────────────────
113
+
114
+ main().catch((err) => {
115
+ console.error(chalk.red('\n Fatal error:'), err.message || err);
116
+ if (process.env.DEBUG) console.error(err.stack);
117
+ process.exit(1);
118
+ });
@@ -0,0 +1,62 @@
1
+ import { execa } from 'execa';
2
+ import { spinner, log } from './utils/logger.js';
3
+ import { getInstallCmd } from './utils/pkg.js';
4
+
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // Dependency Installer
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+
9
+ /**
10
+ * Installs project dependencies in the generated project directory.
11
+ * Uses the package manager chosen by the user.
12
+ *
13
+ * @param {string} projectDir - Absolute path to the generated project
14
+ * @param {string} packageManager - 'npm' | 'pnpm' | 'yarn' | 'bun'
15
+ */
16
+ export async function installDependencies(projectDir, packageManager) {
17
+ const { cmd, args } = getInstallCmd(packageManager);
18
+ const spin = spinner(`Installing dependencies with ${packageManager}…`).start();
19
+
20
+ try {
21
+ await execa(cmd, args, {
22
+ cwd: projectDir,
23
+ // Pipe output so the spinner stays clean; errors are captured
24
+ stdio: ['ignore', 'pipe', 'pipe'],
25
+ });
26
+
27
+ spin.succeed(`Dependencies installed with ${packageManager}`);
28
+ } catch (err) {
29
+ spin.fail(`Dependency installation failed`);
30
+ log.warn('You can install them manually:');
31
+ log.dim(` cd ${projectDir}`);
32
+ log.dim(` ${cmd} ${args.join(' ')}`);
33
+
34
+ // Re-throw so the caller can decide whether to abort or continue
35
+ throw new Error(`Installation failed: ${err.message}`);
36
+ }
37
+ }
38
+
39
+ // ─────────────────────────────────────────────────────────────────────────────
40
+ // Prisma Post-Install Steps
41
+ // ─────────────────────────────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * Runs `prisma generate` after installation for PostgreSQL projects.
45
+ * @param {string} projectDir
46
+ * @param {string} packageManager
47
+ */
48
+ export async function runPrismaGenerate(projectDir, packageManager) {
49
+ const spin = spinner('Running prisma generate…').start();
50
+
51
+ try {
52
+ const runner = packageManager === 'npm' ? 'npx' : packageManager;
53
+ await execa(runner, ['prisma', 'generate'], {
54
+ cwd: projectDir,
55
+ stdio: ['ignore', 'pipe', 'pipe'],
56
+ });
57
+ spin.succeed('Prisma client generated');
58
+ } catch {
59
+ spin.warn('Could not auto-run prisma generate β€” run it manually:');
60
+ log.dim(' npx prisma generate');
61
+ }
62
+ }
package/src/prompts.js ADDED
@@ -0,0 +1,113 @@
1
+ import { input, select, confirm } from '@inquirer/prompts';
2
+ import chalk from 'chalk';
3
+
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ // Interactive CLI Prompts
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+
8
+ /**
9
+ * Runs the full interactive questionnaire and returns a resolved options object.
10
+ * @param {string} [initialName] - Pre-filled project name from CLI argv
11
+ * @returns {Promise<object>} options
12
+ */
13
+ export async function runPrompts(initialName) {
14
+ console.log(chalk.dim(' Answer a few questions to scaffold your project.\n'));
15
+
16
+ // 1. Project Name
17
+ const projectName = await input({
18
+ message: 'Project name:',
19
+ default: initialName || 'my-api',
20
+ validate: (v) => {
21
+ if (!v.trim()) return 'Project name cannot be empty.';
22
+ if (!/^[a-z0-9-_]+$/i.test(v)) return 'Use only letters, numbers, hyphens or underscores.';
23
+ return true;
24
+ },
25
+ });
26
+
27
+ // 2. Language
28
+ const language = await select({
29
+ message: 'Language:',
30
+ choices: [
31
+ { name: `${chalk.yellow('JavaScript')} (ES Modules)`, value: 'javascript' },
32
+ { name: `${chalk.blue('TypeScript')} (strict mode)`, value: 'typescript' },
33
+ ],
34
+ });
35
+
36
+ // 3. Package Manager
37
+ const packageManager = await select({
38
+ message: 'Package manager:',
39
+ choices: [
40
+ { name: `${chalk.red('npm')}`, value: 'npm' },
41
+ { name: `${chalk.yellow('pnpm')} (faster, disk-efficient)`, value: 'pnpm' },
42
+ { name: `${chalk.blue('yarn')}`, value: 'yarn' },
43
+ { name: `${chalk.magenta('bun')} (fastest)`, value: 'bun' },
44
+ ],
45
+ });
46
+
47
+ // 4. Database
48
+ const database = await select({
49
+ message: 'Database:',
50
+ choices: [
51
+ { name: `${chalk.green('MongoDB')} β€” Mongoose ODM`, value: 'mongodb' },
52
+ { name: `${chalk.blue('PostgreSQL')} β€” Prisma ORM`, value: 'postgresql' },
53
+ { name: `${chalk.cyan('MySQL')} β€” mysql2 driver`, value: 'mysql' },
54
+ { name: `${chalk.magenta('SQLite')} β€” better-sqlite3`, value: 'sqlite' },
55
+ { name: `${chalk.dim('None')} β€” no database`, value: 'none' },
56
+ ],
57
+ });
58
+
59
+ // 5. Authentication
60
+ const auth = await select({
61
+ message: 'Authentication:',
62
+ choices: [
63
+ { name: `${chalk.yellow('JWT')} β€” jsonwebtoken + bcryptjs`, value: 'jwt' },
64
+ { name: `${chalk.blue('Clerk')} β€” @clerk/express`, value: 'clerk' },
65
+ { name: `${chalk.orange?.('Firebase') || chalk.red('Firebase')} β€” firebase-admin`, value: 'firebase' },
66
+ { name: `${chalk.dim('None')} β€” no auth`, value: 'none' },
67
+ ],
68
+ });
69
+
70
+ // 6. Validation
71
+ const validation = await select({
72
+ message: 'Validation library:',
73
+ choices: [
74
+ { name: `${chalk.blue('Zod')} β€” TypeScript-first schema validation`, value: 'zod' },
75
+ { name: `${chalk.cyan('Joi')} β€” the classic schema validator`, value: 'joi' },
76
+ { name: `${chalk.green('Express Validator')} β€” middleware-based validation`, value: 'express-validator' },
77
+ { name: `${chalk.dim('None')} β€” no validation library`, value: 'none' },
78
+ ],
79
+ });
80
+
81
+ // 7. API Style
82
+ const apiStyle = await select({
83
+ message: 'API style:',
84
+ choices: [
85
+ { name: `${chalk.green('REST')} β€” standard RESTful API`, value: 'rest' },
86
+ { name: `${chalk.magenta('GraphQL')} β€” Apollo Server 4`, value: 'graphql' },
87
+ ],
88
+ });
89
+
90
+ // 8. Docker
91
+ const docker = await confirm({
92
+ message: 'Include Docker + docker-compose?',
93
+ default: true,
94
+ });
95
+
96
+ // 9. Install Dependencies
97
+ const installDeps = await confirm({
98
+ message: `Install dependencies now with ${chalk.cyan(packageManager)}?`,
99
+ default: true,
100
+ });
101
+
102
+ return {
103
+ projectName: projectName.trim(),
104
+ language,
105
+ packageManager,
106
+ database,
107
+ auth,
108
+ validation,
109
+ apiStyle,
110
+ docker,
111
+ installDeps,
112
+ };
113
+ }