nodejs-quickstart-structure 1.4.3 → 1.7.5

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +18 -20
  3. package/bin/index.js +1 -0
  4. package/docs/generateCase.md +127 -61
  5. package/docs/generatorFlow.md +15 -3
  6. package/docs/releaseNoteRule.md +42 -0
  7. package/lib/generator.js +46 -314
  8. package/lib/modules/app-setup.js +96 -0
  9. package/lib/modules/caching-setup.js +56 -0
  10. package/lib/modules/config-files.js +109 -0
  11. package/lib/modules/database-setup.js +111 -0
  12. package/lib/modules/kafka-setup.js +112 -0
  13. package/lib/modules/project-setup.js +31 -0
  14. package/lib/prompts.js +12 -4
  15. package/package.json +4 -3
  16. package/templates/clean-architecture/js/src/index.js.ejs +19 -6
  17. package/templates/clean-architecture/js/src/infrastructure/log/logger.js +16 -2
  18. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +18 -6
  19. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +2 -0
  20. package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +1 -2
  21. package/templates/clean-architecture/ts/src/index.ts.ejs +27 -19
  22. package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +16 -2
  23. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +17 -6
  24. package/templates/common/.env.example.ejs +39 -0
  25. package/templates/common/Dockerfile +3 -0
  26. package/templates/common/Jenkinsfile.ejs +41 -0
  27. package/templates/common/README.md.ejs +113 -106
  28. package/templates/common/caching/clean/js/CreateUser.js.ejs +25 -0
  29. package/templates/common/caching/clean/js/GetAllUsers.js.ejs +33 -0
  30. package/templates/common/caching/clean/ts/createUser.ts.ejs +23 -0
  31. package/templates/common/caching/clean/ts/getAllUsers.ts.ejs +30 -0
  32. package/templates/common/caching/js/redisClient.js.ejs +71 -0
  33. package/templates/common/caching/ts/redisClient.ts.ejs +76 -0
  34. package/templates/common/docker-compose.yml.ejs +156 -116
  35. package/templates/common/package.json.ejs +13 -2
  36. package/templates/mvc/js/src/controllers/userController.js.ejs +35 -3
  37. package/templates/mvc/js/src/index.js.ejs +26 -17
  38. package/templates/mvc/js/src/utils/logger.js +16 -6
  39. package/templates/mvc/ts/src/config/swagger.ts.ejs +1 -2
  40. package/templates/mvc/ts/src/controllers/userController.ts.ejs +35 -3
  41. package/templates/mvc/ts/src/index.ts.ejs +27 -18
  42. package/templates/mvc/ts/src/utils/logger.ts +16 -2
  43. package/templates/mvc/js/src/config/database.js +0 -12
  44. /package/templates/db/mysql/{V1__Initial_Setup.sql → V1__Initial_Setup.sql.ejs} +0 -0
  45. /package/templates/db/postgres/{V1__Initial_Setup.sql → V1__Initial_Setup.sql.ejs} +0 -0
package/lib/generator.js CHANGED
@@ -1,342 +1,74 @@
1
- import fs from 'fs-extra';
2
1
  import path from 'path';
3
- import ejs from 'ejs';
4
2
  import { fileURLToPath } from 'url';
3
+ import { setupProjectDirectory, copyBaseStructure, copyCommonFiles } from './modules/project-setup.js';
4
+ import { renderPackageJson, renderDockerCompose, renderReadme, renderDockerfile, renderProfessionalConfig, setupCiCd, renderTestSample, renderEnvExample } from './modules/config-files.js';
5
+ import { renderIndexFile, renderDynamicComponents, renderSwaggerConfig, setupViews as setupSrcViews } from './modules/app-setup.js';
6
+ import { setupDatabase } from './modules/database-setup.js';
7
+ import { setupKafka, setupViews } from './modules/kafka-setup.js';
8
+ import { setupCaching } from './modules/caching-setup.js';
5
9
 
6
10
  const __filename = fileURLToPath(import.meta.url);
7
11
  const __dirname = path.dirname(__filename);
8
12
 
9
13
  export const generateProject = async (config) => {
10
- const { projectName, architecture, database, dbName, communication, language, viewEngine, ciProvider } = config;
14
+ const { projectName, architecture, language } = config;
11
15
  const targetDir = path.resolve(process.cwd(), projectName);
12
16
  const templatesDir = path.join(__dirname, '../templates');
13
17
 
14
18
  // 1. Create project directory
15
- if (await fs.pathExists(targetDir)) {
16
- throw new Error(`Directory ${projectName} already exists.`);
17
- }
18
- await fs.ensureDir(targetDir);
19
+ await setupProjectDirectory(targetDir, projectName);
19
20
 
20
- // 2. Select Structure Template
21
- const structureMap = {
22
- 'MVC': 'mvc',
23
- 'Clean Architecture': 'clean-architecture'
24
- };
25
- const archTemplate = structureMap[architecture];
26
- const langExt = language === 'TypeScript' ? 'ts' : 'js';
27
- const templatePath = path.join(templatesDir, archTemplate, langExt);
28
-
29
- // Copy base structure
30
- await fs.copy(templatePath, targetDir);
21
+ // 2. Select & Copy Base Structure
22
+ const { templatePath } = await copyBaseStructure(templatesDir, targetDir, architecture, language);
31
23
 
32
24
  // 3. Render package.json
33
- const packageJsonPath = path.join(targetDir, 'package.json');
34
- const packageTemplate = await fs.readFile(path.join(templatesDir, 'common', 'package.json.ejs'), 'utf-8');
35
- const packageContent = ejs.render(packageTemplate, {
36
- projectName,
37
- database,
38
- communication,
39
- language,
40
- viewEngine
41
- });
42
- await fs.writeFile(packageJsonPath, packageContent);
25
+ await renderPackageJson(templatesDir, targetDir, config);
43
26
 
44
27
  // 4. Render docker-compose.yml
45
- const dockerComposePath = path.join(targetDir, 'docker-compose.yml');
46
- const dockerTemplate = await fs.readFile(path.join(templatesDir, 'common', 'docker-compose.yml.ejs'), 'utf-8');
47
- const dockerContent = ejs.render(dockerTemplate, {
48
- projectName,
49
- database,
50
- dbName,
51
- communication
52
- });
53
- await fs.writeFile(dockerComposePath, dockerContent);
54
-
55
- // Render README.md
56
- const readmePath = path.join(targetDir, 'README.md');
57
- const readmeTemplate = await fs.readFile(path.join(templatesDir, 'common', 'README.md.ejs'), 'utf-8');
58
- const readmeContent = ejs.render(readmeTemplate, {
59
- projectName,
60
- architecture,
61
- database,
62
- communication,
63
- language,
64
- ciProvider
65
- });
66
- await fs.writeFile(readmePath, readmeContent);
67
-
68
- // Render index file (ts/js)
69
- const indexFileName = language === 'TypeScript' ? 'index.ts' : 'index.js';
70
- const indexPath = path.join(targetDir, 'src', indexFileName);
71
- const indexTemplateSource = path.join(templatePath, 'src', `${indexFileName}.ejs`);
72
-
73
- if (await fs.pathExists(indexTemplateSource)) {
74
- const indexTemplate = await fs.readFile(indexTemplateSource, 'utf-8');
75
- const indexContent = ejs.render(indexTemplate, {
76
- communication,
77
- viewEngine,
78
- database,
79
- architecture,
80
- projectName
81
- });
82
- await fs.writeFile(indexPath, indexContent);
83
- await fs.remove(path.join(targetDir, 'src', `${indexFileName}.ejs`));
84
- }
85
-
86
- // Render Dynamic Controllers/Repositories (User) because they depend on DB type
87
- // MVC Controller
88
- if (architecture === 'MVC') {
89
- const userControllerName = language === 'TypeScript' ? 'userController.ts' : 'userController.js';
90
- const userControllerPath = path.join(targetDir, 'src/controllers', userControllerName);
91
- const userControllerTemplate = path.join(templatePath, 'src/controllers', `${userControllerName}.ejs`);
92
-
93
- if (await fs.pathExists(userControllerTemplate)) {
94
- const content = ejs.render(await fs.readFile(userControllerTemplate, 'utf-8'), { database });
95
- await fs.writeFile(userControllerPath, content);
96
- await fs.remove(path.join(targetDir, 'src/controllers', `${userControllerName}.ejs`));
97
- }
98
- }
99
- // Clean Architecture Repo
100
- else if (architecture === 'Clean Architecture') {
101
- const repoName = language === 'TypeScript' ? 'UserRepository.ts' : 'UserRepository.js';
102
- const repoPath = path.join(targetDir, 'src/infrastructure/repositories', repoName);
103
- const repoTemplate = path.join(templatePath, 'src/infrastructure/repositories', `${repoName}.ejs`);
104
-
105
- if (await fs.pathExists(repoTemplate)) {
106
- const content = ejs.render(await fs.readFile(repoTemplate, 'utf-8'), { database });
107
- await fs.writeFile(repoPath, content);
108
- await fs.remove(path.join(targetDir, 'src/infrastructure/repositories', `${repoName}.ejs`));
109
- }
110
- }
111
- // Render Server (Clean Arch JS only)
112
- if (architecture === 'Clean Architecture' && language === 'JavaScript') {
113
- const serverName = 'server.js';
114
- const serverPath = path.join(targetDir, 'src/infrastructure/webserver', serverName);
115
- const serverTemplate = path.join(templatePath, 'src/infrastructure/webserver', `${serverName}.ejs`);
116
-
117
- if (await fs.pathExists(serverTemplate)) {
118
- const content = ejs.render(await fs.readFile(serverTemplate, 'utf-8'), { communication });
119
- await fs.writeFile(serverPath, content);
120
- await fs.remove(path.join(targetDir, 'src/infrastructure/webserver', `${serverName}.ejs`));
121
- }
122
- }
123
-
124
- // Copy Kafka files if selected
125
- if (communication === 'Kafka') {
126
- const kafkaSource = path.join(templatesDir, 'common', 'kafka', langExt);
127
- await fs.copy(kafkaSource, path.join(targetDir, 'src'));
128
-
129
- // Render Kafka Service with dynamic logger path
130
- const kafkaServiceFileName = `kafkaService.${langExt}`;
131
- const kafkaServiceTemplate = path.join(targetDir, 'src', 'services', `${kafkaServiceFileName}.ejs`);
132
-
133
- if (await fs.pathExists(kafkaServiceTemplate)) {
134
- let loggerPath = architecture === 'Clean Architecture' ? '../log/logger' : '../utils/logger';
135
- let configPath = '../config/kafka';
136
-
137
- if (language === 'TypeScript') {
138
- loggerPath = architecture === 'Clean Architecture' ? '@/infrastructure/log/logger' : '@/utils/logger';
139
- configPath = architecture === 'Clean Architecture' ? '@/infrastructure/config/kafka' : '@/config/kafka';
140
- }
141
-
142
- const content = ejs.render(await fs.readFile(kafkaServiceTemplate, 'utf-8'), { loggerPath, configPath });
143
- await fs.writeFile(path.join(targetDir, 'src', 'services', kafkaServiceFileName), content);
144
- await fs.remove(kafkaServiceTemplate);
145
- }
146
-
147
- if (architecture === 'Clean Architecture') {
148
- // Clean Architecture Restructuring
149
- await fs.ensureDir(path.join(targetDir, 'src/infrastructure/messaging'));
150
- await fs.ensureDir(path.join(targetDir, 'src/infrastructure/config'));
151
-
152
- const serviceExt = language === 'TypeScript' ? 'ts' : 'js';
153
-
154
- // Move Service to Infrastructure/Messaging
155
- await fs.move(
156
- path.join(targetDir, `src/services/kafkaService.${serviceExt}`),
157
- path.join(targetDir, `src/infrastructure/messaging/kafkaClient.${serviceExt}`),
158
- { overwrite: true }
159
- );
160
-
161
- // Move Config to Infrastructure/Config
162
- await fs.move(
163
- path.join(targetDir, `src/config/kafka.${serviceExt}`),
164
- path.join(targetDir, `src/infrastructure/config/kafka.${serviceExt}`),
165
- { overwrite: true }
166
- );
167
-
168
- // Cleanup old folders
169
- await fs.remove(path.join(targetDir, 'src/services'));
170
- // Only remove src/config if empty? But src/config came from kafka copy.
171
- // However, other parts might use src/config?
172
- // In Clean Arch, config is usually in infrastructure?
173
- // Only Kafka adds src/config. Base template doesn't have src/config for Clean Arch (it uses src/infrastructure/database).
174
- await fs.remove(path.join(targetDir, 'src/config'));
175
-
176
- // Remove REST-specific folders (Interfaces)
177
- await fs.remove(path.join(targetDir, 'src/interfaces/routes'));
178
- await fs.remove(path.join(targetDir, 'src/interfaces/controllers'));
179
- } else if (architecture === 'MVC' && (!viewEngine || viewEngine === 'None')) {
180
- // MVC Cleanup
181
- await fs.remove(path.join(targetDir, 'src/controllers'));
182
- await fs.remove(path.join(targetDir, 'src/routes'));
183
- }
184
- }
185
-
186
- // 5. Copy Common Files (.gitignore, Dockerfile, etc.)
187
- await fs.copy(path.join(templatesDir, 'common', '_gitignore'), path.join(targetDir, '.gitignore'));
188
- await fs.copy(path.join(templatesDir, 'common', '.dockerignore'), path.join(targetDir, '.dockerignore'));
189
- // await fs.copy(path.join(templatesDir, 'common', 'Dockerfile'), path.join(targetDir, 'Dockerfile'));
190
- const dockerfileTemplate = await fs.readFile(path.join(templatesDir, 'common', 'Dockerfile'), 'utf-8');
191
- const dockerfileContent = ejs.render(dockerfileTemplate, {
192
- language,
193
- viewEngine
194
- });
195
- await fs.writeFile(path.join(targetDir, 'Dockerfile'), dockerfileContent);
196
-
197
- if (language === 'TypeScript') {
198
- await fs.copy(path.join(templatesDir, 'common', 'tsconfig.json'), path.join(targetDir, 'tsconfig.json'));
199
- }
200
-
201
- // 6. Database Migrations
202
- if (database === 'MongoDB') {
203
- // Copy migrate-mongo config
204
- const migrateConfigTemplate = await fs.readFile(path.join(templatesDir, 'common', 'migrate-mongo-config.js.ejs'), 'utf-8');
205
- const migrateConfigContent = ejs.render(migrateConfigTemplate, { dbName });
206
- await fs.writeFile(path.join(targetDir, 'migrate-mongo-config.js'), migrateConfigContent);
207
-
208
- // Setup migrations directory
209
- await fs.ensureDir(path.join(targetDir, 'migrations'));
210
-
211
- // Create initial migration file with timestamp
212
- const timestamp = new Date().toISOString().replace(/[-T:.Z]/g, '').slice(0, 14); // YYYYMMDDHHMMSS
213
- const migrationTemplate = await fs.readFile(path.join(templatesDir, 'common', 'migrations', 'init.js.ejs'), 'utf-8');
214
- await fs.writeFile(path.join(targetDir, 'migrations', `${timestamp}-initial-setup.js`), migrationTemplate);
215
-
216
- } else {
217
- // Flyway for SQL
218
- await fs.ensureDir(path.join(targetDir, 'flyway/sql'));
219
- const dbType = database === 'PostgreSQL' ? 'postgres' : 'mysql';
220
- await fs.copy(path.join(templatesDir, 'db', dbType), path.join(targetDir, 'flyway/sql'));
221
- }
222
-
223
- // 7. Database Config
224
- const dbConfigFileName = language === 'TypeScript' ? (database === 'MongoDB' ? 'mongoose.ts' : 'database.ts') : (database === 'MongoDB' ? 'mongoose.js' : 'database.js');
225
- const dbConfigTemplateSource = database === 'MongoDB'
226
- ? path.join(templatesDir, 'common', 'database', langExt, `${dbConfigFileName}.ejs`)
227
- : path.join(templatesDir, 'common', 'database', langExt, `${dbConfigFileName}.ejs`);
228
-
229
- let dbConfigTarget;
230
-
28
+ await renderDockerCompose(templatesDir, targetDir, config);
231
29
 
232
- // Copy configurations
233
- if (architecture === 'MVC') {
234
- // Copy Views
235
- if (viewEngine && viewEngine !== 'None') {
236
- await fs.copy(path.join(templatesDir, 'common', 'views', viewEngine.toLowerCase()), path.join(targetDir, 'src/views'));
237
- }
238
- await fs.ensureDir(path.join(targetDir, 'src/config'));
239
- dbConfigTarget = path.join(targetDir, 'src/config', database === 'MongoDB' ? (language === 'TypeScript' ? 'database.ts' : 'database.js') : dbConfigFileName);
240
- } else {
241
- // Clean Architecture
242
- await fs.ensureDir(path.join(targetDir, 'src/infrastructure/database'));
243
- dbConfigTarget = path.join(targetDir, 'src/infrastructure/database', language === 'TypeScript' ? 'database.ts' : 'database.js');
244
- }
30
+ // 5. Render README.md
31
+ await renderReadme(templatesDir, targetDir, config);
245
32
 
246
- if (await fs.pathExists(dbConfigTemplateSource)) {
247
- const dbTemplate = await fs.readFile(dbConfigTemplateSource, 'utf-8');
248
- const dbContent = ejs.render(dbTemplate, { database, dbName, architecture });
249
- // Ensure consistent naming for imports in other files
250
- // For MVC, we might want to rename mongoose.js to database.js to minimize refactoring in index.js?
251
- // Actually, let's keep it consistent. If MVC, we typically call it 'database.js' in require.
252
- // So we should save it as 'database.js' even if source is mongoose.js.ejs
253
- await fs.writeFile(dbConfigTarget, dbContent);
254
- }
33
+ // 6. Render index file (ts/js)
34
+ await renderIndexFile(templatePath, targetDir, config);
255
35
 
256
- // Render Models
257
- const modelFileName = language === 'TypeScript' ? 'User.ts' : 'User.js';
258
- const sourceModelName = database === 'MongoDB' ? `${modelFileName}.mongoose.ejs` : `${modelFileName}.ejs`;
259
- const modelTemplateSource = path.join(templatesDir, 'common', 'database', langExt, 'models', sourceModelName);
260
- let modelTarget;
36
+ // 7. Render Dynamic Components (Controllers/Repos/Server)
37
+ await renderDynamicComponents(templatePath, targetDir, config);
261
38
 
262
- if (architecture === 'MVC') {
263
- await fs.ensureDir(path.join(targetDir, 'src/models'));
264
- modelTarget = path.join(targetDir, 'src/models', modelFileName);
265
- } else {
266
- await fs.ensureDir(path.join(targetDir, 'src/infrastructure/database/models'));
267
- modelTarget = path.join(targetDir, 'src/infrastructure/database/models', modelFileName);
268
- }
39
+ // 8. Kafka Setup
40
+ await setupKafka(templatesDir, targetDir, config);
269
41
 
270
- if (await fs.pathExists(modelTemplateSource)) {
271
- // Models need architecture to decide import path
272
- const modelTemplate = await fs.readFile(modelTemplateSource, 'utf-8');
273
- const modelContent = ejs.render(modelTemplate, { architecture });
274
- await fs.writeFile(modelTarget, modelContent);
275
- }
42
+ // 9. Common Files (.gitignore, Dockerfile, tsconfig)
43
+ await copyCommonFiles(templatesDir, targetDir, language);
44
+ await renderDockerfile(templatesDir, targetDir, config);
276
45
 
277
- // 8. View Engine (MVC)
278
- if (architecture === 'MVC' && viewEngine && viewEngine !== 'None') {
279
- const publicDir = path.join(templatesDir, 'common', 'public');
280
- if (await fs.pathExists(publicDir)) {
281
- await fs.copy(publicDir, path.join(targetDir, 'public'));
282
- }
283
- }
46
+ // 10. Database Setup (Migrations, Config, Models)
47
+ // Note: logic for detailed view copying is also handled nicely if we ensure setupDatabase checks correctly,
48
+ // or we can move strict view logic to setupViews.
49
+ // In strict refactor, database-setup handles the content that was in the DB block.
50
+ await setupDatabase(templatesDir, targetDir, config);
284
51
 
285
- // 9. Render Swagger Config (if .ejs exists)
286
- // MVC TS
287
- const swaggerMvcTs = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
288
- if (await fs.pathExists(swaggerMvcTs)) {
289
- if (communication === 'REST APIs') {
290
- const content = ejs.render(await fs.readFile(swaggerMvcTs, 'utf-8'), { communication });
291
- await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
292
- }
293
- await fs.remove(swaggerMvcTs);
294
- }
295
- // Clean Architecture TS
296
- const swaggerCleanTs = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
297
- // Note: In Clean Arch, it might be in src/infrastructure/webserver or src/config depending on refactor.
298
- // Based on previous moves, we saw it in src/config for TS.
299
- if (await fs.pathExists(swaggerCleanTs)) {
300
- if (communication === 'REST APIs') {
301
- const content = ejs.render(await fs.readFile(swaggerCleanTs, 'utf-8'), { communication });
302
- await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
303
- }
304
- await fs.remove(swaggerCleanTs);
305
- }
52
+ // 10a. Caching Setup
53
+ await setupCaching(templatesDir, targetDir, config);
306
54
 
307
- // 10. Copy Professional Config Files (Eslint, Prettier, Husky)
308
- const eslintTemplate = await fs.readFile(path.join(templatesDir, 'common', '.eslintrc.json.ejs'), 'utf-8');
309
- const eslintContent = ejs.render(eslintTemplate, { language });
310
- await fs.writeFile(path.join(targetDir, '.eslintrc.json'), eslintContent);
55
+ // 11. View Engine Public Assets (MVC)
56
+ await setupViews(templatesDir, targetDir, config);
57
+ // Copy src/views (MVC)
58
+ await setupSrcViews(templatesDir, targetDir, config);
311
59
 
312
- await fs.copy(path.join(templatesDir, 'common', '.prettierrc'), path.join(targetDir, '.prettierrc'));
313
- await fs.copy(path.join(templatesDir, 'common', '.lintstagedrc'), path.join(targetDir, '.lintstagedrc'));
60
+ // 12. Swagger Config
61
+ await renderSwaggerConfig(targetDir, config);
314
62
 
315
- // 10. Copy Test Config & Samples
316
- const jestTemplate = await fs.readFile(path.join(templatesDir, 'common', 'jest.config.js.ejs'), 'utf-8');
317
- const jestContent = ejs.render(jestTemplate, { language });
318
- await fs.writeFile(path.join(targetDir, 'jest.config.js'), jestContent);
63
+ // 13. Professional Config & Tests
64
+ await renderProfessionalConfig(templatesDir, targetDir, language);
65
+ await renderTestSample(templatesDir, targetDir, language);
319
66
 
320
- // Create tests directory
321
- await fs.ensureDir(path.join(targetDir, 'tests'));
322
- const healthTestTemplate = await fs.readFile(path.join(templatesDir, 'common', 'tests', 'health.test.ts.ejs'), 'utf-8');
323
- // For now, the sample test is simple and doesn't explicitly depend on projectName, but we render it just in case we add more dynamic content later
324
- const healthTestContent = ejs.render(healthTestTemplate, { language });
325
- const testFileName = language === 'TypeScript' ? 'health.test.ts' : 'health.test.js';
326
- await fs.writeFile(path.join(targetDir, 'tests', testFileName), healthTestContent);
67
+ // 14. CI/CD
68
+ await setupCiCd(templatesDir, targetDir, config);
327
69
 
328
- // 11. Copy CI/CD Config (Optional)
329
- // 11. Copy CI/CD Config
330
- if (ciProvider === 'GitHub Actions') {
331
- await fs.ensureDir(path.join(targetDir, '.github/workflows'));
332
- await fs.copy(path.join(templatesDir, 'common', '_github/workflows/ci.yml'), path.join(targetDir, '.github/workflows/ci.yml'));
333
- } else if (ciProvider === 'Jenkins') {
334
- const jenkinsTemplate = await fs.readFile(path.join(templatesDir, 'common', 'Jenkinsfile.ejs'), 'utf-8');
335
- const jenkinsContent = ejs.render(jenkinsTemplate, {
336
- projectName
337
- });
338
- await fs.writeFile(path.join(targetDir, 'Jenkinsfile'), jenkinsContent);
339
- }
70
+ // 15. Env Example
71
+ await renderEnvExample(templatesDir, targetDir, config);
340
72
 
341
73
  console.log(`
342
74
  ====================================================
@@ -346,8 +78,8 @@ export const generateProject = async (config) => {
346
78
  Project: ${projectName}
347
79
  Architecture: ${architecture}
348
80
  Language: ${language}
349
- Database: ${database}
350
- Communication: ${communication}
81
+ Database: ${config.database}
82
+ Communication: ${config.communication}${config.caching && config.caching === 'Redis' ? `\n Caching: ${config.caching}` : ''}
351
83
 
352
84
  ----------------------------------------------------
353
85
  ✨ High-Quality Standards Applied:
@@ -357,7 +89,7 @@ export const generateProject = async (config) => {
357
89
  ✅ Security: Helmet, CORS, Rate-Limiting added
358
90
  ✅ Testing: Jest setup for Unit/Integration tests
359
91
  ✅ Docker: Production-ready multi-stage build
360
- ${ciProvider !== 'None' ? `✅ CI/CD: ${ciProvider} Workflow ready` : '❌ CI/CD: Skipped (User preferred)'}
92
+ ${config.ciProvider !== 'None' ? `✅ CI/CD: ${config.ciProvider} Workflow ready` : '❌ CI/CD: Skipped (User preferred)'}
361
93
 
362
94
  ----------------------------------------------------
363
95
  👉 Next Steps:
@@ -0,0 +1,96 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import ejs from 'ejs';
4
+
5
+ export const renderIndexFile = async (templatePath, targetDir, config) => {
6
+ const { communication, viewEngine, database, architecture, projectName, language } = config;
7
+ const indexFileName = language === 'TypeScript' ? 'index.ts' : 'index.js';
8
+ const indexPath = path.join(targetDir, 'src', indexFileName);
9
+ const indexTemplateSource = path.join(templatePath, 'src', `${indexFileName}.ejs`);
10
+
11
+ if (await fs.pathExists(indexTemplateSource)) {
12
+ const indexTemplate = await fs.readFile(indexTemplateSource, 'utf-8');
13
+ const indexContent = ejs.render(indexTemplate, {
14
+ communication,
15
+ viewEngine,
16
+ database,
17
+ architecture,
18
+ projectName
19
+ });
20
+ await fs.writeFile(indexPath, indexContent);
21
+ await fs.remove(path.join(targetDir, 'src', `${indexFileName}.ejs`));
22
+ }
23
+ };
24
+
25
+ export const renderDynamicComponents = async (templatePath, targetDir, config) => {
26
+ const { architecture, language, database, caching } = config;
27
+
28
+ // MVC Controller
29
+ if (architecture === 'MVC') {
30
+ const userControllerName = language === 'TypeScript' ? 'userController.ts' : 'userController.js';
31
+ const userControllerPath = path.join(targetDir, 'src/controllers', userControllerName);
32
+ const userControllerTemplate = path.join(templatePath, 'src/controllers', `${userControllerName}.ejs`);
33
+
34
+ if (await fs.pathExists(userControllerTemplate)) {
35
+ const content = ejs.render(await fs.readFile(userControllerTemplate, 'utf-8'), { database, caching });
36
+ await fs.writeFile(userControllerPath, content);
37
+ await fs.remove(path.join(targetDir, 'src/controllers', `${userControllerName}.ejs`));
38
+ }
39
+ }
40
+ // Clean Architecture Repo
41
+ else if (architecture === 'Clean Architecture') {
42
+ const repoName = language === 'TypeScript' ? 'UserRepository.ts' : 'UserRepository.js';
43
+ const repoPath = path.join(targetDir, 'src/infrastructure/repositories', repoName);
44
+ const repoTemplate = path.join(templatePath, 'src/infrastructure/repositories', `${repoName}.ejs`);
45
+
46
+ if (await fs.pathExists(repoTemplate)) {
47
+ const content = ejs.render(await fs.readFile(repoTemplate, 'utf-8'), { database });
48
+ await fs.writeFile(repoPath, content);
49
+ await fs.remove(path.join(targetDir, 'src/infrastructure/repositories', `${repoName}.ejs`));
50
+ }
51
+ }
52
+ // Render Server (Clean Arch JS only)
53
+ if (architecture === 'Clean Architecture' && language === 'JavaScript') {
54
+ const serverName = 'server.js';
55
+ const serverPath = path.join(targetDir, 'src/infrastructure/webserver', serverName);
56
+ const serverTemplate = path.join(templatePath, 'src/infrastructure/webserver', `${serverName}.ejs`);
57
+
58
+ if (await fs.pathExists(serverTemplate)) {
59
+ const content = ejs.render(await fs.readFile(serverTemplate, 'utf-8'), { communication: config.communication });
60
+ await fs.writeFile(serverPath, content);
61
+ await fs.remove(path.join(targetDir, 'src/infrastructure/webserver', `${serverName}.ejs`));
62
+ }
63
+ }
64
+ };
65
+
66
+ export const renderSwaggerConfig = async (targetDir, config) => {
67
+ const { communication } = config;
68
+
69
+ // Check for Swagger config template (typically in src/config/swagger.ts.ejs)
70
+ // This path is common for both MVC and Clean Arch TS templates based on current structure
71
+ // Check for Swagger config template (typically in src/config/swagger.ts.ejs)
72
+ const swaggerTsTemplate = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
73
+
74
+ // Ensure config directory exists
75
+ await fs.ensureDir(path.join(targetDir, 'src', 'config'));
76
+
77
+ if (await fs.pathExists(swaggerTsTemplate)) {
78
+ // Render if REST APIs
79
+ if (communication === 'REST APIs') {
80
+ const content = ejs.render(await fs.readFile(swaggerTsTemplate, 'utf-8'), { communication });
81
+ await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
82
+ }
83
+ // Always remove the template after processing or if not needed
84
+ await fs.remove(swaggerTsTemplate);
85
+ }
86
+ };
87
+
88
+ export const setupViews = async (templatesDir, targetDir, config) => {
89
+ const { architecture, viewEngine } = config;
90
+ if (architecture === 'MVC' && viewEngine && viewEngine !== 'None') {
91
+ const viewsSource = path.join(templatesDir, 'common', 'views', viewEngine.toLowerCase());
92
+ if (await fs.pathExists(viewsSource)) {
93
+ await fs.copy(viewsSource, path.join(targetDir, 'src/views'));
94
+ }
95
+ }
96
+ };
@@ -0,0 +1,56 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import ejs from 'ejs';
4
+
5
+ export const setupCaching = async (templatesDir, targetDir, config) => {
6
+ const { caching, language, architecture } = config;
7
+ if (!caching || caching === 'None') return;
8
+
9
+ if (caching === 'Redis') {
10
+ const langExt = language === 'TypeScript' ? 'ts' : 'js';
11
+ const redisClientObj = language === 'TypeScript' ? 'redisClient.ts' : 'redisClient.js';
12
+ const redisSource = path.join(templatesDir, 'common', 'caching', langExt, `${redisClientObj}.ejs`);
13
+
14
+ let redisTarget;
15
+ let loggerPath;
16
+
17
+ if (architecture === 'MVC') {
18
+ await fs.ensureDir(path.join(targetDir, 'src/config'));
19
+ redisTarget = path.join(targetDir, 'src/config', redisClientObj);
20
+ loggerPath = language === 'TypeScript' ? '@/utils/logger' : '../utils/logger';
21
+ } else {
22
+ // Clean Architecture
23
+ await fs.ensureDir(path.join(targetDir, 'src/infrastructure/caching'));
24
+ redisTarget = path.join(targetDir, 'src/infrastructure/caching', redisClientObj);
25
+ loggerPath = language === 'TypeScript' ? '@/infrastructure/log/logger' : '../log/logger';
26
+
27
+ // Overwrite UseCase with Caching Enabled
28
+ const useCaseName = language === 'TypeScript' ? 'getAllUsers.ts' : 'GetAllUsers.js';
29
+ const useCaseSource = path.join(templatesDir, 'common', 'caching', 'clean', langExt, `${useCaseName}.ejs`);
30
+
31
+ // Both TS and JS templates use 'usecases' directory
32
+ const useCaseTargetDir = path.join(targetDir, 'src/usecases');
33
+ await fs.ensureDir(useCaseTargetDir);
34
+
35
+ if (await fs.pathExists(useCaseSource)) {
36
+ const ucContent = await fs.readFile(useCaseSource, 'utf-8');
37
+ await fs.writeFile(path.join(useCaseTargetDir, useCaseName), ucContent);
38
+ }
39
+
40
+ // Also Overwrite CreateUser with Caching (Invalidation) Enabled
41
+ const createUserParams = language === 'TypeScript' ? { name: 'createUser.ts', src: 'createUser.ts.ejs' } : { name: 'CreateUser.js', src: 'CreateUser.js.ejs' };
42
+ const createUserSource = path.join(templatesDir, 'common', 'caching', 'clean', langExt, createUserParams.src);
43
+
44
+ if (await fs.pathExists(createUserSource)) {
45
+ const createUserContent = await fs.readFile(createUserSource, 'utf-8');
46
+ await fs.writeFile(path.join(useCaseTargetDir, createUserParams.name), createUserContent);
47
+ }
48
+ }
49
+
50
+ if (await fs.pathExists(redisSource)) {
51
+ const redisTemplate = await fs.readFile(redisSource, 'utf-8');
52
+ const content = ejs.render(redisTemplate, { loggerPath });
53
+ await fs.writeFile(redisTarget, content);
54
+ }
55
+ }
56
+ };