nodejs-quickstart-structure 1.9.2 → 1.10.0
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 +23 -0
- package/README.md +7 -7
- package/bin/index.js +2 -2
- package/docs/generateCase.md +160 -164
- package/lib/generator.js +1 -1
- package/lib/modules/app-setup.js +84 -10
- package/lib/prompts.js +1 -1
- package/package.json +4 -2
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +25 -11
- package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.js +4 -21
- package/templates/clean-architecture/js/src/interfaces/controllers/{userController.js → userController.js.ejs} +23 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/index.js.ejs +5 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/index.js.ejs +6 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +27 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/index.js.ejs +6 -0
- package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +17 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.js +0 -67
- package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +4 -21
- package/templates/clean-architecture/ts/src/index.ts.ejs +51 -20
- package/templates/clean-architecture/ts/src/interfaces/controllers/{userController.ts → userController.ts.ejs} +28 -1
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +17 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/index.ts.ejs +3 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/index.ts.ejs +4 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +27 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/index.ts.ejs +4 -0
- package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +15 -0
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +0 -66
- package/templates/common/Dockerfile +2 -2
- package/templates/common/README.md.ejs +27 -0
- package/templates/common/database/js/mongoose.js.ejs +0 -1
- package/templates/common/database/ts/mongoose.ts.ejs +0 -1
- package/templates/common/package.json.ejs +7 -4
- package/templates/common/swagger.yml.ejs +66 -0
- package/templates/mvc/js/src/config/swagger.js +4 -21
- package/templates/mvc/js/src/controllers/userController.js.ejs +55 -0
- package/templates/mvc/js/src/graphql/context.js.ejs +7 -0
- package/templates/mvc/js/src/graphql/index.js.ejs +5 -0
- package/templates/mvc/js/src/graphql/resolvers/index.js.ejs +6 -0
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +25 -0
- package/templates/mvc/js/src/graphql/typeDefs/index.js.ejs +6 -0
- package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +17 -0
- package/templates/mvc/js/src/index.js.ejs +38 -26
- package/templates/mvc/js/src/routes/api.js +0 -66
- package/templates/mvc/ts/src/config/swagger.ts.ejs +4 -21
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +56 -1
- package/templates/mvc/ts/src/graphql/context.ts.ejs +12 -0
- package/templates/mvc/ts/src/graphql/index.ts.ejs +3 -0
- package/templates/mvc/ts/src/graphql/resolvers/index.ts.ejs +4 -0
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +27 -0
- package/templates/mvc/ts/src/graphql/typeDefs/index.ts.ejs +4 -0
- package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +15 -0
- package/templates/mvc/ts/src/index.ts.ejs +54 -26
- package/templates/mvc/ts/src/routes/api.ts +0 -66
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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.10.0] - 2026-02-27
|
|
9
|
+
### Added
|
|
10
|
+
- **GraphQL Support:** The generator now supports scaffolding GraphQL APIs using Apollo Server (v4) alongside standard REST APIs. This feature includes built-in integrations for both MVC and Clean Architecture designs across TypeScript and JavaScript.
|
|
11
|
+
- Generates strongly-typed schemas (`typeDefs`) and structured `resolvers`.
|
|
12
|
+
- Automatically provisions Apollo Sandbox for local development.
|
|
13
|
+
- Integrates smoothly with existing databases (MySQL, PostgreSQL, MongoDB) and caching (Redis, Memory Cache) layouts.
|
|
14
|
+
- Automatically configures Content Security Policy (Helmet) to allow embeddable Apollo Sandbox UI out of the box.
|
|
15
|
+
|
|
16
|
+
## [1.9.4] - 2026-02-26
|
|
17
|
+
### Fixed
|
|
18
|
+
- Fixed intermittent "Health check timeout" errors in concurrent (concurrency > 1) E2E validation scripts. Increased the total verification timeout window to 120s to accommodate heavy Docker image cluster instantiations (Kafka/MySQL/Redis), and enhanced the Node.js `fetch` client to use `127.0.0.1` IPv4 loopback resolution alongside strict 5-second `AbortSignal` timeouts to mitigate dangling TCP connections resulting from overloaded internal Docker proxy bridges.
|
|
19
|
+
- Fixed an `EBADENGINE` compatibility issue during Docker builds by upgrading the default template `Dockerfile` base image from `node:18-alpine` to `node:22-alpine`, supporting modern dependencies like `eslint@9` and `cpx2`.
|
|
20
|
+
- Fixed a port collision bug (`Bind for 0.0.0.0:6379 failed: port is already allocated`) during parallel E2E testing. The `validation-core.js` script now dynamically assigns random network ports for `REDIS_PORT` when running concurrent testing matrices.
|
|
21
|
+
|
|
22
|
+
## [1.9.3] - 2026-02-26
|
|
23
|
+
### Added
|
|
24
|
+
- Refactored Swagger documentation to use a standalone `swagger.yml` file instead of inline JSDoc comments across all templates (MVC, Clean Architecture, TS, and JS).
|
|
25
|
+
- Integrated `yamljs` to parse the `swagger.yml` file and removed the `swagger-jsdoc` dependency, significantly reducing boilerplate in route files.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- Fixed an issue in `package.json.ejs` where `jest` configuration had malformed JSON due to whitespace stripping.
|
|
29
|
+
- Fixed a Docker build crash for non-REST API projects caused by the build script unconditionally attempting to copy `swagger.yml` using `cpx2`.
|
|
30
|
+
|
|
8
31
|
## [1.9.2] - 2026-02-23
|
|
9
32
|
### Fixed
|
|
10
33
|
- Fixed an issue where the generator output misleading instructions (`DATABASE_URL`) for standalone `docker run` executions. The CLI success commands and `README.md` now conditionally include dynamic compose network bindings (`--network`) and accurate environment variables matching the user's selected DB stack.
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ A powerful CLI tool to scaffold production-ready Node.js microservices with buil
|
|
|
12
12
|
- **Multiple Architectures**: Supports both **MVC** (Model-View-Controller) and **Clean Architecture**.
|
|
13
13
|
- **Language Support**: Choose between **JavaScript** and **TypeScript**.
|
|
14
14
|
- **Database Integration**: Pre-configured setup for **MySQL**, **PostgreSQL**, or **MongoDB**.
|
|
15
|
-
- **
|
|
15
|
+
- **Communication Flow**: Scaffold APIs using **REST**, **GraphQL** (with Apollo Server), or **Kafka** (event-driven).
|
|
16
16
|
- **Caching Layer**: Choose between **Redis** or built-in **Memory Cache** for data caching.
|
|
17
17
|
- **Dockerized**: Automatically generates `docker-compose.yml` for DB, Kafka, Redis, and Zookeeper.
|
|
18
18
|
- **Database Migrations/Schemas**: Integrated **Flyway** for SQL migrations or **Mongoose** schemas for MongoDB.
|
|
@@ -29,14 +29,14 @@ We don't just generate boilerplate; we generate **production-ready** foundations
|
|
|
29
29
|
- **⚓ Git Hooks**: `Husky` and `Lint-Staged` to ensure no bad code is ever committed.
|
|
30
30
|
- **🐳 DevOps**: Highly optimized **Multi-Stage Dockerfile** for small, secure production images.
|
|
31
31
|
|
|
32
|
-
## 🧩
|
|
32
|
+
## 🧩 240+ Project Combinations
|
|
33
33
|
|
|
34
34
|
The CLI supports a massive number of configurations to fit your exact needs:
|
|
35
35
|
|
|
36
|
-
- **
|
|
37
|
-
- **MVC Architecture**:
|
|
38
|
-
- **Clean Architecture**:
|
|
39
|
-
- **
|
|
36
|
+
- **240 Core Combinations**:
|
|
37
|
+
- **MVC Architecture**: 180 variants (Languages × View Engines × Databases × Communication Patterns × Caching)
|
|
38
|
+
- **Clean Architecture**: 60 variants (Languages × Databases × Communication Patterns × Caching)
|
|
39
|
+
- **480 Total Scenarios**:
|
|
40
40
|
- Every combination can be generated with or without **GitHub Actions CI/CD**, doubling the possibilities.
|
|
41
41
|
|
|
42
42
|
For a detailed list of all supported cases, check out [docs/generateCase.md](docs/generateCase.md).
|
|
@@ -66,7 +66,7 @@ The CLI will guide you through the following steps:
|
|
|
66
66
|
3. **Architecture**: `MVC` or `Clean Architecture`.
|
|
67
67
|
4. **Database**: `MySQL`, `PostgreSQL`, or `MongoDB`.
|
|
68
68
|
5. **Database Name**: The name of the initial database.
|
|
69
|
-
6. **Communication**: `REST APIs` (default) or `Kafka`.
|
|
69
|
+
6. **Communication**: `REST APIs` (default), `GraphQL`, or `Kafka`.
|
|
70
70
|
7. **Caching**: `None`, `Redis`, or `Memory Cache`.
|
|
71
71
|
8. **CI/CD**: `GitHub Actions`, `Jenkins`, `GitLab CI` or `None`.
|
|
72
72
|
|
package/bin/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import chalk from 'chalk';
|
|
@@ -31,7 +31,7 @@ program
|
|
|
31
31
|
.option('--view-engine <view>', 'View Engine (None, EJS, Pug) - MVC only')
|
|
32
32
|
.option('-d, --database <database>', 'Database (MySQL, PostgreSQL)')
|
|
33
33
|
.option('--db-name <name>', 'Database name')
|
|
34
|
-
.option('-c, --communication <communication>', 'Communication (REST APIs, Kafka)')
|
|
34
|
+
.option('-c, --communication <communication>', 'Communication (REST APIs, GraphQL, Kafka)')
|
|
35
35
|
.option('--ci-provider <provider>', 'CI/CD Provider (None, GitHub Actions, Jenkins)')
|
|
36
36
|
.option('--caching <type>', 'Caching Layer (None/Redis)')
|
|
37
37
|
.action(async (options) => {
|
package/docs/generateCase.md
CHANGED
|
@@ -5,11 +5,7 @@ This document lists the **160 possible project combinations** supported by the `
|
|
|
5
5
|
## Summary
|
|
6
6
|
- **CI Providers**: `None`, `GitHub Actions`, `Jenkins`, `GitLab CI`
|
|
7
7
|
- **MVC Architecture**: 120 Combinations
|
|
8
|
-
- **With Database (108)**: 2 Lang × 3 View × 3 DB × 2 Comm = 36 * 3 (Caching: None/Redis/Memory Cache) = 108
|
|
9
|
-
- **No Database (12)**: 2 Lang × 3 View × 1 DB × 2 Comm = 12 * 1 (Caching: None) = 12
|
|
10
8
|
- **Clean Architecture**: 40 Combinations
|
|
11
|
-
- **With Database (36)**: 2 Lang × 1 View (None) × 3 DB × 2 Comm = 12 * 3 (Caching: None/Redis/Memory Cache) = 36
|
|
12
|
-
- **No Database (4)**: 2 Lang × 1 View (None) × 1 DB × 2 Comm = 4 * 1 (Caching: None) = 4
|
|
13
9
|
|
|
14
10
|
**Total Core Combinations: 160**
|
|
15
11
|
|
|
@@ -21,169 +17,169 @@ This document lists the **160 possible project combinations** supported by the `
|
|
|
21
17
|
|
|
22
18
|
| # | Language | Architecture | View Engine | Database | Communication | Caching |
|
|
23
19
|
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
|
24
|
-
| 1 |
|
|
25
|
-
| 2 |
|
|
26
|
-
| 3 |
|
|
27
|
-
| 4 |
|
|
28
|
-
| 5 |
|
|
29
|
-
| 6 |
|
|
30
|
-
| 7 |
|
|
31
|
-
| 8 |
|
|
32
|
-
| 9 |
|
|
33
|
-
| 10 |
|
|
34
|
-
| 11 |
|
|
35
|
-
| 12 |
|
|
36
|
-
| 13 |
|
|
37
|
-
| 14 |
|
|
38
|
-
| 15 |
|
|
39
|
-
| 16 |
|
|
40
|
-
| 17 |
|
|
41
|
-
| 18 |
|
|
42
|
-
| 19 |
|
|
43
|
-
| 20 |
|
|
44
|
-
| 21 |
|
|
45
|
-
| 22 |
|
|
46
|
-
| 23 |
|
|
47
|
-
| 24 |
|
|
48
|
-
| 25 |
|
|
49
|
-
| 26 |
|
|
50
|
-
| 27 |
|
|
51
|
-
| 28 |
|
|
52
|
-
| 29 |
|
|
53
|
-
| 30 |
|
|
54
|
-
| 31 |
|
|
55
|
-
| 32 |
|
|
56
|
-
| 33 |
|
|
57
|
-
| 34 |
|
|
58
|
-
| 35 |
|
|
59
|
-
| 36 |
|
|
60
|
-
| 37 |
|
|
61
|
-
| 38 |
|
|
62
|
-
| 39 |
|
|
63
|
-
| 40 |
|
|
64
|
-
| 41 |
|
|
65
|
-
| 42 |
|
|
66
|
-
| 43 |
|
|
67
|
-
| 44 |
|
|
68
|
-
| 45 |
|
|
69
|
-
| 46 |
|
|
70
|
-
| 47 |
|
|
71
|
-
| 48 |
|
|
72
|
-
| 49 |
|
|
73
|
-
| 50 |
|
|
74
|
-
| 51 |
|
|
75
|
-
| 52 |
|
|
76
|
-
| 53 |
|
|
77
|
-
| 54 |
|
|
78
|
-
| 55 |
|
|
79
|
-
| 56 |
|
|
80
|
-
| 57 |
|
|
81
|
-
| 58 |
|
|
82
|
-
| 59 |
|
|
83
|
-
| 60 |
|
|
84
|
-
| 61 |
|
|
85
|
-
| 62 |
|
|
86
|
-
| 63 |
|
|
87
|
-
| 64 |
|
|
88
|
-
| 65 |
|
|
89
|
-
| 66 |
|
|
90
|
-
| 67 |
|
|
91
|
-
| 68 |
|
|
92
|
-
| 69 |
|
|
93
|
-
| 70 |
|
|
94
|
-
| 71 |
|
|
95
|
-
| 72 |
|
|
96
|
-
| 73 |
|
|
97
|
-
| 74 |
|
|
98
|
-
| 75 |
|
|
99
|
-
| 76 |
|
|
100
|
-
| 77 |
|
|
101
|
-
| 78 |
|
|
102
|
-
| 79 |
|
|
103
|
-
| 80 |
|
|
104
|
-
| 81 |
|
|
105
|
-
| 82 |
|
|
106
|
-
| 83 |
|
|
107
|
-
| 84 |
|
|
108
|
-
| 85 |
|
|
109
|
-
| 86 |
|
|
110
|
-
| 87 |
|
|
111
|
-
| 88 |
|
|
112
|
-
| 89 |
|
|
113
|
-
| 90 |
|
|
114
|
-
| 91 |
|
|
115
|
-
| 92 |
|
|
116
|
-
| 93 |
|
|
117
|
-
| 94 |
|
|
118
|
-
| 95 |
|
|
119
|
-
| 96 |
|
|
120
|
-
| 97 |
|
|
121
|
-
| 98 |
|
|
122
|
-
| 99 |
|
|
123
|
-
| 100 |
|
|
124
|
-
| 101 |
|
|
125
|
-
| 102 |
|
|
126
|
-
| 103 |
|
|
127
|
-
| 104 |
|
|
128
|
-
| 105 |
|
|
129
|
-
| 106 |
|
|
130
|
-
| 107 |
|
|
131
|
-
| 108 |
|
|
132
|
-
| 109 |
|
|
133
|
-
| 110 |
|
|
134
|
-
| 111 |
|
|
135
|
-
| 112 |
|
|
136
|
-
| 113 |
|
|
137
|
-
| 114 |
|
|
138
|
-
| 115 |
|
|
139
|
-
| 116 |
|
|
140
|
-
| 117 |
|
|
141
|
-
| 118 |
|
|
142
|
-
| 119 |
|
|
143
|
-
| 120 |
|
|
20
|
+
| 1 | TypeScript | MVC | EJS | None | REST APIs | None |
|
|
21
|
+
| 2 | TypeScript | MVC | EJS | None | Kafka | None |
|
|
22
|
+
| 3 | TypeScript | MVC | EJS | MySQL | REST APIs | None |
|
|
23
|
+
| 4 | TypeScript | MVC | EJS | MySQL | REST APIs | Redis |
|
|
24
|
+
| 5 | TypeScript | MVC | EJS | MySQL | REST APIs | Memory Cache |
|
|
25
|
+
| 6 | TypeScript | MVC | EJS | MySQL | Kafka | None |
|
|
26
|
+
| 7 | TypeScript | MVC | EJS | MySQL | Kafka | Redis |
|
|
27
|
+
| 8 | TypeScript | MVC | EJS | MySQL | Kafka | Memory Cache |
|
|
28
|
+
| 9 | TypeScript | MVC | EJS | PostgreSQL | REST APIs | None |
|
|
29
|
+
| 10 | TypeScript | MVC | EJS | PostgreSQL | REST APIs | Redis |
|
|
30
|
+
| 11 | TypeScript | MVC | EJS | PostgreSQL | REST APIs | Memory Cache |
|
|
31
|
+
| 12 | TypeScript | MVC | EJS | PostgreSQL | Kafka | None |
|
|
32
|
+
| 13 | TypeScript | MVC | EJS | PostgreSQL | Kafka | Redis |
|
|
33
|
+
| 14 | TypeScript | MVC | EJS | PostgreSQL | Kafka | Memory Cache |
|
|
34
|
+
| 15 | TypeScript | MVC | EJS | MongoDB | REST APIs | None |
|
|
35
|
+
| 16 | TypeScript | MVC | EJS | MongoDB | REST APIs | Redis |
|
|
36
|
+
| 17 | TypeScript | MVC | EJS | MongoDB | REST APIs | Memory Cache |
|
|
37
|
+
| 18 | TypeScript | MVC | EJS | MongoDB | Kafka | None |
|
|
38
|
+
| 19 | TypeScript | MVC | EJS | MongoDB | Kafka | Redis |
|
|
39
|
+
| 20 | TypeScript | MVC | EJS | MongoDB | Kafka | Memory Cache |
|
|
40
|
+
| 21 | TypeScript | MVC | Pug | None | REST APIs | None |
|
|
41
|
+
| 22 | TypeScript | MVC | Pug | None | Kafka | None |
|
|
42
|
+
| 23 | TypeScript | MVC | Pug | MySQL | REST APIs | None |
|
|
43
|
+
| 24 | TypeScript | MVC | Pug | MySQL | REST APIs | Redis |
|
|
44
|
+
| 25 | TypeScript | MVC | Pug | MySQL | REST APIs | Memory Cache |
|
|
45
|
+
| 26 | TypeScript | MVC | Pug | MySQL | Kafka | None |
|
|
46
|
+
| 27 | TypeScript | MVC | Pug | MySQL | Kafka | Redis |
|
|
47
|
+
| 28 | TypeScript | MVC | Pug | MySQL | Kafka | Memory Cache |
|
|
48
|
+
| 29 | TypeScript | MVC | Pug | PostgreSQL | REST APIs | None |
|
|
49
|
+
| 30 | TypeScript | MVC | Pug | PostgreSQL | REST APIs | Redis |
|
|
50
|
+
| 31 | TypeScript | MVC | Pug | PostgreSQL | REST APIs | Memory Cache |
|
|
51
|
+
| 32 | TypeScript | MVC | Pug | PostgreSQL | Kafka | None |
|
|
52
|
+
| 33 | TypeScript | MVC | Pug | PostgreSQL | Kafka | Redis |
|
|
53
|
+
| 34 | TypeScript | MVC | Pug | PostgreSQL | Kafka | Memory Cache |
|
|
54
|
+
| 35 | TypeScript | MVC | Pug | MongoDB | REST APIs | None |
|
|
55
|
+
| 36 | TypeScript | MVC | Pug | MongoDB | REST APIs | Redis |
|
|
56
|
+
| 37 | TypeScript | MVC | Pug | MongoDB | REST APIs | Memory Cache |
|
|
57
|
+
| 38 | TypeScript | MVC | Pug | MongoDB | Kafka | None |
|
|
58
|
+
| 39 | TypeScript | MVC | Pug | MongoDB | Kafka | Redis |
|
|
59
|
+
| 40 | TypeScript | MVC | Pug | MongoDB | Kafka | Memory Cache |
|
|
60
|
+
| 41 | TypeScript | MVC | None | None | REST APIs | None |
|
|
61
|
+
| 42 | TypeScript | MVC | None | None | Kafka | None |
|
|
62
|
+
| 43 | TypeScript | MVC | None | MySQL | REST APIs | None |
|
|
63
|
+
| 44 | TypeScript | MVC | None | MySQL | REST APIs | Redis |
|
|
64
|
+
| 45 | TypeScript | MVC | None | MySQL | REST APIs | Memory Cache |
|
|
65
|
+
| 46 | TypeScript | MVC | None | MySQL | Kafka | None |
|
|
66
|
+
| 47 | TypeScript | MVC | None | MySQL | Kafka | Redis |
|
|
67
|
+
| 48 | TypeScript | MVC | None | MySQL | Kafka | Memory Cache |
|
|
68
|
+
| 49 | TypeScript | MVC | None | PostgreSQL | REST APIs | None |
|
|
69
|
+
| 50 | TypeScript | MVC | None | PostgreSQL | REST APIs | Redis |
|
|
70
|
+
| 51 | TypeScript | MVC | None | PostgreSQL | REST APIs | Memory Cache |
|
|
71
|
+
| 52 | TypeScript | MVC | None | PostgreSQL | Kafka | None |
|
|
72
|
+
| 53 | TypeScript | MVC | None | PostgreSQL | Kafka | Redis |
|
|
73
|
+
| 54 | TypeScript | MVC | None | PostgreSQL | Kafka | Memory Cache |
|
|
74
|
+
| 55 | TypeScript | MVC | None | MongoDB | REST APIs | None |
|
|
75
|
+
| 56 | TypeScript | MVC | None | MongoDB | REST APIs | Redis |
|
|
76
|
+
| 57 | TypeScript | MVC | None | MongoDB | REST APIs | Memory Cache |
|
|
77
|
+
| 58 | TypeScript | MVC | None | MongoDB | Kafka | None |
|
|
78
|
+
| 59 | TypeScript | MVC | None | MongoDB | Kafka | Redis |
|
|
79
|
+
| 60 | TypeScript | MVC | None | MongoDB | Kafka | Memory Cache |
|
|
80
|
+
| 61 | JavaScript | MVC | EJS | None | REST APIs | None |
|
|
81
|
+
| 62 | JavaScript | MVC | EJS | None | Kafka | None |
|
|
82
|
+
| 63 | JavaScript | MVC | EJS | MySQL | REST APIs | None |
|
|
83
|
+
| 64 | JavaScript | MVC | EJS | MySQL | REST APIs | Redis |
|
|
84
|
+
| 65 | JavaScript | MVC | EJS | MySQL | REST APIs | Memory Cache |
|
|
85
|
+
| 66 | JavaScript | MVC | EJS | MySQL | Kafka | None |
|
|
86
|
+
| 67 | JavaScript | MVC | EJS | MySQL | Kafka | Redis |
|
|
87
|
+
| 68 | JavaScript | MVC | EJS | MySQL | Kafka | Memory Cache |
|
|
88
|
+
| 69 | JavaScript | MVC | EJS | PostgreSQL | REST APIs | None |
|
|
89
|
+
| 70 | JavaScript | MVC | EJS | PostgreSQL | REST APIs | Redis |
|
|
90
|
+
| 71 | JavaScript | MVC | EJS | PostgreSQL | REST APIs | Memory Cache |
|
|
91
|
+
| 72 | JavaScript | MVC | EJS | PostgreSQL | Kafka | None |
|
|
92
|
+
| 73 | JavaScript | MVC | EJS | PostgreSQL | Kafka | Redis |
|
|
93
|
+
| 74 | JavaScript | MVC | EJS | PostgreSQL | Kafka | Memory Cache |
|
|
94
|
+
| 75 | JavaScript | MVC | EJS | MongoDB | REST APIs | None |
|
|
95
|
+
| 76 | JavaScript | MVC | EJS | MongoDB | REST APIs | Redis |
|
|
96
|
+
| 77 | JavaScript | MVC | EJS | MongoDB | REST APIs | Memory Cache |
|
|
97
|
+
| 78 | JavaScript | MVC | EJS | MongoDB | Kafka | None |
|
|
98
|
+
| 79 | JavaScript | MVC | EJS | MongoDB | Kafka | Redis |
|
|
99
|
+
| 80 | JavaScript | MVC | EJS | MongoDB | Kafka | Memory Cache |
|
|
100
|
+
| 81 | JavaScript | MVC | Pug | None | REST APIs | None |
|
|
101
|
+
| 82 | JavaScript | MVC | Pug | None | Kafka | None |
|
|
102
|
+
| 83 | JavaScript | MVC | Pug | MySQL | REST APIs | None |
|
|
103
|
+
| 84 | JavaScript | MVC | Pug | MySQL | REST APIs | Redis |
|
|
104
|
+
| 85 | JavaScript | MVC | Pug | MySQL | REST APIs | Memory Cache |
|
|
105
|
+
| 86 | JavaScript | MVC | Pug | MySQL | Kafka | None |
|
|
106
|
+
| 87 | JavaScript | MVC | Pug | MySQL | Kafka | Redis |
|
|
107
|
+
| 88 | JavaScript | MVC | Pug | MySQL | Kafka | Memory Cache |
|
|
108
|
+
| 89 | JavaScript | MVC | Pug | PostgreSQL | REST APIs | None |
|
|
109
|
+
| 90 | JavaScript | MVC | Pug | PostgreSQL | REST APIs | Redis |
|
|
110
|
+
| 91 | JavaScript | MVC | Pug | PostgreSQL | REST APIs | Memory Cache |
|
|
111
|
+
| 92 | JavaScript | MVC | Pug | PostgreSQL | Kafka | None |
|
|
112
|
+
| 93 | JavaScript | MVC | Pug | PostgreSQL | Kafka | Redis |
|
|
113
|
+
| 94 | JavaScript | MVC | Pug | PostgreSQL | Kafka | Memory Cache |
|
|
114
|
+
| 95 | JavaScript | MVC | Pug | MongoDB | REST APIs | None |
|
|
115
|
+
| 96 | JavaScript | MVC | Pug | MongoDB | REST APIs | Redis |
|
|
116
|
+
| 97 | JavaScript | MVC | Pug | MongoDB | REST APIs | Memory Cache |
|
|
117
|
+
| 98 | JavaScript | MVC | Pug | MongoDB | Kafka | None |
|
|
118
|
+
| 99 | JavaScript | MVC | Pug | MongoDB | Kafka | Redis |
|
|
119
|
+
| 100 | JavaScript | MVC | Pug | MongoDB | Kafka | Memory Cache |
|
|
120
|
+
| 101 | JavaScript | MVC | None | None | REST APIs | None |
|
|
121
|
+
| 102 | JavaScript | MVC | None | None | Kafka | None |
|
|
122
|
+
| 103 | JavaScript | MVC | None | MySQL | REST APIs | None |
|
|
123
|
+
| 104 | JavaScript | MVC | None | MySQL | REST APIs | Redis |
|
|
124
|
+
| 105 | JavaScript | MVC | None | MySQL | REST APIs | Memory Cache |
|
|
125
|
+
| 106 | JavaScript | MVC | None | MySQL | Kafka | None |
|
|
126
|
+
| 107 | JavaScript | MVC | None | MySQL | Kafka | Redis |
|
|
127
|
+
| 108 | JavaScript | MVC | None | MySQL | Kafka | Memory Cache |
|
|
128
|
+
| 109 | JavaScript | MVC | None | PostgreSQL | REST APIs | None |
|
|
129
|
+
| 110 | JavaScript | MVC | None | PostgreSQL | REST APIs | Redis |
|
|
130
|
+
| 111 | JavaScript | MVC | None | PostgreSQL | REST APIs | Memory Cache |
|
|
131
|
+
| 112 | JavaScript | MVC | None | PostgreSQL | Kafka | None |
|
|
132
|
+
| 113 | JavaScript | MVC | None | PostgreSQL | Kafka | Redis |
|
|
133
|
+
| 114 | JavaScript | MVC | None | PostgreSQL | Kafka | Memory Cache |
|
|
134
|
+
| 115 | JavaScript | MVC | None | MongoDB | REST APIs | None |
|
|
135
|
+
| 116 | JavaScript | MVC | None | MongoDB | REST APIs | Redis |
|
|
136
|
+
| 117 | JavaScript | MVC | None | MongoDB | REST APIs | Memory Cache |
|
|
137
|
+
| 118 | JavaScript | MVC | None | MongoDB | Kafka | None |
|
|
138
|
+
| 119 | JavaScript | MVC | None | MongoDB | Kafka | Redis |
|
|
139
|
+
| 120 | JavaScript | MVC | None | MongoDB | Kafka | Memory Cache |
|
|
144
140
|
|
|
145
141
|
## 2. Clean Architecture (40 Cases)
|
|
146
142
|
*Note: Clean Architecture does not use server-side view engines (EJS/Pug).*
|
|
147
143
|
|
|
148
144
|
| # | Language | Architecture | View Engine | Database | Communication | Caching |
|
|
149
145
|
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
|
150
|
-
| 121 |
|
|
151
|
-
| 122 |
|
|
152
|
-
| 123 |
|
|
153
|
-
| 124 |
|
|
154
|
-
| 125 |
|
|
155
|
-
| 126 |
|
|
156
|
-
| 127 |
|
|
157
|
-
| 128 |
|
|
158
|
-
| 129 |
|
|
159
|
-
| 130 |
|
|
160
|
-
| 131 |
|
|
161
|
-
| 132 |
|
|
162
|
-
| 133 |
|
|
163
|
-
| 134 |
|
|
164
|
-
| 135 |
|
|
165
|
-
| 136 |
|
|
166
|
-
| 137 |
|
|
167
|
-
| 138 |
|
|
168
|
-
| 139 |
|
|
169
|
-
| 140 |
|
|
170
|
-
| 141 |
|
|
171
|
-
| 142 |
|
|
172
|
-
| 143 |
|
|
173
|
-
| 144 |
|
|
174
|
-
| 145 |
|
|
175
|
-
| 146 |
|
|
176
|
-
| 147 |
|
|
177
|
-
| 148 |
|
|
178
|
-
| 149 |
|
|
179
|
-
| 150 |
|
|
180
|
-
| 151 |
|
|
181
|
-
| 152 |
|
|
182
|
-
| 153 |
|
|
183
|
-
| 154 |
|
|
184
|
-
| 155 |
|
|
185
|
-
| 156 |
|
|
186
|
-
| 157 |
|
|
187
|
-
| 158 |
|
|
188
|
-
| 159 |
|
|
189
|
-
| 160 |
|
|
146
|
+
| 121 | TypeScript | Clean Architecture | N/A | None | REST APIs | None |
|
|
147
|
+
| 122 | TypeScript | Clean Architecture | N/A | None | Kafka | None |
|
|
148
|
+
| 123 | TypeScript | Clean Architecture | N/A | MySQL | REST APIs | None |
|
|
149
|
+
| 124 | TypeScript | Clean Architecture | N/A | MySQL | REST APIs | Redis |
|
|
150
|
+
| 125 | TypeScript | Clean Architecture | N/A | MySQL | REST APIs | Memory Cache |
|
|
151
|
+
| 126 | TypeScript | Clean Architecture | N/A | MySQL | Kafka | None |
|
|
152
|
+
| 127 | TypeScript | Clean Architecture | N/A | MySQL | Kafka | Redis |
|
|
153
|
+
| 128 | TypeScript | Clean Architecture | N/A | MySQL | Kafka | Memory Cache |
|
|
154
|
+
| 129 | TypeScript | Clean Architecture | N/A | PostgreSQL | REST APIs | None |
|
|
155
|
+
| 130 | TypeScript | Clean Architecture | N/A | PostgreSQL | REST APIs | Redis |
|
|
156
|
+
| 131 | TypeScript | Clean Architecture | N/A | PostgreSQL | REST APIs | Memory Cache |
|
|
157
|
+
| 132 | TypeScript | Clean Architecture | N/A | PostgreSQL | Kafka | None |
|
|
158
|
+
| 133 | TypeScript | Clean Architecture | N/A | PostgreSQL | Kafka | Redis |
|
|
159
|
+
| 134 | TypeScript | Clean Architecture | N/A | PostgreSQL | Kafka | Memory Cache |
|
|
160
|
+
| 135 | TypeScript | Clean Architecture | N/A | MongoDB | REST APIs | None |
|
|
161
|
+
| 136 | TypeScript | Clean Architecture | N/A | MongoDB | REST APIs | Redis |
|
|
162
|
+
| 137 | TypeScript | Clean Architecture | N/A | MongoDB | REST APIs | Memory Cache |
|
|
163
|
+
| 138 | TypeScript | Clean Architecture | N/A | MongoDB | Kafka | None |
|
|
164
|
+
| 139 | TypeScript | Clean Architecture | N/A | MongoDB | Kafka | Redis |
|
|
165
|
+
| 140 | TypeScript | Clean Architecture | N/A | MongoDB | Kafka | Memory Cache |
|
|
166
|
+
| 141 | JavaScript | Clean Architecture | N/A | None | REST APIs | None |
|
|
167
|
+
| 142 | JavaScript | Clean Architecture | N/A | None | Kafka | None |
|
|
168
|
+
| 143 | JavaScript | Clean Architecture | N/A | MySQL | REST APIs | None |
|
|
169
|
+
| 144 | JavaScript | Clean Architecture | N/A | MySQL | REST APIs | Redis |
|
|
170
|
+
| 145 | JavaScript | Clean Architecture | N/A | MySQL | REST APIs | Memory Cache |
|
|
171
|
+
| 146 | JavaScript | Clean Architecture | N/A | MySQL | Kafka | None |
|
|
172
|
+
| 147 | JavaScript | Clean Architecture | N/A | MySQL | Kafka | Redis |
|
|
173
|
+
| 148 | JavaScript | Clean Architecture | N/A | MySQL | Kafka | Memory Cache |
|
|
174
|
+
| 149 | JavaScript | Clean Architecture | N/A | PostgreSQL | REST APIs | None |
|
|
175
|
+
| 150 | JavaScript | Clean Architecture | N/A | PostgreSQL | REST APIs | Redis |
|
|
176
|
+
| 151 | JavaScript | Clean Architecture | N/A | PostgreSQL | REST APIs | Memory Cache |
|
|
177
|
+
| 152 | JavaScript | Clean Architecture | N/A | PostgreSQL | Kafka | None |
|
|
178
|
+
| 153 | JavaScript | Clean Architecture | N/A | PostgreSQL | Kafka | Redis |
|
|
179
|
+
| 154 | JavaScript | Clean Architecture | N/A | PostgreSQL | Kafka | Memory Cache |
|
|
180
|
+
| 155 | JavaScript | Clean Architecture | N/A | MongoDB | REST APIs | None |
|
|
181
|
+
| 156 | JavaScript | Clean Architecture | N/A | MongoDB | REST APIs | Redis |
|
|
182
|
+
| 157 | JavaScript | Clean Architecture | N/A | MongoDB | REST APIs | Memory Cache |
|
|
183
|
+
| 158 | JavaScript | Clean Architecture | N/A | MongoDB | Kafka | None |
|
|
184
|
+
| 159 | JavaScript | Clean Architecture | N/A | MongoDB | Kafka | Redis |
|
|
185
|
+
| 160 | JavaScript | Clean Architecture | N/A | MongoDB | Kafka | Memory Cache |
|
package/lib/generator.js
CHANGED
|
@@ -58,7 +58,7 @@ export const generateProject = async (config) => {
|
|
|
58
58
|
await setupSrcViews(templatesDir, targetDir, config);
|
|
59
59
|
|
|
60
60
|
// 12. Swagger Config
|
|
61
|
-
await renderSwaggerConfig(targetDir, config);
|
|
61
|
+
await renderSwaggerConfig(templatesDir, targetDir, config);
|
|
62
62
|
|
|
63
63
|
// 13. Professional Config & Tests
|
|
64
64
|
await renderProfessionalConfig(templatesDir, targetDir, language);
|
package/lib/modules/app-setup.js
CHANGED
|
@@ -32,7 +32,7 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
32
32
|
const userControllerTemplate = path.join(templatePath, 'src/controllers', `${userControllerName}.ejs`);
|
|
33
33
|
|
|
34
34
|
if (await fs.pathExists(userControllerTemplate)) {
|
|
35
|
-
const content = ejs.render(await fs.readFile(userControllerTemplate, 'utf-8'), { database, caching });
|
|
35
|
+
const content = ejs.render(await fs.readFile(userControllerTemplate, 'utf-8'), { database, caching, communication: config.communication });
|
|
36
36
|
await fs.writeFile(userControllerPath, content);
|
|
37
37
|
await fs.remove(path.join(targetDir, 'src/controllers', `${userControllerName}.ejs`));
|
|
38
38
|
}
|
|
@@ -48,6 +48,16 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
48
48
|
await fs.writeFile(repoPath, content);
|
|
49
49
|
await fs.remove(path.join(targetDir, 'src/infrastructure/repositories', `${repoName}.ejs`));
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
const controllerName = language === 'TypeScript' ? 'userController.ts' : 'userController.js';
|
|
53
|
+
const controllerPath = path.join(targetDir, 'src/interfaces/controllers', controllerName);
|
|
54
|
+
const controllerTemplate = path.join(templatePath, 'src/interfaces/controllers', `${controllerName}.ejs`);
|
|
55
|
+
|
|
56
|
+
if (await fs.pathExists(controllerTemplate)) {
|
|
57
|
+
const content = ejs.render(await fs.readFile(controllerTemplate, 'utf-8'), { communication: config.communication });
|
|
58
|
+
await fs.writeFile(controllerPath, content);
|
|
59
|
+
await fs.remove(path.join(targetDir, 'src/interfaces/controllers', `${controllerName}.ejs`));
|
|
60
|
+
}
|
|
51
61
|
}
|
|
52
62
|
// Render Server (Clean Arch JS only)
|
|
53
63
|
if (architecture === 'Clean Architecture' && language === 'JavaScript') {
|
|
@@ -61,28 +71,92 @@ export const renderDynamicComponents = async (templatePath, targetDir, config) =
|
|
|
61
71
|
await fs.remove(path.join(targetDir, 'src/infrastructure/webserver', `${serverName}.ejs`));
|
|
62
72
|
}
|
|
63
73
|
}
|
|
74
|
+
|
|
75
|
+
// GraphQL Setup
|
|
76
|
+
if (config.communication === 'GraphQL') {
|
|
77
|
+
const ext = language === 'TypeScript' ? 'ts' : 'js';
|
|
78
|
+
let graphqlInternalPath = architecture === 'MVC' ? 'src/graphql' : 'src/interfaces/graphql';
|
|
79
|
+
|
|
80
|
+
const sourceGraphqDir = path.join(templatePath, graphqlInternalPath);
|
|
81
|
+
const targetGraphqlDir = path.join(targetDir, graphqlInternalPath);
|
|
82
|
+
|
|
83
|
+
if (await fs.pathExists(sourceGraphqDir)) {
|
|
84
|
+
// Read and render all .ejs files in the directory recursively
|
|
85
|
+
const renderEjsDir = async (src, dest) => {
|
|
86
|
+
await fs.ensureDir(dest);
|
|
87
|
+
const items = await fs.readdir(src);
|
|
88
|
+
for (let item of items) {
|
|
89
|
+
const srcPath = path.join(src, item);
|
|
90
|
+
const destPath = path.join(dest, item);
|
|
91
|
+
const stat = await fs.stat(srcPath);
|
|
92
|
+
|
|
93
|
+
if (stat.isDirectory()) {
|
|
94
|
+
await renderEjsDir(srcPath, destPath);
|
|
95
|
+
} else if (item.endsWith('.ejs')) {
|
|
96
|
+
const content = ejs.render(await fs.readFile(srcPath, 'utf-8'), { language, architecture, database, caching });
|
|
97
|
+
// Remove .ejs extension
|
|
98
|
+
const finalDestPath = destPath.slice(0, -4);
|
|
99
|
+
await fs.writeFile(finalDestPath, content);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
await renderEjsDir(sourceGraphqDir, targetGraphqlDir);
|
|
105
|
+
// After successful generation, remove the raw template folder in the target dir (since copyBaseStructure copies EVERYTHING)
|
|
106
|
+
await fs.remove(path.join(targetDir, graphqlInternalPath)); // remove the raw dir
|
|
107
|
+
await renderEjsDir(sourceGraphqDir, targetGraphqlDir); // render them into place
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
// Cleanup GraphQL template dirs if REST or Kafka is selected
|
|
111
|
+
let graphqlInternalPath = architecture === 'MVC' ? 'src/graphql' : 'src/interfaces/graphql';
|
|
112
|
+
await fs.remove(path.join(targetDir, graphqlInternalPath));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Cleanup REST routes if GraphQL or Kafka is selected
|
|
116
|
+
if (config.communication !== 'REST APIs') {
|
|
117
|
+
if (architecture === 'MVC') {
|
|
118
|
+
await fs.remove(path.join(targetDir, 'src/routes'));
|
|
119
|
+
} else if (architecture === 'Clean Architecture') {
|
|
120
|
+
await fs.remove(path.join(targetDir, 'src/interfaces/routes'));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
64
123
|
};
|
|
65
124
|
|
|
66
|
-
export const renderSwaggerConfig = async (targetDir, config) => {
|
|
67
|
-
const { communication } = config;
|
|
125
|
+
export const renderSwaggerConfig = async (templatesDir, targetDir, config) => {
|
|
126
|
+
const { communication, projectName, architecture, language } = config;
|
|
68
127
|
|
|
69
|
-
// Check for Swagger config template (typically in src/config/swagger.ts.ejs)
|
|
70
|
-
// This path is common for both MVC and Clean Arch TS templates based on current structure
|
|
71
128
|
// Check for Swagger config template (typically in src/config/swagger.ts.ejs)
|
|
72
129
|
const swaggerTsTemplate = path.join(targetDir, 'src', 'config', 'swagger.ts.ejs');
|
|
73
130
|
|
|
74
131
|
// Ensure config directory exists
|
|
75
|
-
|
|
132
|
+
let configDir = path.join(targetDir, 'src', 'config');
|
|
133
|
+
if (architecture === 'Clean Architecture' && language === 'JavaScript') {
|
|
134
|
+
configDir = path.join(targetDir, 'src', 'infrastructure', 'webserver');
|
|
135
|
+
}
|
|
136
|
+
await fs.ensureDir(configDir);
|
|
76
137
|
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
if (
|
|
138
|
+
if (communication === 'REST APIs') {
|
|
139
|
+
const swaggerYmlTemplateSource = path.join(templatesDir, 'common', 'swagger.yml.ejs');
|
|
140
|
+
if (await fs.pathExists(swaggerYmlTemplateSource)) {
|
|
141
|
+
const ymlContent = ejs.render(await fs.readFile(swaggerYmlTemplateSource, 'utf-8'), { projectName });
|
|
142
|
+
await fs.writeFile(path.join(configDir, 'swagger.yml'), ymlContent);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (await fs.pathExists(swaggerTsTemplate)) {
|
|
80
146
|
const content = ejs.render(await fs.readFile(swaggerTsTemplate, 'utf-8'), { communication });
|
|
81
147
|
await fs.writeFile(path.join(targetDir, 'src', 'config', 'swagger.ts'), content);
|
|
82
148
|
}
|
|
83
|
-
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Always remove the template after processing or if not needed
|
|
152
|
+
if (await fs.pathExists(swaggerTsTemplate)) {
|
|
84
153
|
await fs.remove(swaggerTsTemplate);
|
|
85
154
|
}
|
|
155
|
+
// Also cleanup yml template if not REST APIs since copyBaseSturcture copies it earlier
|
|
156
|
+
const swaggerYmlDestPath = path.join(targetDir, 'src', 'config', 'swagger.yml.ejs');
|
|
157
|
+
if (await fs.pathExists(swaggerYmlDestPath)) {
|
|
158
|
+
await fs.remove(swaggerYmlDestPath);
|
|
159
|
+
}
|
|
86
160
|
};
|
|
87
161
|
|
|
88
162
|
export const setupViews = async (templatesDir, targetDir, config) => {
|
package/lib/prompts.js
CHANGED
|
@@ -58,7 +58,7 @@ export const getProjectDetails = async (options = {}) => {
|
|
|
58
58
|
type: 'list',
|
|
59
59
|
name: 'communication',
|
|
60
60
|
message: 'Microservices Communication:',
|
|
61
|
-
choices: ['REST APIs', 'Kafka'],
|
|
61
|
+
choices: ['REST APIs', 'GraphQL', 'Kafka'],
|
|
62
62
|
default: 'REST APIs',
|
|
63
63
|
when: !options.communication
|
|
64
64
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-quickstart-structure",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A CLI to scaffold Node.js microservices with MVC or Clean Architecture",
|
|
6
6
|
"main": "bin/index.js",
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"cli",
|
|
20
20
|
"scaffold",
|
|
21
21
|
"mvc",
|
|
22
|
-
"clean-architecture"
|
|
22
|
+
"clean-architecture",
|
|
23
|
+
"microservices",
|
|
24
|
+
"backend"
|
|
23
25
|
],
|
|
24
26
|
"author": "Pau Dang <[EMAIL_ADDRESS]>",
|
|
25
27
|
"repository": {
|