nodejs-quickstart-structure 1.1.11 โ†’ 1.3.2

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 CHANGED
@@ -22,7 +22,7 @@ We don't just generate boilerplate; we generate **production-ready** foundations
22
22
  - **๐Ÿ” Code Quality**: Pre-configured `Eslint` and `Prettier` for consistent coding standards.
23
23
  - **๐Ÿ›ก๏ธ Security**: Built-in `Helmet`, `HPP`, `CORS`, and Rate-Limiting middleware.
24
24
  - **๐Ÿงช Testing Strategy**: Integrated `Jest` and `Supertest` setup for Unit and Integration testing.
25
- - **๐Ÿ”„ CI/CD Support**: Optional **GitHub Actions** workflow integration.
25
+ - **๐Ÿ”„ CI/CD Support**: Choice between **GitHub Actions** and **Jenkins**.
26
26
  - **โš“ Git Hooks**: `Husky` and `Lint-Staged` to ensure no bad code is ever committed.
27
27
  - **๐Ÿณ DevOps**: Highly optimized **Multi-Stage Dockerfile** for small, secure production images.
28
28
 
@@ -70,7 +70,7 @@ The CLI will guide you through the following steps:
70
70
  4. **Database**: `MySQL` or `PostgreSQL`.
71
71
  5. **Database Name**: The name of the initial database.
72
72
  6. **Communication**: `REST APIs` (default) or `Kafka`.
73
- 7. **CI/CD**: `Include GitHub Actions Workflow` (Optional).
73
+ 7. **CI/CD**: `GitHub Actions`, `Jenkins`, or `None`.
74
74
 
75
75
  ## Generated Project Structure
76
76
 
package/bin/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander';
4
4
  import chalk from 'chalk';
@@ -32,12 +32,11 @@ program
32
32
  .option('-d, --database <database>', 'Database (MySQL, PostgreSQL)')
33
33
  .option('--db-name <name>', 'Database name')
34
34
  .option('-c, --communication <communication>', 'Communication (REST APIs, Kafka)')
35
- .option('--include-ci', 'Include GitHub Actions CI Workflow')
35
+ .option('--ci-provider <provider>', 'CI/CD Provider (None, GitHub Actions, Jenkins)')
36
36
  .action(async (options) => {
37
37
  // Fix for Commander camelCase conversion
38
- if (options.includeCi) {
39
- options.includeCI = options.includeCi;
40
- delete options.includeCi;
38
+ if (options.ciProvider) {
39
+ options.ciProvider = options.ciProvider;
41
40
  }
42
41
 
43
42
  console.log(chalk.blue('Welcome to the Node.js Quickstart Generator!'));
@@ -58,7 +57,6 @@ program
58
57
  process.exit(1);
59
58
  }
60
59
  });
61
-
62
60
  program.parse(process.argv);
63
61
 
64
62
  if (!process.argv.slice(2).length) {
package/lib/generator.js CHANGED
@@ -7,7 +7,7 @@ const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = path.dirname(__filename);
8
8
 
9
9
  export const generateProject = async (config) => {
10
- const { projectName, architecture, database, dbName, communication, language, viewEngine, includeCI } = config;
10
+ const { projectName, architecture, database, dbName, communication, language, viewEngine, ciProvider } = config;
11
11
  const targetDir = path.resolve(process.cwd(), projectName);
12
12
  const templatesDir = path.join(__dirname, '../templates');
13
13
 
@@ -60,7 +60,8 @@ export const generateProject = async (config) => {
60
60
  architecture,
61
61
  database,
62
62
  communication,
63
- language
63
+ language,
64
+ ciProvider
64
65
  });
65
66
  await fs.writeFile(readmePath, readmeContent);
66
67
 
@@ -275,9 +276,16 @@ export const generateProject = async (config) => {
275
276
  await fs.writeFile(path.join(targetDir, 'tests', testFileName), healthTestContent);
276
277
 
277
278
  // 11. Copy CI/CD Config (Optional)
278
- if (includeCI) {
279
+ // 11. Copy CI/CD Config
280
+ if (ciProvider === 'GitHub Actions') {
279
281
  await fs.ensureDir(path.join(targetDir, '.github/workflows'));
280
282
  await fs.copy(path.join(templatesDir, 'common', '_github/workflows/ci.yml'), path.join(targetDir, '.github/workflows/ci.yml'));
283
+ } else if (ciProvider === 'Jenkins') {
284
+ const jenkinsTemplate = await fs.readFile(path.join(templatesDir, 'common', 'Jenkinsfile.ejs'), 'utf-8');
285
+ const jenkinsContent = ejs.render(jenkinsTemplate, {
286
+ projectName
287
+ });
288
+ await fs.writeFile(path.join(targetDir, 'Jenkinsfile'), jenkinsContent);
281
289
  }
282
290
 
283
291
  console.log(`
@@ -299,7 +307,7 @@ export const generateProject = async (config) => {
299
307
  โœ… Security: Helmet, CORS, Rate-Limiting added
300
308
  โœ… Testing: Jest setup for Unit/Integration tests
301
309
  โœ… Docker: Production-ready multi-stage build
302
- ${includeCI ? 'โœ… CI/CD: GitHub Actions Workflow ready' : 'โŒ CI/CD: Skipped (User preferred)'}
310
+ ${ciProvider !== 'None' ? `โœ… CI/CD: ${ciProvider} Workflow ready` : 'โŒ CI/CD: Skipped (User preferred)'}
303
311
 
304
312
  ----------------------------------------------------
305
313
  ๐Ÿ‘‰ Next Steps:
package/lib/prompts.js CHANGED
@@ -63,11 +63,12 @@ export const getProjectDetails = async (options = {}) => {
63
63
  when: !options.communication
64
64
  },
65
65
  {
66
- type: 'confirm',
67
- name: 'includeCI',
68
- message: 'Include GitHub Actions CI Workflow? (Professional CI/CD pipeline included)',
69
- default: false,
70
- when: !options.includeCI
66
+ type: 'list',
67
+ name: 'ciProvider',
68
+ message: 'Select CI/CD Provider:',
69
+ choices: ['None', 'GitHub Actions', 'Jenkins'],
70
+ default: 'None',
71
+ when: !options.ciProvider
71
72
  }
72
73
  ];
73
74
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-quickstart-structure",
3
- "version": "1.1.11",
3
+ "version": "1.3.2",
4
4
  "type": "module",
5
5
  "description": "A CLI to scaffold Node.js microservices with MVC or Clean Architecture",
6
6
  "main": "bin/index.js",
@@ -20,7 +20,7 @@
20
20
  "mvc",
21
21
  "clean-architecture"
22
22
  ],
23
- "author": "Pau Dang <paudang@example.com>",
23
+ "author": "Pau Dang <phucdangb1400718@gmail.com>",
24
24
  "repository": {
25
25
  "type": "git",
26
26
  "url": "git+https://github.com/paudang/nodejs-quickstart-structure.git"
@@ -1,11 +1,11 @@
1
1
  const express = require('express');
2
2
  const cors = require('cors');
3
3
  require('dotenv').config();
4
- <% if (communication === 'REST APIs') { %>const apiRoutes = require('../../interfaces/routes/api');<% } %>
5
- <% if (communication === 'REST APIs') { %>
4
+ <% if (communication === 'REST APIs') { %>const apiRoutes = require('../../interfaces/routes/api');<% } -%>
5
+ <% if (communication === 'REST APIs') { -%>
6
6
  const swaggerUi = require('swagger-ui-express');
7
7
  const swaggerSpecs = require('./swagger');
8
- <% } %>
8
+ <% } -%>
9
9
 
10
10
  const startServer = (port) => {
11
11
  const app = express();
@@ -13,11 +13,13 @@ const startServer = (port) => {
13
13
  app.use(cors());
14
14
  app.use(express.json());
15
15
 
16
- <% if (communication === 'REST APIs') { %>app.use('/api', apiRoutes);<% } %>
16
+ <% if (communication === 'REST APIs') { -%>
17
+ app.use('/api', apiRoutes);
18
+ <% } -%>
17
19
 
18
- <% if (communication === 'REST APIs') { %>
20
+ <% if (communication === 'REST APIs') { -%>
19
21
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
20
- <% } %>
22
+ <% } -%>
21
23
 
22
24
  app.get('/health', (req, res) => {
23
25
  res.json({ status: 'UP' });
@@ -5,11 +5,11 @@ import hpp from 'hpp';
5
5
  import rateLimit from 'express-rate-limit';
6
6
  import dotenv from 'dotenv';
7
7
  import logger from './infrastructure/log/logger';
8
- <% if (communication === 'REST APIs') { %>import userRoutes from './interfaces/routes/userRoutes';<% } %>
9
- <% if (communication === 'REST APIs') { %>
8
+ <% if (communication === 'REST APIs') { %>import userRoutes from './interfaces/routes/userRoutes';<% } -%>
9
+ <% if (communication === 'REST APIs') { -%>
10
10
  import swaggerUi from 'swagger-ui-express';
11
- import swaggerSpecs from './config/swagger';<% } %>
12
- <% if (communication === 'Kafka') { %>import { KafkaService } from './infrastructure/messaging/kafkaClient';<% } %>
11
+ import swaggerSpecs from './config/swagger';<% } -%>
12
+ <% if (communication === 'Kafka') { %>import { KafkaService } from './infrastructure/messaging/kafkaClient';<% } -%>
13
13
 
14
14
  dotenv.config();
15
15
 
@@ -26,10 +26,12 @@ app.use(limiter);
26
26
  app.use(express.json());
27
27
 
28
28
  <% if (communication === 'REST APIs') { -%>
29
- app.use('/api/users', userRoutes);<% } -%>
29
+ app.use('/api/users', userRoutes);
30
+ <% } -%>
30
31
 
31
- <% if (communication === 'REST APIs') { %>
32
- app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));<% } %>
32
+ <% if (communication === 'REST APIs') { -%>
33
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
34
+ <% } -%>
33
35
 
34
36
  app.get('/health', (req: Request, res: Response) => {
35
37
  res.json({ status: 'UP' });
@@ -46,7 +48,7 @@ const syncDatabase = async () => {
46
48
 
47
49
  app.listen(port, async () => {
48
50
  logger.info(`Server running on port ${port}`);
49
- <% if (communication === 'Kafka') { %>
51
+ <%_ if (communication === 'Kafka') { -%>
50
52
  try {
51
53
  const kafkaService = new KafkaService();
52
54
  await kafkaService.connect();
@@ -56,7 +58,7 @@ const syncDatabase = async () => {
56
58
  } catch (err) {
57
59
  logger.error('Failed to connect to Kafka:', err);
58
60
  }
59
- <% } -%>
61
+ <%_ } -%>
60
62
  });
61
63
  break;
62
64
  } catch (err) {
@@ -0,0 +1,35 @@
1
+ pipeline {
2
+ agent any
3
+
4
+ environment {
5
+ CI = 'true'
6
+ }
7
+
8
+ stages {
9
+ stage('Install Dependencies') {
10
+ steps {
11
+ // Use npm ci for clean install if package-lock.json exists, else npm install
12
+ sh 'if [ -f package-lock.json ]; then npm ci; else npm install; fi'
13
+ }
14
+ }
15
+
16
+ stage('Lint') {
17
+ steps {
18
+ sh 'npm run lint'
19
+ }
20
+ }
21
+
22
+ stage('Test') {
23
+ steps {
24
+ sh 'npm test'
25
+ }
26
+ }
27
+ }
28
+
29
+ post {
30
+ always {
31
+ // Clean up workspace
32
+ cleanWs()
33
+ }
34
+ }
35
+ }
@@ -16,6 +16,23 @@ This project comes pre-configured with industry-standard tooling for **Code Qual
16
16
  - **Testing**: Jest (Unit & Integration).
17
17
  - **DevOps**: Multi-stage Docker build, CI/CD ready.
18
18
 
19
+ ## ๐Ÿ”„ CI/CD Pipeline
20
+ <% if (ciProvider === 'GitHub Actions') { -%>
21
+ This project includes a **GitHub Actions** workflow located in `.github/workflows/ci.yml`.
22
+ It automatically runs:
23
+ - Linting
24
+ - Tests
25
+ - Builds
26
+ <% } else if (ciProvider === 'Jenkins') { -%>
27
+ This project includes a **Jenkinsfile** for comprehensive CI/CD.
28
+ Pipeline stages:
29
+ - Install Dependencies
30
+ - Lint
31
+ - Test
32
+ <% } else { -%>
33
+ CI/CD is not currently configured, but the project is ready for integration.
34
+ <% } -%>
35
+
19
36
  ## ๐Ÿ› ๏ธ Getting Started
20
37
 
21
38
  ### 1. Prerequisites
@@ -24,6 +41,9 @@ This project comes pre-configured with industry-standard tooling for **Code Qual
24
41
 
25
42
  ### 2. Quick Start
26
43
  ```bash
44
+ # Initialize Git (Required for Husky)
45
+ git init
46
+
27
47
  # Install dependencies
28
48
  npm install
29
49
 
@@ -1,12 +1,12 @@
1
1
  const express = require('express');
2
2
  const cors = require('cors');
3
3
  require('dotenv').config();
4
- <% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>const apiRoutes = require('./routes/api');<% } %>
5
- <% if (communication === 'Kafka') { %>const { connectKafka, sendMessage } = require('./services/kafkaService');<% } %>
6
- <% if (communication === 'REST APIs') { %>
4
+ <% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>const apiRoutes = require('./routes/api');<% } -%>
5
+ <% if (communication === 'Kafka') { %>const { connectKafka, sendMessage } = require('./services/kafkaService');<% } -%>
6
+ <% if (communication === 'REST APIs') { -%>
7
7
  const swaggerUi = require('swagger-ui-express');
8
8
  const swaggerSpecs = require('./config/swagger');
9
- <% } %>
9
+ <% } -%>
10
10
 
11
11
  const app = express();
12
12
  const PORT = process.env.PORT || 3000;
@@ -15,11 +15,11 @@ const logger = require('./utils/logger');
15
15
  app.use(cors());
16
16
  app.use(express.json());
17
17
 
18
- <% if (communication === 'REST APIs') { %>
18
+ <% if (communication === 'REST APIs') { -%>
19
19
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
20
- <% } %>
20
+ <% } -%>
21
21
 
22
- <% if (viewEngine === 'EJS' || viewEngine === 'Pug') { %>
22
+ <% if (viewEngine === 'EJS' || viewEngine === 'Pug') { -%>
23
23
  // View Engine Setup
24
24
  const path = require('path');
25
25
  app.set('views', path.join(__dirname, 'views'));
@@ -57,7 +57,7 @@ const syncDatabase = async () => {
57
57
  // Start Server after DB is ready
58
58
  app.listen(PORT, async () => {
59
59
  logger.info(`Server running on port ${PORT}`);
60
- <% if (communication === 'Kafka') { %>
60
+ <%_ if (communication === 'Kafka') { -%>
61
61
  try {
62
62
  await connectKafka();
63
63
  logger.info('Kafka connected');
@@ -66,7 +66,7 @@ const syncDatabase = async () => {
66
66
  } catch (err) {
67
67
  logger.error('Failed to connect to Kafka:', err);
68
68
  }
69
- <% } -%>
69
+ <%_ } -%>
70
70
  });
71
71
  break;
72
72
  } catch (err) {
@@ -5,11 +5,11 @@ import hpp from 'hpp';
5
5
  import rateLimit from 'express-rate-limit';
6
6
  import dotenv from 'dotenv';
7
7
  import logger from './utils/logger';
8
- <% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>import apiRoutes from './routes/api';<% } %>
8
+ <% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { %>import apiRoutes from './routes/api';<% } -%>
9
9
  <% if (communication === 'REST APIs') { %>
10
10
  import swaggerUi from 'swagger-ui-express';
11
- import swaggerSpecs from './config/swagger';<% } %>
12
- <% if (communication === 'Kafka') { %>import { KafkaService } from './services/kafkaService';<% } %>
11
+ import swaggerSpecs from './config/swagger';<% } -%>
12
+ <% if (communication === 'Kafka') { %>import { KafkaService } from './services/kafkaService';<% } -%>
13
13
 
14
14
  dotenv.config();
15
15
 
@@ -25,18 +25,21 @@ app.use(limiter);
25
25
 
26
26
  app.use(express.json());
27
27
 
28
- <% if (communication === 'REST APIs') { %>
29
- app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));<% } %>
28
+ <% if (communication === 'REST APIs') { -%>
29
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
30
+ <% } -%>
30
31
 
31
- <% if (viewEngine === 'EJS' || viewEngine === 'Pug') { %>
32
+ <% if (viewEngine === 'EJS' || viewEngine === 'Pug') { -%>
32
33
  // View Engine Setup
33
34
  import path from 'path';
34
35
  app.set('views', path.join(__dirname, 'views'));
35
- app.set('view engine', '<%= viewEngine.toLowerCase() %>');<% } -%>
36
+ app.set('view engine', '<%= viewEngine.toLowerCase() %>');
37
+ <% } -%>
36
38
 
37
39
  // Routes
38
40
  <% if (communication === 'REST APIs' || (viewEngine && viewEngine !== 'None')) { -%>
39
- app.use('/api', apiRoutes);<% } -%>
41
+ app.use('/api', apiRoutes);
42
+ <% } -%>
40
43
  <% if (viewEngine && viewEngine !== 'None') { -%>
41
44
  app.get('/', (req: Request, res: Response) => {
42
45
  res.render('index', {
@@ -64,7 +67,7 @@ const syncDatabase = async () => {
64
67
  // Start Server after DB is ready
65
68
  app.listen(port, async () => {
66
69
  logger.info(`Server running on port ${port}`);
67
- <% if (communication === 'Kafka') { %>
70
+ <%_ if (communication === 'Kafka') { -%>
68
71
  try {
69
72
  const kafkaService = new KafkaService();
70
73
  await kafkaService.connect();
@@ -74,7 +77,7 @@ const syncDatabase = async () => {
74
77
  } catch (err) {
75
78
  logger.error('Failed to connect to Kafka:', err);
76
79
  }
77
- <% } -%>
80
+ <%_ } -%>
78
81
  });
79
82
  break;
80
83
  } catch (err) {