nodejs-quickstart-structure 1.1.6
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 +98 -0
- package/bin/index.js +66 -0
- package/docs/demo.gif +0 -0
- package/docs/generateCase.md +58 -0
- package/lib/generator.js +315 -0
- package/lib/prompts.js +76 -0
- package/package.json +40 -0
- package/templates/clean-architecture/js/src/domain/models/User.js +9 -0
- package/templates/clean-architecture/js/src/domain/repositories/UserRepository.js +9 -0
- package/templates/clean-architecture/js/src/index.js.ejs +37 -0
- package/templates/clean-architecture/js/src/infrastructure/log/logger.js +22 -0
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +19 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +31 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.js +23 -0
- package/templates/clean-architecture/js/src/interfaces/controllers/UserController.js +30 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.js +77 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.js +14 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +12 -0
- package/templates/clean-architecture/js/src/utils/httpCodes.js +9 -0
- package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +24 -0
- package/templates/clean-architecture/ts/src/domain/user.ts +7 -0
- package/templates/clean-architecture/ts/src/index.ts.ejs +71 -0
- package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +22 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +18 -0
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts +45 -0
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +76 -0
- package/templates/clean-architecture/ts/src/usecases/createUser.ts +13 -0
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +10 -0
- package/templates/clean-architecture/ts/src/utils/httpCodes.ts +7 -0
- package/templates/common/.dockerignore +10 -0
- package/templates/common/.eslintrc.json.ejs +26 -0
- package/templates/common/.lintstagedrc +6 -0
- package/templates/common/Dockerfile +49 -0
- package/templates/common/README.md.ejs +87 -0
- package/templates/common/_gitignore +5 -0
- package/templates/common/database/js/database.js.ejs +20 -0
- package/templates/common/database/js/models/User.js.ejs +30 -0
- package/templates/common/database/ts/database.ts.ejs +22 -0
- package/templates/common/database/ts/models/User.ts.ejs +34 -0
- package/templates/common/docker-compose.yml.ejs +93 -0
- package/templates/common/jest.config.js.ejs +11 -0
- package/templates/common/kafka/js/config/kafka.js +8 -0
- package/templates/common/kafka/js/services/kafkaService.js +29 -0
- package/templates/common/kafka/ts/config/kafka.ts +6 -0
- package/templates/common/kafka/ts/services/kafkaService.ts +36 -0
- package/templates/common/package.json.ejs +78 -0
- package/templates/common/tsconfig.json +19 -0
- package/templates/common/views/ejs/index.ejs +31 -0
- package/templates/common/views/pug/index.pug +26 -0
- package/templates/db/mysql/V1__Initial_Setup.sql +9 -0
- package/templates/db/postgres/V1__Initial_Setup.sql +9 -0
- package/templates/mvc/js/src/config/database.js +12 -0
- package/templates/mvc/js/src/config/swagger.js +23 -0
- package/templates/mvc/js/src/controllers/userController.js +14 -0
- package/templates/mvc/js/src/controllers/userController.js.ejs +23 -0
- package/templates/mvc/js/src/index.js.ejs +81 -0
- package/templates/mvc/js/src/routes/api.js +74 -0
- package/templates/mvc/js/src/utils/httpCodes.js +9 -0
- package/templates/mvc/js/src/utils/logger.js +30 -0
- package/templates/mvc/ts/src/config/swagger.ts.ejs +24 -0
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +32 -0
- package/templates/mvc/ts/src/index.ts.ejs +89 -0
- package/templates/mvc/ts/src/routes/api.ts +76 -0
- package/templates/mvc/ts/src/utils/httpCodes.ts +7 -0
- package/templates/mvc/ts/src/utils/logger.ts +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Node.js Quickstart Generator
|
|
2
|
+
|
|
3
|
+
A powerful CLI tool to scaffold production-ready Node.js microservices with built-in best practices, allowing you to choose between **MVC** or **Clean Architecture**, **JavaScript** or **TypeScript**, and your preferred database.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Interactive CLI**: Easy-to-use prompts to configure your project.
|
|
10
|
+
- **Multiple Architectures**: Supports both **MVC** (Model-View-Controller) and **Clean Architecture**.
|
|
11
|
+
- **Language Support**: Choose between **JavaScript** and **TypeScript**.
|
|
12
|
+
- **Database Integration**: Pre-configured setup for **MySQL** or **PostgreSQL**.
|
|
13
|
+
- **Microservices Ready**: Optional **Kafka** integration for event-driven communication.
|
|
14
|
+
- **Dockerized**: Automatically generates `docker-compose.yml` for DB, Kafka, and Zookeeper.
|
|
15
|
+
- **Database Migrations**: Integrated **Flyway** support for SQL migrations.
|
|
16
|
+
- **Professional Standards**: Generated projects come with highly professional, industry-standard tooling.
|
|
17
|
+
|
|
18
|
+
## ๐ Professional Standards (New)
|
|
19
|
+
|
|
20
|
+
We don't just generate boilerplate; we generate **production-ready** foundations. Every project includes:
|
|
21
|
+
|
|
22
|
+
- **๐ Code Quality**: Pre-configured `Eslint` and `Prettier` for consistent coding standards.
|
|
23
|
+
- **๐ก๏ธ Security**: Built-in `Helmet`, `HPP`, `CORS`, and Rate-Limiting middleware.
|
|
24
|
+
- **๐งช Testing Strategy**: Integrated `Jest` and `Supertest` setup for Unit and Integration testing.
|
|
25
|
+
- **๐ CI/CD Support**: Optional **GitHub Actions** workflow integration.
|
|
26
|
+
- **โ Git Hooks**: `Husky` and `Lint-Staged` to ensure no bad code is ever committed.
|
|
27
|
+
- **๐ณ DevOps**: Highly optimized **Multi-Stage Dockerfile** for small, secure production images.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
You can install the tool globally directly from GitHub:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g paudang/nodejs-quickstart-structure
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Manual Installation (For Development)
|
|
38
|
+
|
|
39
|
+
If you want to modify the CLI itself:
|
|
40
|
+
|
|
41
|
+
1. Clone this repository:
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/paudang/nodejs-quickstart-structure.git
|
|
44
|
+
cd nodejs-quickstart-structure
|
|
45
|
+
```
|
|
46
|
+
2. Install dependencies:
|
|
47
|
+
```bash
|
|
48
|
+
npm install
|
|
49
|
+
```
|
|
50
|
+
3. Link globally:
|
|
51
|
+
```bash
|
|
52
|
+
npm link
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
Once installed, simply run the following command in any directory where you want to create a new project:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
nodejs-quickstart init
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Configuration Options
|
|
64
|
+
|
|
65
|
+
The CLI will guide you through the following steps:
|
|
66
|
+
|
|
67
|
+
1. **Project Name**: The name of the folder to create.
|
|
68
|
+
2. **Language**: `JavaScript` or `TypeScript`.
|
|
69
|
+
3. **Architecture**: `MVC` or `Clean Architecture`.
|
|
70
|
+
4. **Database**: `MySQL` or `PostgreSQL`.
|
|
71
|
+
5. **Database Name**: The name of the initial database.
|
|
72
|
+
6. **Communication**: `REST APIs` (default) or `Kafka`.
|
|
73
|
+
7. **CI/CD**: `Include GitHub Actions Workflow` (Optional).
|
|
74
|
+
|
|
75
|
+
## Generated Project Structure
|
|
76
|
+
|
|
77
|
+
The generated project will include:
|
|
78
|
+
|
|
79
|
+
- `src/`: Source code (controllers, routes, services/use-cases).
|
|
80
|
+
- `flyway/sql/`: SQL migration scripts.
|
|
81
|
+
- `docker-compose.yml`: Services configuration for DB, Flyway, and Kafka.
|
|
82
|
+
- `package.json`: Dependencies and scripts (`start`, `dev`, `build`).
|
|
83
|
+
- `tsconfig.json`: (If TypeScript is selected) Type checking configuration.
|
|
84
|
+
|
|
85
|
+
### Getting Started with the Generated App
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd <your-project-name>
|
|
89
|
+
|
|
90
|
+
# Start infrastructure (DB, etc.)
|
|
91
|
+
npm install
|
|
92
|
+
|
|
93
|
+
docker-compose up
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
ISC
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { getProjectDetails } from '../lib/prompts.js';
|
|
6
|
+
import { generateProject } from '../lib/generator.js';
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name('nodejs-quickstart')
|
|
18
|
+
.description('๐ CLI to scaffold production-ready Node.js microservices.\n\nGenerates projects with:\n- MVC or Clean Architecture\n- REST or Kafka\n- MySQL or PostgreSQL\n- Docker & Flyway support')
|
|
19
|
+
.version(pkg.version, '-v, --version', 'Output the current version')
|
|
20
|
+
.addHelpText('after', `
|
|
21
|
+
\n${chalk.yellow('Example:')}
|
|
22
|
+
$ nodejs-quickstart init ${chalk.gray('# Start the interactive setup')}
|
|
23
|
+
`);
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.command('init')
|
|
27
|
+
.description('Initialize a new Node.js project')
|
|
28
|
+
.option('-n, --project-name <name>', 'Project name')
|
|
29
|
+
.option('-l, --language <language>', 'Language (JavaScript, TypeScript)')
|
|
30
|
+
.option('-a, --architecture <architecture>', 'Architecture (MVC, Clean Architecture)')
|
|
31
|
+
.option('--view-engine <view>', 'View Engine (None, EJS, Pug) - MVC only')
|
|
32
|
+
.option('-d, --database <database>', 'Database (MySQL, PostgreSQL)')
|
|
33
|
+
.option('--db-name <name>', 'Database name')
|
|
34
|
+
.option('-c, --communication <communication>', 'Communication (REST APIs, Kafka)')
|
|
35
|
+
.option('--include-ci', 'Include GitHub Actions CI Workflow')
|
|
36
|
+
.action(async (options) => {
|
|
37
|
+
// Fix for Commander camelCase conversion
|
|
38
|
+
if (options.includeCi) {
|
|
39
|
+
options.includeCI = options.includeCi;
|
|
40
|
+
delete options.includeCi;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(chalk.blue('Welcome to the Node.js Quickstart Generator!'));
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const answers = await getProjectDetails(options);
|
|
47
|
+
console.log(chalk.green('\nConfiguration received:'));
|
|
48
|
+
console.log(JSON.stringify(answers, null, 2));
|
|
49
|
+
|
|
50
|
+
console.log(chalk.yellow('\nGenerating project...'));
|
|
51
|
+
await generateProject(answers);
|
|
52
|
+
|
|
53
|
+
console.log(chalk.green('\nโ Project generated successfully!'));
|
|
54
|
+
console.log(chalk.cyan(`\nNext steps:\n cd ${answers.projectName}\n npm install\n docker-compose up\n-----------------------\nStart the app manually:\n npm install\n npm run dev`));
|
|
55
|
+
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(chalk.red('Error generating project:'), error);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
program.parse(process.argv);
|
|
63
|
+
|
|
64
|
+
if (!process.argv.slice(2).length) {
|
|
65
|
+
program.outputHelp();
|
|
66
|
+
}
|
package/docs/demo.gif
ADDED
|
Binary file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# NodeJS Quickstart Generator - Test Cases
|
|
2
|
+
|
|
3
|
+
This document lists the **32 possible project combinations** supported by the `nodejs-quickstart` CLI. These combinations cover all supported languages, architectures, databases, and communication patterns.
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
- **MVC Architecture**: 24 Combinations
|
|
7
|
+
- (2 Languages ร 3 View Engines ร 2 Databases ร 2 Patterns)
|
|
8
|
+
- **Clean Architecture**: 8 Combinations
|
|
9
|
+
- (2 Languages ร 1 View Engine (None) ร 2 Databases ร 2 Patterns)
|
|
10
|
+
|
|
11
|
+
**Total Core Combinations: 32**
|
|
12
|
+
|
|
13
|
+
> **Note on CI/CD**: Each of these 32 combinations can be generated with or without the **GitHub Actions CI Workflow** (`--include-ci`). This effectively creates **64 possible project states**. The validation script currently defaults to *including* CI to verify the full "Professional Standards" feature set.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. MVC Architecture (24 Cases)
|
|
18
|
+
|
|
19
|
+
| # | Language | Architecture | View Engine | Database | Communication |
|
|
20
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
21
|
+
| 1 | JavaScript | MVC | None | MySQL | REST APIs |
|
|
22
|
+
| 2 | JavaScript | MVC | None | MySQL | Kafka |
|
|
23
|
+
| 3 | JavaScript | MVC | None | PostgreSQL | REST APIs |
|
|
24
|
+
| 4 | JavaScript | MVC | None | PostgreSQL | Kafka |
|
|
25
|
+
| 5 | JavaScript | MVC | EJS | MySQL | REST APIs |
|
|
26
|
+
| 6 | JavaScript | MVC | EJS | MySQL | Kafka |
|
|
27
|
+
| 7 | JavaScript | MVC | EJS | PostgreSQL | REST APIs |
|
|
28
|
+
| 8 | JavaScript | MVC | EJS | PostgreSQL | Kafka |
|
|
29
|
+
| 9 | JavaScript | MVC | Pug | MySQL | REST APIs |
|
|
30
|
+
| 10 | JavaScript | MVC | Pug | MySQL | Kafka |
|
|
31
|
+
| 11 | JavaScript | MVC | Pug | PostgreSQL | REST APIs |
|
|
32
|
+
| 12 | JavaScript | MVC | Pug | PostgreSQL | Kafka |
|
|
33
|
+
| 13 | TypeScript | MVC | None | MySQL | REST APIs |
|
|
34
|
+
| 14 | TypeScript | MVC | None | MySQL | Kafka |
|
|
35
|
+
| 15 | TypeScript | MVC | None | PostgreSQL | REST APIs |
|
|
36
|
+
| 16 | TypeScript | MVC | None | PostgreSQL | Kafka |
|
|
37
|
+
| 17 | TypeScript | MVC | EJS | MySQL | REST APIs |
|
|
38
|
+
| 18 | TypeScript | MVC | EJS | MySQL | Kafka |
|
|
39
|
+
| 19 | TypeScript | MVC | EJS | PostgreSQL | REST APIs |
|
|
40
|
+
| 20 | TypeScript | MVC | EJS | PostgreSQL | Kafka |
|
|
41
|
+
| 21 | TypeScript | MVC | Pug | MySQL | REST APIs |
|
|
42
|
+
| 22 | TypeScript | MVC | Pug | MySQL | Kafka |
|
|
43
|
+
| 23 | TypeScript | MVC | Pug | PostgreSQL | REST APIs |
|
|
44
|
+
| 24 | TypeScript | MVC | Pug | PostgreSQL | Kafka |
|
|
45
|
+
|
|
46
|
+
## 2. Clean Architecture (8 Cases)
|
|
47
|
+
*Note: Clean Architecture does not use server-side view engines (EJS/Pug).*
|
|
48
|
+
|
|
49
|
+
| # | Language | Architecture | View Engine | Database | Communication |
|
|
50
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
51
|
+
| 25 | JavaScript | Clean Architecture | N/A | MySQL | REST APIs |
|
|
52
|
+
| 26 | JavaScript | Clean Architecture | N/A | MySQL | Kafka |
|
|
53
|
+
| 27 | JavaScript | Clean Architecture | N/A | PostgreSQL | REST APIs |
|
|
54
|
+
| 28 | JavaScript | Clean Architecture | N/A | PostgreSQL | Kafka |
|
|
55
|
+
| 29 | TypeScript | Clean Architecture | N/A | MySQL | REST APIs |
|
|
56
|
+
| 30 | TypeScript | Clean Architecture | N/A | MySQL | Kafka |
|
|
57
|
+
| 31 | TypeScript | Clean Architecture | N/A | PostgreSQL | REST APIs |
|
|
58
|
+
| 32 | TypeScript | Clean Architecture | N/A | PostgreSQL | Kafka |
|
package/lib/generator.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ejs from 'ejs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export const generateProject = async (config) => {
|
|
10
|
+
const { projectName, architecture, database, dbName, communication, language, viewEngine, includeCI } = config;
|
|
11
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
12
|
+
const templatesDir = path.join(__dirname, '../templates');
|
|
13
|
+
|
|
14
|
+
// 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
|
+
|
|
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);
|
|
31
|
+
|
|
32
|
+
// 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);
|
|
43
|
+
|
|
44
|
+
// 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
|
+
});
|
|
65
|
+
await fs.writeFile(readmePath, readmeContent);
|
|
66
|
+
|
|
67
|
+
// Render index file (ts/js)
|
|
68
|
+
const indexFileName = language === 'TypeScript' ? 'index.ts' : 'index.js';
|
|
69
|
+
const indexPath = path.join(targetDir, 'src', indexFileName);
|
|
70
|
+
const indexTemplateSource = path.join(templatePath, 'src', `${indexFileName}.ejs`);
|
|
71
|
+
|
|
72
|
+
if (await fs.pathExists(indexTemplateSource)) {
|
|
73
|
+
const indexTemplate = await fs.readFile(indexTemplateSource, 'utf-8');
|
|
74
|
+
const indexContent = ejs.render(indexTemplate, {
|
|
75
|
+
communication,
|
|
76
|
+
viewEngine,
|
|
77
|
+
database,
|
|
78
|
+
architecture,
|
|
79
|
+
projectName
|
|
80
|
+
});
|
|
81
|
+
await fs.writeFile(indexPath, indexContent);
|
|
82
|
+
await fs.remove(path.join(targetDir, 'src', `${indexFileName}.ejs`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Render Dynamic Controllers/Repositories (User) because they depend on DB type
|
|
86
|
+
// MVC Controller
|
|
87
|
+
if (architecture === 'MVC') {
|
|
88
|
+
const userControllerName = language === 'TypeScript' ? 'userController.ts' : 'userController.js';
|
|
89
|
+
const userControllerPath = path.join(targetDir, 'src/controllers', userControllerName);
|
|
90
|
+
const userControllerTemplate = path.join(templatePath, 'src/controllers', `${userControllerName}.ejs`);
|
|
91
|
+
|
|
92
|
+
if (await fs.pathExists(userControllerTemplate)) {
|
|
93
|
+
const content = ejs.render(await fs.readFile(userControllerTemplate, 'utf-8'), { database });
|
|
94
|
+
await fs.writeFile(userControllerPath, content);
|
|
95
|
+
await fs.remove(path.join(targetDir, 'src/controllers', `${userControllerName}.ejs`));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Clean Architecture Repo
|
|
99
|
+
else if (architecture === 'Clean Architecture') {
|
|
100
|
+
const repoName = language === 'TypeScript' ? 'UserRepository.ts' : 'UserRepository.js';
|
|
101
|
+
const repoPath = path.join(targetDir, 'src/infrastructure/repositories', repoName);
|
|
102
|
+
const repoTemplate = path.join(templatePath, 'src/infrastructure/repositories', `${repoName}.ejs`);
|
|
103
|
+
|
|
104
|
+
if (await fs.pathExists(repoTemplate)) {
|
|
105
|
+
const content = ejs.render(await fs.readFile(repoTemplate, 'utf-8'), { database });
|
|
106
|
+
await fs.writeFile(repoPath, content);
|
|
107
|
+
await fs.remove(path.join(targetDir, 'src/infrastructure/repositories', `${repoName}.ejs`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Render Server (Clean Arch JS only)
|
|
111
|
+
if (architecture === 'Clean Architecture' && language === 'JavaScript') {
|
|
112
|
+
const serverName = 'server.js';
|
|
113
|
+
const serverPath = path.join(targetDir, 'src/infrastructure/webserver', serverName);
|
|
114
|
+
const serverTemplate = path.join(templatePath, 'src/infrastructure/webserver', `${serverName}.ejs`);
|
|
115
|
+
|
|
116
|
+
if (await fs.pathExists(serverTemplate)) {
|
|
117
|
+
const content = ejs.render(await fs.readFile(serverTemplate, 'utf-8'), { communication });
|
|
118
|
+
await fs.writeFile(serverPath, content);
|
|
119
|
+
await fs.remove(path.join(targetDir, 'src/infrastructure/webserver', `${serverName}.ejs`));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Copy Kafka files if selected
|
|
124
|
+
if (communication === 'Kafka') {
|
|
125
|
+
const kafkaSource = path.join(templatesDir, 'common', 'kafka', langExt);
|
|
126
|
+
await fs.copy(kafkaSource, path.join(targetDir, 'src'));
|
|
127
|
+
|
|
128
|
+
if (architecture === 'Clean Architecture') {
|
|
129
|
+
// Clean Architecture Restructuring
|
|
130
|
+
await fs.ensureDir(path.join(targetDir, 'src/infrastructure/messaging'));
|
|
131
|
+
await fs.ensureDir(path.join(targetDir, 'src/infrastructure/config'));
|
|
132
|
+
|
|
133
|
+
const serviceExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
134
|
+
|
|
135
|
+
// Move Service to Infrastructure/Messaging
|
|
136
|
+
await fs.move(
|
|
137
|
+
path.join(targetDir, `src/services/kafkaService.${serviceExt}`),
|
|
138
|
+
path.join(targetDir, `src/infrastructure/messaging/kafkaClient.${serviceExt}`),
|
|
139
|
+
{ overwrite: true }
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Move Config to Infrastructure/Config
|
|
143
|
+
await fs.move(
|
|
144
|
+
path.join(targetDir, `src/config/kafka.${serviceExt}`),
|
|
145
|
+
path.join(targetDir, `src/infrastructure/config/kafka.${serviceExt}`),
|
|
146
|
+
{ overwrite: true }
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Cleanup old folders
|
|
150
|
+
await fs.remove(path.join(targetDir, 'src/services'));
|
|
151
|
+
// Only remove src/config if empty? But src/config came from kafka copy.
|
|
152
|
+
// However, other parts might use src/config?
|
|
153
|
+
// In Clean Arch, config is usually in infrastructure?
|
|
154
|
+
// Only Kafka adds src/config. Base template doesn't have src/config for Clean Arch (it uses src/infrastructure/database).
|
|
155
|
+
await fs.remove(path.join(targetDir, 'src/config'));
|
|
156
|
+
|
|
157
|
+
// Remove REST-specific folders (Interfaces)
|
|
158
|
+
await fs.remove(path.join(targetDir, 'src/interfaces/routes'));
|
|
159
|
+
await fs.remove(path.join(targetDir, 'src/interfaces/controllers'));
|
|
160
|
+
} else if (architecture === 'MVC' && (!viewEngine || viewEngine === 'None')) {
|
|
161
|
+
// MVC Cleanup
|
|
162
|
+
await fs.remove(path.join(targetDir, 'src/controllers'));
|
|
163
|
+
await fs.remove(path.join(targetDir, 'src/routes'));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 5. Copy Common Files (.gitignore, Dockerfile, etc.)
|
|
168
|
+
await fs.copy(path.join(templatesDir, 'common', '_gitignore'), path.join(targetDir, '.gitignore'));
|
|
169
|
+
await fs.copy(path.join(templatesDir, 'common', '.dockerignore'), path.join(targetDir, '.dockerignore'));
|
|
170
|
+
// await fs.copy(path.join(templatesDir, 'common', 'Dockerfile'), path.join(targetDir, 'Dockerfile'));
|
|
171
|
+
const dockerfileTemplate = await fs.readFile(path.join(templatesDir, 'common', 'Dockerfile'), 'utf-8');
|
|
172
|
+
const dockerfileContent = ejs.render(dockerfileTemplate, {
|
|
173
|
+
language,
|
|
174
|
+
viewEngine
|
|
175
|
+
});
|
|
176
|
+
await fs.writeFile(path.join(targetDir, 'Dockerfile'), dockerfileContent);
|
|
177
|
+
|
|
178
|
+
if (language === 'TypeScript') {
|
|
179
|
+
await fs.copy(path.join(templatesDir, 'common', 'tsconfig.json'), path.join(targetDir, 'tsconfig.json'));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 6. Database Migrations (Flyway)
|
|
183
|
+
await fs.ensureDir(path.join(targetDir, 'flyway/sql'));
|
|
184
|
+
const dbType = database === 'PostgreSQL' ? 'postgres' : 'mysql';
|
|
185
|
+
await fs.copy(path.join(templatesDir, 'db', dbType), path.join(targetDir, 'flyway/sql'));
|
|
186
|
+
|
|
187
|
+
// 7. Database Config
|
|
188
|
+
const dbConfigFileName = language === 'TypeScript' ? 'database.ts' : 'database.js';
|
|
189
|
+
const dbConfigTemplateSource = path.join(templatesDir, 'common', 'database', langExt, `${dbConfigFileName}.ejs`);
|
|
190
|
+
|
|
191
|
+
let dbConfigTarget;
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
// Copy configurations
|
|
195
|
+
if (architecture === 'MVC') {
|
|
196
|
+
// Copy Views
|
|
197
|
+
if (viewEngine && viewEngine !== 'None') {
|
|
198
|
+
await fs.copy(path.join(templatesDir, 'common', 'views', viewEngine.toLowerCase()), path.join(targetDir, 'src/views'));
|
|
199
|
+
}
|
|
200
|
+
await fs.ensureDir(path.join(targetDir, 'src/config'));
|
|
201
|
+
dbConfigTarget = path.join(targetDir, 'src/config', dbConfigFileName);
|
|
202
|
+
} else {
|
|
203
|
+
// Clean Architecture
|
|
204
|
+
await fs.ensureDir(path.join(targetDir, 'src/infrastructure/database'));
|
|
205
|
+
dbConfigTarget = path.join(targetDir, 'src/infrastructure/database', dbConfigFileName);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (await fs.pathExists(dbConfigTemplateSource)) {
|
|
209
|
+
const dbTemplate = await fs.readFile(dbConfigTemplateSource, 'utf-8');
|
|
210
|
+
const dbContent = ejs.render(dbTemplate, { database, dbName });
|
|
211
|
+
await fs.writeFile(dbConfigTarget, dbContent);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Render Models
|
|
215
|
+
const modelFileName = language === 'TypeScript' ? 'User.ts' : 'User.js';
|
|
216
|
+
const modelTemplateSource = path.join(templatesDir, 'common', 'database', langExt, 'models', `${modelFileName}.ejs`);
|
|
217
|
+
let modelTarget;
|
|
218
|
+
|
|
219
|
+
if (architecture === 'MVC') {
|
|
220
|
+
await fs.ensureDir(path.join(targetDir, 'src/models'));
|
|
221
|
+
modelTarget = path.join(targetDir, 'src/models', modelFileName);
|
|
222
|
+
} else {
|
|
223
|
+
await fs.ensureDir(path.join(targetDir, 'src/infrastructure/database/models'));
|
|
224
|
+
modelTarget = path.join(targetDir, 'src/infrastructure/database/models', modelFileName);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (await fs.pathExists(modelTemplateSource)) {
|
|
228
|
+
// Models need architecture to decide import path
|
|
229
|
+
const modelTemplate = await fs.readFile(modelTemplateSource, 'utf-8');
|
|
230
|
+
const modelContent = ejs.render(modelTemplate, { architecture });
|
|
231
|
+
await fs.writeFile(modelTarget, modelContent);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 8. View Engine (MVC)
|
|
235
|
+
if (architecture === 'MVC' && viewEngine && viewEngine !== 'None') {
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 9. Render Swagger Config (if .ejs exists)
|
|
239
|
+
// MVC TS
|
|
240
|
+
const swaggerMvcTs = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
|
|
241
|
+
if (await fs.pathExists(swaggerMvcTs)) {
|
|
242
|
+
const content = ejs.render(await fs.readFile(swaggerMvcTs, 'utf-8'), { communication });
|
|
243
|
+
await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
|
|
244
|
+
await fs.remove(swaggerMvcTs);
|
|
245
|
+
}
|
|
246
|
+
// Clean Architecture TS
|
|
247
|
+
const swaggerCleanTs = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
|
|
248
|
+
// Note: In Clean Arch, it might be in src/infrastructure/webserver or src/config depending on refactor.
|
|
249
|
+
// Based on previous moves, we saw it in src/config for TS.
|
|
250
|
+
if (await fs.pathExists(swaggerCleanTs)) {
|
|
251
|
+
const content = ejs.render(await fs.readFile(swaggerCleanTs, 'utf-8'), { communication });
|
|
252
|
+
await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
|
|
253
|
+
await fs.remove(swaggerCleanTs);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 10. Copy Professional Config Files (Eslint, Prettier, Husky)
|
|
257
|
+
const eslintTemplate = await fs.readFile(path.join(templatesDir, 'common', '.eslintrc.json.ejs'), 'utf-8');
|
|
258
|
+
const eslintContent = ejs.render(eslintTemplate, { language });
|
|
259
|
+
await fs.writeFile(path.join(targetDir, '.eslintrc.json'), eslintContent);
|
|
260
|
+
|
|
261
|
+
await fs.copy(path.join(templatesDir, 'common', '.prettierrc'), path.join(targetDir, '.prettierrc'));
|
|
262
|
+
await fs.copy(path.join(templatesDir, 'common', '.lintstagedrc'), path.join(targetDir, '.lintstagedrc'));
|
|
263
|
+
|
|
264
|
+
// 10. Copy Test Config & Samples
|
|
265
|
+
const jestTemplate = await fs.readFile(path.join(templatesDir, 'common', 'jest.config.js.ejs'), 'utf-8');
|
|
266
|
+
const jestContent = ejs.render(jestTemplate, { language });
|
|
267
|
+
await fs.writeFile(path.join(targetDir, 'jest.config.js'), jestContent);
|
|
268
|
+
|
|
269
|
+
// Create tests directory
|
|
270
|
+
await fs.ensureDir(path.join(targetDir, 'tests'));
|
|
271
|
+
const healthTestTemplate = await fs.readFile(path.join(templatesDir, 'common', 'tests', 'health.test.ts.ejs'), 'utf-8');
|
|
272
|
+
// 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
|
|
273
|
+
const healthTestContent = ejs.render(healthTestTemplate, { language });
|
|
274
|
+
const testFileName = language === 'TypeScript' ? 'health.test.ts' : 'health.test.js';
|
|
275
|
+
await fs.writeFile(path.join(targetDir, 'tests', testFileName), healthTestContent);
|
|
276
|
+
|
|
277
|
+
// 11. Copy CI/CD Config (Optional)
|
|
278
|
+
if (includeCI) {
|
|
279
|
+
await fs.ensureDir(path.join(targetDir, '.github/workflows'));
|
|
280
|
+
await fs.copy(path.join(templatesDir, 'common', '.github/workflows/ci.yml'), path.join(targetDir, '.github/workflows/ci.yml'));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
console.log(`
|
|
284
|
+
====================================================
|
|
285
|
+
Node.js Project Created Successfully!
|
|
286
|
+
====================================================
|
|
287
|
+
|
|
288
|
+
Project: ${projectName}
|
|
289
|
+
Architecture: ${architecture}
|
|
290
|
+
Language: ${language}
|
|
291
|
+
Database: ${database}
|
|
292
|
+
Communication: ${communication}
|
|
293
|
+
|
|
294
|
+
----------------------------------------------------
|
|
295
|
+
โจ High-Quality Standards Applied:
|
|
296
|
+
----------------------------------------------------
|
|
297
|
+
โ
Linting & Formatting: Eslint + Prettier configured
|
|
298
|
+
โ
Git Hooks: Husky + Lint-Staged ready
|
|
299
|
+
โ
Security: Helmet, CORS, Rate-Limiting added
|
|
300
|
+
โ
Testing: Jest setup for Unit/Integration tests
|
|
301
|
+
โ
Docker: Production-ready multi-stage build
|
|
302
|
+
${includeCI ? 'โ
CI/CD: GitHub Actions Workflow ready' : 'โ CI/CD: Skipped (User preferred)'}
|
|
303
|
+
|
|
304
|
+
----------------------------------------------------
|
|
305
|
+
๐ Next Steps:
|
|
306
|
+
----------------------------------------------------
|
|
307
|
+
1. cd ${projectName}
|
|
308
|
+
2. git init
|
|
309
|
+
3. npm install
|
|
310
|
+
4. npm run prepare (To setup Husky hooks)
|
|
311
|
+
5. docker-compose up -d (To start DB/Infrastructure)
|
|
312
|
+
6. npm run dev (To start development server)
|
|
313
|
+
7. npm test (To run tests)
|
|
314
|
+
`);
|
|
315
|
+
};
|
package/lib/prompts.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
|
|
3
|
+
const validateName = (name) => {
|
|
4
|
+
return /^[a-zA-Z0-9-_]+$/.test(name) ? true : 'Project name may only include letters, numbers, underscores and hashes.';
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const getProjectDetails = async (options = {}) => {
|
|
8
|
+
const questions = [
|
|
9
|
+
{
|
|
10
|
+
type: 'input',
|
|
11
|
+
name: 'projectName',
|
|
12
|
+
message: 'Project name:',
|
|
13
|
+
default: 'nodejs-service',
|
|
14
|
+
validate: validateName,
|
|
15
|
+
when: !options.projectName
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: 'list',
|
|
19
|
+
name: 'language',
|
|
20
|
+
message: 'Select Language:',
|
|
21
|
+
choices: ['JavaScript', 'TypeScript'],
|
|
22
|
+
default: 'TypeScript',
|
|
23
|
+
when: !options.language
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'list',
|
|
27
|
+
name: 'architecture',
|
|
28
|
+
message: 'Select Architecture:',
|
|
29
|
+
choices: ['MVC', 'Clean Architecture'],
|
|
30
|
+
default: 'MVC',
|
|
31
|
+
when: !options.architecture
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'list',
|
|
35
|
+
name: 'viewEngine',
|
|
36
|
+
message: 'Select View Engine:',
|
|
37
|
+
choices: ['None', 'EJS', 'Pug'],
|
|
38
|
+
when: (answers) => (options.architecture || answers.architecture) === 'MVC' && !options.viewEngine,
|
|
39
|
+
default: 'None'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'list',
|
|
43
|
+
name: 'database',
|
|
44
|
+
message: 'Select Database:',
|
|
45
|
+
choices: ['MySQL', 'PostgreSQL'],
|
|
46
|
+
default: 'MySQL',
|
|
47
|
+
when: !options.database
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'input',
|
|
51
|
+
name: 'dbName',
|
|
52
|
+
message: 'Database Name:',
|
|
53
|
+
default: 'demo',
|
|
54
|
+
validate: validateName,
|
|
55
|
+
when: !options.dbName
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'list',
|
|
59
|
+
name: 'communication',
|
|
60
|
+
message: 'Microservices Communication:',
|
|
61
|
+
choices: ['REST APIs', 'Kafka'],
|
|
62
|
+
default: 'REST APIs',
|
|
63
|
+
when: !options.communication
|
|
64
|
+
},
|
|
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
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const answers = await inquirer.prompt(questions);
|
|
75
|
+
return { ...options, ...answers };
|
|
76
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nodejs-quickstart-structure",
|
|
3
|
+
"version": "1.1.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A CLI to scaffold Node.js microservices with MVC or Clean Architecture",
|
|
6
|
+
"main": "bin/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"nodejs-quickstart": "./bin/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
|
+
"test:e2e": "npm run test:e2e:windows",
|
|
13
|
+
"test:e2e:windows": "node scripts/validate-windows.js",
|
|
14
|
+
"test:e2e:linux": "node scripts/validate-linux.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"nodejs",
|
|
18
|
+
"cli",
|
|
19
|
+
"scaffold",
|
|
20
|
+
"mvc",
|
|
21
|
+
"clean-architecture"
|
|
22
|
+
],
|
|
23
|
+
"author": "Pau Dang <paudang@example.com>",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/paudang/nodejs-quickstart-structure.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/paudang/nodejs-quickstart-structure/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/paudang/nodejs-quickstart-structure#readme",
|
|
32
|
+
"license": "ISC",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"chalk": "^5.4.1",
|
|
35
|
+
"commander": "^13.1.0",
|
|
36
|
+
"ejs": "^3.1.10",
|
|
37
|
+
"fs-extra": "^11.3.0",
|
|
38
|
+
"inquirer": "^12.4.1"
|
|
39
|
+
}
|
|
40
|
+
}
|