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.
- package/CHANGELOG.md +78 -0
- package/README.md +18 -20
- package/bin/index.js +1 -0
- package/docs/generateCase.md +127 -61
- package/docs/generatorFlow.md +15 -3
- package/docs/releaseNoteRule.md +42 -0
- package/lib/generator.js +46 -314
- package/lib/modules/app-setup.js +96 -0
- package/lib/modules/caching-setup.js +56 -0
- package/lib/modules/config-files.js +109 -0
- package/lib/modules/database-setup.js +111 -0
- package/lib/modules/kafka-setup.js +112 -0
- package/lib/modules/project-setup.js +31 -0
- package/lib/prompts.js +12 -4
- package/package.json +4 -3
- package/templates/clean-architecture/js/src/index.js.ejs +19 -6
- package/templates/clean-architecture/js/src/infrastructure/log/logger.js +16 -2
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +18 -6
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +2 -0
- package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +1 -2
- package/templates/clean-architecture/ts/src/index.ts.ejs +27 -19
- package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +16 -2
- package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +17 -6
- package/templates/common/.env.example.ejs +39 -0
- package/templates/common/Dockerfile +3 -0
- package/templates/common/Jenkinsfile.ejs +41 -0
- package/templates/common/README.md.ejs +113 -106
- package/templates/common/caching/clean/js/CreateUser.js.ejs +25 -0
- package/templates/common/caching/clean/js/GetAllUsers.js.ejs +33 -0
- package/templates/common/caching/clean/ts/createUser.ts.ejs +23 -0
- package/templates/common/caching/clean/ts/getAllUsers.ts.ejs +30 -0
- package/templates/common/caching/js/redisClient.js.ejs +71 -0
- package/templates/common/caching/ts/redisClient.ts.ejs +76 -0
- package/templates/common/docker-compose.yml.ejs +156 -116
- package/templates/common/package.json.ejs +13 -2
- package/templates/mvc/js/src/controllers/userController.js.ejs +35 -3
- package/templates/mvc/js/src/index.js.ejs +26 -17
- package/templates/mvc/js/src/utils/logger.js +16 -6
- package/templates/mvc/ts/src/config/swagger.ts.ejs +1 -2
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +35 -3
- package/templates/mvc/ts/src/index.ts.ejs +27 -18
- package/templates/mvc/ts/src/utils/logger.ts +16 -2
- package/templates/mvc/js/src/config/database.js +0 -12
- /package/templates/db/mysql/{V1__Initial_Setup.sql → V1__Initial_Setup.sql.ejs} +0 -0
- /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,
|
|
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
|
-
|
|
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
|
|
21
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
233
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
|
257
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
//
|
|
286
|
-
|
|
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
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
await
|
|
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
|
-
|
|
313
|
-
await
|
|
60
|
+
// 12. Swagger Config
|
|
61
|
+
await renderSwaggerConfig(targetDir, config);
|
|
314
62
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
//
|
|
321
|
-
await
|
|
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
|
-
//
|
|
329
|
-
|
|
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
|
+
};
|