nodejs-structure-cli 1.0.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.
Files changed (243) hide show
  1. package/README.md +32 -0
  2. package/bin/index.js +143 -0
  3. package/lib/generator.js +145 -0
  4. package/lib/modules/app-setup.js +479 -0
  5. package/lib/modules/caching-setup.js +76 -0
  6. package/lib/modules/config-files.js +151 -0
  7. package/lib/modules/database-setup.js +116 -0
  8. package/lib/modules/kafka-setup.js +249 -0
  9. package/lib/modules/project-setup.js +32 -0
  10. package/lib/prompts.js +128 -0
  11. package/package.json +66 -0
  12. package/templates/clean-architecture/js/src/domain/models/User.js.ejs +11 -0
  13. package/templates/clean-architecture/js/src/errors/ApiError.js +14 -0
  14. package/templates/clean-architecture/js/src/errors/BadRequestError.js +11 -0
  15. package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +22 -0
  16. package/templates/clean-architecture/js/src/errors/NotFoundError.js +11 -0
  17. package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +22 -0
  18. package/templates/clean-architecture/js/src/index.js.ejs +56 -0
  19. package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +47 -0
  20. package/templates/clean-architecture/js/src/infrastructure/log/logger.js +36 -0
  21. package/templates/clean-architecture/js/src/infrastructure/log/logger.spec.js.ejs +63 -0
  22. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +88 -0
  23. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +142 -0
  24. package/templates/clean-architecture/js/src/infrastructure/webserver/middleware/errorMiddleware.js +30 -0
  25. package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +93 -0
  26. package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.js.ejs +6 -0
  27. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +190 -0
  28. package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +234 -0
  29. package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -0
  30. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +31 -0
  31. package/templates/clean-architecture/js/src/interfaces/graphql/index.js.ejs +5 -0
  32. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/index.js.ejs +6 -0
  33. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +27 -0
  34. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +66 -0
  35. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/index.js.ejs +6 -0
  36. package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +19 -0
  37. package/templates/clean-architecture/js/src/interfaces/routes/api.js.ejs +17 -0
  38. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +38 -0
  39. package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +14 -0
  40. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +51 -0
  41. package/templates/clean-architecture/js/src/usecases/DeleteUser.js +11 -0
  42. package/templates/clean-architecture/js/src/usecases/DeleteUser.spec.js.ejs +47 -0
  43. package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +12 -0
  44. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +61 -0
  45. package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +11 -0
  46. package/templates/clean-architecture/js/src/usecases/UpdateUser.spec.js.ejs +48 -0
  47. package/templates/clean-architecture/js/src/utils/errorMessages.js +14 -0
  48. package/templates/clean-architecture/js/src/utils/httpCodes.js +9 -0
  49. package/templates/clean-architecture/ts/src/config/env.ts.ejs +46 -0
  50. package/templates/clean-architecture/ts/src/config/swagger.ts.ejs +6 -0
  51. package/templates/clean-architecture/ts/src/domain/user.ts.ejs +9 -0
  52. package/templates/clean-architecture/ts/src/errors/ApiError.ts +15 -0
  53. package/templates/clean-architecture/ts/src/errors/BadRequestError.spec.ts.ejs +22 -0
  54. package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +9 -0
  55. package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +22 -0
  56. package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +9 -0
  57. package/templates/clean-architecture/ts/src/index.ts.ejs +144 -0
  58. package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +63 -0
  59. package/templates/clean-architecture/ts/src/infrastructure/log/logger.ts +36 -0
  60. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +175 -0
  61. package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +125 -0
  62. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +331 -0
  63. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +208 -0
  64. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -0
  65. package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +17 -0
  66. package/templates/clean-architecture/ts/src/interfaces/graphql/index.ts.ejs +3 -0
  67. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/index.ts.ejs +4 -0
  68. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -0
  69. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +29 -0
  70. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/index.ts.ejs +4 -0
  71. package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +17 -0
  72. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -0
  73. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +18 -0
  74. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -0
  75. package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +11 -0
  76. package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +47 -0
  77. package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +9 -0
  78. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -0
  79. package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +10 -0
  80. package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +48 -0
  81. package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +10 -0
  82. package/templates/clean-architecture/ts/src/utils/errorMessages.ts +12 -0
  83. package/templates/clean-architecture/ts/src/utils/errorMiddleware.ts.ejs +27 -0
  84. package/templates/clean-architecture/ts/src/utils/httpCodes.ts +7 -0
  85. package/templates/common/.cursorrules.ejs +60 -0
  86. package/templates/common/.dockerignore +12 -0
  87. package/templates/common/.env.example.ejs +60 -0
  88. package/templates/common/.gitattributes +46 -0
  89. package/templates/common/.gitlab-ci.yml.ejs +86 -0
  90. package/templates/common/.lintstagedrc +6 -0
  91. package/templates/common/.prettierrc +7 -0
  92. package/templates/common/.snyk.ejs +45 -0
  93. package/templates/common/Dockerfile +73 -0
  94. package/templates/common/Jenkinsfile.ejs +87 -0
  95. package/templates/common/README.md.ejs +148 -0
  96. package/templates/common/_github/workflows/ci.yml.ejs +46 -0
  97. package/templates/common/_github/workflows/security.yml.ejs +36 -0
  98. package/templates/common/_gitignore +5 -0
  99. package/templates/common/_husky/pre-commit +4 -0
  100. package/templates/common/caching/clean/js/CreateUser.js.ejs +29 -0
  101. package/templates/common/caching/clean/js/DeleteUser.js.ejs +27 -0
  102. package/templates/common/caching/clean/js/GetAllUsers.js.ejs +37 -0
  103. package/templates/common/caching/clean/js/UpdateUser.js.ejs +27 -0
  104. package/templates/common/caching/clean/ts/createUser.ts.ejs +27 -0
  105. package/templates/common/caching/clean/ts/deleteUser.ts.ejs +24 -0
  106. package/templates/common/caching/clean/ts/getAllUsers.ts.ejs +34 -0
  107. package/templates/common/caching/clean/ts/updateUser.ts.ejs +25 -0
  108. package/templates/common/caching/js/memoryCache.js.ejs +60 -0
  109. package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -0
  110. package/templates/common/caching/js/redisClient.js.ejs +75 -0
  111. package/templates/common/caching/js/redisClient.spec.js.ejs +147 -0
  112. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -0
  113. package/templates/common/caching/ts/memoryCache.ts.ejs +73 -0
  114. package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -0
  115. package/templates/common/caching/ts/redisClient.ts.ejs +89 -0
  116. package/templates/common/database/js/database.js.ejs +19 -0
  117. package/templates/common/database/js/database.spec.js.ejs +56 -0
  118. package/templates/common/database/js/models/User.js.ejs +91 -0
  119. package/templates/common/database/js/models/User.js.mongoose.ejs +35 -0
  120. package/templates/common/database/js/models/User.spec.js.ejs +94 -0
  121. package/templates/common/database/js/mongoose.js.ejs +33 -0
  122. package/templates/common/database/js/mongoose.spec.js.ejs +43 -0
  123. package/templates/common/database/ts/database.spec.ts.ejs +56 -0
  124. package/templates/common/database/ts/database.ts.ejs +21 -0
  125. package/templates/common/database/ts/models/User.spec.ts.ejs +100 -0
  126. package/templates/common/database/ts/models/User.ts.ejs +102 -0
  127. package/templates/common/database/ts/models/User.ts.mongoose.ejs +34 -0
  128. package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -0
  129. package/templates/common/database/ts/mongoose.ts.ejs +28 -0
  130. package/templates/common/docker-compose.yml.ejs +159 -0
  131. package/templates/common/ecosystem.config.js.ejs +40 -0
  132. package/templates/common/eslint.config.mjs.ejs +77 -0
  133. package/templates/common/health/js/healthRoute.js.ejs +50 -0
  134. package/templates/common/health/js/healthRoute.spec.js.ejs +70 -0
  135. package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -0
  136. package/templates/common/health/ts/healthRoute.ts.ejs +49 -0
  137. package/templates/common/jest.config.js.ejs +32 -0
  138. package/templates/common/jest.e2e.config.js.ejs +8 -0
  139. package/templates/common/kafka/js/config/kafka.js +9 -0
  140. package/templates/common/kafka/js/config/kafka.spec.js.ejs +27 -0
  141. package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +30 -0
  142. package/templates/common/kafka/js/messaging/baseConsumer.spec.js.ejs +58 -0
  143. package/templates/common/kafka/js/messaging/userEventSchema.js.ejs +12 -0
  144. package/templates/common/kafka/js/messaging/userEventSchema.spec.js.ejs +27 -0
  145. package/templates/common/kafka/js/messaging/welcomeEmailConsumer.js.ejs +44 -0
  146. package/templates/common/kafka/js/messaging/welcomeEmailConsumer.spec.js.ejs +86 -0
  147. package/templates/common/kafka/js/services/kafkaService.js.ejs +93 -0
  148. package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +106 -0
  149. package/templates/common/kafka/js/utils/kafkaEvents.js.ejs +7 -0
  150. package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +27 -0
  151. package/templates/common/kafka/ts/config/kafka.ts +7 -0
  152. package/templates/common/kafka/ts/messaging/baseConsumer.spec.ts.ejs +50 -0
  153. package/templates/common/kafka/ts/messaging/baseConsumer.ts.ejs +27 -0
  154. package/templates/common/kafka/ts/messaging/userEventSchema.spec.ts.ejs +51 -0
  155. package/templates/common/kafka/ts/messaging/userEventSchema.ts.ejs +12 -0
  156. package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.spec.ts.ejs +86 -0
  157. package/templates/common/kafka/ts/messaging/welcomeEmailConsumer.ts.ejs +38 -0
  158. package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +81 -0
  159. package/templates/common/kafka/ts/services/kafkaService.ts.ejs +95 -0
  160. package/templates/common/kafka/ts/utils/kafkaEvents.ts.ejs +5 -0
  161. package/templates/common/migrate-mongo-config.js.ejs +31 -0
  162. package/templates/common/migrations/init.js.ejs +23 -0
  163. package/templates/common/package.json.ejs +137 -0
  164. package/templates/common/prompts/add-feature.md.ejs +26 -0
  165. package/templates/common/prompts/project-context.md.ejs +43 -0
  166. package/templates/common/prompts/troubleshoot.md.ejs +28 -0
  167. package/templates/common/public/css/style.css +147 -0
  168. package/templates/common/scripts/run-e2e.js.ejs +63 -0
  169. package/templates/common/shutdown/js/gracefulShutdown.js.ejs +65 -0
  170. package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +149 -0
  171. package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +179 -0
  172. package/templates/common/shutdown/ts/gracefulShutdown.ts.ejs +59 -0
  173. package/templates/common/sonar-project.properties.ejs +27 -0
  174. package/templates/common/src/config/auth.js.ejs +19 -0
  175. package/templates/common/src/config/auth.ts.ejs +19 -0
  176. package/templates/common/src/controllers/authController.js.ejs +101 -0
  177. package/templates/common/src/controllers/authController.ts.ejs +101 -0
  178. package/templates/common/src/middleware/auth.js.ejs +20 -0
  179. package/templates/common/src/middleware/auth.ts.ejs +25 -0
  180. package/templates/common/src/middleware/upload.js.ejs +31 -0
  181. package/templates/common/src/middleware/upload.ts.ejs +32 -0
  182. package/templates/common/src/routes/authRoutes.js.ejs +20 -0
  183. package/templates/common/src/routes/authRoutes.ts.ejs +20 -0
  184. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +120 -0
  185. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +120 -0
  186. package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -0
  187. package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -0
  188. package/templates/common/swagger.yml.ejs +118 -0
  189. package/templates/common/tsconfig.json +23 -0
  190. package/templates/common/views/ejs/index.ejs +55 -0
  191. package/templates/common/views/pug/index.pug +40 -0
  192. package/templates/db/mysql/V1__Initial_Setup.sql.ejs +10 -0
  193. package/templates/db/postgres/V1__Initial_Setup.sql.ejs +10 -0
  194. package/templates/mvc/js/src/config/env.js.ejs +46 -0
  195. package/templates/mvc/js/src/config/swagger.js.ejs +6 -0
  196. package/templates/mvc/js/src/controllers/userController.js.ejs +288 -0
  197. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +481 -0
  198. package/templates/mvc/js/src/errors/ApiError.js +14 -0
  199. package/templates/mvc/js/src/errors/BadRequestError.js +11 -0
  200. package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +22 -0
  201. package/templates/mvc/js/src/errors/NotFoundError.js +11 -0
  202. package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +22 -0
  203. package/templates/mvc/js/src/graphql/context.js.ejs +7 -0
  204. package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -0
  205. package/templates/mvc/js/src/graphql/index.js.ejs +5 -0
  206. package/templates/mvc/js/src/graphql/resolvers/index.js.ejs +6 -0
  207. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +25 -0
  208. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +64 -0
  209. package/templates/mvc/js/src/graphql/typeDefs/index.js.ejs +6 -0
  210. package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +19 -0
  211. package/templates/mvc/js/src/index.js.ejs +141 -0
  212. package/templates/mvc/js/src/routes/api.js.ejs +15 -0
  213. package/templates/mvc/js/src/routes/api.spec.js.ejs +41 -0
  214. package/templates/mvc/js/src/utils/errorMessages.js +14 -0
  215. package/templates/mvc/js/src/utils/errorMiddleware.js +29 -0
  216. package/templates/mvc/js/src/utils/httpCodes.js +9 -0
  217. package/templates/mvc/js/src/utils/logger.js +40 -0
  218. package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -0
  219. package/templates/mvc/ts/src/config/env.ts.ejs +45 -0
  220. package/templates/mvc/ts/src/config/swagger.ts.ejs +6 -0
  221. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +481 -0
  222. package/templates/mvc/ts/src/controllers/userController.ts.ejs +292 -0
  223. package/templates/mvc/ts/src/errors/ApiError.ts +15 -0
  224. package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +22 -0
  225. package/templates/mvc/ts/src/errors/BadRequestError.ts +9 -0
  226. package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +27 -0
  227. package/templates/mvc/ts/src/errors/NotFoundError.ts +9 -0
  228. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -0
  229. package/templates/mvc/ts/src/graphql/context.ts.ejs +12 -0
  230. package/templates/mvc/ts/src/graphql/index.ts.ejs +3 -0
  231. package/templates/mvc/ts/src/graphql/resolvers/index.ts.ejs +4 -0
  232. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +68 -0
  233. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +29 -0
  234. package/templates/mvc/ts/src/graphql/typeDefs/index.ts.ejs +4 -0
  235. package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +17 -0
  236. package/templates/mvc/ts/src/index.ts.ejs +157 -0
  237. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +59 -0
  238. package/templates/mvc/ts/src/routes/api.ts.ejs +17 -0
  239. package/templates/mvc/ts/src/utils/errorMessages.ts +12 -0
  240. package/templates/mvc/ts/src/utils/errorMiddleware.ts.ejs +27 -0
  241. package/templates/mvc/ts/src/utils/httpCodes.ts +7 -0
  242. package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +63 -0
  243. package/templates/mvc/ts/src/utils/logger.ts +36 -0
@@ -0,0 +1,147 @@
1
+ :root {
2
+ --primary-color: #4f46e5;
3
+ --primary-hover: #4338ca;
4
+ --bg-color: #f9fafb;
5
+ --card-bg: #ffffff;
6
+ --text-main: #111827;
7
+ --text-muted: #6b7280;
8
+ --success-color: #10b981;
9
+ --border-color: #e5e7eb;
10
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
11
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
12
+ }
13
+
14
+ body {
15
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
16
+ background-color: var(--bg-color);
17
+ color: var(--text-main);
18
+ margin: 0;
19
+ padding: 0;
20
+ line-height: 1.5;
21
+ display: flex;
22
+ justify-content: center;
23
+ min-height: 100vh;
24
+ }
25
+
26
+ .container {
27
+ width: 100%;
28
+ max-width: 800px;
29
+ padding: 40px 20px;
30
+ }
31
+
32
+ .header {
33
+ text-align: center;
34
+ margin-bottom: 40px;
35
+ }
36
+
37
+ .logo {
38
+ font-size: 3rem;
39
+ margin-bottom: 10px;
40
+ }
41
+
42
+ h1 {
43
+ font-size: 2.5rem;
44
+ font-weight: 800;
45
+ color: var(--text-main);
46
+ margin: 0 0 10px 0;
47
+ letter-spacing: -0.025em;
48
+ }
49
+
50
+ .subtitle {
51
+ color: var(--text-muted);
52
+ font-size: 1.125rem;
53
+ }
54
+
55
+ .card-grid {
56
+ display: grid;
57
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
58
+ gap: 20px;
59
+ margin-bottom: 40px;
60
+ }
61
+
62
+ .card {
63
+ background: var(--card-bg);
64
+ padding: 24px;
65
+ border-radius: 12px;
66
+ box-shadow: var(--shadow-sm);
67
+ border: 1px solid var(--border-color);
68
+ transition: transform 0.2s, box-shadow 0.2s;
69
+ }
70
+
71
+ .card:hover {
72
+ transform: translateY(-2px);
73
+ box-shadow: var(--shadow-md);
74
+ }
75
+
76
+ .card h3 {
77
+ margin-top: 0;
78
+ font-size: 0.875rem;
79
+ text-transform: uppercase;
80
+ letter-spacing: 0.05em;
81
+ color: var(--text-muted);
82
+ font-weight: 600;
83
+ }
84
+
85
+ .card p {
86
+ font-size: 1.25rem;
87
+ font-weight: 600;
88
+ color: var(--text-main);
89
+ margin: 5px 0 0 0;
90
+ }
91
+
92
+ .status-card {
93
+ background: var(--card-bg);
94
+ border-radius: 12px;
95
+ padding: 24px;
96
+ box-shadow: var(--shadow-md);
97
+ border-left: 6px solid var(--success-color);
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 20px;
101
+ }
102
+
103
+ .status-icon {
104
+ background: #d1fae5;
105
+ color: var(--success-color);
106
+ width: 48px;
107
+ height: 48px;
108
+ border-radius: 50%;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ font-size: 1.5rem;
113
+ }
114
+
115
+ .status-content h3 {
116
+ margin: 0;
117
+ font-size: 1.25rem;
118
+ font-weight: 700;
119
+ }
120
+
121
+ .status-content p {
122
+ margin: 5px 0 0 0;
123
+ color: var(--text-muted);
124
+ }
125
+
126
+ .action-btn {
127
+ display: inline-block;
128
+ margin-top: 40px;
129
+ background-color: var(--primary-color);
130
+ color: white;
131
+ padding: 12px 24px;
132
+ border-radius: 8px;
133
+ text-decoration: none;
134
+ font-weight: 600;
135
+ transition: background-color 0.2s;
136
+ }
137
+
138
+ .action-btn:hover {
139
+ background-color: var(--primary-hover);
140
+ }
141
+
142
+ footer {
143
+ margin-top: 60px;
144
+ text-align: center;
145
+ color: var(--text-muted);
146
+ font-size: 0.875rem;
147
+ }
@@ -0,0 +1,63 @@
1
+ /* eslint-disable */
2
+ const { execSync } = require('child_process');
3
+ const path = require('path');
4
+
5
+ // Set a specific port for E2E tests to avoid collisions with local development
6
+ process.env.PORT = '3001';
7
+ const TEST_PORT = process.env.PORT;
8
+
9
+ const execute = (command) => {
10
+ console.log(`\n> ${command}`);
11
+ // Run commands from the project root instead of the scripts folder
12
+ execSync(command, { stdio: 'inherit', cwd: path.resolve(__dirname, '../') });
13
+ };
14
+
15
+ let composeCmd = 'docker-compose';
16
+ try {
17
+ execSync('docker compose version', { stdio: 'ignore' });
18
+ composeCmd = 'docker compose';
19
+ } catch (e) {
20
+ // fallback to docker-compose
21
+ }
22
+
23
+ let currentProcessStartedDocker = false;
24
+
25
+ try {
26
+ let isAlreadyUp = false;
27
+ try {
28
+ // Silently check if the endpoint is already live (1.5-second timeout)
29
+ execSync(`npx wait-on http-get://127.0.0.1:${TEST_PORT}/health -t 1500`, {
30
+ stdio: 'ignore',
31
+ cwd: path.resolve(__dirname, '../')
32
+ });
33
+ isAlreadyUp = true;
34
+ } catch (e) {
35
+ isAlreadyUp = false;
36
+ }
37
+
38
+ if (isAlreadyUp) {
39
+ console.log('Infrastructure is already running! Skipping Docker spin-up...');
40
+ } else {
41
+ console.log(`Starting Docker Compose infrastructure using '${composeCmd}'...`);
42
+ execute(`${composeCmd} up -d --build`);
43
+ currentProcessStartedDocker = true;
44
+
45
+ console.log('Waiting for application healthcheck to turn green (120s timeout)...');
46
+ // Using wait-on to poll the universal /health endpoint injected into all architectures
47
+ execute(`npx wait-on http-get://127.0.0.1:${TEST_PORT}/health -t 120000`);
48
+ console.log('Infrastructure is healthy!');
49
+ }
50
+
51
+ console.log('Running E2E tests...');
52
+ execute('npm run test:e2e:run');
53
+ } catch (error) {
54
+ console.error('E2E tests failed or infrastructure did not boot in time.');
55
+ process.exitCode = 1;
56
+ } finally {
57
+ if (currentProcessStartedDocker) {
58
+ console.log('Tearing down isolated Docker Compose infrastructure...');
59
+ execute(`${composeCmd} down`);
60
+ } else {
61
+ console.log('Leaving preexisting infrastructure running.');
62
+ }
63
+ }
@@ -0,0 +1,65 @@
1
+ <%_
2
+ let loggerPath = './logger';
3
+ let dbPath = '../config/database';
4
+ let redisPath = '../config/redisClient';
5
+ let kafkaPath = '../services/kafkaService';
6
+
7
+ if (architecture === 'Clean Architecture') {
8
+ loggerPath = '../infrastructure/log/logger';
9
+ dbPath = '../infrastructure/database/database';
10
+ redisPath = '../infrastructure/caching/redisClient';
11
+ kafkaPath = '../infrastructure/messaging/kafkaClient';
12
+ }
13
+ -%>
14
+ const logger = require('<%- loggerPath %>');
15
+
16
+ const setupGracefulShutdown = (server) => {
17
+ const gracefulShutdown = async (signal) => {
18
+ logger.info(`Received ${signal}. Shutting down gracefully...`);
19
+ server.close(async (err) => {
20
+ if (err) {
21
+ logger.error('Error closing HTTP server:', err);
22
+ process.exit(1);
23
+ }
24
+ logger.info('HTTP server closed.');
25
+ try {
26
+ <%_ if (database !== 'None') { -%>
27
+ <%_ if (database === 'MongoDB') { -%>
28
+ const mongoose = require('mongoose');
29
+ await mongoose.connection.close(false);
30
+ logger.info('MongoDB connection closed.');
31
+ <%_ } else { -%>
32
+ const sequelize = require('<%- dbPath %>');
33
+ await sequelize.close();
34
+ logger.info('Database connection closed.');
35
+ <%_ } -%>
36
+ <%_ } -%>
37
+ <%_ if (caching === 'Redis') { -%>
38
+ const redisService = require('<%- redisPath %>');
39
+ await redisService.quit();
40
+ logger.info('Redis connection closed.');
41
+ <%_ } -%>
42
+ <%_ if (communication === 'Kafka') { -%>
43
+ const { disconnectKafka } = require('<%- kafkaPath %>');
44
+ await disconnectKafka();
45
+ logger.info('Kafka connection closed.');
46
+ <%_ } -%>
47
+ logger.info('Graceful shutdown fully completed.');
48
+ process.exit(0);
49
+ } catch (err) {
50
+ logger.error('Error during shutdown:', err);
51
+ process.exit(1);
52
+ }
53
+ });
54
+
55
+ setTimeout(() => {
56
+ logger.error('Could not close connections in time, forcefully shutting down');
57
+ process.exit(1);
58
+ }, 15000);
59
+ };
60
+
61
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
62
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
63
+ };
64
+
65
+ module.exports = setupGracefulShutdown;
@@ -0,0 +1,149 @@
1
+ <%_
2
+ let loggerPath = '@/utils/logger';
3
+ let dbPath = '@/config/database';
4
+ let redisPath = '@/config/redisClient';
5
+ let kafkaPath = '@/services/kafkaService';
6
+
7
+ if (architecture === 'Clean Architecture') {
8
+ loggerPath = '@/infrastructure/log/logger';
9
+ dbPath = '@/infrastructure/database/database';
10
+ redisPath = '@/infrastructure/caching/redisClient';
11
+ kafkaPath = '@/infrastructure/messaging/kafkaClient';
12
+ }
13
+ -%>
14
+ const setupGracefulShutdown = require('@/utils/gracefulShutdown');
15
+
16
+ <%_ if (database === 'MongoDB') { -%>
17
+ jest.mock('mongoose', () => {
18
+ return {
19
+ connection: {
20
+ close: jest.fn().mockResolvedValue(true)
21
+ }
22
+ };
23
+ });
24
+ <%_ } else if (database !== 'None') { -%>
25
+ jest.mock('<%- dbPath %>', () => {
26
+ return {
27
+ close: jest.fn().mockResolvedValue(true)
28
+ };
29
+ });
30
+ <%_ } -%>
31
+
32
+ <%_ if (caching === 'Redis') { -%>
33
+ jest.mock('<%- redisPath %>', () => {
34
+ return {
35
+ quit: jest.fn().mockResolvedValue(true)
36
+ };
37
+ });
38
+ <%_ } -%>
39
+
40
+ <%_ if (communication === 'Kafka') { -%>
41
+ jest.mock('<%- kafkaPath %>', () => {
42
+ return {
43
+ disconnectKafka: jest.fn().mockResolvedValue(true)
44
+ };
45
+ });
46
+ <%_ } -%>
47
+
48
+ const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
49
+
50
+ describe('Graceful Shutdown', () => {
51
+ let mockServer;
52
+ let mockExit;
53
+ let processListeners;
54
+
55
+ beforeEach(() => {
56
+ jest.useFakeTimers({ legacyFakeTimers: true });
57
+ jest.clearAllMocks();
58
+ processListeners = {};
59
+
60
+ mockServer = {
61
+ close: jest.fn().mockImplementation((cb) => {
62
+ if (cb) Promise.resolve().then(() => cb());
63
+ return mockServer;
64
+ })
65
+ };
66
+
67
+ mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
68
+ jest.spyOn(process, 'on').mockImplementation((event, handler) => {
69
+ processListeners[event] = handler;
70
+ return process;
71
+ });
72
+ });
73
+
74
+ afterEach(() => {
75
+ jest.restoreAllMocks();
76
+ jest.useRealTimers();
77
+ });
78
+
79
+ it('should register SIGTERM and SIGINT events', () => {
80
+ setupGracefulShutdown(mockServer);
81
+ expect(processListeners['SIGTERM']).toBeDefined();
82
+ expect(processListeners['SIGINT']).toBeDefined();
83
+ });
84
+
85
+ it('should cleanly shutdown all connections and exit 0', async () => {
86
+ setupGracefulShutdown(mockServer);
87
+
88
+ processListeners['SIGTERM']();
89
+
90
+ await flushPromises();
91
+ await flushPromises();
92
+ await flushPromises();
93
+
94
+ expect(mockServer.close).toHaveBeenCalled();
95
+
96
+ <%_ if (database === 'MongoDB') { -%>
97
+ const mongoose = require('mongoose');
98
+ expect(mongoose.connection.close).toHaveBeenCalledWith(false);
99
+ <%_ } else if (database !== 'None') { -%>
100
+ const sequelize = require('<%- dbPath %>');
101
+ expect(sequelize.close).toHaveBeenCalled();
102
+ <%_ } -%>
103
+
104
+ <%_ if (caching === 'Redis') { -%>
105
+ const redisService = require('<%- redisPath %>');
106
+ expect(redisService.quit).toHaveBeenCalled();
107
+ <%_ } -%>
108
+
109
+ <%_ if (communication === 'Kafka') { -%>
110
+ const { disconnectKafka } = require('<%- kafkaPath %>');
111
+ expect(disconnectKafka).toHaveBeenCalled();
112
+ <%_ } -%>
113
+
114
+ expect(mockExit).toHaveBeenCalledWith(0);
115
+ });
116
+
117
+ it('should exit 0 on SIGINT', async () => {
118
+ setupGracefulShutdown(mockServer);
119
+ processListeners['SIGINT']();
120
+ await flushPromises();
121
+ await flushPromises();
122
+ expect(mockExit).toHaveBeenCalledWith(0);
123
+ });
124
+
125
+ it('should handle errors during shutdown and exit 1', async () => {
126
+ // Force server.close to fail to test the error path
127
+ mockServer.close.mockImplementationOnce((cb) => {
128
+ if (cb) Promise.resolve().then(() => cb(new Error('Server Close Error')));
129
+ return mockServer;
130
+ });
131
+
132
+ setupGracefulShutdown(mockServer);
133
+ processListeners['SIGTERM']();
134
+
135
+ await flushPromises();
136
+ await flushPromises();
137
+
138
+ expect(mockExit).toHaveBeenCalledWith(1);
139
+ });
140
+
141
+ it('should forcefully shutdown if cleanup takes too long', async () => {
142
+ setupGracefulShutdown(mockServer);
143
+ processListeners['SIGTERM']();
144
+
145
+ jest.advanceTimersByTime(15000);
146
+
147
+ expect(mockExit).toHaveBeenCalledWith(1);
148
+ });
149
+ });
@@ -0,0 +1,179 @@
1
+ import { setupGracefulShutdown } from '@/utils/gracefulShutdown';
2
+ import { Server } from 'http';
3
+ <%_ if (database === 'MongoDB') { -%>
4
+ import mongoose from 'mongoose';
5
+ <%_ } else if (database !== 'None') { -%>
6
+ import sequelize from '<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>';
7
+ <%_ } -%>
8
+ <%_ if (caching === 'Redis') { -%>
9
+ import redisService from '<% if (architecture === "MVC") { %>@/config/redisClient<% } else { %>@/infrastructure/caching/redisClient<% } %>';
10
+ <%_ } -%>
11
+
12
+ <%_ if (database === 'MongoDB') { -%>
13
+ jest.mock('mongoose', () => {
14
+ return {
15
+ __esModule: true,
16
+ default: {
17
+ connection: {
18
+ close: jest.fn().mockResolvedValue(true)
19
+ }
20
+ }
21
+ };
22
+ });
23
+ <%_ } else if (database !== 'None') { -%>
24
+ jest.mock('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>', () => {
25
+ return {
26
+ __esModule: true,
27
+ default: {
28
+ close: jest.fn().mockResolvedValue(true)
29
+ }
30
+ };
31
+ });
32
+ <%_ } -%>
33
+
34
+ <%_ if (caching === 'Redis') { -%>
35
+ jest.mock('<% if (architecture === "MVC") { %>@/config/redisClient<% } else { %>@/infrastructure/caching/redisClient<% } %>', () => {
36
+ return {
37
+ __esModule: true,
38
+ default: {
39
+ quit: jest.fn().mockResolvedValue(true)
40
+ }
41
+ };
42
+ });
43
+ <%_ } -%>
44
+
45
+ const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
46
+
47
+ describe('Graceful Shutdown', () => {
48
+ let mockServer: Partial<Server>;
49
+ let mockExit: jest.SpyInstance;
50
+ let processListeners: Record<string, (...args: any[]) => void>;
51
+ <%_ if (communication === 'Kafka') { -%>
52
+ let mockKafkaService: { disconnect: jest.Mock };
53
+ <%_ } -%>
54
+
55
+ beforeEach(() => {
56
+ jest.useFakeTimers({ legacyFakeTimers: true });
57
+ jest.clearAllMocks();
58
+ processListeners = {};
59
+
60
+ mockServer = {
61
+ close: jest.fn().mockImplementation((cb?: (err?: Error) => void) => {
62
+ if (cb) Promise.resolve().then(() => cb());
63
+ return mockServer;
64
+ })
65
+ };
66
+
67
+ mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {}) as any);
68
+ jest.spyOn(process, 'on').mockImplementation(((event: string, handler: (...args: any[]) => void) => {
69
+ processListeners[event] = handler;
70
+ return process;
71
+ }) as any);
72
+
73
+ <%_ if (communication === 'Kafka') { -%>
74
+ mockKafkaService = { disconnect: jest.fn().mockResolvedValue(true) };
75
+ <%_ } -%>
76
+ });
77
+
78
+ afterEach(() => {
79
+ jest.restoreAllMocks();
80
+ jest.useRealTimers();
81
+ });
82
+
83
+ it('should register SIGTERM and SIGINT events', () => {
84
+ setupGracefulShutdown(mockServer as Server<% if (communication === 'Kafka') { %>, mockKafkaService<% } %>);
85
+ expect(processListeners['SIGTERM']).toBeDefined();
86
+ expect(processListeners['SIGINT']).toBeDefined();
87
+ });
88
+
89
+ it('should cleanly shutdown all connections and exit 0 on SIGTERM', async () => {
90
+ setupGracefulShutdown(mockServer as Server<% if (communication === 'Kafka') { %>, mockKafkaService<% } %>);
91
+
92
+ processListeners['SIGTERM']();
93
+
94
+ // Flush microtask queue multiple times for nested async operations
95
+ await flushPromises();
96
+ await flushPromises();
97
+ await flushPromises();
98
+
99
+ expect(mockServer.close).toHaveBeenCalled();
100
+
101
+ <%_ if (database === 'MongoDB') { -%>
102
+ expect(mongoose.connection.close).toHaveBeenCalledWith(false);
103
+ <%_ } else if (database !== 'None') { -%>
104
+ expect(sequelize.close).toHaveBeenCalled();
105
+ <%_ } -%>
106
+
107
+ <%_ if (caching === 'Redis') { -%>
108
+ expect(redisService.quit).toHaveBeenCalled();
109
+ <%_ } -%>
110
+
111
+ <%_ if (communication === 'Kafka') { -%>
112
+ expect(mockKafkaService.disconnect).toHaveBeenCalled();
113
+ <%_ } -%>
114
+
115
+ expect(mockExit).toHaveBeenCalledWith(0);
116
+ });
117
+
118
+ it('should exit 0 on SIGINT', async () => {
119
+ setupGracefulShutdown(mockServer as Server<% if (communication === 'Kafka') { %>, mockKafkaService<% } %>);
120
+ processListeners['SIGINT']();
121
+ await flushPromises();
122
+ await flushPromises();
123
+ expect(mockExit).toHaveBeenCalledWith(0);
124
+ });
125
+
126
+ <%_ if (communication === 'Kafka' || database !== 'None' || caching === 'Redis') { -%>
127
+ it('should handle errors during shutdown and exit 1', async () => {
128
+ <%_ if (communication === 'Kafka') { -%>
129
+ mockKafkaService.disconnect.mockRejectedValueOnce(new Error('Shutdown Error'));
130
+ <%_ if (database === 'MongoDB') { -%>
131
+ (mongoose.connection.close as jest.Mock).mockResolvedValueOnce(true);
132
+ <%_ } else if (database !== 'None') { -%>
133
+ (sequelize.close as jest.Mock).mockResolvedValueOnce(true);
134
+ <%_ } -%>
135
+ <%_ } else if (database === 'MongoDB') { -%>
136
+ (mongoose.connection.close as jest.Mock).mockRejectedValueOnce(new Error('Shutdown Error'));
137
+ <%_ } else if (database !== 'None') { -%>
138
+ (sequelize.close as jest.Mock).mockRejectedValueOnce(new Error('Shutdown Error'));
139
+ <%_ } else if (caching === 'Redis') { -%>
140
+ (redisService.quit as jest.Mock).mockRejectedValueOnce(new Error('Shutdown Error'));
141
+ <%_ } -%>
142
+
143
+ setupGracefulShutdown(mockServer as Server<% if (communication === 'Kafka') { %>, mockKafkaService<% } %>);
144
+ processListeners['SIGTERM']();
145
+
146
+ await flushPromises();
147
+ await flushPromises();
148
+ await flushPromises();
149
+
150
+ expect(mockExit).toHaveBeenCalledWith(1);
151
+ });
152
+
153
+ it('should handle server close errors', async () => {
154
+ const serverError = new Error('Server Close Error');
155
+ mockServer.close = jest.fn().mockImplementation((cb?: (err?: Error) => void) => {
156
+ if (cb) Promise.resolve().then(() => cb(serverError));
157
+ return mockServer;
158
+ });
159
+
160
+ setupGracefulShutdown(mockServer as Server<% if (communication === 'Kafka') { %>, mockKafkaService<% } %>);
161
+ processListeners['SIGTERM']();
162
+
163
+ await flushPromises();
164
+ // Since it's inside server.close callback, we need to wait
165
+ await flushPromises();
166
+
167
+ expect(mockExit).toHaveBeenCalledWith(1);
168
+ });
169
+
170
+ it('should forcefully shutdown if cleanup takes too long', async () => {
171
+ setupGracefulShutdown(mockServer as Server<% if (communication === 'Kafka') { %>, mockKafkaService<% } %>);
172
+ processListeners['SIGTERM']();
173
+
174
+ jest.advanceTimersByTime(15000);
175
+
176
+ expect(mockExit).toHaveBeenCalledWith(1);
177
+ });
178
+ <%_ } -%>
179
+ });
@@ -0,0 +1,59 @@
1
+ import { Server } from 'http';
2
+ <%_ if (architecture === 'MVC') { -%>
3
+ import logger from '@/utils/logger';
4
+ <%_ } else { -%>
5
+ import logger from '@/infrastructure/log/logger';
6
+ <%_ } -%>
7
+ <%_ if (database === 'MongoDB') { -%>
8
+ import mongoose from 'mongoose';
9
+ <%_ } else if (database !== 'None') { -%>
10
+ import sequelize from '<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>';
11
+ <%_ } -%>
12
+ <%_ if (caching === 'Redis') { -%>
13
+ import redisService from '<% if (architecture === "MVC") { %>@/config/redisClient<% } else { %>@/infrastructure/caching/redisClient<% } %>';
14
+ <%_ } -%>
15
+
16
+ export const setupGracefulShutdown = (server: Server<% if (communication === 'Kafka') { %>, kafkaService: { disconnect: () => Promise<void> }<% } %>) => {
17
+ const gracefulShutdown = async (signal: string) => {
18
+ logger.info(`Received ${signal}. Shutting down gracefully...`);
19
+ server.close(async (err: Error | undefined) => {
20
+ if (err) {
21
+ logger.error('Error closing HTTP server:', err);
22
+ process.exit(1);
23
+ }
24
+ logger.info('HTTP server closed.');
25
+ try {
26
+ <%_ if (database !== 'None') { -%>
27
+ <%_ if (database === 'MongoDB') { -%>
28
+ await mongoose.connection.close(false);
29
+ logger.info('MongoDB connection closed.');
30
+ <%_ } else { -%>
31
+ await sequelize.close();
32
+ logger.info('Database connection closed.');
33
+ <%_ } -%>
34
+ <%_ } -%>
35
+ <%_ if (caching === 'Redis') { -%>
36
+ await redisService.quit();
37
+ logger.info('Redis connection closed.');
38
+ <%_ } -%>
39
+ <%_ if (communication === 'Kafka') { -%>
40
+ await kafkaService.disconnect();
41
+ logger.info('Kafka connection closed.');
42
+ <%_ } -%>
43
+ logger.info('Graceful shutdown fully completed.');
44
+ process.exit(0);
45
+ } catch (err) {
46
+ logger.error('Error during shutdown:', err);
47
+ process.exit(1);
48
+ }
49
+ });
50
+
51
+ setTimeout(() => {
52
+ logger.error('Could not close connections in time, forcefully shutting down');
53
+ process.exit(1);
54
+ }, 15000);
55
+ };
56
+
57
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
58
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
59
+ };
@@ -0,0 +1,27 @@
1
+ # SonarCloud/SonarQube Project Configuration
2
+ # See https://docs.sonarqube.org/latest/analysis/analysis-parameters/
3
+
4
+ sonar.projectKey=your-org-key_<%= projectName %>
5
+ sonar.projectName=<%= projectName %>
6
+ sonar.organization=your-org-key
7
+ sonar.projectVersion=1.0.0
8
+
9
+ # Path to the source directories
10
+ sonar.sources=src
11
+ # Path to the test directories
12
+ sonar.tests=tests
13
+
14
+ # Language specific settings
15
+ sonar.javascript.environments=node
16
+ sonar.typescript.tsconfigPath=tsconfig.json
17
+ sonar.javascript.lcov.reportPaths=coverage/lcov.info
18
+
19
+ # Exclusions
20
+ sonar.exclusions=node_modules/**, dist/**, coverage/**, tests/**
21
+
22
+ # Quality Gates
23
+ sonar.qualitygate.wait=true
24
+ sonar.qualitygate.timeout=300
25
+
26
+ # Security Hotspots
27
+ sonar.security.reportPaths=snyk-report.json