nodejs-quickstart-structure 1.13.0 → 1.15.1
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 +32 -0
- package/README.md +4 -3
- package/bin/index.js +84 -80
- package/lib/generator.js +28 -4
- package/lib/modules/app-setup.js +111 -19
- package/lib/modules/caching-setup.js +13 -0
- package/lib/modules/config-files.js +50 -62
- package/lib/modules/database-setup.js +35 -30
- package/lib/modules/kafka-setup.js +78 -10
- package/package.json +8 -4
- package/templates/clean-architecture/js/src/errors/BadRequestError.js +1 -1
- package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +21 -0
- package/templates/clean-architecture/js/src/errors/NotFoundError.js +1 -1
- package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +21 -0
- package/templates/clean-architecture/js/src/infrastructure/log/logger.spec.js.ejs +63 -0
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +2 -3
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +81 -0
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +8 -4
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +102 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +31 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +49 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +38 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +51 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +61 -0
- package/templates/clean-architecture/ts/src/errors/BadRequestError.spec.ts.ejs +21 -0
- package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +1 -1
- package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +21 -0
- package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +1 -1
- package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +64 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +85 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +2 -3
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +166 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -0
- package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -0
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -0
- package/templates/clean-architecture/ts/src/utils/errorMiddleware.ts.ejs +1 -2
- package/templates/common/.cursorrules.ejs +60 -0
- package/templates/common/.dockerignore +2 -0
- package/templates/common/.gitlab-ci.yml.ejs +5 -5
- package/templates/common/Jenkinsfile.ejs +1 -1
- package/templates/common/README.md.ejs +11 -1
- package/templates/common/_github/workflows/ci.yml +7 -4
- package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -0
- package/templates/common/caching/js/redisClient.spec.js.ejs +149 -0
- package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -0
- package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -0
- package/templates/common/database/js/database.spec.js.ejs +56 -0
- package/templates/common/database/js/models/User.js.ejs +22 -0
- package/templates/common/database/js/models/User.spec.js.ejs +84 -0
- package/templates/common/database/js/mongoose.spec.js.ejs +43 -0
- package/templates/common/database/ts/database.spec.ts.ejs +56 -0
- package/templates/common/database/ts/models/User.spec.ts.ejs +84 -0
- package/templates/common/database/ts/models/User.ts.ejs +26 -0
- package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -0
- package/templates/common/eslint.config.mjs.ejs +11 -2
- package/templates/common/health/js/healthRoute.spec.js.ejs +70 -0
- package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -0
- package/templates/common/jest.config.js.ejs +19 -5
- package/templates/common/kafka/js/config/kafka.spec.js.ejs +21 -0
- package/templates/common/kafka/js/services/kafkaService.js.ejs +9 -5
- package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +60 -0
- package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +21 -0
- package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +61 -0
- package/templates/common/kafka/ts/services/kafkaService.ts.ejs +1 -1
- package/templates/common/package.json.ejs +0 -3
- package/templates/common/prompts/add-feature.md.ejs +26 -0
- package/templates/common/prompts/project-context.md.ejs +43 -0
- package/templates/common/prompts/troubleshoot.md.ejs +28 -0
- package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +160 -0
- package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +158 -0
- package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -0
- package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -0
- package/templates/common/tsconfig.json +1 -1
- package/templates/mvc/js/src/controllers/userController.js.ejs +4 -31
- package/templates/mvc/js/src/controllers/userController.spec.js.ejs +170 -0
- package/templates/mvc/js/src/errors/BadRequestError.js +1 -1
- package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +21 -0
- package/templates/mvc/js/src/errors/NotFoundError.js +1 -1
- package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +21 -0
- package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -0
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +47 -0
- package/templates/mvc/js/src/index.js.ejs +1 -1
- package/templates/mvc/js/src/routes/api.spec.js.ejs +36 -0
- package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -0
- package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +185 -0
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +4 -31
- package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +21 -0
- package/templates/mvc/ts/src/errors/BadRequestError.ts +1 -1
- package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +21 -0
- package/templates/mvc/ts/src/errors/NotFoundError.ts +1 -1
- package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -0
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
- package/templates/mvc/ts/src/routes/api.spec.ts.ejs +40 -0
- package/templates/mvc/ts/src/utils/errorMiddleware.ts.ejs +1 -2
- package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +64 -0
- package/docs/demo.gif +0 -0
- package/docs/generateCase.md +0 -265
- package/docs/generatorFlow.md +0 -233
- package/docs/releaseNoteRule.md +0 -42
- package/docs/ruleDevelop.md +0 -30
- package/templates/common/tests/health.test.ts.ejs +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,38 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.15.1] - 2026-03-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Magic AI Scaffolding**: Automated AI context generation ( `.cursorrules`, `prompts/`) by intelligently inferring project goals from the project name, removing the need for manual business domain prompts.
|
|
12
|
+
- **Enhanced README**: Added specialized guidance for AI-native development with Cursor and LLMs.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **GitHub Actions Compliance**: Fully resolved Node.js 20 deprecation warnings by moving `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` to global workflow scope.
|
|
16
|
+
- **CI Modernization**: Upgraded daily audit and GitLab CI runners to Node.js 22.
|
|
17
|
+
- **CI Enforcement**: Updated all CI/CD templates (GitHub, GitLab, Jenkins) to strictly enforce the >70% test coverage gate.
|
|
18
|
+
|
|
19
|
+
## [1.15.0] - 2026-03-12
|
|
20
|
+
### Added
|
|
21
|
+
- **AI-Native Scaffolding & Agent Skill Templates:**
|
|
22
|
+
- Added a new CLI prompt for `businessDomain` to inject custom domain knowledge into generated templates.
|
|
23
|
+
- Generates a `.cursorrules` file at the root to enforce >70% Test coverage and Architecture patterns automatically for AI Code Editors.
|
|
24
|
+
- Scaffolds a `prompts/` directory with specialized Agent Skill templates (`project-context.md`, `add-feature.md`, `troubleshoot.md`) designed to provide deep structural understanding to LLMs like ChatGPT or Claude.
|
|
25
|
+
- Added an "AI-Native Development" section to the generated `README.md` and prominent print messages in the CLI upon successful completion.
|
|
26
|
+
|
|
27
|
+
## [1.14.0] - 2026-03-09
|
|
28
|
+
### Added
|
|
29
|
+
- **Unit test:**
|
|
30
|
+
- Unit Testing Framework: Integrated Jest and ts-jest as the core testing suite.
|
|
31
|
+
|
|
32
|
+
- Scaffolding Logic: Automatically generates .spec.ts files in the tests/ directory mirroring the src/ structure during project initialization.
|
|
33
|
+
|
|
34
|
+
- Quality Gates: Implemented a mandatory Coverage Threshold (>=70%) for lines and functions to ensure long-term maintainability.
|
|
35
|
+
|
|
36
|
+
- Mocking Standards: Included pre-configured mocks for Mongoose (database pings) and Express request/response objects.
|
|
37
|
+
|
|
38
|
+
- NPM Scripts: Added npm test, npm run test:watch, and npm run test:coverage for seamless developer workflow.
|
|
39
|
+
|
|
8
40
|
## [1.13.0] - 2026-03-05
|
|
9
41
|
### Added
|
|
10
42
|
- **Enhanced Health Check System:**
|
package/README.md
CHANGED
|
@@ -31,14 +31,14 @@ We don't just generate boilerplate; we generate **production-ready** foundations
|
|
|
31
31
|
- **🔍 Code Quality**: Pre-configured `Eslint` and `Prettier` for consistent coding standards.
|
|
32
32
|
- **🛡️ Security**: Built-in `Helmet`, `HPP`, `CORS`, and Rate-Limiting middleware.
|
|
33
33
|
- **🚨 Error Handling**: Centralized global error middleware with custom error classes and structured JSON responses. GraphQL uses Apollo's `formatError` hook; REST uses Express error middleware.
|
|
34
|
-
- **🧪 Testing
|
|
34
|
+
- **🧪 Testing Excellence**: Integrated `Jest` and `Supertest`. Every generated project maintains **>70% Unit Test coverage** for controllers, services, and resolvers out of the box.
|
|
35
35
|
- **🔄 CI/CD Integration**: Pre-configured workflows for **GitHub Actions**, **Jenkins**, and **GitLab CI**.
|
|
36
36
|
- **⚓ Git Hooks**: `Husky` and `Lint-Staged` to ensure no bad code is ever committed.
|
|
37
37
|
- **🤝 Reliability**: Advanced Health Checks (`/health`) with deep database pings and Graceful Shutdown workflows for zero-downtime rollouts.
|
|
38
38
|
- **🐳 DevOps**: Highly optimized **Multi-Stage Dockerfile** for small, secure production images.
|
|
39
39
|
- **🚀 Deployment**: Ship confidently with an integrated **PM2 Ecosystem Configuration** for zero-downtime reloads and robust process management.
|
|
40
40
|
|
|
41
|
-
## 🧩
|
|
41
|
+
## 🧩 480+ Project Combinations
|
|
42
42
|
|
|
43
43
|
The CLI supports a massive number of configurations to fit your exact needs:
|
|
44
44
|
|
|
@@ -46,7 +46,8 @@ The CLI supports a massive number of configurations to fit your exact needs:
|
|
|
46
46
|
- **MVC Architecture**: 180 variants (Languages × View Engines × Databases × Communication Patterns × Caching)
|
|
47
47
|
- **Clean Architecture**: 60 variants (Languages × Databases × Communication Patterns × Caching)
|
|
48
48
|
- **480 Total Scenarios**:
|
|
49
|
-
- Every combination can be generated with or without **GitHub Actions CI/CD
|
|
49
|
+
- Every combination can be generated with or without (**GitHub Actions CI/CD** / **Jenkins** or **GitLab CI**), tripling the possibilities.
|
|
50
|
+
- Every single one of these 480 scenarios is verified to be compatible with our 70% Coverage Threshold policy.
|
|
50
51
|
|
|
51
52
|
For a detailed list of all supported cases, check out [docs/generateCase.md](docs/generateCase.md).
|
|
52
53
|
|
package/bin/index.js
CHANGED
|
@@ -1,81 +1,85 @@
|
|
|
1
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, PostgreSQL, or MongoDB\n- Docker, Flyway & Mongoose support')
|
|
19
|
-
.version(pkg.version, '-v, --version', 'Output the current version')
|
|
20
|
-
.addHelpText('after', `
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.
|
|
27
|
-
.
|
|
28
|
-
.option('
|
|
29
|
-
.option('-
|
|
30
|
-
.option('-
|
|
31
|
-
.option('
|
|
32
|
-
.option('-
|
|
33
|
-
.option('--
|
|
34
|
-
.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
console.log(chalk.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
console.log(chalk.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
console.log(chalk.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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, PostgreSQL, or MongoDB\n- Docker, Flyway & Mongoose support')
|
|
19
|
+
.version(pkg.version, '-v, --version', 'Output the current version')
|
|
20
|
+
.addHelpText('after', `\n${chalk.yellow('Example:')}\n $ nodejs-quickstart init ${chalk.gray('# Start the interactive setup')}\n`);
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command('init')
|
|
24
|
+
.description('Initialize a new Node.js project')
|
|
25
|
+
.option('-n, --project-name <name>', 'Project name')
|
|
26
|
+
.option('-l, --language <language>', 'Language (JavaScript, TypeScript)')
|
|
27
|
+
.option('-a, --architecture <architecture>', 'Architecture (MVC, Clean Architecture)')
|
|
28
|
+
.option('--view-engine <view>', 'View Engine (None, EJS, Pug) - MVC only')
|
|
29
|
+
.option('-d, --database <database>', 'Database (MySQL, PostgreSQL)')
|
|
30
|
+
.option('--db-name <name>', 'Database name')
|
|
31
|
+
.option('-c, --communication <communication>', 'Communication (REST APIs, GraphQL, Kafka)')
|
|
32
|
+
.option('--ci-provider <provider>', 'CI/CD Provider (None, GitHub Actions, Jenkins)')
|
|
33
|
+
.option('--caching <type>', 'Caching Layer (None/Redis)')
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
// Fix for Commander camelCase conversion
|
|
36
|
+
if (options.ciProvider) {
|
|
37
|
+
options.ciProvider = options.ciProvider;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(chalk.blue('Welcome to the Node.js Quickstart Generator!'));
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const answers = await getProjectDetails(options);
|
|
44
|
+
console.log(chalk.green('\nConfiguration received:'));
|
|
45
|
+
console.log(JSON.stringify(answers, null, 2));
|
|
46
|
+
|
|
47
|
+
console.log(chalk.yellow('\nGenerating project...'));
|
|
48
|
+
await generateProject(answers);
|
|
49
|
+
|
|
50
|
+
console.log(chalk.green('\n✔ Project generated successfully!'));
|
|
51
|
+
|
|
52
|
+
console.log(chalk.magenta('\n🚀 Project is AI-Ready!'));
|
|
53
|
+
console.log(chalk.magenta('-----------------------------------------'));
|
|
54
|
+
console.log(chalk.magenta('🤖 We detected you are using AI tools.'));
|
|
55
|
+
console.log(chalk.magenta(`📍 Use Cursor? We've configured '.cursorrules' for you.`));
|
|
56
|
+
console.log(chalk.magenta(`📍 Use ChatGPT/Gemini? Check the 'prompts/' folder for Agent Skills.`));
|
|
57
|
+
console.log(chalk.magenta('-----------------------------------------'));
|
|
58
|
+
|
|
59
|
+
let manualStartInstructions = `\n${chalk.yellow('Development:')}\n cd ${answers.projectName}\n npm install`;
|
|
60
|
+
|
|
61
|
+
const needsInfrastructure = answers.database !== 'None' || answers.caching === 'Redis' || answers.communication === 'Kafka';
|
|
62
|
+
|
|
63
|
+
if (needsInfrastructure) {
|
|
64
|
+
let servicesToStart = '';
|
|
65
|
+
if (answers.database !== 'None') servicesToStart += ' db';
|
|
66
|
+
if (answers.caching === 'Redis') servicesToStart += ' redis';
|
|
67
|
+
if (answers.communication === 'Kafka') servicesToStart += ' zookeeper kafka';
|
|
68
|
+
|
|
69
|
+
manualStartInstructions += `\n docker-compose up -d${servicesToStart} # Start infrastructure first\n npm run dev`;
|
|
70
|
+
} else {
|
|
71
|
+
manualStartInstructions += `\n npm run dev`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(chalk.cyan(`\nNext steps:\n cd ${answers.projectName}\n npm install\n docker-compose up\n-----------------------${manualStartInstructions}\n\n${chalk.yellow('Production (PM2):')}\n npm run build\n npm run deploy\n npx pm2 logs`));
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(chalk.red('Error generating project:'), error);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
program.parse(process.argv);
|
|
82
|
+
|
|
83
|
+
if (!process.argv.slice(2).length) {
|
|
84
|
+
program.outputHelp();
|
|
85
|
+
}
|
package/lib/generator.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { setupProjectDirectory, copyBaseStructure, copyCommonFiles } from './modules/project-setup.js';
|
|
4
|
-
import { renderPackageJson, renderDockerCompose, renderReadme, renderDockerfile, renderProfessionalConfig, setupCiCd, renderTestSample, renderEnvExample, renderPm2Config } from './modules/config-files.js';
|
|
5
|
-
import { renderIndexFile, renderEnvConfig, renderErrorMiddleware, renderDynamicComponents, renderSwaggerConfig, setupViews as setupSrcViews } from './modules/app-setup.js';
|
|
4
|
+
import { renderPackageJson, renderDockerCompose, renderReadme, renderDockerfile, renderProfessionalConfig, setupCiCd, renderTestSample, renderEnvExample, renderPm2Config, renderAiNativeFiles } from './modules/config-files.js';
|
|
5
|
+
import { renderIndexFile, renderEnvConfig, renderErrorMiddleware, renderDynamicComponents, renderSwaggerConfig, setupViews as setupSrcViews, processAllTests } from './modules/app-setup.js';
|
|
6
6
|
import { setupDatabase } from './modules/database-setup.js';
|
|
7
7
|
import { setupKafka, setupViews } from './modules/kafka-setup.js';
|
|
8
8
|
import { setupCaching } from './modules/caching-setup.js';
|
|
@@ -11,6 +11,17 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
11
11
|
const __dirname = path.dirname(__filename);
|
|
12
12
|
|
|
13
13
|
export const generateProject = async (config) => {
|
|
14
|
+
// 0. Normalize configuration with defaults
|
|
15
|
+
config = {
|
|
16
|
+
viewEngine: 'None',
|
|
17
|
+
caching: 'None',
|
|
18
|
+
dbName: 'demo',
|
|
19
|
+
ciProvider: 'None',
|
|
20
|
+
communication: 'REST APIs',
|
|
21
|
+
database: 'None',
|
|
22
|
+
...config
|
|
23
|
+
};
|
|
24
|
+
|
|
14
25
|
const { projectName, architecture, language } = config;
|
|
15
26
|
const targetDir = path.resolve(process.cwd(), projectName);
|
|
16
27
|
const templatesDir = path.join(__dirname, '../templates');
|
|
@@ -67,8 +78,11 @@ export const generateProject = async (config) => {
|
|
|
67
78
|
await renderSwaggerConfig(templatesDir, targetDir, config);
|
|
68
79
|
|
|
69
80
|
// 13. Professional Config & Tests
|
|
70
|
-
await renderProfessionalConfig(templatesDir, targetDir,
|
|
71
|
-
await renderTestSample(templatesDir, targetDir,
|
|
81
|
+
await renderProfessionalConfig(templatesDir, targetDir, config);
|
|
82
|
+
await renderTestSample(templatesDir, targetDir, config);
|
|
83
|
+
|
|
84
|
+
// 13.5 AI-Native Scaffolding
|
|
85
|
+
await renderAiNativeFiles(templatesDir, targetDir, config);
|
|
72
86
|
|
|
73
87
|
// 14. CI/CD
|
|
74
88
|
await setupCiCd(templatesDir, targetDir, config);
|
|
@@ -79,6 +93,9 @@ export const generateProject = async (config) => {
|
|
|
79
93
|
// 16. PM2 Configuration
|
|
80
94
|
await renderPm2Config(templatesDir, targetDir, config);
|
|
81
95
|
|
|
96
|
+
// 17. Process All Tests
|
|
97
|
+
await processAllTests(targetDir, config);
|
|
98
|
+
|
|
82
99
|
console.log(`
|
|
83
100
|
====================================================
|
|
84
101
|
Node.js Project Created Successfully!
|
|
@@ -100,6 +117,13 @@ export const generateProject = async (config) => {
|
|
|
100
117
|
✅ Docker: Production-ready multi-stage build
|
|
101
118
|
${config.ciProvider !== 'None' ? `✅ CI/CD: ${config.ciProvider} Workflow ready` : '❌ CI/CD: Skipped (User preferred)'}
|
|
102
119
|
|
|
120
|
+
----------------------------------------------------
|
|
121
|
+
🚀 Project is AI-Ready!
|
|
122
|
+
----------------------------------------------------
|
|
123
|
+
🤖 We detected you are using AI tools.
|
|
124
|
+
📍 Use Cursor? We've configured '.cursorrules' for you.
|
|
125
|
+
📍 Use ChatGPT/Gemini? Check the 'prompts/' folder for Agent Skills.
|
|
126
|
+
|
|
103
127
|
----------------------------------------------------
|
|
104
128
|
👉 Next Steps:
|
|
105
129
|
----------------------------------------------------
|
package/lib/modules/app-setup.js
CHANGED
|
@@ -10,14 +10,7 @@ export const renderIndexFile = async (templatePath, targetDir, config) => {
|
|
|
10
10
|
|
|
11
11
|
if (await fs.pathExists(indexTemplateSource)) {
|
|
12
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
|
-
caching
|
|
20
|
-
});
|
|
13
|
+
const indexContent = ejs.render(indexTemplate, { ...config });
|
|
21
14
|
await fs.writeFile(indexPath, indexContent);
|
|
22
15
|
await fs.remove(path.join(targetDir, 'src', `${indexFileName}.ejs`));
|
|
23
16
|
}
|
|
@@ -37,11 +30,7 @@ export const renderEnvConfig = async (templatePath, targetDir, config) => {
|
|
|
37
30
|
|
|
38
31
|
if (await fs.pathExists(envTemplatePath)) {
|
|
39
32
|
const envTemplate = await fs.readFile(envTemplatePath, 'utf-8');
|
|
40
|
-
const envContent = ejs.render(envTemplate, {
|
|
41
|
-
database,
|
|
42
|
-
caching,
|
|
43
|
-
communication
|
|
44
|
-
});
|
|
33
|
+
const envContent = ejs.render(envTemplate, { ...config });
|
|
45
34
|
await fs.writeFile(envDestPath, envContent);
|
|
46
35
|
await fs.remove(envTemplatePath);
|
|
47
36
|
}
|
|
@@ -79,6 +68,16 @@ export const renderErrorMiddleware = async (templatePath, targetDir, config) =>
|
|
|
79
68
|
await fs.remove(mwEjsCopy);
|
|
80
69
|
}
|
|
81
70
|
}
|
|
71
|
+
|
|
72
|
+
// Render errorMiddleware spec template
|
|
73
|
+
const specExt = language === 'TypeScript' ? 'ts' : 'js';
|
|
74
|
+
const specTemplatePath = path.join(templatePath, '../../common/src/utils', `errorMiddleware.spec.${specExt}.ejs`);
|
|
75
|
+
if (await fs.pathExists(specTemplatePath)) {
|
|
76
|
+
const testUtilsDir = path.join(targetDir, 'tests', 'utils');
|
|
77
|
+
await fs.ensureDir(testUtilsDir);
|
|
78
|
+
const specContent = ejs.render(await fs.readFile(specTemplatePath, 'utf-8'), config);
|
|
79
|
+
await fs.writeFile(path.join(testUtilsDir, `errorMiddleware.spec.${specExt}`), specContent);
|
|
80
|
+
}
|
|
82
81
|
};
|
|
83
82
|
|
|
84
83
|
export const renderDynamicComponents = async (templatePath, targetDir, config) => {
|
|
@@ -87,36 +86,71 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
87
86
|
// MVC Controller
|
|
88
87
|
if (architecture === 'MVC') {
|
|
89
88
|
const userControllerName = language === 'TypeScript' ? 'userController.ts' : 'userController.js';
|
|
89
|
+
const userControllerSpecName = language === 'TypeScript' ? 'userController.spec.ts' : 'userController.spec.js';
|
|
90
|
+
|
|
90
91
|
const userControllerPath = path.join(targetDir, 'src/controllers', userControllerName);
|
|
92
|
+
const userControllerSpecPath = path.join(targetDir, 'tests/controllers', userControllerSpecName);
|
|
93
|
+
|
|
91
94
|
const userControllerTemplate = path.join(templatePath, 'src/controllers', `${userControllerName}.ejs`);
|
|
95
|
+
const userControllerSpecTemplate = path.join(templatePath, 'src/controllers', `${userControllerSpecName}.ejs`);
|
|
92
96
|
|
|
93
97
|
if (await fs.pathExists(userControllerTemplate)) {
|
|
94
|
-
const content = ejs.render(await fs.readFile(userControllerTemplate, 'utf-8'), {
|
|
98
|
+
const content = ejs.render(await fs.readFile(userControllerTemplate, 'utf-8'), { ...config });
|
|
95
99
|
await fs.writeFile(userControllerPath, content);
|
|
96
100
|
await fs.remove(path.join(targetDir, 'src/controllers', `${userControllerName}.ejs`));
|
|
97
101
|
}
|
|
102
|
+
|
|
103
|
+
if (await fs.pathExists(userControllerSpecTemplate)) {
|
|
104
|
+
await fs.ensureDir(path.join(targetDir, 'tests/controllers'));
|
|
105
|
+
const content = ejs.render(await fs.readFile(userControllerSpecTemplate, 'utf-8'), { ...config });
|
|
106
|
+
await fs.writeFile(userControllerSpecPath, content);
|
|
107
|
+
await fs.remove(path.join(targetDir, 'src/controllers', `${userControllerSpecName}.ejs`));
|
|
108
|
+
}
|
|
98
109
|
}
|
|
99
110
|
// Clean Architecture Repo
|
|
100
111
|
else if (architecture === 'Clean Architecture') {
|
|
101
112
|
const repoName = language === 'TypeScript' ? 'UserRepository.ts' : 'UserRepository.js';
|
|
113
|
+
const repoSpecName = language === 'TypeScript' ? 'UserRepository.spec.ts' : 'UserRepository.spec.js';
|
|
114
|
+
|
|
102
115
|
const repoPath = path.join(targetDir, 'src/infrastructure/repositories', repoName);
|
|
116
|
+
const repoSpecPath = path.join(targetDir, 'tests/infrastructure/repositories', repoSpecName);
|
|
117
|
+
|
|
103
118
|
const repoTemplate = path.join(templatePath, 'src/infrastructure/repositories', `${repoName}.ejs`);
|
|
119
|
+
const repoSpecTemplate = path.join(templatePath, 'src/infrastructure/repositories', `${repoSpecName}.ejs`);
|
|
104
120
|
|
|
105
121
|
if (await fs.pathExists(repoTemplate)) {
|
|
106
|
-
const content = ejs.render(await fs.readFile(repoTemplate, 'utf-8'), {
|
|
122
|
+
const content = ejs.render(await fs.readFile(repoTemplate, 'utf-8'), { ...config });
|
|
107
123
|
await fs.writeFile(repoPath, content);
|
|
108
124
|
await fs.remove(path.join(targetDir, 'src/infrastructure/repositories', `${repoName}.ejs`));
|
|
109
125
|
}
|
|
126
|
+
if (await fs.pathExists(repoSpecTemplate)) {
|
|
127
|
+
await fs.ensureDir(path.join(targetDir, 'tests/infrastructure/repositories'));
|
|
128
|
+
const content = ejs.render(await fs.readFile(repoSpecTemplate, 'utf-8'), { ...config });
|
|
129
|
+
await fs.writeFile(repoSpecPath, content);
|
|
130
|
+
await fs.remove(path.join(targetDir, 'src/infrastructure/repositories', `${repoSpecName}.ejs`));
|
|
131
|
+
}
|
|
110
132
|
|
|
111
133
|
const controllerName = language === 'TypeScript' ? 'userController.ts' : 'userController.js';
|
|
134
|
+
const controllerSpecName = language === 'TypeScript' ? 'userController.spec.ts' : 'userController.spec.js';
|
|
135
|
+
|
|
112
136
|
const controllerPath = path.join(targetDir, 'src/interfaces/controllers', controllerName);
|
|
137
|
+
const controllerSpecPath = path.join(targetDir, 'tests/interfaces/controllers', controllerSpecName);
|
|
138
|
+
|
|
113
139
|
const controllerTemplate = path.join(templatePath, 'src/interfaces/controllers', `${controllerName}.ejs`);
|
|
140
|
+
const controllerSpecTemplate = path.join(templatePath, 'src/interfaces/controllers', `${controllerSpecName}.ejs`);
|
|
114
141
|
|
|
115
142
|
if (await fs.pathExists(controllerTemplate)) {
|
|
116
|
-
const content = ejs.render(await fs.readFile(controllerTemplate, 'utf-8'), {
|
|
143
|
+
const content = ejs.render(await fs.readFile(controllerTemplate, 'utf-8'), { ...config });
|
|
117
144
|
await fs.writeFile(controllerPath, content);
|
|
118
145
|
await fs.remove(path.join(targetDir, 'src/interfaces/controllers', `${controllerName}.ejs`));
|
|
119
146
|
}
|
|
147
|
+
|
|
148
|
+
if (await fs.pathExists(controllerSpecTemplate)) {
|
|
149
|
+
await fs.ensureDir(path.join(targetDir, 'tests/interfaces/controllers'));
|
|
150
|
+
const content = ejs.render(await fs.readFile(controllerSpecTemplate, 'utf-8'), { ...config });
|
|
151
|
+
await fs.writeFile(controllerSpecPath, content);
|
|
152
|
+
await fs.remove(path.join(targetDir, 'src/interfaces/controllers', `${controllerSpecName}.ejs`));
|
|
153
|
+
}
|
|
120
154
|
}
|
|
121
155
|
// Render Server (Clean Arch JS only)
|
|
122
156
|
if (architecture === 'Clean Architecture' && language === 'JavaScript') {
|
|
@@ -125,7 +159,7 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
125
159
|
const serverTemplate = path.join(templatePath, 'src/infrastructure/webserver', `${serverName}.ejs`);
|
|
126
160
|
|
|
127
161
|
if (await fs.pathExists(serverTemplate)) {
|
|
128
|
-
const content = ejs.render(await fs.readFile(serverTemplate, 'utf-8'), {
|
|
162
|
+
const content = ejs.render(await fs.readFile(serverTemplate, 'utf-8'), { ...config });
|
|
129
163
|
await fs.writeFile(serverPath, content);
|
|
130
164
|
await fs.remove(path.join(targetDir, 'src/infrastructure/webserver', `${serverName}.ejs`));
|
|
131
165
|
}
|
|
@@ -151,8 +185,20 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
151
185
|
}
|
|
152
186
|
await fs.ensureDir(routeDestDir);
|
|
153
187
|
|
|
154
|
-
const healthRouteContent = ejs.render(await fs.readFile(healthTemplatePath, 'utf-8'), {
|
|
188
|
+
const healthRouteContent = ejs.render(await fs.readFile(healthTemplatePath, 'utf-8'), { ...config });
|
|
155
189
|
await fs.writeFile(path.join(routeDestDir, `healthRoute.${healthExt}`), healthRouteContent);
|
|
190
|
+
|
|
191
|
+
// Render health route spec template
|
|
192
|
+
const healthSpecTemplatePath = path.join(templatePath, '../../common/health', healthExt, `healthRoute.spec.${healthExt}.ejs`);
|
|
193
|
+
if (await fs.pathExists(healthSpecTemplatePath)) {
|
|
194
|
+
let testRouteDestDir = path.join(targetDir, 'tests', 'routes');
|
|
195
|
+
if (architecture === 'Clean Architecture') {
|
|
196
|
+
testRouteDestDir = path.join(targetDir, 'tests', 'interfaces', 'routes');
|
|
197
|
+
}
|
|
198
|
+
await fs.ensureDir(testRouteDestDir);
|
|
199
|
+
const specContent = ejs.render(await fs.readFile(healthSpecTemplatePath, 'utf-8'), config);
|
|
200
|
+
await fs.writeFile(path.join(testRouteDestDir, `healthRoute.spec.${healthExt}`), specContent);
|
|
201
|
+
}
|
|
156
202
|
}
|
|
157
203
|
|
|
158
204
|
// Advanced Graceful Shutdown Modularization
|
|
@@ -163,8 +209,17 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
163
209
|
let utilsDestDir = path.join(targetDir, 'src', 'utils');
|
|
164
210
|
await fs.ensureDir(utilsDestDir);
|
|
165
211
|
|
|
166
|
-
const shutdownContent = ejs.render(await fs.readFile(shutdownTemplatePath, 'utf-8'), {
|
|
212
|
+
const shutdownContent = ejs.render(await fs.readFile(shutdownTemplatePath, 'utf-8'), { ...config });
|
|
167
213
|
await fs.writeFile(path.join(utilsDestDir, `gracefulShutdown.${shutdownExt}`), shutdownContent);
|
|
214
|
+
|
|
215
|
+
// Render graceful shutdown spec template
|
|
216
|
+
const shutdownSpecTemplatePath = path.join(templatePath, '../../common/shutdown', shutdownExt, `gracefulShutdown.spec.${shutdownExt}.ejs`);
|
|
217
|
+
if (await fs.pathExists(shutdownSpecTemplatePath)) {
|
|
218
|
+
const testUtilsDestDir = path.join(targetDir, 'tests', 'utils');
|
|
219
|
+
await fs.ensureDir(testUtilsDestDir);
|
|
220
|
+
const specContent = ejs.render(await fs.readFile(shutdownSpecTemplatePath, 'utf-8'), config);
|
|
221
|
+
await fs.writeFile(path.join(testUtilsDestDir, `gracefulShutdown.spec.${shutdownExt}`), specContent);
|
|
222
|
+
}
|
|
168
223
|
}
|
|
169
224
|
|
|
170
225
|
// GraphQL Setup
|
|
@@ -271,3 +326,40 @@ export const setupViews = async (templatesDir, targetDir, config) => {
|
|
|
271
326
|
}
|
|
272
327
|
}
|
|
273
328
|
};
|
|
329
|
+
|
|
330
|
+
export const processAllTests = async (targetDir, config) => {
|
|
331
|
+
const srcDir = path.join(targetDir, 'src');
|
|
332
|
+
const testsDir = path.join(targetDir, 'tests');
|
|
333
|
+
|
|
334
|
+
const processDir = async (currentDir) => {
|
|
335
|
+
if (!(await fs.pathExists(currentDir))) return;
|
|
336
|
+
const items = await fs.readdir(currentDir);
|
|
337
|
+
for (const item of items) {
|
|
338
|
+
|
|
339
|
+
const itemPath = path.join(currentDir, item);
|
|
340
|
+
const stat = await fs.stat(itemPath);
|
|
341
|
+
if (stat.isDirectory()) {
|
|
342
|
+
await processDir(itemPath);
|
|
343
|
+
} else if (itemPath.endsWith('.spec.ts') ||
|
|
344
|
+
itemPath.endsWith('.spec.js') ||
|
|
345
|
+
itemPath.endsWith('.spec.ts.ejs') ||
|
|
346
|
+
itemPath.endsWith('.spec.js.ejs')) {
|
|
347
|
+
const relativePath = path.relative(srcDir, itemPath);
|
|
348
|
+
|
|
349
|
+
const cleanRelativePath = relativePath.replace(/\.ejs$/, '');
|
|
350
|
+
|
|
351
|
+
const targetTestPath = path.join(testsDir, cleanRelativePath);
|
|
352
|
+
|
|
353
|
+
await fs.ensureDir(path.dirname(targetTestPath));
|
|
354
|
+
|
|
355
|
+
const template = await fs.readFile(itemPath, 'utf-8');
|
|
356
|
+
const content = ejs.render(template, config);
|
|
357
|
+
|
|
358
|
+
await fs.writeFile(targetTestPath, content);
|
|
359
|
+
await fs.remove(itemPath);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
await processDir(srcDir);
|
|
365
|
+
};
|
|
@@ -55,6 +55,19 @@ export const setupCaching = async (templatesDir, targetDir, config) => {
|
|
|
55
55
|
const cacheTemplate = await fs.readFile(cacheSource, 'utf-8');
|
|
56
56
|
const content = ejs.render(cacheTemplate, { loggerPath });
|
|
57
57
|
await fs.writeFile(cacheTarget, content);
|
|
58
|
+
|
|
59
|
+
// Render Spec if exists
|
|
60
|
+
const specTemplateName = clientObj.replace(`.${langExt}`, `.spec.${langExt}.ejs`);
|
|
61
|
+
const specTemplateSource = path.join(templatesDir, 'common', 'caching', langExt, specTemplateName);
|
|
62
|
+
if (await fs.pathExists(specTemplateSource)) {
|
|
63
|
+
const specTemplate = await fs.readFile(specTemplateSource, 'utf-8');
|
|
64
|
+
const specLoggerPath = architecture === 'Clean Architecture' ? '@/infrastructure/log/logger' : '@/utils/logger';
|
|
65
|
+
const specRedisPath = architecture === 'Clean Architecture' ? '@/infrastructure/caching/redisClient' : '@/config/redisClient';
|
|
66
|
+
const specContent = ejs.render(specTemplate, { ...config, loggerPath: specLoggerPath, redisClientPath: specRedisPath });
|
|
67
|
+
const specTarget = cacheTarget.replace(`${path.sep}src${path.sep}`, `${path.sep}tests${path.sep}`).replace(`.${langExt}`, `.spec.${langExt}`);
|
|
68
|
+
await fs.ensureDir(path.dirname(specTarget));
|
|
69
|
+
await fs.writeFile(specTarget, specContent);
|
|
70
|
+
}
|
|
58
71
|
}
|
|
59
72
|
}
|
|
60
73
|
};
|