@saulpaulus17/node-module-generator 2.0.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.
Files changed (58) hide show
  1. package/.github/workflows/ci.yml +28 -0
  2. package/.github/workflows/release.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +103 -0
  5. package/SECURITY.md +21 -0
  6. package/bin/cli.js +14 -0
  7. package/commands/dto.command.js +15 -0
  8. package/commands/module.command.js +10 -0
  9. package/commands/repository.command.js +10 -0
  10. package/commands/resource.command.js +10 -0
  11. package/commands/usecase.command.js +15 -0
  12. package/generator/dto.generator.js +21 -0
  13. package/generator/module.generator.js +34 -0
  14. package/generator/repository.generator.js +36 -0
  15. package/generator/resource.generator.js +57 -0
  16. package/generator/usecase.generator.js +26 -0
  17. package/package.json +30 -0
  18. package/release.yml +0 -0
  19. package/src/modules/order/application/usecases/create-order.usecase.js +15 -0
  20. package/src/modules/order/application/usecases/create-order.usecase.test.js +26 -0
  21. package/src/modules/order/domain/entities/order.entity.js +8 -0
  22. package/src/modules/order/domain/repositories/order.repository.interface.js +11 -0
  23. package/src/modules/order/infrastructure/repositories/order.repository.impl.js +23 -0
  24. package/src/modules/order/infrastructure/validation/create-order.schema.js +7 -0
  25. package/src/modules/order/interfaces/controllers/order.controller.js +16 -0
  26. package/src/modules/order/interfaces/controllers/order.controller.test.js +46 -0
  27. package/src/modules/order/interfaces/routes/order.routes.js +9 -0
  28. package/src/modules/order/order.module.js +16 -0
  29. package/src/modules/product/application/usecases/getProduct.usecase.js +15 -0
  30. package/src/modules/product/application/usecases/getProduct.usecase.test.js +26 -0
  31. package/src/modules/product/domain/entities/product.entity.js +8 -0
  32. package/src/modules/product/domain/repositories/product.repository.interface.js +11 -0
  33. package/src/modules/product/infrastructure/repositories/product.repository.impl.js +23 -0
  34. package/src/modules/product/infrastructure/validation/getProduct.schema.js +7 -0
  35. package/src/modules/product/product.module.js +5 -0
  36. package/src/modules/user/application/usecases/create-user.usecase.js +15 -0
  37. package/src/modules/user/application/usecases/create-user.usecase.test.js +26 -0
  38. package/src/modules/user/domain/entities/user.entity.js +8 -0
  39. package/src/modules/user/domain/repositories/user.repository.interface.js +11 -0
  40. package/src/modules/user/infrastructure/repositories/user.repository.impl.js +23 -0
  41. package/src/modules/user/infrastructure/validation/create-user.schema.js +7 -0
  42. package/src/modules/user/interfaces/controllers/user.controller.js +16 -0
  43. package/src/modules/user/interfaces/controllers/user.controller.test.js +46 -0
  44. package/src/modules/user/interfaces/routes/user.routes.js +9 -0
  45. package/src/modules/user/user.module.js +16 -0
  46. package/templates/module/controller.ejs +16 -0
  47. package/templates/module/controller.test.ejs +46 -0
  48. package/templates/module/di.ejs +16 -0
  49. package/templates/module/dto.ejs +7 -0
  50. package/templates/module/entity.ejs +8 -0
  51. package/templates/module/repository.impl.ejs +23 -0
  52. package/templates/module/repository.interface.ejs +11 -0
  53. package/templates/module/route.ejs +9 -0
  54. package/templates/module/usecase.ejs +15 -0
  55. package/templates/module/usecase.test.ejs +26 -0
  56. package/utils/case.util.js +10 -0
  57. package/utils/file.util.js +0 -0
  58. package/utils/logger.util.js +0 -0
@@ -0,0 +1,28 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: ["main"]
5
+ pull_request:
6
+ branches: ["main"]
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ node-version: [18, 20]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - name: Use Node.js ${{ matrix.node-version }}
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: ${{ matrix.node-version }}
20
+ - name: Cache node modules
21
+ uses: actions/cache@v4
22
+ with:
23
+ path: ~/.npm
24
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
25
+ - name: Install dependencies
26
+ run: npm ci
27
+ - name: Run tests
28
+ run: npm test
@@ -0,0 +1,31 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*.*.*'
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Setup Node
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: 20
20
+ registry-url: 'https://registry.npmjs.org'
21
+
22
+ - name: Install deps
23
+ run: npm install
24
+
25
+ - name: Run test
26
+ run: npm test
27
+
28
+ - name: Publish to npm
29
+ run: npm publish --access public
30
+ env:
31
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ixspx-dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ <div align="center">
2
+ <h1>🚀 Node Module Generator</h1>
3
+ <p>A robust CLI tool for scaffolding Express.js projects using Clean Architecture & Dependency Injection.</p>
4
+ <p>
5
+ <a href="https://github.com/saul-paulus/node-module-generator/actions/workflows/ci.yml">
6
+ <img src="https://github.com/saul-paulus/node-module-generator/actions/workflows/ci.yml/badge.svg" alt="CI Status">
7
+ </a>
8
+ </p>
9
+ </div>
10
+
11
+ ---
12
+
13
+ ## 📖 Overview
14
+
15
+ **Node Module Generator** provides a world-class Developer Experience (DX) equivalent to the robust CLIs found in ecosystems like NestJS or Angular, but designed specifically for pure Node.js environments utilizing **Express.js** and **Clean Architecture**.
16
+
17
+ It instantly scaffolds fully-tested, decoupled, and highly cohesive module structures with built-in Dependency Injection using **Awilix**.
18
+
19
+ ## ✨ Features
20
+
21
+ - 🏗️ **Clean Architecture by Default**: Automatically separates concerns into Domain, Application, Infrastructure, and Interface layers.
22
+ - 💉 **Dependency Injection Ready**: Auto-generates Awilix configurations mapped correctly across use cases, controllers, and repositories.
23
+ - 🧪 **Test-Driven Design**: Scaffolds adjoining `*.test.js` files containing boilerplates for Jest to promote TDD.
24
+ - 🤖 **Continuous Integration**: Codebase natively incorporates GitHub Actions for automated unit testing checks.
25
+ - 🧩 **Granular Scaffolding**: Generate specific components (UseCases, Repositories, DTOs) dynamically on demand without overriding existing folders!
26
+
27
+ ## 📦 Installation
28
+
29
+ To use this CLI tool locally or globally on your machine, you can install it directly from NPM.
30
+
31
+ ```bash
32
+ # Install globally via NPM
33
+ npm install -g @saulpaulus17/node-module-generator
34
+ ```
35
+
36
+ ## 🚀 Usage
37
+
38
+ Once installed globally, you can execute the CLI commands from any of your Node.js project directories using the `nmg` command keyword.
39
+
40
+ ### Granular CLI Commands
41
+ Scaffold specifically what you need, exactly how you do it in NestJS:
42
+
43
+ ```bash
44
+ # 1. Scaffolds a new module architecture and empty DI registry
45
+ nmg module product
46
+
47
+ # 2. Creates a specific Use Case and its Test inside an existing module
48
+ nmg usecase updateProduct --module=product
49
+
50
+ # 3. Creates Domain Entity and Repository Interfaces/Implementations
51
+ nmg repository product
52
+
53
+ # 4. Scaffolds a DTO validation schema
54
+ nmg dto getProduct --module=product
55
+
56
+ # 5. Generates an entire full-stack CRUD Resource (Controller, Entity, Repos, DI, etc.)
57
+ nmg resource order
58
+ ```
59
+
60
+ _(Note: If you haven't installed the package globally, you can run it via `npx @saulpaulus17/node-module-generator <command> <name>`)_
61
+
62
+ ## 📂 Full Resource Structure
63
+
64
+ Running the powerhouse command `nmg resource user` instantly generates the following decoupled blueprint within your project's `src/modules` directory:
65
+
66
+ ```text
67
+ src/modules/user/
68
+ ├── application/ # Orchestration & Use Cases
69
+ │ ├── dtos/ # (Generated schema references)
70
+ │ └── usecases/
71
+ │ ├── create-user.usecase.js # Business logically perfectly isolated
72
+ │ └── create-user.usecase.test.js # Scaffolded test harness
73
+ ├── domain/ # Blueprints & Business Rules (Pure Logic)
74
+ │ ├── entities/
75
+ │ │ └── user.entity.js
76
+ │ └── repositories/ # Repository Interfaces (Contracts)
77
+ │ └── user.repository.interface.js
78
+ ├── infrastructure/ # Technical Details & Implementation
79
+ │ ├── repositories/ # Implementations (mocked to replace with Prisma/TypeORM)
80
+ │ │ └── user.repository.impl.js
81
+ │ └── validation/ # Input validation schemas
82
+ │ └── create-user.schema.js # Auto-stubbed validation schema
83
+ ├── interfaces/ # Entry Points (Web/API)
84
+ │ ├── controllers/
85
+ │ │ ├── user.controller.js # Express.js class handlers
86
+ │ │ └── user.controller.test.js
87
+ │ └── routes/
88
+ │ └── user.routes.js # Express routers integrating the controller
89
+ └── user.module.js # Centralised Awilix Dependency Injection bindings
90
+ ```
91
+
92
+ ## 🛠️ Technological Footprint
93
+
94
+ The scaffolded code integrates perfectly if you are using the following libraries in your Express application:
95
+
96
+ - **[Express.js](https://expressjs.com/)** - Web Framework
97
+ - **[Awilix](https://github.com/jeffijoe/awilix)** - Powerful Dependency Injection container
98
+ - **[Jest](https://jestjs.io/)** - For the colocated testing environments
99
+ - **[Joi](https://joi.dev/)** - For infrastructure schema validations
100
+
101
+ ## 📝 License
102
+
103
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
package/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Use this section to tell people about which versions of your project are
6
+ currently being supported with security updates.
7
+
8
+ | Version | Supported |
9
+ | ------- | ------------------ |
10
+ | 5.1.x | :white_check_mark: |
11
+ | 5.0.x | :x: |
12
+ | 4.0.x | :white_check_mark: |
13
+ | < 4.0 | :x: |
14
+
15
+ ## Reporting a Vulnerability
16
+
17
+ Use this section to tell people how to report a vulnerability.
18
+
19
+ Tell them where to go, how often they can expect to get an update on a
20
+ reported vulnerability, what to expect if the vulnerability is accepted or
21
+ declined, etc.
package/bin/cli.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require("commander");
4
+ const program = new Command();
5
+
6
+ program.name("nmg").version("1.0.0").description("Clean nodejs CLI");
7
+
8
+ require("../commands/module.command")(program);
9
+ require("../commands/usecase.command")(program);
10
+ require("../commands/resource.command")(program);
11
+ require("../commands/repository.command")(program);
12
+ require("../commands/dto.command")(program);
13
+
14
+ program.parse();
@@ -0,0 +1,15 @@
1
+ const generateDto = require("../generator/dto.generator");
2
+
3
+ module.exports = (program) => {
4
+ program
5
+ .command("dto <name>")
6
+ .description("Create a new DTO (validation schema)")
7
+ .option("-m, --module <moduleName>", "Specify the target module")
8
+ .action((name, options) => {
9
+ if (!options.module) {
10
+ console.error("error: required option '-m, --module <moduleName>' not specified");
11
+ process.exit(1);
12
+ }
13
+ generateDto(name, options.module);
14
+ });
15
+ };
@@ -0,0 +1,10 @@
1
+ const generateModule = require("../generator/module.generator");
2
+
3
+ module.exports = (program) => {
4
+ program
5
+ .command("module <name>")
6
+ .description("Create a new module")
7
+ .action((name) => {
8
+ generateModule(name);
9
+ });
10
+ };
@@ -0,0 +1,10 @@
1
+ const generateRepository = require("../generator/repository.generator");
2
+
3
+ module.exports = (program) => {
4
+ program
5
+ .command("repository <moduleName>")
6
+ .description("Create domain and infrastructure repositories for a module")
7
+ .action((moduleName) => {
8
+ generateRepository(moduleName);
9
+ });
10
+ };
@@ -0,0 +1,10 @@
1
+ const generateResource = require("../generator/resource.generator");
2
+
3
+ module.exports = (program) => {
4
+ program
5
+ .command("resource <name>")
6
+ .description("Create a new resource")
7
+ .action((name) => {
8
+ generateResource(name);
9
+ });
10
+ };
@@ -0,0 +1,15 @@
1
+ const generateUsecase = require("../generator/usecase.generator");
2
+
3
+ module.exports = (program) => {
4
+ program
5
+ .command("usecase <name>")
6
+ .description("Create a new usecase inside an existing module")
7
+ .option("-m, --module <moduleName>", "Specify the target module")
8
+ .action((name, options) => {
9
+ if (!options.module) {
10
+ console.error("error: required option '-m, --module <moduleName>' not specified");
11
+ process.exit(1);
12
+ }
13
+ generateUsecase(name, options.module);
14
+ });
15
+ };
@@ -0,0 +1,21 @@
1
+ const path = require("path");
2
+ const fs = require("fs-extra");
3
+ const ejs = require("ejs");
4
+ const { pascalCase, camelCase } = require("../utils/case.util");
5
+
6
+ module.exports = async function (schemaName, moduleName) {
7
+ const basePath = path.join(process.cwd(), "src/modules", moduleName);
8
+ const validationDir = path.join(basePath, "infrastructure/validation");
9
+ fs.ensureDirSync(validationDir);
10
+
11
+ const templateData = {
12
+ name: moduleName,
13
+ className: pascalCase(schemaName),
14
+ camelName: camelCase(moduleName),
15
+ };
16
+
17
+ const templateContent = await ejs.renderFile(path.join(__dirname, "../templates/module/dto.ejs"), templateData);
18
+ fs.writeFileSync(path.join(validationDir, `${schemaName}.schema.js`), templateContent);
19
+
20
+ console.log(`✔ DTO Schema ${schemaName} generated inside module ${moduleName}.`);
21
+ };
@@ -0,0 +1,34 @@
1
+ const path = require("path");
2
+ const fs = require("fs-extra");
3
+ const ejs = require("ejs");
4
+ const { pascalCase, camelCase } = require("../utils/case.util");
5
+
6
+ module.exports = async function (name) {
7
+ const basePath = path.join(process.cwd(), "src/modules", name);
8
+
9
+ const dirs = [
10
+ "domain/entities",
11
+ "domain/repositories",
12
+ "domain/services",
13
+ "application/usecases",
14
+ "application/dtos",
15
+ "infrastructure/repositories",
16
+ "infrastructure/validation",
17
+ "infrastructure/security",
18
+ "infrastructure/services",
19
+ "interfaces/controllers",
20
+ "interfaces/routes",
21
+ ];
22
+
23
+ // Create directories
24
+ dirs.forEach((dir) => fs.ensureDirSync(path.join(basePath, dir)));
25
+
26
+ console.log(`✔ Module ${name} directory structure created.`);
27
+
28
+ // Optionally create an empty module DI file
29
+ const diFile = path.join(basePath, `${name}.module.js`);
30
+ if (!fs.existsSync(diFile)) {
31
+ fs.writeFileSync(diFile, `module.exports = function register${pascalCase(name)}Module(container) {\n container.register({\n // Inject dependencies here\n });\n};\n`);
32
+ console.log(`✔ Module DI registry ${name}.module.js created.`);
33
+ }
34
+ };
@@ -0,0 +1,36 @@
1
+ const path = require("path");
2
+ const fs = require("fs-extra");
3
+ const ejs = require("ejs");
4
+ const { pascalCase, camelCase } = require("../utils/case.util");
5
+
6
+ module.exports = async function (moduleName) {
7
+ const basePath = path.join(process.cwd(), "src/modules", moduleName);
8
+
9
+ const entityDir = path.join(basePath, "domain/entities");
10
+ const repoInterfaceDir = path.join(basePath, "domain/repositories");
11
+ const repoImplDir = path.join(basePath, "infrastructure/repositories");
12
+
13
+ fs.ensureDirSync(entityDir);
14
+ fs.ensureDirSync(repoInterfaceDir);
15
+ fs.ensureDirSync(repoImplDir);
16
+
17
+ const templateData = {
18
+ name: moduleName,
19
+ className: pascalCase(moduleName),
20
+ camelName: camelCase(moduleName),
21
+ };
22
+
23
+ const renderAndWrite = async (templateName, outputPath) => {
24
+ const templateContent = await ejs.renderFile(
25
+ path.join(__dirname, "../templates/module", templateName),
26
+ templateData
27
+ );
28
+ fs.writeFileSync(path.join(basePath, outputPath), templateContent);
29
+ };
30
+
31
+ await renderAndWrite("entity.ejs", `domain/entities/${moduleName}.entity.js`);
32
+ await renderAndWrite("repository.interface.ejs", `domain/repositories/${moduleName}.repository.interface.js`);
33
+ await renderAndWrite("repository.impl.ejs", `infrastructure/repositories/${moduleName}.repository.impl.js`);
34
+
35
+ console.log(`✔ Repository patterns for ${moduleName} generated successfully.`);
36
+ };
@@ -0,0 +1,57 @@
1
+ const path = require("path");
2
+ const fs = require("fs-extra");
3
+ const ejs = require("ejs");
4
+ const { pascalCase, camelCase } = require("../utils/case.util");
5
+
6
+ module.exports = async function (name) {
7
+ const basePath = path.join(process.cwd(), "src/modules", name);
8
+
9
+ const dirs = [
10
+ "domain/entities",
11
+ "domain/repositories",
12
+ "domain/services",
13
+ "application/usecases",
14
+ "application/dtos",
15
+ "infrastructure/repositories",
16
+ "infrastructure/validation",
17
+ "infrastructure/security",
18
+ "infrastructure/services",
19
+ "interfaces/controllers",
20
+ "interfaces/routes",
21
+ ];
22
+
23
+ dirs.forEach((dir) => fs.ensureDirSync(path.join(basePath, dir)));
24
+
25
+ const templateData = {
26
+ name,
27
+ className: pascalCase(name),
28
+ camelName: camelCase(name),
29
+ useCaseClassName: `Create${pascalCase(name)}UseCase`,
30
+ useCaseFileName: `create-${name}`,
31
+ };
32
+
33
+ const renderAndWrite = async (templateName, outputPath) => {
34
+ const templateContent = await ejs.renderFile(
35
+ path.join(__dirname, "../templates/module", templateName),
36
+ templateData
37
+ );
38
+ fs.writeFileSync(path.join(basePath, outputPath), templateContent);
39
+ };
40
+
41
+ await renderAndWrite("controller.ejs", `interfaces/controllers/${name}.controller.js`);
42
+ await renderAndWrite("controller.test.ejs", `interfaces/controllers/${name}.controller.test.js`);
43
+ await renderAndWrite("route.ejs", `interfaces/routes/${name}.routes.js`);
44
+
45
+ await renderAndWrite("usecase.ejs", `application/usecases/create-${name}.usecase.js`);
46
+ await renderAndWrite("usecase.test.ejs", `application/usecases/create-${name}.usecase.test.js`);
47
+
48
+ await renderAndWrite("entity.ejs", `domain/entities/${name}.entity.js`);
49
+ await renderAndWrite("repository.interface.ejs", `domain/repositories/${name}.repository.interface.js`);
50
+
51
+ await renderAndWrite("repository.impl.ejs", `infrastructure/repositories/${name}.repository.impl.js`);
52
+ await renderAndWrite("dto.ejs", `infrastructure/validation/create-${name}.schema.js`);
53
+
54
+ await renderAndWrite("di.ejs", `${name}.module.js`);
55
+
56
+ console.log(`✔ Resource ${name} generated successfully with Clean Architecture and Awilix DI!`);
57
+ };
@@ -0,0 +1,26 @@
1
+ const path = require("path");
2
+ const fs = require("fs-extra");
3
+ const ejs = require("ejs");
4
+ const { pascalCase, camelCase } = require("../utils/case.util");
5
+
6
+ module.exports = async function (useCaseName, moduleName) {
7
+ const basePath = path.join(process.cwd(), "src/modules", moduleName);
8
+ const ucDir = path.join(basePath, "application/usecases");
9
+ fs.ensureDirSync(ucDir);
10
+
11
+ const templateData = {
12
+ name: moduleName,
13
+ className: pascalCase(moduleName),
14
+ camelName: camelCase(moduleName),
15
+ useCaseClassName: `${pascalCase(useCaseName)}UseCase`,
16
+ useCaseFileName: useCaseName,
17
+ };
18
+
19
+ const templateContent = await ejs.renderFile(path.join(__dirname, "../templates/module/usecase.ejs"), templateData);
20
+ fs.writeFileSync(path.join(ucDir, `${useCaseName}.usecase.js`), templateContent);
21
+
22
+ const testContent = await ejs.renderFile(path.join(__dirname, "../templates/module/usecase.test.ejs"), templateData);
23
+ fs.writeFileSync(path.join(ucDir, `${useCaseName}.usecase.test.js`), testContent);
24
+
25
+ console.log(`✔ Usecase ${useCaseName} generated inside module ${moduleName}.`);
26
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@saulpaulus17/node-module-generator",
3
+ "version": "2.0.2",
4
+ "description": "CLI tool to grenerate modular scaffolding for nodejs projects following clean arsitecture principles. ",
5
+ "keywords": [
6
+ "nodejs",
7
+ "module-generator",
8
+ "module",
9
+ "clean-architecture"
10
+ ],
11
+ "license": "MIT",
12
+ "author": "saul-paulus (ixspx)",
13
+ "type": "commonjs",
14
+ "main": "index.js",
15
+ "bin": {
16
+ "nmg": "./bin/cli.js"
17
+ },
18
+ "scripts": {
19
+ "test": "jest"
20
+ },
21
+ "dependencies": {
22
+ "chalk": "^5.6.2",
23
+ "commander": "^14.0.3",
24
+ "ejs": "^5.0.1",
25
+ "fs-extra": "^11.3.4"
26
+ },
27
+ "devDependencies": {
28
+ "jest": "^30.3.0"
29
+ }
30
+ }
package/release.yml ADDED
File without changes
@@ -0,0 +1,15 @@
1
+ class CreateOrderUseCase {
2
+ constructor({ orderRepository }) {
3
+ this.orderRepository = orderRepository;
4
+ }
5
+
6
+ async execute(dto) {
7
+ // Validate DTO...
8
+ // Apply business logic...
9
+
10
+ const entity = await this.orderRepository.save(dto);
11
+ return entity;
12
+ }
13
+ }
14
+
15
+ module.exports = CreateOrderUseCase;
@@ -0,0 +1,26 @@
1
+ const CreateOrderUseCase = require('./create-order.usecase');
2
+
3
+ describe('CreateOrderUseCase', () => {
4
+ let useCase;
5
+ let mockRepository;
6
+
7
+ beforeEach(() => {
8
+ mockRepository = {
9
+ save: jest.fn()
10
+ };
11
+ useCase = new CreateOrderUseCase({ orderRepository: mockRepository });
12
+ });
13
+
14
+ describe('execute', () => {
15
+ it('should save data using the repository and return the entity', async () => {
16
+ const mockDto = { name: 'Test' };
17
+ const mockEntity = { id: 1, ...mockDto };
18
+ mockRepository.save.mockResolvedValue(mockEntity);
19
+
20
+ const result = await useCase.execute(mockDto);
21
+
22
+ expect(mockRepository.save).toHaveBeenCalledWith(mockDto);
23
+ expect(result).toEqual(mockEntity);
24
+ });
25
+ });
26
+ });
@@ -0,0 +1,8 @@
1
+ class OrderEntity {
2
+ constructor({ id, ...props }) {
3
+ this.id = id;
4
+ Object.assign(this, props);
5
+ }
6
+ }
7
+
8
+ module.exports = OrderEntity;
@@ -0,0 +1,11 @@
1
+ class IOrderRepository {
2
+ async save(entity) {
3
+ throw new Error('Method not implemented.');
4
+ }
5
+
6
+ async findById(id) {
7
+ throw new Error('Method not implemented.');
8
+ }
9
+ }
10
+
11
+ module.exports = IOrderRepository;
@@ -0,0 +1,23 @@
1
+ const IOrderRepository = require('../../domain/repositories/order.repository.interface');
2
+ const OrderEntity = require('../../domain/entities/order.entity');
3
+
4
+ class OrderRepositoryImpl extends IOrderRepository {
5
+ // If using Prisma, you would do: constructor({ prisma }) { super(); this.prisma = prisma; }
6
+ constructor() {
7
+ super();
8
+ this.db = new Map();
9
+ }
10
+
11
+ async save(dto) {
12
+ const id = Date.now().toString(); // Mock ID generation
13
+ const entity = new OrderEntity({ id, ...dto });
14
+ this.db.set(id, entity);
15
+ return entity;
16
+ }
17
+
18
+ async findById(id) {
19
+ return this.db.get(id) || null;
20
+ }
21
+ }
22
+
23
+ module.exports = OrderRepositoryImpl;
@@ -0,0 +1,7 @@
1
+ const Joi = require('joi'); // Usually you'd use joi or another lib
2
+
3
+ const createOrderSchema = Joi.object({
4
+ // Define DTO validation rules
5
+ });
6
+
7
+ module.exports = createOrderSchema;
@@ -0,0 +1,16 @@
1
+ class OrderController {
2
+ constructor({ orderUseCase }) {
3
+ this.orderUseCase = orderUseCase;
4
+ }
5
+
6
+ create = async (req, res, next) => {
7
+ try {
8
+ const result = await this.orderUseCase.execute(req.body);
9
+ res.status(201).json({ success: true, data: result });
10
+ } catch (error) {
11
+ next(error);
12
+ }
13
+ };
14
+ }
15
+
16
+ module.exports = OrderController;
@@ -0,0 +1,46 @@
1
+ const OrderController = require('./order.controller');
2
+
3
+ describe('OrderController', () => {
4
+ let controller;
5
+ let mockUseCase;
6
+ let mockReq;
7
+ let mockRes;
8
+ let mockNext;
9
+
10
+ beforeEach(() => {
11
+ mockUseCase = {
12
+ execute: jest.fn()
13
+ };
14
+ controller = new OrderController({ orderUseCase: mockUseCase });
15
+ mockReq = {
16
+ body: { test: 'data' }
17
+ };
18
+ mockRes = {
19
+ status: jest.fn().mockReturnThis(),
20
+ json: jest.fn()
21
+ };
22
+ mockNext = jest.fn();
23
+ });
24
+
25
+ describe('create', () => {
26
+ it('should return 201 and result if use case succeeds', async () => {
27
+ const mockResult = { id: 1, ...mockReq.body };
28
+ mockUseCase.execute.mockResolvedValue(mockResult);
29
+
30
+ await controller.create(mockReq, mockRes, mockNext);
31
+
32
+ expect(mockUseCase.execute).toHaveBeenCalledWith(mockReq.body);
33
+ expect(mockRes.status).toHaveBeenCalledWith(201);
34
+ expect(mockRes.json).toHaveBeenCalledWith({ success: true, data: mockResult });
35
+ });
36
+
37
+ it('should call next with error if use case fails', async () => {
38
+ const error = new Error('Test error');
39
+ mockUseCase.execute.mockRejectedValue(error);
40
+
41
+ await controller.create(mockReq, mockRes, mockNext);
42
+
43
+ expect(mockNext).toHaveBeenCalledWith(error);
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,9 @@
1
+ const { Router } = require('express');
2
+
3
+ module.exports = function({ orderController }) {
4
+ const router = Router();
5
+
6
+ router.post('/', orderController.create);
7
+
8
+ return router;
9
+ };
@@ -0,0 +1,16 @@
1
+ const { asClass, asFunction } = require('awilix');
2
+
3
+ const OrderController = require('./interfaces/controllers/order.controller');
4
+ const createOrderRoutes = require('./interfaces/routes/order.routes');
5
+ const CreateOrderUseCase = require('./application/usecases/create-order.usecase');
6
+ const OrderRepository = require('./infrastructure/repositories/order.repository.impl');
7
+
8
+ module.exports = function registerOrderModule(container) {
9
+ container.register({
10
+ orderController: asClass(OrderController).singleton(),
11
+ orderRoutes: asFunction(createOrderRoutes).singleton(),
12
+ orderUseCase: asClass(CreateOrderUseCase).singleton(),
13
+ // Providing the interface's name as the injection token for the implementation
14
+ orderRepository: asClass(OrderRepository).singleton(),
15
+ });
16
+ };
@@ -0,0 +1,15 @@
1
+ class GetproductUseCase {
2
+ constructor({ productRepository }) {
3
+ this.productRepository = productRepository;
4
+ }
5
+
6
+ async execute(dto) {
7
+ // Validate DTO...
8
+ // Apply business logic...
9
+
10
+ const entity = await this.productRepository.save(dto);
11
+ return entity;
12
+ }
13
+ }
14
+
15
+ module.exports = GetproductUseCase;
@@ -0,0 +1,26 @@
1
+ const GetproductUseCase = require('./getProduct.usecase');
2
+
3
+ describe('GetproductUseCase', () => {
4
+ let useCase;
5
+ let mockRepository;
6
+
7
+ beforeEach(() => {
8
+ mockRepository = {
9
+ save: jest.fn()
10
+ };
11
+ useCase = new GetproductUseCase({ productRepository: mockRepository });
12
+ });
13
+
14
+ describe('execute', () => {
15
+ it('should save data using the repository and return the entity', async () => {
16
+ const mockDto = { name: 'Test' };
17
+ const mockEntity = { id: 1, ...mockDto };
18
+ mockRepository.save.mockResolvedValue(mockEntity);
19
+
20
+ const result = await useCase.execute(mockDto);
21
+
22
+ expect(mockRepository.save).toHaveBeenCalledWith(mockDto);
23
+ expect(result).toEqual(mockEntity);
24
+ });
25
+ });
26
+ });
@@ -0,0 +1,8 @@
1
+ class ProductEntity {
2
+ constructor({ id, ...props }) {
3
+ this.id = id;
4
+ Object.assign(this, props);
5
+ }
6
+ }
7
+
8
+ module.exports = ProductEntity;
@@ -0,0 +1,11 @@
1
+ class IProductRepository {
2
+ async save(entity) {
3
+ throw new Error('Method not implemented.');
4
+ }
5
+
6
+ async findById(id) {
7
+ throw new Error('Method not implemented.');
8
+ }
9
+ }
10
+
11
+ module.exports = IProductRepository;
@@ -0,0 +1,23 @@
1
+ const IProductRepository = require('../../domain/repositories/product.repository.interface');
2
+ const ProductEntity = require('../../domain/entities/product.entity');
3
+
4
+ class ProductRepositoryImpl extends IProductRepository {
5
+ // If using Prisma, you would do: constructor({ prisma }) { super(); this.prisma = prisma; }
6
+ constructor() {
7
+ super();
8
+ this.db = new Map();
9
+ }
10
+
11
+ async save(dto) {
12
+ const id = Date.now().toString(); // Mock ID generation
13
+ const entity = new ProductEntity({ id, ...dto });
14
+ this.db.set(id, entity);
15
+ return entity;
16
+ }
17
+
18
+ async findById(id) {
19
+ return this.db.get(id) || null;
20
+ }
21
+ }
22
+
23
+ module.exports = ProductRepositoryImpl;
@@ -0,0 +1,7 @@
1
+ const Joi = require('joi'); // Usually you'd use joi or another lib
2
+
3
+ const createGetproductSchema = Joi.object({
4
+ // Define DTO validation rules
5
+ });
6
+
7
+ module.exports = createGetproductSchema;
@@ -0,0 +1,5 @@
1
+ module.exports = function registerProductModule(container) {
2
+ container.register({
3
+ // Inject dependencies here
4
+ });
5
+ };
@@ -0,0 +1,15 @@
1
+ class CreateUserUseCase {
2
+ constructor({ userRepository }) {
3
+ this.userRepository = userRepository;
4
+ }
5
+
6
+ async execute(dto) {
7
+ // Validate DTO...
8
+ // Apply business logic...
9
+
10
+ const entity = await this.userRepository.save(dto);
11
+ return entity;
12
+ }
13
+ }
14
+
15
+ module.exports = CreateUserUseCase;
@@ -0,0 +1,26 @@
1
+ const CreateUserUseCase = require('./create-user.usecase');
2
+
3
+ describe('CreateUserUseCase', () => {
4
+ let useCase;
5
+ let mockRepository;
6
+
7
+ beforeEach(() => {
8
+ mockRepository = {
9
+ save: jest.fn()
10
+ };
11
+ useCase = new CreateUserUseCase({ userRepository: mockRepository });
12
+ });
13
+
14
+ describe('execute', () => {
15
+ it('should save data using the repository and return the entity', async () => {
16
+ const mockDto = { name: 'Test' };
17
+ const mockEntity = { id: 1, ...mockDto };
18
+ mockRepository.save.mockResolvedValue(mockEntity);
19
+
20
+ const result = await useCase.execute(mockDto);
21
+
22
+ expect(mockRepository.save).toHaveBeenCalledWith(mockDto);
23
+ expect(result).toEqual(mockEntity);
24
+ });
25
+ });
26
+ });
@@ -0,0 +1,8 @@
1
+ class UserEntity {
2
+ constructor({ id, ...props }) {
3
+ this.id = id;
4
+ Object.assign(this, props);
5
+ }
6
+ }
7
+
8
+ module.exports = UserEntity;
@@ -0,0 +1,11 @@
1
+ class IUserRepository {
2
+ async save(entity) {
3
+ throw new Error('Method not implemented.');
4
+ }
5
+
6
+ async findById(id) {
7
+ throw new Error('Method not implemented.');
8
+ }
9
+ }
10
+
11
+ module.exports = IUserRepository;
@@ -0,0 +1,23 @@
1
+ const IUserRepository = require('../../domain/repositories/user.repository.interface');
2
+ const UserEntity = require('../../domain/entities/user.entity');
3
+
4
+ class UserRepositoryImpl extends IUserRepository {
5
+ // If using Prisma, you would do: constructor({ prisma }) { super(); this.prisma = prisma; }
6
+ constructor() {
7
+ super();
8
+ this.db = new Map();
9
+ }
10
+
11
+ async save(dto) {
12
+ const id = Date.now().toString(); // Mock ID generation
13
+ const entity = new UserEntity({ id, ...dto });
14
+ this.db.set(id, entity);
15
+ return entity;
16
+ }
17
+
18
+ async findById(id) {
19
+ return this.db.get(id) || null;
20
+ }
21
+ }
22
+
23
+ module.exports = UserRepositoryImpl;
@@ -0,0 +1,7 @@
1
+ const Joi = require('joi'); // Usually you'd use joi or another lib
2
+
3
+ const createUserSchema = Joi.object({
4
+ // Define DTO validation rules
5
+ });
6
+
7
+ module.exports = createUserSchema;
@@ -0,0 +1,16 @@
1
+ class UserController {
2
+ constructor({ userUseCase }) {
3
+ this.userUseCase = userUseCase;
4
+ }
5
+
6
+ create = async (req, res, next) => {
7
+ try {
8
+ const result = await this.userUseCase.execute(req.body);
9
+ res.status(201).json({ success: true, data: result });
10
+ } catch (error) {
11
+ next(error);
12
+ }
13
+ };
14
+ }
15
+
16
+ module.exports = UserController;
@@ -0,0 +1,46 @@
1
+ const UserController = require('./user.controller');
2
+
3
+ describe('UserController', () => {
4
+ let controller;
5
+ let mockUseCase;
6
+ let mockReq;
7
+ let mockRes;
8
+ let mockNext;
9
+
10
+ beforeEach(() => {
11
+ mockUseCase = {
12
+ execute: jest.fn()
13
+ };
14
+ controller = new UserController({ userUseCase: mockUseCase });
15
+ mockReq = {
16
+ body: { test: 'data' }
17
+ };
18
+ mockRes = {
19
+ status: jest.fn().mockReturnThis(),
20
+ json: jest.fn()
21
+ };
22
+ mockNext = jest.fn();
23
+ });
24
+
25
+ describe('create', () => {
26
+ it('should return 201 and result if use case succeeds', async () => {
27
+ const mockResult = { id: 1, ...mockReq.body };
28
+ mockUseCase.execute.mockResolvedValue(mockResult);
29
+
30
+ await controller.create(mockReq, mockRes, mockNext);
31
+
32
+ expect(mockUseCase.execute).toHaveBeenCalledWith(mockReq.body);
33
+ expect(mockRes.status).toHaveBeenCalledWith(201);
34
+ expect(mockRes.json).toHaveBeenCalledWith({ success: true, data: mockResult });
35
+ });
36
+
37
+ it('should call next with error if use case fails', async () => {
38
+ const error = new Error('Test error');
39
+ mockUseCase.execute.mockRejectedValue(error);
40
+
41
+ await controller.create(mockReq, mockRes, mockNext);
42
+
43
+ expect(mockNext).toHaveBeenCalledWith(error);
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,9 @@
1
+ const { Router } = require('express');
2
+
3
+ module.exports = function({ userController }) {
4
+ const router = Router();
5
+
6
+ router.post('/', userController.create);
7
+
8
+ return router;
9
+ };
@@ -0,0 +1,16 @@
1
+ const { asClass, asFunction } = require('awilix');
2
+
3
+ const UserController = require('./interfaces/controllers/user.controller');
4
+ const createUserRoutes = require('./interfaces/routes/user.routes');
5
+ const CreateUserUseCase = require('./application/usecases/create-user.usecase');
6
+ const UserRepository = require('./infrastructure/repositories/user.repository.impl');
7
+
8
+ module.exports = function registerUserModule(container) {
9
+ container.register({
10
+ userController: asClass(UserController).singleton(),
11
+ userRoutes: asFunction(createUserRoutes).singleton(),
12
+ userUseCase: asClass(CreateUserUseCase).singleton(),
13
+ // Providing the interface's name as the injection token for the implementation
14
+ userRepository: asClass(UserRepository).singleton(),
15
+ });
16
+ };
@@ -0,0 +1,16 @@
1
+ class <%= className %>Controller {
2
+ constructor({ <%= camelName %>UseCase }) {
3
+ this.<%= camelName %>UseCase = <%= camelName %>UseCase;
4
+ }
5
+
6
+ create = async (req, res, next) => {
7
+ try {
8
+ const result = await this.<%= camelName %>UseCase.execute(req.body);
9
+ res.status(201).json({ success: true, data: result });
10
+ } catch (error) {
11
+ next(error);
12
+ }
13
+ };
14
+ }
15
+
16
+ module.exports = <%= className %>Controller;
@@ -0,0 +1,46 @@
1
+ const <%= className %>Controller = require('./<%= name %>.controller');
2
+
3
+ describe('<%= className %>Controller', () => {
4
+ let controller;
5
+ let mockUseCase;
6
+ let mockReq;
7
+ let mockRes;
8
+ let mockNext;
9
+
10
+ beforeEach(() => {
11
+ mockUseCase = {
12
+ execute: jest.fn()
13
+ };
14
+ controller = new <%= className %>Controller({ <%= camelName %>UseCase: mockUseCase });
15
+ mockReq = {
16
+ body: { test: 'data' }
17
+ };
18
+ mockRes = {
19
+ status: jest.fn().mockReturnThis(),
20
+ json: jest.fn()
21
+ };
22
+ mockNext = jest.fn();
23
+ });
24
+
25
+ describe('create', () => {
26
+ it('should return 201 and result if use case succeeds', async () => {
27
+ const mockResult = { id: 1, ...mockReq.body };
28
+ mockUseCase.execute.mockResolvedValue(mockResult);
29
+
30
+ await controller.create(mockReq, mockRes, mockNext);
31
+
32
+ expect(mockUseCase.execute).toHaveBeenCalledWith(mockReq.body);
33
+ expect(mockRes.status).toHaveBeenCalledWith(201);
34
+ expect(mockRes.json).toHaveBeenCalledWith({ success: true, data: mockResult });
35
+ });
36
+
37
+ it('should call next with error if use case fails', async () => {
38
+ const error = new Error('Test error');
39
+ mockUseCase.execute.mockRejectedValue(error);
40
+
41
+ await controller.create(mockReq, mockRes, mockNext);
42
+
43
+ expect(mockNext).toHaveBeenCalledWith(error);
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,16 @@
1
+ const { asClass, asFunction } = require('awilix');
2
+
3
+ const <%= className %>Controller = require('./interfaces/controllers/<%= name %>.controller');
4
+ const create<%= className %>Routes = require('./interfaces/routes/<%= name %>.routes');
5
+ const Create<%= className %>UseCase = require('./application/usecases/create-<%= name %>.usecase');
6
+ const <%= className %>Repository = require('./infrastructure/repositories/<%= name %>.repository.impl');
7
+
8
+ module.exports = function register<%= className %>Module(container) {
9
+ container.register({
10
+ <%= camelName %>Controller: asClass(<%= className %>Controller).singleton(),
11
+ <%= camelName %>Routes: asFunction(create<%= className %>Routes).singleton(),
12
+ <%= camelName %>UseCase: asClass(Create<%= className %>UseCase).singleton(),
13
+ // Providing the interface's name as the injection token for the implementation
14
+ <%= camelName %>Repository: asClass(<%= className %>Repository).singleton(),
15
+ });
16
+ };
@@ -0,0 +1,7 @@
1
+ const Joi = require('joi'); // Usually you'd use joi or another lib
2
+
3
+ const create<%= className %>Schema = Joi.object({
4
+ // Define DTO validation rules
5
+ });
6
+
7
+ module.exports = create<%= className %>Schema;
@@ -0,0 +1,8 @@
1
+ class <%= className %>Entity {
2
+ constructor({ id, ...props }) {
3
+ this.id = id;
4
+ Object.assign(this, props);
5
+ }
6
+ }
7
+
8
+ module.exports = <%= className %>Entity;
@@ -0,0 +1,23 @@
1
+ const I<%= className %>Repository = require('../../domain/repositories/<%= name %>.repository.interface');
2
+ const <%= className %>Entity = require('../../domain/entities/<%= name %>.entity');
3
+
4
+ class <%= className %>RepositoryImpl extends I<%= className %>Repository {
5
+ // If using Prisma, you would do: constructor({ prisma }) { super(); this.prisma = prisma; }
6
+ constructor() {
7
+ super();
8
+ this.db = new Map();
9
+ }
10
+
11
+ async save(dto) {
12
+ const id = Date.now().toString(); // Mock ID generation
13
+ const entity = new <%= className %>Entity({ id, ...dto });
14
+ this.db.set(id, entity);
15
+ return entity;
16
+ }
17
+
18
+ async findById(id) {
19
+ return this.db.get(id) || null;
20
+ }
21
+ }
22
+
23
+ module.exports = <%= className %>RepositoryImpl;
@@ -0,0 +1,11 @@
1
+ class I<%= className %>Repository {
2
+ async save(entity) {
3
+ throw new Error('Method not implemented.');
4
+ }
5
+
6
+ async findById(id) {
7
+ throw new Error('Method not implemented.');
8
+ }
9
+ }
10
+
11
+ module.exports = I<%= className %>Repository;
@@ -0,0 +1,9 @@
1
+ const { Router } = require('express');
2
+
3
+ module.exports = function({ <%= camelName %>Controller }) {
4
+ const router = Router();
5
+
6
+ router.post('/', <%= camelName %>Controller.create);
7
+
8
+ return router;
9
+ };
@@ -0,0 +1,15 @@
1
+ class <%= useCaseClassName %> {
2
+ constructor({ <%= camelName %>Repository }) {
3
+ this.<%= camelName %>Repository = <%= camelName %>Repository;
4
+ }
5
+
6
+ async execute(dto) {
7
+ // Validate DTO...
8
+ // Apply business logic...
9
+
10
+ const entity = await this.<%= camelName %>Repository.save(dto);
11
+ return entity;
12
+ }
13
+ }
14
+
15
+ module.exports = <%= useCaseClassName %>;
@@ -0,0 +1,26 @@
1
+ const <%= useCaseClassName %> = require('./<%= useCaseFileName %>.usecase');
2
+
3
+ describe('<%= useCaseClassName %>', () => {
4
+ let useCase;
5
+ let mockRepository;
6
+
7
+ beforeEach(() => {
8
+ mockRepository = {
9
+ save: jest.fn()
10
+ };
11
+ useCase = new <%= useCaseClassName %>({ <%= camelName %>Repository: mockRepository });
12
+ });
13
+
14
+ describe('execute', () => {
15
+ it('should save data using the repository and return the entity', async () => {
16
+ const mockDto = { name: 'Test' };
17
+ const mockEntity = { id: 1, ...mockDto };
18
+ mockRepository.save.mockResolvedValue(mockEntity);
19
+
20
+ const result = await useCase.execute(mockDto);
21
+
22
+ expect(mockRepository.save).toHaveBeenCalledWith(mockDto);
23
+ expect(result).toEqual(mockEntity);
24
+ });
25
+ });
26
+ });
@@ -0,0 +1,10 @@
1
+ exports.pascalCase = (str) => {
2
+ const words = str.match(/[a-z0-9]+/gi) || [];
3
+ return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
4
+ };
5
+
6
+ exports.camelCase = (str) => {
7
+ const words = str.match(/[a-z0-9]+/gi) || [];
8
+ if (!words.length) return '';
9
+ return words[0].toLowerCase() + words.slice(1).map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
10
+ };
File without changes
File without changes