express-genix 1.1.4 → 2.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/LICENSE +21 -0
- package/README.md +204 -259
- package/index.js +229 -113
- package/lib/cleanup.js +41 -129
- package/lib/features.js +239 -0
- package/lib/generator.js +286 -204
- package/lib/utils.js +43 -91
- package/package.json +81 -63
- package/templates/cicd/github-actions.yml.ejs +70 -0
- package/templates/config/database.mongo.js.ejs +29 -33
- package/templates/config/database.postgres.js.ejs +41 -40
- package/templates/config/database.prisma.js.ejs +26 -0
- package/templates/config/redis.js.ejs +28 -0
- package/templates/config/schema.prisma.ejs +20 -0
- package/templates/config/swagger.js.ejs +30 -0
- package/templates/config/websocket.js.ejs +62 -0
- package/templates/controllers/authController.js.ejs +152 -129
- package/templates/controllers/exampleController.js.ejs +92 -152
- package/templates/controllers/userController.js.ejs +52 -60
- package/templates/core/Dockerfile.ejs +41 -31
- package/templates/core/README.md.ejs +191 -179
- package/templates/core/app.js.ejs +114 -64
- package/templates/core/docker-compose.yml.ejs +59 -47
- package/templates/core/dockerignore.ejs +7 -0
- package/templates/core/env.ejs +25 -19
- package/templates/core/env.example.ejs +26 -0
- package/templates/core/eslintrc.json.ejs +50 -20
- package/templates/core/gitignore.ejs +51 -51
- package/templates/core/healthcheck.js.ejs +24 -24
- package/templates/core/jest.config.js.ejs +19 -22
- package/templates/core/package.json.ejs +70 -33
- package/templates/core/prettierrc.json.ejs +11 -11
- package/templates/core/server.js.ejs +64 -48
- package/templates/core/tsconfig.json.ejs +19 -0
- package/templates/middleware/auth.js.ejs +80 -66
- package/templates/middleware/cache.js.ejs +67 -0
- package/templates/middleware/errorHandler.js.ejs +50 -46
- package/templates/middleware/requestId.js.ejs +9 -0
- package/templates/middleware/validation.js.ejs +109 -47
- package/templates/migrations/create-users.js.ejs +50 -0
- package/templates/migrations/seed-users.js.ejs +34 -0
- package/templates/migrations/sequelizerc.ejs +8 -0
- package/templates/models/User.mongo.js.ejs +29 -29
- package/templates/models/User.postgres.js.ejs +40 -40
- package/templates/models/index.mongo.js.ejs +7 -7
- package/templates/models/index.postgres.js.ejs +11 -11
- package/templates/routes/authRoutes.js.ejs +222 -13
- package/templates/routes/exampleRoutes.js.ejs +100 -12
- package/templates/routes/index.js.ejs +34 -24
- package/templates/routes/userRoutes.js.ejs +78 -15
- package/templates/services/authService.js.ejs +111 -35
- package/templates/services/exampleService.js.ejs +112 -112
- package/templates/services/userService.mongodb.js.ejs +33 -33
- package/templates/services/userService.postgres.js.ejs +30 -30
- package/templates/services/userService.prisma.js.ejs +36 -0
- package/templates/tests/auth.test.js.ejs +83 -66
- package/templates/tests/example.test.js.ejs +109 -112
- package/templates/tests/setup.js.ejs +11 -11
- package/templates/tests/users.test.js.ejs +42 -42
- package/templates/utils/envValidator.js.ejs +23 -0
- package/templates/utils/errors.js.ejs +12 -12
- package/templates/utils/logger.js.ejs +37 -28
- package/templates/utils/response.js.ejs +28 -0
- package/templates/utils/validators.js.ejs +34 -34
- package/templates/config/swagger.json.ejs +0 -194
- package/templates/core/index.js.ejs +0 -24
package/lib/features.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ejs = require('ejs');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const addFeature = async (feature, projectDir) => {
|
|
7
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
8
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
9
|
+
const templatesDir = path.join(__dirname, '../templates');
|
|
10
|
+
|
|
11
|
+
const handlers = {
|
|
12
|
+
docker: () => addDocker(projectDir, templatesDir, pkg),
|
|
13
|
+
cicd: () => addCicd(projectDir, templatesDir, pkg),
|
|
14
|
+
auth: () => addAuth(projectDir, templatesDir, pkg),
|
|
15
|
+
websocket: () => addWebsocket(projectDir, templatesDir, pkg),
|
|
16
|
+
prisma: () => addPrisma(projectDir, templatesDir, pkg),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const handler = handlers[feature];
|
|
20
|
+
if (!handler) {
|
|
21
|
+
throw new Error(`Unknown feature: "${feature}". Supported: ${Object.keys(handlers).join(', ')}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await handler();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const renderTemplate = (templatesDir, templateName, config) => {
|
|
28
|
+
const templatePath = path.join(templatesDir, templateName);
|
|
29
|
+
if (!fs.existsSync(templatePath)) {
|
|
30
|
+
throw new Error(`Template not found: ${templateName}`);
|
|
31
|
+
}
|
|
32
|
+
const template = fs.readFileSync(templatePath, 'utf8');
|
|
33
|
+
return ejs.render(template, config);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const addDocker = (projectDir, templatesDir, pkg) => {
|
|
37
|
+
const config = inferConfig(projectDir, pkg);
|
|
38
|
+
|
|
39
|
+
const files = [
|
|
40
|
+
{ template: 'core/Dockerfile.ejs', output: 'Dockerfile' },
|
|
41
|
+
{ template: 'core/docker-compose.yml.ejs', output: 'docker-compose.yml' },
|
|
42
|
+
{ template: 'core/dockerignore.ejs', output: '.dockerignore' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const content = renderTemplate(templatesDir, file.template, config);
|
|
47
|
+
fs.writeFileSync(path.join(projectDir, file.output), content);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(' Created Dockerfile, docker-compose.yml, .dockerignore');
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const addCicd = (projectDir, templatesDir, pkg) => {
|
|
54
|
+
const config = inferConfig(projectDir, pkg);
|
|
55
|
+
|
|
56
|
+
const workflowDir = path.join(projectDir, '.github', 'workflows');
|
|
57
|
+
fs.mkdirSync(workflowDir, { recursive: true });
|
|
58
|
+
|
|
59
|
+
const content = renderTemplate(templatesDir, 'cicd/github-actions.yml.ejs', config);
|
|
60
|
+
fs.writeFileSync(path.join(workflowDir, 'ci.yml'), content);
|
|
61
|
+
|
|
62
|
+
console.log(' Created .github/workflows/ci.yml');
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const addAuth = (projectDir, templatesDir, pkg) => {
|
|
66
|
+
const config = inferConfig(projectDir, pkg);
|
|
67
|
+
|
|
68
|
+
if (config.hasAuth) {
|
|
69
|
+
throw new Error('Auth is already configured in this project.');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!config.hasDatabase) {
|
|
73
|
+
throw new Error('Auth requires a database. Add a database first or use "express-genix init" with a database option.');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
config.hasAuth = true;
|
|
77
|
+
const ext = config.isTypescript ? 'ts' : 'js';
|
|
78
|
+
|
|
79
|
+
const files = [
|
|
80
|
+
{ template: 'controllers/authController.js.ejs', output: `src/controllers/authController.${ext}` },
|
|
81
|
+
{ template: 'controllers/userController.js.ejs', output: `src/controllers/userController.${ext}` },
|
|
82
|
+
{ template: 'middleware/auth.js.ejs', output: `src/middleware/auth.${ext}` },
|
|
83
|
+
{ template: 'middleware/validation.js.ejs', output: `src/middleware/validation.${ext}` },
|
|
84
|
+
{ template: 'routes/authRoutes.js.ejs', output: `src/routes/authRoutes.${ext}` },
|
|
85
|
+
{ template: 'routes/userRoutes.js.ejs', output: `src/routes/userRoutes.${ext}` },
|
|
86
|
+
{ template: 'services/authService.js.ejs', output: `src/services/authService.${ext}` },
|
|
87
|
+
{ template: 'utils/validators.js.ejs', output: `src/utils/validators.${ext}` },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// Pick the right user service
|
|
91
|
+
if (config.isPrisma) {
|
|
92
|
+
files.push({ template: 'services/userService.prisma.js.ejs', output: `src/services/userService.${ext}` });
|
|
93
|
+
} else if (config.db === 'mongodb') {
|
|
94
|
+
files.push(
|
|
95
|
+
{ template: 'services/userService.mongodb.js.ejs', output: `src/services/userService.${ext}` },
|
|
96
|
+
{ template: 'models/User.mongo.js.ejs', output: `src/models/User.${ext}` }
|
|
97
|
+
);
|
|
98
|
+
} else if (config.db === 'postgresql') {
|
|
99
|
+
files.push(
|
|
100
|
+
{ template: 'services/userService.postgres.js.ejs', output: `src/services/userService.${ext}` },
|
|
101
|
+
{ template: 'models/User.postgres.js.ejs', output: `src/models/User.${ext}` }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
const outputPath = path.join(projectDir, file.output);
|
|
107
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
108
|
+
const content = renderTemplate(templatesDir, file.template, config);
|
|
109
|
+
fs.writeFileSync(outputPath, content);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Add auth dependencies
|
|
113
|
+
const depsToAdd = ['jsonwebtoken', 'bcryptjs', 'validator'];
|
|
114
|
+
const missing = depsToAdd.filter((dep) => !(pkg.dependencies && pkg.dependencies[dep]));
|
|
115
|
+
if (missing.length > 0) {
|
|
116
|
+
console.log(` Installing: ${missing.join(', ')}`);
|
|
117
|
+
execSync(`npm install ${missing.join(' ')}`, { cwd: projectDir, stdio: 'pipe' });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(' Created auth controllers, middleware, routes, services');
|
|
121
|
+
console.log(' ⚠️ Add JWT_SECRET and JWT_REFRESH_SECRET to your .env file');
|
|
122
|
+
console.log(' ⚠️ Wire authRoutes and userRoutes into your routes/index file');
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const addWebsocket = (projectDir, templatesDir, pkg) => {
|
|
126
|
+
const config = inferConfig(projectDir, pkg);
|
|
127
|
+
|
|
128
|
+
if (config.hasWebsocket) {
|
|
129
|
+
throw new Error('WebSocket (Socket.io) is already configured.');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const ext = config.isTypescript ? 'ts' : 'js';
|
|
133
|
+
const outputPath = path.join(projectDir, `src/config/websocket.${ext}`);
|
|
134
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
135
|
+
|
|
136
|
+
const content = renderTemplate(templatesDir, 'config/websocket.js.ejs', config);
|
|
137
|
+
fs.writeFileSync(outputPath, content);
|
|
138
|
+
|
|
139
|
+
// Install socket.io
|
|
140
|
+
if (!(pkg.dependencies && pkg.dependencies['socket.io'])) {
|
|
141
|
+
console.log(' Installing: socket.io');
|
|
142
|
+
execSync('npm install socket.io', { cwd: projectDir, stdio: 'pipe' });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(' Created src/config/websocket.' + ext);
|
|
146
|
+
console.log(' ⚠️ Wire setupWebSocket into your server file:');
|
|
147
|
+
console.log(' const http = require("http");');
|
|
148
|
+
console.log(' const { setupWebSocket } = require("./config/websocket");');
|
|
149
|
+
console.log(' const httpServer = http.createServer(app);');
|
|
150
|
+
console.log(' setupWebSocket(httpServer);');
|
|
151
|
+
console.log(' httpServer.listen(port);');
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const addPrisma = (projectDir, templatesDir, pkg) => {
|
|
155
|
+
const config = inferConfig(projectDir, pkg);
|
|
156
|
+
|
|
157
|
+
if (config.isPrisma) {
|
|
158
|
+
throw new Error('Prisma is already configured.');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
config.isPrisma = true;
|
|
162
|
+
config.hasDatabase = true;
|
|
163
|
+
config.db = 'prisma';
|
|
164
|
+
const ext = config.isTypescript ? 'ts' : 'js';
|
|
165
|
+
|
|
166
|
+
const prismaDir = path.join(projectDir, 'prisma');
|
|
167
|
+
fs.mkdirSync(prismaDir, { recursive: true });
|
|
168
|
+
|
|
169
|
+
const files = [
|
|
170
|
+
{ template: 'config/database.prisma.js.ejs', output: `src/config/database.${ext}` },
|
|
171
|
+
{ template: 'config/schema.prisma.ejs', output: 'prisma/schema.prisma' },
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
for (const file of files) {
|
|
175
|
+
const outputPath = path.join(projectDir, file.output);
|
|
176
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
177
|
+
const content = renderTemplate(templatesDir, file.template, config);
|
|
178
|
+
fs.writeFileSync(outputPath, content);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Install Prisma deps
|
|
182
|
+
const depsToAdd = [];
|
|
183
|
+
if (!(pkg.dependencies && pkg.dependencies['@prisma/client'])) depsToAdd.push('@prisma/client');
|
|
184
|
+
const devDepsToAdd = [];
|
|
185
|
+
if (!(pkg.devDependencies && pkg.devDependencies.prisma)) devDepsToAdd.push('prisma');
|
|
186
|
+
|
|
187
|
+
if (depsToAdd.length > 0) {
|
|
188
|
+
console.log(` Installing: ${depsToAdd.join(', ')}`);
|
|
189
|
+
execSync(`npm install ${depsToAdd.join(' ')}`, { cwd: projectDir, stdio: 'pipe' });
|
|
190
|
+
}
|
|
191
|
+
if (devDepsToAdd.length > 0) {
|
|
192
|
+
console.log(` Installing dev: ${devDepsToAdd.join(', ')}`);
|
|
193
|
+
execSync(`npm install -D ${devDepsToAdd.join(' ')}`, { cwd: projectDir, stdio: 'pipe' });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Generate Prisma client
|
|
197
|
+
try {
|
|
198
|
+
execSync('npx prisma generate', { cwd: projectDir, stdio: 'pipe' });
|
|
199
|
+
} catch {
|
|
200
|
+
console.log(' ⚠️ Run "npx prisma generate" manually');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(' Created prisma/schema.prisma and src/config/database.' + ext);
|
|
204
|
+
console.log(' ⚠️ Add DATABASE_URL to your .env file');
|
|
205
|
+
console.log(' ⚠️ Run "npx prisma migrate dev --name init" to create tables');
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const inferConfig = (projectDir, pkg) => {
|
|
209
|
+
const hasMongo = !!(pkg.dependencies && pkg.dependencies.mongoose);
|
|
210
|
+
const hasSequelize = !!(pkg.dependencies && pkg.dependencies.sequelize);
|
|
211
|
+
const hasPrisma = !!(pkg.dependencies && pkg.dependencies['@prisma/client']);
|
|
212
|
+
const hasAuth = !!(pkg.dependencies && pkg.dependencies.jsonwebtoken);
|
|
213
|
+
const isTypescript = !!(pkg.devDependencies && pkg.devDependencies.typescript);
|
|
214
|
+
|
|
215
|
+
let db = 'none';
|
|
216
|
+
if (hasMongo) db = 'mongodb';
|
|
217
|
+
else if (hasPrisma) db = 'prisma';
|
|
218
|
+
else if (hasSequelize) db = 'postgresql';
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
projectName: pkg.name,
|
|
222
|
+
db,
|
|
223
|
+
hasDatabase: db !== 'none',
|
|
224
|
+
isNoDatabase: db === 'none',
|
|
225
|
+
isPrisma: hasPrisma,
|
|
226
|
+
isTypescript,
|
|
227
|
+
hasAuth,
|
|
228
|
+
hasRateLimit: !!(pkg.dependencies && pkg.dependencies['express-rate-limit']),
|
|
229
|
+
hasSwagger: !!(pkg.dependencies && pkg.dependencies['swagger-jsdoc']),
|
|
230
|
+
hasDocker: fs.existsSync(path.join(projectDir, 'Dockerfile')),
|
|
231
|
+
hasCicd: fs.existsSync(path.join(projectDir, '.github', 'workflows')),
|
|
232
|
+
hasWebsocket: !!(pkg.dependencies && pkg.dependencies['socket.io']),
|
|
233
|
+
hasRedis: !!(pkg.dependencies && pkg.dependencies.ioredis),
|
|
234
|
+
hasRequestId: !!(pkg.dependencies && pkg.dependencies.uuid),
|
|
235
|
+
logger: pkg.dependencies && pkg.dependencies.pino ? 'pino' : 'winston',
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
module.exports = { addFeature };
|