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 +79 -0
- package/package.json +48 -0
- package/src/generator.js +201 -0
- package/src/index.js +118 -0
- package/src/installer.js +62 -0
- package/src/prompts.js +113 -0
- package/src/templates/base.js +387 -0
- package/src/templates/config.js +271 -0
- package/src/templates/controllers.js +99 -0
- package/src/templates/docker.js +205 -0
- package/src/templates/graphql.js +157 -0
- package/src/templates/helpers.js +117 -0
- package/src/templates/index.js +78 -0
- package/src/templates/middlewares.js +294 -0
- package/src/templates/models.js +318 -0
- package/src/templates/routes.js +179 -0
- package/src/templates/services.js +252 -0
- package/src/templates/typescript.js +96 -0
- package/src/templates/utils.js +109 -0
- package/src/templates/validators.js +183 -0
- package/src/utils/fs.js +41 -0
- package/src/utils/logger.js +89 -0
- package/src/utils/pkg.js +158 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Validator Templates — user.validator.js
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/** Generate src/validators/user.validator.js */
|
|
6
|
+
export function userValidator(options) {
|
|
7
|
+
const { validation, language } = options;
|
|
8
|
+
const isTS = language === 'typescript';
|
|
9
|
+
|
|
10
|
+
// ── Zod ─────────────────────────────────────────────────────────────────────
|
|
11
|
+
if (validation === 'zod') {
|
|
12
|
+
return `import { z } from 'zod';
|
|
13
|
+
${isTS ? "import { Request, Response, NextFunction } from 'express';\n" : ''}
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
// Zod Schemas
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export const createUserSchema = z.object({
|
|
19
|
+
body: z.object({
|
|
20
|
+
name: z.string().min(2, 'Name must be at least 2 characters').max(50),
|
|
21
|
+
email: z.string().email('Invalid email address'),
|
|
22
|
+
password: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(8, 'Password must be at least 8 characters')
|
|
25
|
+
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
|
|
26
|
+
.regex(/[0-9]/, 'Password must contain at least one number'),
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const updateUserSchema = z.object({
|
|
31
|
+
body: z.object({
|
|
32
|
+
name: z.string().min(2).max(50).optional(),
|
|
33
|
+
email: z.string().email().optional(),
|
|
34
|
+
}),
|
|
35
|
+
params: z.object({
|
|
36
|
+
id: z.string().min(1, 'User ID is required'),
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const loginSchema = z.object({
|
|
41
|
+
body: z.object({
|
|
42
|
+
email: z.string().email('Invalid email'),
|
|
43
|
+
password: z.string().min(1, 'Password is required'),
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
// Validation Middleware Factory
|
|
49
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates an Express middleware that validates the request against a Zod schema.
|
|
53
|
+
* Throws a ZodError (caught by errorMiddleware) on failure.
|
|
54
|
+
*
|
|
55
|
+
* @param {z.ZodSchema} schema - Zod schema with body/params/query fields
|
|
56
|
+
*/
|
|
57
|
+
export const validate = (schema${isTS ? ': z.ZodSchema' : ''}) =>
|
|
58
|
+
(req${isTS ? ': Request' : ''}, _res${isTS ? ': Response' : ''}, next${isTS ? ': NextFunction' : ''}) => {
|
|
59
|
+
schema.parse({
|
|
60
|
+
body: req.body,
|
|
61
|
+
params: req.params,
|
|
62
|
+
query: req.query,
|
|
63
|
+
});
|
|
64
|
+
next();
|
|
65
|
+
};
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Joi ──────────────────────────────────────────────────────────────────────
|
|
70
|
+
if (validation === 'joi') {
|
|
71
|
+
return `import Joi from 'joi';
|
|
72
|
+
${isTS ? "import { Request, Response, NextFunction } from 'express';\n" : ''}
|
|
73
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
// Joi Schemas
|
|
75
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
export const createUserSchema = Joi.object({
|
|
78
|
+
name: Joi.string().min(2).max(50).required(),
|
|
79
|
+
email: Joi.string().email().required(),
|
|
80
|
+
password: Joi.string()
|
|
81
|
+
.min(8)
|
|
82
|
+
.pattern(/[A-Z]/, 'uppercase')
|
|
83
|
+
.pattern(/[0-9]/, 'number')
|
|
84
|
+
.required()
|
|
85
|
+
.messages({
|
|
86
|
+
'string.pattern.name': 'Password must contain at least one {{#name}}',
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const updateUserSchema = Joi.object({
|
|
91
|
+
name: Joi.string().min(2).max(50),
|
|
92
|
+
email: Joi.string().email(),
|
|
93
|
+
}).min(1);
|
|
94
|
+
|
|
95
|
+
export const loginSchema = Joi.object({
|
|
96
|
+
email: Joi.string().email().required(),
|
|
97
|
+
password: Joi.string().required(),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
// Validation Middleware Factory
|
|
102
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates an Express middleware that validates req.body against a Joi schema.
|
|
106
|
+
* Passes a 422 ApiError to next() on failure.
|
|
107
|
+
*/
|
|
108
|
+
export const validate = (schema${isTS ? ': Joi.Schema' : ''}) =>
|
|
109
|
+
(req${isTS ? ': Request' : ''}, _res${isTS ? ': Response' : ''}, next${isTS ? ': NextFunction' : ''}) => {
|
|
110
|
+
const { error } = schema.validate(req.body, { abortEarly: false });
|
|
111
|
+
if (error) {
|
|
112
|
+
const messages = error.details.map((d${isTS ? ': Joi.ValidationErrorItem' : ''}) => d.message);
|
|
113
|
+
const err = new Error('Validation failed');
|
|
114
|
+
(err${isTS ? ' as any' : ''}).statusCode = 422;
|
|
115
|
+
(err${isTS ? ' as any' : ''}).errors = messages;
|
|
116
|
+
return next(err);
|
|
117
|
+
}
|
|
118
|
+
next();
|
|
119
|
+
};
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Express Validator ──────────────────────────────────────────────────────
|
|
124
|
+
return `import { body, param, validationResult } from 'express-validator';
|
|
125
|
+
${isTS ? "import { Request, Response, NextFunction } from 'express';\n" : ''}
|
|
126
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
127
|
+
// Express Validator Rules
|
|
128
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
export const createUserRules = [
|
|
131
|
+
body('name')
|
|
132
|
+
.trim()
|
|
133
|
+
.isLength({ min: 2, max: 50 })
|
|
134
|
+
.withMessage('Name must be between 2 and 50 characters'),
|
|
135
|
+
body('email')
|
|
136
|
+
.isEmail()
|
|
137
|
+
.normalizeEmail()
|
|
138
|
+
.withMessage('Must be a valid email address'),
|
|
139
|
+
body('password')
|
|
140
|
+
.isLength({ min: 8 })
|
|
141
|
+
.withMessage('Password must be at least 8 characters')
|
|
142
|
+
.matches(/[A-Z]/)
|
|
143
|
+
.withMessage('Password must contain at least one uppercase letter')
|
|
144
|
+
.matches(/[0-9]/)
|
|
145
|
+
.withMessage('Password must contain at least one number'),
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
export const updateUserRules = [
|
|
149
|
+
param('id').notEmpty().withMessage('User ID is required'),
|
|
150
|
+
body('name').optional().trim().isLength({ min: 2, max: 50 }),
|
|
151
|
+
body('email').optional().isEmail().normalizeEmail(),
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
export const loginRules = [
|
|
155
|
+
body('email').isEmail().normalizeEmail().withMessage('Valid email required'),
|
|
156
|
+
body('password').notEmpty().withMessage('Password is required'),
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
160
|
+
// Validation Error Handler Middleware
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Run after validation rule arrays. Collects all errors and passes a
|
|
165
|
+
* 422 error to next() if any rules failed.
|
|
166
|
+
*/
|
|
167
|
+
export const handleValidationErrors = (
|
|
168
|
+
req${isTS ? ': Request' : ''},
|
|
169
|
+
_res${isTS ? ': Response' : ''},
|
|
170
|
+
next${isTS ? ': NextFunction' : ''}
|
|
171
|
+
) => {
|
|
172
|
+
const errors = validationResult(req);
|
|
173
|
+
if (!errors.isEmpty()) {
|
|
174
|
+
const messages = errors.array().map((e${isTS ? ': any' : ''}) => e.msg);
|
|
175
|
+
const err = new Error('Validation failed');
|
|
176
|
+
(err${isTS ? ' as any' : ''}).statusCode = 422;
|
|
177
|
+
(err${isTS ? ' as any' : ''}).errors = messages;
|
|
178
|
+
return next(err);
|
|
179
|
+
}
|
|
180
|
+
next();
|
|
181
|
+
};
|
|
182
|
+
`;
|
|
183
|
+
}
|
package/src/utils/fs.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// File System Utilities (thin wrappers around fs-extra)
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ensures a directory exists, creating it (and parents) if necessary.
|
|
10
|
+
* @param {string} dirPath
|
|
11
|
+
*/
|
|
12
|
+
export async function createDir(dirPath) {
|
|
13
|
+
await fs.ensureDir(dirPath);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Writes content to a file, creating all ancestor directories as needed.
|
|
18
|
+
* @param {string} filePath
|
|
19
|
+
* @param {string} content
|
|
20
|
+
*/
|
|
21
|
+
export async function writeFile(filePath, content) {
|
|
22
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
23
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Checks if a path (file or directory) exists.
|
|
28
|
+
* @param {string} p
|
|
29
|
+
* @returns {Promise<boolean>}
|
|
30
|
+
*/
|
|
31
|
+
export async function pathExists(p) {
|
|
32
|
+
return fs.pathExists(p);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Removes a file or directory recursively. Silent on missing paths.
|
|
37
|
+
* @param {string} p
|
|
38
|
+
*/
|
|
39
|
+
export async function remove(p) {
|
|
40
|
+
return fs.remove(p);
|
|
41
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import boxen from 'boxen';
|
|
4
|
+
import gradient from 'gradient-string';
|
|
5
|
+
import figlet from 'figlet';
|
|
6
|
+
|
|
7
|
+
/** Promisify figlet manually to avoid DEP0174 warning */
|
|
8
|
+
const figletAsync = (text, opts) =>
|
|
9
|
+
new Promise((resolve, reject) => {
|
|
10
|
+
figlet.text(text, opts, (err, data) => (err ? reject(err) : resolve(data)));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
// Colorized Logger
|
|
15
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
export const log = {
|
|
18
|
+
info: (msg) => console.log(chalk.cyan('ℹ ') + chalk.white(msg)),
|
|
19
|
+
success: (msg) => console.log(chalk.green('✔ ') + chalk.green(msg)),
|
|
20
|
+
error: (msg) => console.log(chalk.red('✖ ') + chalk.red(msg)),
|
|
21
|
+
warn: (msg) => console.log(chalk.yellow('⚠ ') + chalk.yellow(msg)),
|
|
22
|
+
step: (msg) => console.log(chalk.blue('→ ') + chalk.white(msg)),
|
|
23
|
+
dim: (msg) => console.log(chalk.dim(msg)),
|
|
24
|
+
label: (label, value) => console.log(chalk.cyan(` ${label}: `) + chalk.white(value)),
|
|
25
|
+
blank: () => console.log(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
// Spinner Factory
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a pre-configured ora spinner.
|
|
34
|
+
* @param {string} text - Initial spinner text
|
|
35
|
+
* @returns {import('ora').Ora}
|
|
36
|
+
*/
|
|
37
|
+
export const spinner = (text) =>
|
|
38
|
+
ora({
|
|
39
|
+
text: chalk.cyan(text),
|
|
40
|
+
color: 'cyan',
|
|
41
|
+
spinner: 'dots',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
// Boxen Wrapper
|
|
46
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Wraps content in a styled boxen box.
|
|
50
|
+
* @param {string} content
|
|
51
|
+
* @param {import('boxen').Options} opts
|
|
52
|
+
*/
|
|
53
|
+
export const box = (content, opts = {}) =>
|
|
54
|
+
boxen(content, {
|
|
55
|
+
padding: 1,
|
|
56
|
+
margin: { top: 0, bottom: 1, left: 1, right: 1 },
|
|
57
|
+
borderStyle: 'round',
|
|
58
|
+
borderColor: 'cyan',
|
|
59
|
+
...opts,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
// ASCII Art Banner
|
|
64
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Renders the WABCK banner with gradient colors and a subtitle box.
|
|
68
|
+
*/
|
|
69
|
+
export async function renderBanner() {
|
|
70
|
+
try {
|
|
71
|
+
const art = await figletAsync('WABCK', {
|
|
72
|
+
font: 'ANSI Shadow',
|
|
73
|
+
horizontalLayout: 'default',
|
|
74
|
+
verticalLayout: 'default',
|
|
75
|
+
});
|
|
76
|
+
console.log(gradient.cristal.multiline(art));
|
|
77
|
+
} catch {
|
|
78
|
+
// Fallback if figlet font fails
|
|
79
|
+
console.log(gradient.cristal('\n ██╗ ██╗ █████╗ ██████╗ ██████╗██╗ ██╗\n ██║ ██║██╔══██╗██╔══██╗██╔════╝██║ ██╔╝\n ██║ █╗ ██║███████║██████╔╝██║ █████╔╝ \n ██║███╗██║██╔══██║██╔══██╗██║ ██╔═██╗ \n ╚███╔███╔╝██║ ██║██████╔╝╚██████╗██║ ██╗\n ╚══╝╚══╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝╚═╝ ╚═╝\n'));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(
|
|
83
|
+
box(
|
|
84
|
+
chalk.white('⚡ Scaffold production-ready Express.js backends in seconds\n') +
|
|
85
|
+
chalk.dim(' by wabck — https://github.com/wabck/wabck'),
|
|
86
|
+
{ borderColor: 'magenta', padding: { top: 0, bottom: 0, left: 2, right: 2 } }
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
}
|
package/src/utils/pkg.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// package.json Builder for the Generated Project
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds a complete package.json object based on the user's selections.
|
|
7
|
+
* @param {object} options - Options from the CLI prompts
|
|
8
|
+
* @returns {object} - Parsed package.json content
|
|
9
|
+
*/
|
|
10
|
+
export function buildPackageJson(options) {
|
|
11
|
+
const { projectName, language, database, auth, validation, apiStyle } = options;
|
|
12
|
+
const isTS = language === 'typescript';
|
|
13
|
+
|
|
14
|
+
// ── Scripts ────────────────────────────────────────────────────────────────
|
|
15
|
+
const scripts = isTS
|
|
16
|
+
? {
|
|
17
|
+
dev: 'tsx watch src/server.ts',
|
|
18
|
+
build: 'tsc --outDir dist',
|
|
19
|
+
start: 'node dist/server.js',
|
|
20
|
+
lint: 'eslint src --ext .ts',
|
|
21
|
+
'lint:fix': 'eslint src --ext .ts --fix',
|
|
22
|
+
'type-check': 'tsc --noEmit',
|
|
23
|
+
}
|
|
24
|
+
: {
|
|
25
|
+
dev: 'nodemon src/server.js',
|
|
26
|
+
start: 'node src/server.js',
|
|
27
|
+
lint: 'eslint src',
|
|
28
|
+
'lint:fix': 'eslint src --fix',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ── Core Dependencies (always included) ────────────────────────────────────
|
|
32
|
+
const dependencies = {
|
|
33
|
+
express: '^4.19.2',
|
|
34
|
+
dotenv: '^16.4.5',
|
|
35
|
+
cors: '^2.8.5',
|
|
36
|
+
helmet: '^7.1.0',
|
|
37
|
+
compression: '^1.7.4',
|
|
38
|
+
morgan: '^1.10.0',
|
|
39
|
+
'cookie-parser': '^1.4.6',
|
|
40
|
+
'express-rate-limit': '^7.3.1',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ── Dev Dependencies ───────────────────────────────────────────────────────
|
|
44
|
+
const devDependencies = {
|
|
45
|
+
eslint: '^9.6.0',
|
|
46
|
+
'@eslint/js': '^9.6.0',
|
|
47
|
+
prettier: '^3.3.2',
|
|
48
|
+
'eslint-config-prettier': '^9.1.0',
|
|
49
|
+
'eslint-plugin-prettier': '^5.1.3',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (isTS) {
|
|
53
|
+
Object.assign(devDependencies, {
|
|
54
|
+
typescript: '^5.4.5',
|
|
55
|
+
tsx: '^4.15.7',
|
|
56
|
+
'@types/express': '^4.17.21',
|
|
57
|
+
'@types/node': '^20.14.9',
|
|
58
|
+
'@types/cors': '^2.8.17',
|
|
59
|
+
'@types/morgan': '^1.9.9',
|
|
60
|
+
'@types/compression': '^1.7.5',
|
|
61
|
+
'@types/cookie-parser': '^1.4.7',
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
devDependencies.nodemon = '^3.1.4';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Database ───────────────────────────────────────────────────────────────
|
|
68
|
+
switch (database) {
|
|
69
|
+
case 'mongodb':
|
|
70
|
+
dependencies.mongoose = '^8.4.4';
|
|
71
|
+
break;
|
|
72
|
+
case 'postgresql':
|
|
73
|
+
dependencies['@prisma/client'] = '^5.16.1';
|
|
74
|
+
devDependencies.prisma = '^5.16.1';
|
|
75
|
+
break;
|
|
76
|
+
case 'mysql':
|
|
77
|
+
dependencies.mysql2 = '^3.10.1';
|
|
78
|
+
break;
|
|
79
|
+
case 'sqlite':
|
|
80
|
+
dependencies['better-sqlite3'] = '^9.6.0';
|
|
81
|
+
if (isTS) devDependencies['@types/better-sqlite3'] = '^7.6.11';
|
|
82
|
+
break;
|
|
83
|
+
// 'none' → no extra dependency
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Authentication ─────────────────────────────────────────────────────────
|
|
87
|
+
switch (auth) {
|
|
88
|
+
case 'jwt':
|
|
89
|
+
dependencies.jsonwebtoken = '^9.0.2';
|
|
90
|
+
dependencies.bcryptjs = '^2.4.3';
|
|
91
|
+
if (isTS) {
|
|
92
|
+
devDependencies['@types/jsonwebtoken'] = '^9.0.6';
|
|
93
|
+
devDependencies['@types/bcryptjs'] = '^2.4.6';
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case 'clerk':
|
|
97
|
+
dependencies['@clerk/express'] = '^1.1.2';
|
|
98
|
+
break;
|
|
99
|
+
case 'firebase':
|
|
100
|
+
dependencies['firebase-admin'] = '^12.2.0';
|
|
101
|
+
break;
|
|
102
|
+
// 'none' → no extra dependency
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Validation ─────────────────────────────────────────────────────────────
|
|
106
|
+
switch (validation) {
|
|
107
|
+
case 'zod':
|
|
108
|
+
dependencies.zod = '^3.23.8';
|
|
109
|
+
break;
|
|
110
|
+
case 'joi':
|
|
111
|
+
dependencies.joi = '^17.13.1';
|
|
112
|
+
break;
|
|
113
|
+
case 'express-validator':
|
|
114
|
+
dependencies['express-validator'] = '^7.1.0';
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── API Style ──────────────────────────────────────────────────────────────
|
|
119
|
+
if (apiStyle === 'graphql') {
|
|
120
|
+
dependencies['@apollo/server'] = '^4.10.5';
|
|
121
|
+
dependencies['graphql'] = '^16.9.0';
|
|
122
|
+
dependencies['@as-integrations/express4'] = '^1.0.0';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
name: projectName,
|
|
127
|
+
version: '1.0.0',
|
|
128
|
+
description: `${projectName} — Production-ready Express.js API`,
|
|
129
|
+
type: 'module',
|
|
130
|
+
main: isTS ? 'dist/server.js' : 'src/server.js',
|
|
131
|
+
scripts,
|
|
132
|
+
dependencies,
|
|
133
|
+
devDependencies,
|
|
134
|
+
engines: { node: '>=18.0.0' },
|
|
135
|
+
keywords: ['express', 'api', 'backend', 'nodejs'],
|
|
136
|
+
author: '',
|
|
137
|
+
license: 'MIT',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
142
|
+
// Install Command Builder
|
|
143
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Returns the install command args for the chosen package manager.
|
|
147
|
+
* @param {'npm'|'pnpm'|'yarn'|'bun'} pm - Package manager
|
|
148
|
+
* @returns {{ cmd: string, args: string[] }}
|
|
149
|
+
*/
|
|
150
|
+
export function getInstallCmd(pm) {
|
|
151
|
+
const map = {
|
|
152
|
+
npm: { cmd: 'npm', args: ['install'] },
|
|
153
|
+
pnpm: { cmd: 'pnpm', args: ['install'] },
|
|
154
|
+
yarn: { cmd: 'yarn', args: [] },
|
|
155
|
+
bun: { cmd: 'bun', args: ['install'] },
|
|
156
|
+
};
|
|
157
|
+
return map[pm] ?? map.npm;
|
|
158
|
+
}
|