@expressots/adapter-express 3.0.0-beta.4.2 → 4.0.0-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +39 -96
  2. package/lib/CHANGELOG.md +43 -0
  3. package/lib/README.md +39 -96
  4. package/lib/cjs/adapter-express/application-express.base.js +3 -1
  5. package/lib/cjs/adapter-express/application-express.js +1049 -85
  6. package/lib/cjs/adapter-express/express-utils/conditional-middleware.js +102 -0
  7. package/lib/cjs/adapter-express/express-utils/constants.js +17 -0
  8. package/lib/cjs/adapter-express/express-utils/content-negotiation-decorators.js +129 -0
  9. package/lib/cjs/adapter-express/express-utils/decorators.js +186 -49
  10. package/lib/cjs/adapter-express/express-utils/exception-filter-decorators.js +11 -0
  11. package/lib/cjs/adapter-express/express-utils/guard-context-factory.js +84 -0
  12. package/lib/cjs/adapter-express/express-utils/guard-middleware.js +115 -0
  13. package/lib/cjs/adapter-express/express-utils/guard-utils.js +18 -0
  14. package/lib/cjs/adapter-express/express-utils/http-context-store.js +15 -0
  15. package/lib/cjs/adapter-express/express-utils/http-status-middleware.js +37 -2
  16. package/lib/cjs/adapter-express/express-utils/index.js +67 -1
  17. package/lib/cjs/adapter-express/express-utils/interceptor-middleware.js +132 -0
  18. package/lib/cjs/adapter-express/express-utils/inversify-express-server.js +810 -63
  19. package/lib/cjs/adapter-express/express-utils/lazy-module-middleware.js +241 -0
  20. package/lib/cjs/adapter-express/express-utils/middleware-composition.js +95 -0
  21. package/lib/cjs/adapter-express/express-utils/permission-preloader.middleware.js +48 -0
  22. package/lib/cjs/adapter-express/express-utils/route-constraints.js +95 -0
  23. package/lib/cjs/adapter-express/express-utils/scope-extractor.interface.js +2 -0
  24. package/lib/cjs/adapter-express/express-utils/scope-extractor.js +66 -0
  25. package/lib/cjs/adapter-express/express-utils/setup-authorization.js +71 -0
  26. package/lib/cjs/adapter-express/express-utils/setup-event-system.js +113 -0
  27. package/lib/cjs/adapter-express/express-utils/setup-interceptors.js +103 -0
  28. package/lib/cjs/adapter-express/express-utils/setup-lazy-loading.js +228 -0
  29. package/lib/cjs/adapter-express/express-utils/utils.js +30 -12
  30. package/lib/cjs/adapter-express/express-utils/validation-decorators.js +205 -0
  31. package/lib/cjs/adapter-express/express-utils/validation-service.js +252 -0
  32. package/lib/cjs/adapter-express/index.js +7 -5
  33. package/lib/cjs/adapter-express/micro-api/application-express-micro-route.js +31 -1
  34. package/lib/cjs/adapter-express/micro-api/application-express-micro.js +11 -37
  35. package/lib/cjs/adapter-express/micro-api/gateway/circuit-breaker.js +174 -0
  36. package/lib/cjs/adapter-express/micro-api/gateway/index.js +11 -0
  37. package/lib/cjs/adapter-express/micro-api/gateway/service-proxy.js +214 -0
  38. package/lib/cjs/adapter-express/micro-api/index.js +27 -3
  39. package/lib/cjs/adapter-express/micro-api/micro.js +217 -0
  40. package/lib/cjs/adapter-express/micro-api/queue/index.js +8 -0
  41. package/lib/cjs/adapter-express/micro-api/queue/queue.interface.js +2 -0
  42. package/lib/cjs/adapter-express/micro-api/queue/rabbitmq-consumer.js +255 -0
  43. package/lib/cjs/adapter-express/micro-api/serverless/aws-lambda.adapter.js +183 -0
  44. package/lib/cjs/adapter-express/micro-api/serverless/cloudflare.adapter.js +158 -0
  45. package/lib/cjs/adapter-express/micro-api/serverless/index.js +12 -0
  46. package/lib/cjs/adapter-express/micro-api/serverless/vercel.adapter.js +102 -0
  47. package/lib/cjs/adapter-express/micro-api/service-mesh/index.js +10 -0
  48. package/lib/cjs/adapter-express/micro-api/service-mesh/service-client.js +194 -0
  49. package/lib/cjs/adapter-express/micro-api/service-mesh/service-discovery.js +261 -0
  50. package/lib/cjs/adapter-express/middleware/index.js +21 -0
  51. package/lib/cjs/adapter-express/middleware/request-logging.middleware.js +244 -0
  52. package/lib/cjs/adapter-express/render/engine.js +15 -15
  53. package/lib/cjs/adapter-express/render/index.js +5 -0
  54. package/lib/cjs/adapter-express/studio/index.js +9 -0
  55. package/lib/cjs/adapter-express/studio/studio-integration.js +214 -0
  56. package/lib/cjs/index.js +1 -1
  57. package/lib/cjs/types/adapter-express/application-express.base.d.ts +20 -7
  58. package/lib/cjs/types/adapter-express/application-express.d.ts +273 -32
  59. package/lib/cjs/types/adapter-express/express-utils/base-middleware.d.ts +2 -2
  60. package/lib/cjs/types/adapter-express/express-utils/conditional-middleware.d.ts +97 -0
  61. package/lib/cjs/types/adapter-express/express-utils/constants.d.ts +13 -0
  62. package/lib/cjs/types/adapter-express/express-utils/content-negotiation-decorators.d.ts +94 -0
  63. package/lib/cjs/types/adapter-express/express-utils/decorators.d.ts +54 -6
  64. package/lib/cjs/types/adapter-express/express-utils/exception-filter-decorators.d.ts +6 -0
  65. package/lib/cjs/types/adapter-express/express-utils/guard-context-factory.d.ts +17 -0
  66. package/lib/cjs/types/adapter-express/express-utils/guard-middleware.d.ts +22 -0
  67. package/lib/cjs/types/adapter-express/express-utils/guard-utils.d.ts +11 -0
  68. package/lib/cjs/types/adapter-express/express-utils/http-context-store.d.ts +20 -0
  69. package/lib/cjs/types/adapter-express/express-utils/httpResponseMessage.d.ts +1 -1
  70. package/lib/cjs/types/adapter-express/express-utils/index.d.ts +30 -2
  71. package/lib/cjs/types/adapter-express/express-utils/interceptor-middleware.d.ts +40 -0
  72. package/lib/cjs/types/adapter-express/express-utils/interfaces.d.ts +42 -5
  73. package/lib/cjs/types/adapter-express/express-utils/inversify-express-server.d.ts +114 -2
  74. package/lib/cjs/types/adapter-express/express-utils/lazy-module-middleware.d.ts +122 -0
  75. package/lib/cjs/types/adapter-express/express-utils/middleware-composition.d.ts +85 -0
  76. package/lib/cjs/types/adapter-express/express-utils/permission-preloader.middleware.d.ts +10 -0
  77. package/lib/cjs/types/adapter-express/express-utils/route-constraints.d.ts +89 -0
  78. package/lib/cjs/types/adapter-express/express-utils/scope-extractor.d.ts +21 -0
  79. package/lib/cjs/types/adapter-express/express-utils/scope-extractor.interface.d.ts +12 -0
  80. package/lib/cjs/types/adapter-express/express-utils/setup-authorization.d.ts +34 -0
  81. package/lib/cjs/types/adapter-express/express-utils/setup-event-system.d.ts +118 -0
  82. package/lib/cjs/types/adapter-express/express-utils/setup-interceptors.d.ts +115 -0
  83. package/lib/cjs/types/adapter-express/express-utils/setup-lazy-loading.d.ts +123 -0
  84. package/lib/cjs/types/adapter-express/express-utils/utils.d.ts +17 -2
  85. package/lib/cjs/types/adapter-express/express-utils/validation-decorators.d.ts +145 -0
  86. package/lib/cjs/types/adapter-express/express-utils/validation-service.d.ts +88 -0
  87. package/lib/cjs/types/adapter-express/index.d.ts +6 -4
  88. package/lib/cjs/types/adapter-express/micro-api/application-express-micro-route.d.ts +25 -14
  89. package/lib/cjs/types/adapter-express/micro-api/application-express-micro.d.ts +3 -10
  90. package/lib/cjs/types/adapter-express/micro-api/gateway/circuit-breaker.d.ts +111 -0
  91. package/lib/cjs/types/adapter-express/micro-api/gateway/index.d.ts +5 -0
  92. package/lib/cjs/types/adapter-express/micro-api/gateway/service-proxy.d.ts +83 -0
  93. package/lib/cjs/types/adapter-express/micro-api/index.d.ts +7 -1
  94. package/lib/cjs/types/adapter-express/micro-api/micro.d.ts +66 -0
  95. package/lib/cjs/types/adapter-express/micro-api/queue/index.d.ts +5 -0
  96. package/lib/cjs/types/adapter-express/micro-api/queue/queue.interface.d.ts +60 -0
  97. package/lib/cjs/types/adapter-express/micro-api/queue/rabbitmq-consumer.d.ts +86 -0
  98. package/lib/cjs/types/adapter-express/micro-api/serverless/aws-lambda.adapter.d.ts +77 -0
  99. package/lib/cjs/types/adapter-express/micro-api/serverless/cloudflare.adapter.d.ts +64 -0
  100. package/lib/cjs/types/adapter-express/micro-api/serverless/index.d.ts +6 -0
  101. package/lib/cjs/types/adapter-express/micro-api/serverless/vercel.adapter.d.ts +56 -0
  102. package/lib/cjs/types/adapter-express/micro-api/service-mesh/index.d.ts +5 -0
  103. package/lib/cjs/types/adapter-express/micro-api/service-mesh/service-client.d.ts +122 -0
  104. package/lib/cjs/types/adapter-express/micro-api/service-mesh/service-discovery.d.ts +150 -0
  105. package/lib/cjs/types/adapter-express/middleware/index.d.ts +5 -0
  106. package/lib/cjs/types/adapter-express/middleware/request-logging.middleware.d.ts +65 -0
  107. package/lib/cjs/types/adapter-express/render/index.d.ts +1 -0
  108. package/lib/cjs/types/adapter-express/studio/index.d.ts +1 -0
  109. package/lib/cjs/types/adapter-express/studio/studio-integration.d.ts +92 -0
  110. package/lib/cjs/types/index.d.ts +1 -1
  111. package/lib/esm/adapter-express/application-express.base.js +24 -0
  112. package/lib/esm/adapter-express/application-express.js +1300 -0
  113. package/lib/esm/adapter-express/application-express.types.js +1 -0
  114. package/lib/esm/adapter-express/express-utils/base-middleware.js +19 -0
  115. package/lib/esm/adapter-express/express-utils/conditional-middleware.js +96 -0
  116. package/lib/esm/adapter-express/express-utils/constants.js +63 -0
  117. package/lib/esm/adapter-express/express-utils/content/httpContent.js +6 -0
  118. package/lib/esm/adapter-express/express-utils/content-negotiation-decorators.js +120 -0
  119. package/lib/esm/adapter-express/express-utils/decorators.js +575 -0
  120. package/lib/esm/adapter-express/express-utils/exception-filter-decorators.js +6 -0
  121. package/lib/esm/adapter-express/express-utils/guard-context-factory.js +83 -0
  122. package/lib/esm/adapter-express/express-utils/guard-middleware.js +115 -0
  123. package/lib/esm/adapter-express/express-utils/guard-utils.js +14 -0
  124. package/lib/esm/adapter-express/express-utils/http-context-store.js +10 -0
  125. package/lib/esm/adapter-express/express-utils/http-status-middleware.js +116 -0
  126. package/lib/esm/adapter-express/express-utils/httpResponseMessage.js +29 -0
  127. package/lib/esm/adapter-express/express-utils/index.js +24 -0
  128. package/lib/esm/adapter-express/express-utils/interceptor-middleware.js +130 -0
  129. package/lib/esm/adapter-express/express-utils/interfaces.js +1 -0
  130. package/lib/esm/adapter-express/express-utils/inversify-express-server.js +1031 -0
  131. package/lib/esm/adapter-express/express-utils/lazy-module-middleware.js +236 -0
  132. package/lib/esm/adapter-express/express-utils/middleware-composition.js +89 -0
  133. package/lib/esm/adapter-express/express-utils/permission-preloader.middleware.js +45 -0
  134. package/lib/esm/adapter-express/express-utils/resolver-multer.js +30 -0
  135. package/lib/esm/adapter-express/express-utils/route-constraints.js +91 -0
  136. package/lib/esm/adapter-express/express-utils/scope-extractor.interface.js +1 -0
  137. package/lib/esm/adapter-express/express-utils/scope-extractor.js +63 -0
  138. package/lib/esm/adapter-express/express-utils/setup-authorization.js +68 -0
  139. package/lib/esm/adapter-express/express-utils/setup-event-system.js +110 -0
  140. package/lib/esm/adapter-express/express-utils/setup-interceptors.js +100 -0
  141. package/lib/esm/adapter-express/express-utils/setup-lazy-loading.js +225 -0
  142. package/lib/esm/adapter-express/express-utils/utils.js +68 -0
  143. package/lib/esm/adapter-express/express-utils/validation-decorators.js +199 -0
  144. package/lib/esm/adapter-express/express-utils/validation-service.js +251 -0
  145. package/lib/esm/adapter-express/index.js +7 -0
  146. package/lib/esm/adapter-express/micro-api/application-express-micro-container.js +48 -0
  147. package/lib/esm/adapter-express/micro-api/application-express-micro-route.js +128 -0
  148. package/lib/esm/adapter-express/micro-api/application-express-micro.js +161 -0
  149. package/lib/esm/adapter-express/micro-api/gateway/circuit-breaker.js +174 -0
  150. package/lib/esm/adapter-express/micro-api/gateway/index.js +5 -0
  151. package/lib/esm/adapter-express/micro-api/gateway/service-proxy.js +210 -0
  152. package/lib/esm/adapter-express/micro-api/index.js +10 -0
  153. package/lib/esm/adapter-express/micro-api/micro.js +211 -0
  154. package/lib/esm/adapter-express/micro-api/queue/index.js +4 -0
  155. package/lib/esm/adapter-express/micro-api/queue/queue.interface.js +1 -0
  156. package/lib/esm/adapter-express/micro-api/queue/rabbitmq-consumer.js +229 -0
  157. package/lib/esm/adapter-express/micro-api/serverless/aws-lambda.adapter.js +180 -0
  158. package/lib/esm/adapter-express/micro-api/serverless/cloudflare.adapter.js +155 -0
  159. package/lib/esm/adapter-express/micro-api/serverless/index.js +6 -0
  160. package/lib/esm/adapter-express/micro-api/serverless/vercel.adapter.js +99 -0
  161. package/lib/esm/adapter-express/micro-api/service-mesh/index.js +5 -0
  162. package/lib/esm/adapter-express/micro-api/service-mesh/service-client.js +191 -0
  163. package/lib/esm/adapter-express/micro-api/service-mesh/service-discovery.js +259 -0
  164. package/lib/esm/adapter-express/middleware/index.js +5 -0
  165. package/lib/esm/adapter-express/middleware/request-logging.middleware.js +239 -0
  166. package/lib/esm/adapter-express/render/constants.js +37 -0
  167. package/lib/esm/adapter-express/render/engine.js +51 -0
  168. package/lib/esm/adapter-express/render/index.js +1 -0
  169. package/lib/esm/adapter-express/render/resolve-render.js +30 -0
  170. package/lib/esm/adapter-express/studio/index.js +1 -0
  171. package/lib/esm/adapter-express/studio/studio-integration.js +184 -0
  172. package/lib/esm/index.mjs +1 -0
  173. package/lib/esm/package.json +3 -0
  174. package/lib/esm/types/adapter-express/application-express.base.d.ts +77 -0
  175. package/lib/esm/types/adapter-express/application-express.d.ts +411 -0
  176. package/lib/esm/types/adapter-express/application-express.types.d.ts +23 -0
  177. package/lib/esm/types/adapter-express/express-utils/base-middleware.d.ts +8 -0
  178. package/lib/esm/types/adapter-express/express-utils/conditional-middleware.d.ts +97 -0
  179. package/lib/esm/types/adapter-express/express-utils/constants.d.ts +57 -0
  180. package/lib/esm/types/adapter-express/express-utils/content/httpContent.d.ts +6 -0
  181. package/lib/esm/types/adapter-express/express-utils/content-negotiation-decorators.d.ts +94 -0
  182. package/lib/esm/types/adapter-express/express-utils/decorators.d.ts +257 -0
  183. package/lib/esm/types/adapter-express/express-utils/exception-filter-decorators.d.ts +6 -0
  184. package/lib/esm/types/adapter-express/express-utils/guard-context-factory.d.ts +17 -0
  185. package/lib/esm/types/adapter-express/express-utils/guard-middleware.d.ts +22 -0
  186. package/lib/esm/types/adapter-express/express-utils/guard-utils.d.ts +11 -0
  187. package/lib/esm/types/adapter-express/express-utils/http-context-store.d.ts +20 -0
  188. package/lib/esm/types/adapter-express/express-utils/http-status-middleware.d.ts +26 -0
  189. package/lib/esm/types/adapter-express/express-utils/httpResponseMessage.d.ts +14 -0
  190. package/lib/esm/types/adapter-express/express-utils/index.d.ts +30 -0
  191. package/lib/esm/types/adapter-express/express-utils/interceptor-middleware.d.ts +40 -0
  192. package/lib/esm/types/adapter-express/express-utils/interfaces.d.ts +115 -0
  193. package/lib/esm/types/adapter-express/express-utils/inversify-express-server.d.ts +172 -0
  194. package/lib/esm/types/adapter-express/express-utils/lazy-module-middleware.d.ts +122 -0
  195. package/lib/esm/types/adapter-express/express-utils/middleware-composition.d.ts +85 -0
  196. package/lib/esm/types/adapter-express/express-utils/permission-preloader.middleware.d.ts +10 -0
  197. package/lib/esm/types/adapter-express/express-utils/resolver-multer.d.ts +7 -0
  198. package/lib/esm/types/adapter-express/express-utils/route-constraints.d.ts +89 -0
  199. package/lib/esm/types/adapter-express/express-utils/scope-extractor.d.ts +21 -0
  200. package/lib/esm/types/adapter-express/express-utils/scope-extractor.interface.d.ts +12 -0
  201. package/lib/esm/types/adapter-express/express-utils/setup-authorization.d.ts +34 -0
  202. package/lib/esm/types/adapter-express/express-utils/setup-event-system.d.ts +118 -0
  203. package/lib/esm/types/adapter-express/express-utils/setup-interceptors.d.ts +115 -0
  204. package/lib/esm/types/adapter-express/express-utils/setup-lazy-loading.d.ts +123 -0
  205. package/lib/esm/types/adapter-express/express-utils/utils.d.ts +24 -0
  206. package/lib/esm/types/adapter-express/express-utils/validation-decorators.d.ts +145 -0
  207. package/lib/esm/types/adapter-express/express-utils/validation-service.d.ts +88 -0
  208. package/lib/esm/types/adapter-express/index.d.ts +7 -0
  209. package/lib/esm/types/adapter-express/micro-api/application-express-micro-container.d.ts +47 -0
  210. package/lib/esm/types/adapter-express/micro-api/application-express-micro-route.d.ts +104 -0
  211. package/lib/esm/types/adapter-express/micro-api/application-express-micro.d.ts +72 -0
  212. package/lib/esm/types/adapter-express/micro-api/gateway/circuit-breaker.d.ts +111 -0
  213. package/lib/esm/types/adapter-express/micro-api/gateway/index.d.ts +5 -0
  214. package/lib/esm/types/adapter-express/micro-api/gateway/service-proxy.d.ts +83 -0
  215. package/lib/esm/types/adapter-express/micro-api/index.d.ts +7 -0
  216. package/lib/esm/types/adapter-express/micro-api/micro.d.ts +66 -0
  217. package/lib/esm/types/adapter-express/micro-api/queue/index.d.ts +5 -0
  218. package/lib/esm/types/adapter-express/micro-api/queue/queue.interface.d.ts +60 -0
  219. package/lib/esm/types/adapter-express/micro-api/queue/rabbitmq-consumer.d.ts +86 -0
  220. package/lib/esm/types/adapter-express/micro-api/serverless/aws-lambda.adapter.d.ts +77 -0
  221. package/lib/esm/types/adapter-express/micro-api/serverless/cloudflare.adapter.d.ts +64 -0
  222. package/lib/esm/types/adapter-express/micro-api/serverless/index.d.ts +6 -0
  223. package/lib/esm/types/adapter-express/micro-api/serverless/vercel.adapter.d.ts +56 -0
  224. package/lib/esm/types/adapter-express/micro-api/service-mesh/index.d.ts +5 -0
  225. package/lib/esm/types/adapter-express/micro-api/service-mesh/service-client.d.ts +122 -0
  226. package/lib/esm/types/adapter-express/micro-api/service-mesh/service-discovery.d.ts +150 -0
  227. package/lib/esm/types/adapter-express/middleware/index.d.ts +5 -0
  228. package/lib/esm/types/adapter-express/middleware/request-logging.middleware.d.ts +65 -0
  229. package/lib/esm/types/adapter-express/render/constants.d.ts +26 -0
  230. package/lib/esm/types/adapter-express/render/engine.d.ts +20 -0
  231. package/lib/esm/types/adapter-express/render/index.d.ts +5 -0
  232. package/lib/esm/types/adapter-express/render/resolve-render.d.ts +7 -0
  233. package/lib/esm/types/adapter-express/studio/index.d.ts +1 -0
  234. package/lib/esm/types/adapter-express/studio/studio-integration.d.ts +92 -0
  235. package/lib/esm/types/index.d.ts +1 -0
  236. package/lib/package.json +156 -146
  237. package/package.json +156 -146
  238. package/lib/cjs/di/di.interfaces.js +0 -10
  239. package/lib/cjs/types/di/di.interfaces.d.ts +0 -289
@@ -0,0 +1,1300 @@
1
+ import express from "express";
2
+ import * as fs from "node:fs";
3
+ // Note: We use the global `process` object directly instead of importing it
4
+ // because signal handlers (SIGTERM, SIGINT, etc.) don't work correctly when
5
+ // process is imported as an ES module in CommonJS compiled code.
6
+ import { AppContainer, Console, INTERCEPTOR_METADATA_KEY, LifecycleRegistry, Logger, Middleware, ProviderManager, BannerGenerator, MetricsCollector, resolveBannerConfig, } from "@expressots/core";
7
+ /**
8
+ * Metadata key used by `@provide` (and friends) in `@expressots/core`'s
9
+ * binding-decorator module. The constant lives at an internal path
10
+ * (`di/binding-decorator/constants`) so we hardcode the string here —
11
+ * it's part of the framework's stable runtime contract and is what
12
+ * `MetricsCollector` reads for its own `providers` count.
13
+ */
14
+ const PROVIDE_METADATA_KEY = "inversify-binding-decorators:provide";
15
+ import { RenderEngine } from "@expressots/shared";
16
+ import { HttpStatusCodeMiddleware } from "./express-utils/http-status-middleware.js";
17
+ import { InversifyExpressServer } from "./express-utils/inversify-express-server.js";
18
+ import { setEngineEjs, setEngineHandlebars, setEnginePug } from "./render/engine.js";
19
+ import { getControllersFromMetadata, getControllersFromContainer, getControllerMethodMetadata, getControllerMetadata, } from "./express-utils/utils.js";
20
+ import { initializeStudio, stopStudio, isStudioEnabled, reportStudioRuntimeInfo, } from "./studio/index.js";
21
+ /**
22
+ * The AppExpress class provides methods for configuring and running an Express application.
23
+ * @class AppExpress
24
+ * @implements {IWebServer} - Interface for the WebServer application implementation.
25
+ * @extends {ApplicationBase} - Base class for the application implementation that provides lifecycle hooks.
26
+ * @method configure - Configures the InversifyJS container.
27
+ * @method listen - Start listening on the given port and environment.
28
+ * @method setGlobalRoutePrefix - Sets the global route prefix for the application.
29
+ * @method setEngine - Configures the application's view engine based on the provided configuration options.
30
+ * @method isDevelopment - Verifies if the current environment is development.
31
+ */
32
+ export class AppExpress {
33
+ logger = new Logger();
34
+ console = new Console();
35
+ app;
36
+ serverInstance = null;
37
+ port;
38
+ environment;
39
+ appContainer;
40
+ globalPrefix = "/";
41
+ middlewareManager;
42
+ middlewares = [];
43
+ providerManager;
44
+ renderOptions = {};
45
+ lifecycleRegistry;
46
+ isShuttingDown = false;
47
+ bannerGenerator = null;
48
+ bannerConfig;
49
+ shutdownHandlers = new Map();
50
+ studioConfig = {};
51
+ /**
52
+ * Latest snapshot of application metrics produced by `MetricsCollector`
53
+ * during banner display. We cache it so that `reportStudioRuntimeInfo`
54
+ * can forward the *runtime* provider/interceptor counts (which match
55
+ * what the CLI banner shows) to the Studio Agent without recomputing.
56
+ */
57
+ lastApplicationMetrics = null;
58
+ /** Track active connections for force-close during shutdown */
59
+ activeConnections = new Set();
60
+ /** Timeout for force-closing connections during shutdown (ms) */
61
+ shutdownTimeout = 5000;
62
+ /** Number of retries when port is in use (for hot-reload scenarios) */
63
+ portRetryAttempts = 10;
64
+ /** Delay between port retry attempts (ms) */
65
+ portRetryDelay = 500;
66
+ // Log buffering for banner-first display
67
+ // IMPORTANT: All these properties must be declared BEFORE initBuffering!
68
+ static originalStdoutWrite = null;
69
+ static originalStderrWrite = null;
70
+ static logBuffer = [];
71
+ static isBuffering = false;
72
+ static bufferingInitialized = false;
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ static originalGlobalConsole = null;
75
+ // Initialize buffering when AppExpress class is loaded (before any instances are created)
76
+ // This ensures ALL logs are buffered from the very beginning for the full template.
77
+ // The micro() function explicitly disables this buffering since it doesn't use the banner system.
78
+ // This MUST be declared AFTER all the static properties it uses!
79
+ static initBuffering = (() => {
80
+ AppExpress.startLogBuffering();
81
+ return true;
82
+ })();
83
+ /**
84
+ * Disable log buffering. Called by micro() to restore normal console output
85
+ * since micro API doesn't use the banner system.
86
+ * @public API
87
+ */
88
+ static disableBuffering() {
89
+ AppExpress.stopBuffering();
90
+ // Clear any buffered logs since micro() doesn't need them
91
+ AppExpress.logBuffer = [];
92
+ }
93
+ /**
94
+ * Start buffering all console output.
95
+ * This captures both console.log and direct process.stdout.write calls.
96
+ * @private
97
+ */
98
+ static startLogBuffering() {
99
+ if (AppExpress.isBuffering)
100
+ return;
101
+ // Store original streams
102
+ AppExpress.originalStdoutWrite = process.stdout.write.bind(process.stdout);
103
+ AppExpress.originalStderrWrite = process.stderr.write.bind(process.stderr);
104
+ // Create wrapper functions that use fs.writeSync directly (always works
105
+ // in both CJS and ESM scope - hence the static `node:fs` import above).
106
+ const createOriginalConsoleMethod = (useStderr = false) => (...args) => {
107
+ const message = args
108
+ .map((a) => {
109
+ if (typeof a === "object" && a !== null) {
110
+ try {
111
+ return JSON.stringify(a, null, 2);
112
+ }
113
+ catch {
114
+ return String(a);
115
+ }
116
+ }
117
+ return String(a);
118
+ })
119
+ .join(" ") + "\n";
120
+ // Use fs.writeSync directly - this always works
121
+ fs.writeSync(useStderr ? 2 : 1, message);
122
+ };
123
+ AppExpress.originalGlobalConsole = {
124
+ log: createOriginalConsoleMethod(false),
125
+ info: createOriginalConsoleMethod(false),
126
+ warn: createOriginalConsoleMethod(true),
127
+ error: createOriginalConsoleMethod(true),
128
+ debug: createOriginalConsoleMethod(false),
129
+ };
130
+ AppExpress.logBuffer = [];
131
+ AppExpress.isBuffering = true;
132
+ // Create buffering functions for console methods
133
+ const bufferConsoleMethod = () => (...args) => {
134
+ const message = args
135
+ .map((a) => (typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)))
136
+ .join(" ") + "\n";
137
+ AppExpress.logBuffer.push(message);
138
+ };
139
+ // Override console methods directly (not replacing the console object)
140
+ // This ensures even cached references to console.log will use the buffered version
141
+ console.log = bufferConsoleMethod();
142
+ console.info = bufferConsoleMethod();
143
+ console.warn = bufferConsoleMethod();
144
+ console.error = bufferConsoleMethod();
145
+ console.debug = bufferConsoleMethod();
146
+ // Also override process.stdout.write for direct writes (like our Logger)
147
+ const bufferWrite = (chunk) => {
148
+ const str = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString();
149
+ AppExpress.logBuffer.push(str);
150
+ return true;
151
+ };
152
+ // Use direct assignment for overriding
153
+ process.stdout.write = bufferWrite;
154
+ process.stderr.write = bufferWrite;
155
+ }
156
+ /**
157
+ * Stop buffering but keep the buffered logs for later flushing.
158
+ * This restores normal console/stdout output.
159
+ * @private
160
+ */
161
+ static stopBuffering() {
162
+ if (!AppExpress.isBuffering)
163
+ return;
164
+ // Restore original console methods using our wrapper functions
165
+ if (AppExpress.originalGlobalConsole) {
166
+ console.log = AppExpress.originalGlobalConsole.log;
167
+ console.info = AppExpress.originalGlobalConsole.info;
168
+ console.warn = AppExpress.originalGlobalConsole.warn;
169
+ console.error = AppExpress.originalGlobalConsole.error;
170
+ console.debug = AppExpress.originalGlobalConsole.debug;
171
+ }
172
+ // Restore original stdout/stderr by direct assignment
173
+ // (Object.defineProperty may not work correctly for stream.write)
174
+ if (AppExpress.originalStdoutWrite) {
175
+ process.stdout.write =
176
+ AppExpress.originalStdoutWrite;
177
+ }
178
+ if (AppExpress.originalStderrWrite) {
179
+ process.stderr.write =
180
+ AppExpress.originalStderrWrite;
181
+ }
182
+ AppExpress.isBuffering = false;
183
+ }
184
+ /**
185
+ * Flush all buffered logs to stdout.
186
+ * Should be called after stopBuffering() and after displaying the banner.
187
+ * @private
188
+ */
189
+ static flushBufferedLogs() {
190
+ const logs = AppExpress.logBuffer;
191
+ AppExpress.logBuffer = [];
192
+ for (const log of logs) {
193
+ if (AppExpress.originalStdoutWrite) {
194
+ AppExpress.originalStdoutWrite.call(process.stdout, log);
195
+ }
196
+ else {
197
+ process.stdout.write(log);
198
+ }
199
+ }
200
+ }
201
+ constructor() {
202
+ // Buffering is already started via static initialization (initBuffering)
203
+ // This ensures ALL logs are captured from the very beginning
204
+ this.globalConfiguration();
205
+ }
206
+ /**
207
+ * Helper function to handle both sync and async method calls.
208
+ * If the result is a Promise, awaits it; otherwise returns immediately.
209
+ * @private
210
+ */
211
+ async handleSyncOrAsync(result) {
212
+ if (result instanceof Promise) {
213
+ return await result;
214
+ }
215
+ }
216
+ /**
217
+ * Implement this method to set up global configurations for the server.
218
+ * This method is called synchronously in the constructor before any other
219
+ * server initialization methods. Use this method to configure global settings
220
+ * that apply to the entire server application.
221
+ *
222
+ * Note: This method is synchronous and called during object construction.
223
+ * For asynchronous initialization, use `configureServices()` instead.
224
+ *
225
+ * @abstract
226
+ * @returns {void}
227
+ * @public API
228
+ */
229
+ globalConfiguration() { }
230
+ /**
231
+ * Implement this method to set up required services or configurations before
232
+ * the server starts. This is essential for initializing dependencies or settings
233
+ * necessary for server operation. Supports both synchronous and asynchronous setup.
234
+ *
235
+ * @abstract
236
+ * @returns {void | Promise<void>}
237
+ * @public API
238
+ */
239
+ configureServices() { }
240
+ /**
241
+ * Implement this method to execute actions or configurations after the server
242
+ * has started. Use this for operations that need to run once the server is
243
+ * operational. Supports both synchronous and asynchronous execution.
244
+ *
245
+ * @abstract
246
+ * @returns {void | Promise<void>}
247
+ * @public API
248
+ */
249
+ postServerInitialization() { }
250
+ /**
251
+ * Implement this method to handle cleanup and final actions when the server
252
+ * is shutting down. Ideal for closing resources, stopping tasks, or other
253
+ * cleanup procedures to ensure a graceful server shutdown. Supports both
254
+ * synchronous and asynchronous cleanup.
255
+ *
256
+ * The signal parameter indicates what triggered the shutdown:
257
+ * - SIGTERM: Graceful termination (e.g., Kubernetes pod shutdown)
258
+ * - SIGINT: User interrupt (e.g., Ctrl+C)
259
+ * - SIGHUP: Terminal hangup
260
+ * - SIGQUIT: Quit with core dump
261
+ * - SIGBREAK: Windows break signal
262
+ *
263
+ * @abstract
264
+ * @param signal - The signal that triggered the shutdown (optional for backward compatibility)
265
+ * @returns {void | Promise<void>}
266
+ * @public API
267
+ */
268
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
269
+ serverShutdown(signal) { }
270
+ /**
271
+ * Performs graceful shutdown of the application.
272
+ *
273
+ * Shutdown sequence:
274
+ * 1. Execute lifecycle shutdown hooks on all IShutdown providers
275
+ * 2. Call user's serverShutdown hook
276
+ * 3. Close the HTTP server to stop accepting new connections
277
+ *
278
+ * @param signal - The signal that triggered the shutdown
279
+ * @returns Promise that resolves when shutdown is complete
280
+ * @internal
281
+ */
282
+ async handleExit(signal) {
283
+ // 1. Stop Studio Agent if running
284
+ await stopStudio();
285
+ // 2. Execute lifecycle shutdown hooks on all IShutdown providers
286
+ if (this.lifecycleRegistry) {
287
+ await this.lifecycleRegistry.executeShutdown(signal);
288
+ }
289
+ // 3. Call user's serverShutdown hook
290
+ await this.handleSyncOrAsync(this.serverShutdown(signal));
291
+ // 4. Gracefully close the HTTP server with connection force-close
292
+ if (this.serverInstance) {
293
+ await new Promise((resolve) => {
294
+ // Set a timeout to force-destroy connections if graceful shutdown takes too long
295
+ const forceCloseTimeout = setTimeout(() => {
296
+ console.log(`⚠️ Force-closing ${this.activeConnections.size} active connections after ${this.shutdownTimeout}ms timeout`);
297
+ this.destroyAllConnections();
298
+ resolve();
299
+ }, this.shutdownTimeout);
300
+ // Try graceful close first
301
+ this.serverInstance.close((err) => {
302
+ clearTimeout(forceCloseTimeout);
303
+ if (err) {
304
+ // Don't fail on close error during shutdown - just log it
305
+ console.log(`Note: Server close returned: ${err.message}`);
306
+ }
307
+ resolve();
308
+ });
309
+ // Immediately destroy idle connections (keep-alive connections with no pending requests)
310
+ // This speeds up shutdown significantly
311
+ this.serverInstance.closeIdleConnections?.();
312
+ });
313
+ // Clear all remaining connections
314
+ this.destroyAllConnections();
315
+ }
316
+ }
317
+ /**
318
+ * Destroy all active connections immediately.
319
+ * Used during forced shutdown.
320
+ * @private
321
+ */
322
+ destroyAllConnections() {
323
+ for (const socket of this.activeConnections) {
324
+ try {
325
+ socket.destroy();
326
+ }
327
+ catch {
328
+ // Ignore errors during connection destruction
329
+ }
330
+ }
331
+ this.activeConnections.clear();
332
+ }
333
+ /**
334
+ * Track a new connection for shutdown management.
335
+ * @private
336
+ */
337
+ trackConnection(socket) {
338
+ this.activeConnections.add(socket);
339
+ socket.once("close", () => {
340
+ this.activeConnections.delete(socket);
341
+ });
342
+ }
343
+ /**
344
+ * Initialize the InversifyJS container with the provided modules and options.
345
+ * @param appModules - An array of application modules to be loaded into the container.
346
+ * @param containerOptions - Container global configuration options.
347
+ * @option skipBaseClassChecks - Skip the base class checks for the container.
348
+ * @option autoBindInjectable - Automatically bind the injectable classes.
349
+ * @option defaultScope - The default scope to use for bindings.
350
+ *
351
+ * @returns The configured AppContainer instance.
352
+ * @public API
353
+ */
354
+ configContainer(appModules, containerOptions) {
355
+ this.appContainer = new AppContainer(containerOptions ? containerOptions : {});
356
+ if (!appModules) {
357
+ this.logger.error("No modules provided for container configuration", "adapter-express");
358
+ return;
359
+ }
360
+ this.appContainer.create(appModules);
361
+ this.providerManager = new ProviderManager(this.appContainer.Container);
362
+ const baseMiddleware = new Middleware();
363
+ // Create a wrapper that automatically injects container for exception filters
364
+ this.middlewareManager = this.createMiddlewareWrapper(baseMiddleware);
365
+ // Initialize lifecycle registry and discover providers implementing IBootstrap/IShutdown
366
+ this.lifecycleRegistry = new LifecycleRegistry(this.appContainer.Container);
367
+ this.lifecycleRegistry.discover();
368
+ return this.appContainer;
369
+ }
370
+ /**
371
+ * Creates a middleware wrapper that automatically injects container when exception filters are enabled
372
+ * This allows users to simply set enableExceptionFilters: true without manually passing the container
373
+ */
374
+ createMiddlewareWrapper(baseMiddleware) {
375
+ const container = this.appContainer?.Container;
376
+ // Create a proxy that intercepts setErrorHandler calls
377
+ return new Proxy(baseMiddleware, {
378
+ get(target, prop) {
379
+ if (prop === "setErrorHandler") {
380
+ return function (options) {
381
+ // Automatically inject container if enableExceptionFilters is true and container is available
382
+ const enhancedOptions = {
383
+ ...options,
384
+ container: options?.enableExceptionFilters && container ? container : options?.container,
385
+ };
386
+ target.setErrorHandler(enhancedOptions);
387
+ };
388
+ }
389
+ // Forward all other property access to the base middleware
390
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
391
+ const value = target[prop];
392
+ return typeof value === "function" ? value.bind(target) : value;
393
+ },
394
+ });
395
+ }
396
+ /**
397
+ * Get the ProviderManager instance.
398
+ * @returns The ProviderManager instance.
399
+ * @public API
400
+ */
401
+ get Provider() {
402
+ return this.providerManager;
403
+ }
404
+ /**
405
+ * Get the Middleware instance.
406
+ * @returns The Middleware instance.
407
+ * @public API
408
+ */
409
+ get Middleware() {
410
+ return this.middlewareManager;
411
+ }
412
+ /**
413
+ * Configures the Express application with the provided middleware entries.
414
+ * @param app - The Express application instance.
415
+ * @param middlewareEntries - An array of Express middleware entries to be applied.
416
+ */
417
+ async configureMiddleware(app, middlewareEntries) {
418
+ for (const entry of middlewareEntries) {
419
+ if (typeof entry === "function") {
420
+ app.use(entry);
421
+ // eslint-disable-next-line no-prototype-builtins
422
+ }
423
+ else if (entry?.hasOwnProperty("path")) {
424
+ const { path, middlewares } = entry;
425
+ const pathGlobal = this.globalPrefix + path;
426
+ for (const mid of middlewares) {
427
+ if (path) {
428
+ if (typeof mid === "function") {
429
+ app.use(pathGlobal, mid);
430
+ }
431
+ else {
432
+ const middleware = mid;
433
+ // Check if it's a BaseMiddleware instance (has handler method, not use)
434
+ const middlewareRecord = middleware;
435
+ if (middlewareRecord.handler && typeof middlewareRecord.handler === "function") {
436
+ // BaseMiddleware instance - wrap handler method
437
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
438
+ const baseMiddleware = middleware;
439
+ app.use(pathGlobal, (req, res, next) => {
440
+ baseMiddleware.handler(req, res, next);
441
+ });
442
+ }
443
+ else if (middleware.use) {
444
+ middleware.use = middleware.use.bind(middleware);
445
+ app.use(pathGlobal, middleware.use);
446
+ }
447
+ else {
448
+ this.logger.warn(`Middleware ${middleware.constructor?.name || "unknown"} does not have a 'use' or 'handler' method`, "application-express");
449
+ }
450
+ }
451
+ }
452
+ }
453
+ }
454
+ else {
455
+ const middleware = entry;
456
+ // Check if it's a BaseMiddleware instance (has handler method, not use)
457
+ // BaseMiddleware instances are handled specially in inversify-express-server
458
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
459
+ if (middleware.handler && typeof middleware.handler === "function") {
460
+ // BaseMiddleware instance - wrap handler method
461
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
462
+ const baseMiddleware = middleware;
463
+ app.use((req, res, next) => {
464
+ baseMiddleware.handler(req, res, next);
465
+ });
466
+ }
467
+ else if (middleware.use) {
468
+ middleware.use = middleware.use.bind(middleware);
469
+ app.use(middleware.use);
470
+ }
471
+ else {
472
+ this.logger.warn(`Middleware ${middleware.constructor?.name || "unknown"} does not have a 'use' or 'handler' method`, "application-express");
473
+ }
474
+ }
475
+ }
476
+ }
477
+ /**
478
+ * Create and configure the Express application.
479
+ * @param container - The InversifyJS container.
480
+ * @param middlewares - An array of Express middlewares to be applied.
481
+ * @returns The configured Application instance.
482
+ */
483
+ async init() {
484
+ if (!this.appContainer) {
485
+ this.logger.error("No container provided for application configuration", "adapter-express");
486
+ process.exit(1);
487
+ }
488
+ // Create Express app early so it's available during configureServices for render()
489
+ const tempApp = express();
490
+ this.Middleware.setExpressApp(tempApp);
491
+ // Initialize Studio Agent if available (adds middleware before user
492
+ // middlewares). At this point we already know the port the user asked
493
+ // us to listen on (set in `listen()` before `init()` is invoked) and
494
+ // the configured global prefix, so forward both — the agent uses them
495
+ // to populate the Studio Status page.
496
+ await initializeStudio(tempApp, {
497
+ ...this.studioConfig,
498
+ appPort: this.port,
499
+ globalPrefix: this.globalPrefix,
500
+ }, this.appContainer);
501
+ await this.handleSyncOrAsync(this.configureServices());
502
+ const sortedMiddlewarePipeline = this.Middleware.getMiddlewarePipeline();
503
+ const pipeline = sortedMiddlewarePipeline.map((entry) => entry.middleware);
504
+ this.middlewares.push(...pipeline);
505
+ /* Apply the status code to the response */
506
+ this.middlewares.unshift(new HttpStatusCodeMiddleware(this.globalPrefix));
507
+ const expressServer = new InversifyExpressServer(this.appContainer.Container, null, { rootPath: this.globalPrefix }, tempApp);
508
+ // Pass ContentNegotiationService to InversifyExpressServer if available
509
+ const contentNegotiationService = this.Middleware.getContentNegotiationService();
510
+ if (contentNegotiationService) {
511
+ expressServer.setContentNegotiationService(contentNegotiationService);
512
+ }
513
+ // Pass ValidationService to InversifyExpressServer if validation is configured
514
+ const validationConfig = this.Middleware.getValidationConfig?.();
515
+ if (validationConfig) {
516
+ // `.js` extension required by NodeNext for ESM consumers; the
517
+ // CJS build accepts it unchanged.
518
+ const { ValidationService } = await import("./express-utils/validation-service.js");
519
+ const { ClassValidatorAdapter } = await import("@expressots/core");
520
+ const validationService = new ValidationService();
521
+ validationService.enable(validationConfig);
522
+ // Register ClassValidatorAdapter by default
523
+ const classValidatorAdapter = new ClassValidatorAdapter();
524
+ validationService.getRegistry().register(classValidatorAdapter);
525
+ // Register any additional adapters from config
526
+ if (validationConfig.adapters) {
527
+ for (const AdapterClass of validationConfig.adapters) {
528
+ const adapter = new AdapterClass();
529
+ validationService.getRegistry().register(adapter);
530
+ }
531
+ }
532
+ expressServer.setValidationService(validationService);
533
+ }
534
+ expressServer.setConfig((app) => {
535
+ this.configureMiddleware(app, this.middlewares);
536
+ });
537
+ expressServer.setErrorConfig((app) => {
538
+ if (this.Middleware.getErrorHandler()) {
539
+ app.use(this.Middleware.getErrorHandler());
540
+ }
541
+ });
542
+ this.app = expressServer.build();
543
+ return this;
544
+ }
545
+ /**
546
+ * Start listening on the given port and environment.
547
+ * @param port - The port number to listen on.
548
+ * @param appInfo - Optional message to display the app name and version.
549
+ * @public API
550
+ */
551
+ async listen(port, appInfo) {
552
+ // Capture wall-clock start so we can report total boot duration to the
553
+ // Studio Status page once `app.listen()` resolves with the actual port.
554
+ const listenStartedAt = Date.now();
555
+ // Close existing server instance if it exists
556
+ if (this.serverInstance) {
557
+ this.logger.warn("Closing existing server instance before starting new one", "adapter-express");
558
+ await this.closeExistingServer();
559
+ this.logger.info("✓ Application reloaded", "adapter-express");
560
+ }
561
+ // Remove old signal handlers to prevent duplicates
562
+ this.removeShutdownHandlers();
563
+ // Reset shutdown flag
564
+ this.isShuttingDown = false;
565
+ // Resolve banner configuration with environment-specific overrides
566
+ const resolvedBannerConfig = resolveBannerConfig(this.bannerConfig, this.environment || "development");
567
+ // Initialize banner generator with resolved config
568
+ this.bannerGenerator = new BannerGenerator(resolvedBannerConfig);
569
+ this.environment = this.environment || "development";
570
+ this.port = typeof port === "string" ? parseInt(port, 10) : port;
571
+ try {
572
+ await this.init();
573
+ await this.configEngine();
574
+ this.app.set("env", this.environment);
575
+ // Stop buffering and restore normal output (but don't flush yet)
576
+ AppExpress.stopBuffering();
577
+ // Flush all buffered logs that were captured during initialization
578
+ AppExpress.flushBufferedLogs();
579
+ }
580
+ catch (error) {
581
+ // Ensure buffering is stopped and logs are flushed even on error
582
+ AppExpress.stopBuffering();
583
+ AppExpress.flushBufferedLogs();
584
+ throw error;
585
+ }
586
+ // Ensure port is available (handles hot-reload scenarios)
587
+ // This will kill the previous process if needed - safest approach for dev experience
588
+ const portAvailable = await this.ensurePortAvailable(this.port);
589
+ if (!portAvailable) {
590
+ const errorMessage = `Port ${this.port} is still in use and could not be freed`;
591
+ this.logger.error(errorMessage, "adapter-express");
592
+ this.logger.info("💡 Try manually killing the process:", "adapter-express");
593
+ this.logger.info(process.platform === "win32"
594
+ ? ` netstat -ano | findstr :${this.port} && taskkill /F /PID <pid>`
595
+ : ` lsof -ti:${this.port} | xargs kill -9`, "adapter-express");
596
+ throw new Error(errorMessage);
597
+ }
598
+ return new Promise((resolve, reject) => {
599
+ this.serverInstance = this.app.listen(this.port, async () => {
600
+ // Track all connections for graceful shutdown
601
+ // This enables force-closing connections during hot-reload
602
+ this.serverInstance.on("connection", (socket) => {
603
+ this.trackConnection(socket);
604
+ });
605
+ // Update port with actual assigned port (important for port 0 auto-assign)
606
+ this.port = this.serverInstance?.address()?.port || this.port;
607
+ // Display startup banner AFTER server starts (so we have the correct port)
608
+ this.displayStartupBanner(appInfo);
609
+ // Push live runtime details to the Studio Agent so the Status
610
+ // page swaps "—" for real values. We forward the same numbers
611
+ // `MetricsCollector` produced for the CLI banner (providers,
612
+ // interceptors, middleware) — they come from DI metadata at
613
+ // runtime and include framework-registered items that the
614
+ // agent's static file scan can't see. We also forward the
615
+ // *names* of those items so the Studio drill-down can list
616
+ // them. No-op when Studio is disabled or when the installed
617
+ // agent is too old to support it.
618
+ reportStudioRuntimeInfo({
619
+ appPort: this.port,
620
+ globalPrefix: this.globalPrefix,
621
+ startupMs: Date.now() - listenStartedAt,
622
+ providerCount: this.lastApplicationMetrics?.providers,
623
+ interceptorCount: this.lastApplicationMetrics?.interceptors,
624
+ middlewareCount: this.lastApplicationMetrics?.middleware,
625
+ runtimeItems: this.collectStudioRuntimeItems(),
626
+ });
627
+ // Setup signal handlers for graceful shutdown
628
+ // Supported signals:
629
+ // - SIGTERM: Standard termination (Kubernetes, Docker, process managers)
630
+ // - SIGINT: User interrupt (Ctrl+C)
631
+ // - SIGHUP: Terminal hangup
632
+ // - SIGQUIT: Quit with core dump request
633
+ // - SIGBREAK: Windows break signal (Ctrl+Break)
634
+ // - SIGUSR2: Used by nodemon for restart (not on Windows)
635
+ const shutdownSignals = [
636
+ "SIGTERM",
637
+ "SIGINT",
638
+ "SIGHUP",
639
+ "SIGQUIT",
640
+ "SIGBREAK",
641
+ ...(process.platform !== "win32" ? ["SIGUSR2"] : []),
642
+ ];
643
+ for (const signal of shutdownSignals) {
644
+ // Skip if handler already registered (prevents duplicates)
645
+ if (this.shutdownHandlers.has(signal)) {
646
+ continue;
647
+ }
648
+ const handler = () => {
649
+ // Prevent multiple shutdown attempts
650
+ if (this.isShuttingDown) {
651
+ return;
652
+ }
653
+ this.isShuttingDown = true;
654
+ // Use console.log for shutdown messages - synchronous and guaranteed to write before exit
655
+ console.log(`\n📡 Signal ${signal} received, initiating graceful shutdown...`);
656
+ // Execute shutdown hooks and exit
657
+ this.handleExit(signal)
658
+ .then(() => {
659
+ console.log("✅ Graceful shutdown completed");
660
+ process.exit(0);
661
+ })
662
+ .catch((error) => {
663
+ console.error(`❌ Error during shutdown: ${error.message}`);
664
+ process.exit(1);
665
+ });
666
+ };
667
+ // Store handler for later removal and register it
668
+ this.shutdownHandlers.set(signal, handler);
669
+ process.on(signal, handler);
670
+ }
671
+ // Setup exit handler to force-close connections immediately
672
+ // This is a last-resort handler for when signals don't arrive or complete in time
673
+ // (e.g., during hot-reload when the process is killed quickly)
674
+ const exitHandler = () => {
675
+ if (this.serverInstance) {
676
+ // Synchronously destroy all connections - this is our last chance
677
+ this.destroyAllConnections();
678
+ // Try to close the server synchronously (won't block but releases the port faster)
679
+ try {
680
+ this.serverInstance.close();
681
+ }
682
+ catch {
683
+ // Ignore errors during exit
684
+ }
685
+ }
686
+ };
687
+ // Register exit handler (only once)
688
+ if (!this.shutdownHandlers.has("exit")) {
689
+ this.shutdownHandlers.set("exit", exitHandler);
690
+ process.once("exit", exitHandler);
691
+ }
692
+ try {
693
+ // Call user's postServerInitialization hook
694
+ await this.handleSyncOrAsync(this.postServerInitialization());
695
+ // Execute bootstrap lifecycle hooks on all IBootstrap providers
696
+ if (this.lifecycleRegistry) {
697
+ await this.lifecycleRegistry.executeBootstrap();
698
+ }
699
+ resolve(this);
700
+ }
701
+ catch (error) {
702
+ this.logger.error(`Error during post-server initialization: ${error}`, "adapter-express");
703
+ reject(error);
704
+ }
705
+ });
706
+ this.serverInstance?.on("error", (error) => {
707
+ // Handle EADDRINUSE error with helpful suggestions
708
+ if (error.code === "EADDRINUSE") {
709
+ const port = this.port;
710
+ const errorMessage = `Port ${port} is already in use`;
711
+ const suggestions = [
712
+ `Try a different port: Set PORT environment variable to another value`,
713
+ `Find and stop the process using port ${port}`,
714
+ process.platform === "win32"
715
+ ? `On Windows: netstat -ano | findstr :${port}`
716
+ : `On Linux/Mac: lsof -ti:${port} | xargs kill`,
717
+ ];
718
+ this.logger.error(errorMessage, "adapter-express");
719
+ this.logger.info("💡 Suggestions:", "adapter-express");
720
+ suggestions.forEach((suggestion) => {
721
+ this.logger.info(` • ${suggestion}`, "adapter-express");
722
+ });
723
+ reject(new Error(`${errorMessage}. ${suggestions[0]}`));
724
+ }
725
+ else {
726
+ this.logger.error(`Error starting server: ${error.message}`, "adapter-express");
727
+ reject(error);
728
+ }
729
+ });
730
+ });
731
+ }
732
+ /**
733
+ * Close existing server instance if it exists.
734
+ * @private
735
+ */
736
+ async closeExistingServer() {
737
+ if (this.serverInstance) {
738
+ return new Promise((resolve) => {
739
+ this.serverInstance.close(() => {
740
+ this.serverInstance = null;
741
+ resolve();
742
+ });
743
+ // Force close after timeout
744
+ setTimeout(() => {
745
+ if (this.serverInstance) {
746
+ this.serverInstance = null;
747
+ resolve();
748
+ }
749
+ }, 1000);
750
+ });
751
+ }
752
+ }
753
+ /**
754
+ * Wait for a specified duration.
755
+ * @private
756
+ */
757
+ delay(ms) {
758
+ return new Promise((resolve) => setTimeout(resolve, ms));
759
+ }
760
+ /**
761
+ * Kill the process using a specific port.
762
+ * @private
763
+ */
764
+ async killProcessOnPort(port) {
765
+ const { exec } = await import("child_process");
766
+ const { promisify } = await import("util");
767
+ const execAsync = promisify(exec);
768
+ try {
769
+ if (process.platform === "win32") {
770
+ // Windows: Find PID using netstat and kill it
771
+ const { stdout } = await execAsync(`netstat -ano | findstr :${port} | findstr LISTENING`);
772
+ const lines = stdout.trim().split("\n");
773
+ for (const line of lines) {
774
+ const parts = line.trim().split(/\s+/);
775
+ const pid = parts[parts.length - 1];
776
+ if (pid && pid !== String(process.pid) && /^\d+$/.test(pid)) {
777
+ try {
778
+ await execAsync(`taskkill /F /PID ${pid}`);
779
+ return true;
780
+ }
781
+ catch {
782
+ // Process might have already exited
783
+ }
784
+ }
785
+ }
786
+ }
787
+ else {
788
+ // Linux/Mac: Use lsof to find PID and kill it
789
+ try {
790
+ const { stdout } = await execAsync(`lsof -ti:${port}`);
791
+ const pids = stdout.trim().split("\n").filter(Boolean);
792
+ for (const pid of pids) {
793
+ if (pid !== String(process.pid)) {
794
+ try {
795
+ await execAsync(`kill -9 ${pid}`);
796
+ return true;
797
+ }
798
+ catch {
799
+ // Process might have already exited
800
+ }
801
+ }
802
+ }
803
+ }
804
+ catch {
805
+ // No process found on port
806
+ }
807
+ }
808
+ }
809
+ catch {
810
+ // Command failed - port might already be free
811
+ }
812
+ return false;
813
+ }
814
+ /**
815
+ * Check if the port is available by attempting to bind to it.
816
+ * @private
817
+ */
818
+ async isPortAvailable(port) {
819
+ const net = await import("net");
820
+ return new Promise((resolve) => {
821
+ const testServer = net.createServer();
822
+ testServer.once("error", () => {
823
+ resolve(false);
824
+ });
825
+ testServer.once("listening", () => {
826
+ testServer.close(() => {
827
+ resolve(true);
828
+ });
829
+ });
830
+ testServer.listen(port);
831
+ });
832
+ }
833
+ /**
834
+ * Ensure the port is available, killing the existing process if needed.
835
+ * This is the safest approach for hot-reload scenarios.
836
+ * @private
837
+ */
838
+ async ensurePortAvailable(port) {
839
+ // First, check if port is already available
840
+ if (await this.isPortAvailable(port)) {
841
+ return true;
842
+ }
843
+ // Try to kill the process on the port
844
+ let killed = await this.killProcessOnPort(port);
845
+ if (killed) {
846
+ // Wait a moment for the port to be released
847
+ await this.delay(500);
848
+ }
849
+ // Retry multiple times to check if port is now available
850
+ // Hot reload scenarios may need more time for the old process to shut down
851
+ for (let attempt = 1; attempt <= this.portRetryAttempts; attempt++) {
852
+ if (await this.isPortAvailable(port)) {
853
+ return true;
854
+ }
855
+ // Try to kill again if still not available (process might be slow to release)
856
+ if (attempt % 3 === 0) {
857
+ killed = await this.killProcessOnPort(port);
858
+ if (killed) {
859
+ await this.delay(300);
860
+ }
861
+ }
862
+ if (attempt < this.portRetryAttempts) {
863
+ await this.delay(this.portRetryDelay);
864
+ }
865
+ }
866
+ return false;
867
+ }
868
+ /**
869
+ * Remove existing shutdown signal handlers to prevent duplicates.
870
+ * @private
871
+ */
872
+ removeShutdownHandlers() {
873
+ this.shutdownHandlers.forEach((handler, signal) => {
874
+ // Handle "exit" event specially (it's not a signal but we track it the same way)
875
+ if (signal === "exit") {
876
+ process.removeListener("exit", handler);
877
+ }
878
+ else {
879
+ process.removeListener(signal, handler);
880
+ }
881
+ });
882
+ this.shutdownHandlers.clear();
883
+ // Also clear any tracked connections from previous runs
884
+ this.activeConnections.clear();
885
+ }
886
+ /**
887
+ * Sets the global route prefix for the application.
888
+ * @method setGlobalRoutePrefix
889
+ * @param {string} prefix - The prefix to use for all routes.
890
+ * @public API
891
+ */
892
+ async setGlobalRoutePrefix(prefix) {
893
+ this.globalPrefix = prefix;
894
+ }
895
+ /**
896
+ * Configures the application's view engine based on the provided configuration options.
897
+ */
898
+ async configEngine() {
899
+ if (this.renderOptions.engine) {
900
+ switch (this.renderOptions.engine) {
901
+ case RenderEngine.Engine.HBS:
902
+ await setEngineHandlebars(this.app, this.renderOptions.options);
903
+ break;
904
+ case RenderEngine.Engine.EJS:
905
+ await setEngineEjs(this.app, this.renderOptions.options);
906
+ break;
907
+ case RenderEngine.Engine.PUG:
908
+ await setEnginePug(this.app, this.renderOptions.options);
909
+ break;
910
+ default:
911
+ throw new Error("Unsupported engine type!");
912
+ }
913
+ }
914
+ }
915
+ /**
916
+ * Configures the application's view engine based on the provided configuration options.
917
+ * @method setEngine
918
+ * @template T - A generic type extending from RenderTemplateOptions.
919
+ *
920
+ * @param {Engine} engine - The view engine to set
921
+ * @param {EngineOptions} [options] - The configuration options for the view engine
922
+ * @public API
923
+ */
924
+ /**
925
+ * Configure the startup banner display.
926
+ * Can be called in configureServices() or globalConfiguration().
927
+ *
928
+ * @param config - Banner configuration options
929
+ * @example
930
+ * ```typescript
931
+ * export class App extends AppExpress {
932
+ * configureServices(): void {
933
+ * this.setBanner({
934
+ * style: "full",
935
+ * showMetrics: true,
936
+ * showFeatures: true,
937
+ * showConfig: true,
938
+ * showPerformance: true,
939
+ * showResources: true,
940
+ * // Environment-specific overrides
941
+ * environment: {
942
+ * production: {
943
+ * style: "compact",
944
+ * showConfig: false,
945
+ * showResources: false,
946
+ * },
947
+ * },
948
+ * });
949
+ * }
950
+ * }
951
+ * ```
952
+ * @public API
953
+ */
954
+ setBanner(config) {
955
+ this.bannerConfig = config;
956
+ }
957
+ /**
958
+ * Configure ExpressoTS Studio integration.
959
+ * When enabled and @expressots/studio-agent is installed, automatically
960
+ * instruments the application for request recording and real-time monitoring.
961
+ *
962
+ * By default, Studio is auto-enabled in development if the package is installed.
963
+ * Use this method to customize behavior or enable in production.
964
+ *
965
+ * @param config - Studio configuration options
966
+ * @example
967
+ * ```typescript
968
+ * export class App extends AppExpress {
969
+ * configureServices(): void {
970
+ * this.setStudio({
971
+ * enabled: true, // Force enable (default: auto in dev)
972
+ * port: 3334, // WebSocket port for UI connection
973
+ * serviceName: 'my-app', // Service name for tracing
974
+ * });
975
+ * }
976
+ * }
977
+ * ```
978
+ * @public API
979
+ */
980
+ setStudio(config) {
981
+ this.studioConfig = config;
982
+ }
983
+ /**
984
+ * Check if ExpressoTS Studio is currently enabled.
985
+ * @returns Boolean indicating if Studio Agent is running.
986
+ * @public API
987
+ */
988
+ isStudioEnabled() {
989
+ return isStudioEnabled();
990
+ }
991
+ /**
992
+ * Configure a view engine for server-side rendering.
993
+ *
994
+ * @deprecated Use `this.Middleware.render()` instead. Will be removed in v5.0.0.
995
+ *
996
+ * @example Migration
997
+ * ```typescript
998
+ * // Before (deprecated)
999
+ * this.setEngine(RenderEngine.Engine.EJS, { viewsDir: 'views' });
1000
+ *
1001
+ * // After (recommended)
1002
+ * this.Middleware.render({ engine: 'ejs', viewsDir: 'views' });
1003
+ *
1004
+ * // Or with auto-detection
1005
+ * this.Middleware.render();
1006
+ * ```
1007
+ *
1008
+ * @param engine - The view engine to set
1009
+ * @param options - The configuration options for the view engine
1010
+ * @public API
1011
+ */
1012
+ async setEngine(engine, options) {
1013
+ this.logger.warn("setEngine() is deprecated. Use this.Middleware.render() instead. Will be removed in v5.0.0.", "adapter-express");
1014
+ try {
1015
+ // Bridge to new render system
1016
+ const engineMap = {
1017
+ ejs: "ejs",
1018
+ pug: "pug",
1019
+ hbs: "hbs",
1020
+ };
1021
+ const engineName = engineMap[engine] || engine;
1022
+ // Try to use the new render system
1023
+ await this.Middleware.render({
1024
+ engine: engineName,
1025
+ viewsDir: options?.viewsDir,
1026
+ partialsDir: options?.partialsDir,
1027
+ });
1028
+ }
1029
+ catch {
1030
+ // Fallback to old system if new system fails
1031
+ if (options) {
1032
+ this.renderOptions = { engine, options };
1033
+ }
1034
+ else {
1035
+ this.renderOptions = { engine };
1036
+ }
1037
+ }
1038
+ }
1039
+ /**
1040
+ * Verifies if the current environment is development.
1041
+ * @returns A boolean value indicating whether the current environment is development or not.
1042
+ * @public API
1043
+ */
1044
+ async isDevelopment() {
1045
+ // Check Express app environment first (most reliable)
1046
+ if (this.app) {
1047
+ return this.app.get("env") === "development";
1048
+ }
1049
+ // Fallback to this.environment (set by bootstrap())
1050
+ if (this.environment) {
1051
+ return this.environment === "development";
1052
+ }
1053
+ // Fallback to process.env.NODE_ENV
1054
+ if (process.env.NODE_ENV) {
1055
+ return process.env.NODE_ENV === "development";
1056
+ }
1057
+ // Default to false if nothing is set
1058
+ return false;
1059
+ }
1060
+ /**
1061
+ * Get the underlying HTTP server. (default: Express.js)
1062
+ * @returns The underlying HTTP server after initialization.
1063
+ * @public API
1064
+ */
1065
+ async getHttpServer() {
1066
+ if (!this.serverInstance) {
1067
+ this.logger.error("Server instance not initialized yet", "adapter-express");
1068
+ throw new Error("Server instance not initialized yet");
1069
+ }
1070
+ return Promise.resolve(this.serverInstance);
1071
+ }
1072
+ /**
1073
+ * Get the port the server is listening on.
1074
+ * Useful for dynamic port assignment (port: 0) in testing scenarios.
1075
+ * @returns The actual port number the server is bound to.
1076
+ * @public API
1077
+ */
1078
+ async getPort() {
1079
+ if (!this.serverInstance) {
1080
+ this.logger.error("Server instance not initialized yet", "adapter-express");
1081
+ throw new Error("Server instance not initialized yet");
1082
+ }
1083
+ const address = this.serverInstance.address();
1084
+ if (address && typeof address === "object" && "port" in address) {
1085
+ return Promise.resolve(address.port);
1086
+ }
1087
+ throw new Error("Unable to determine server port");
1088
+ }
1089
+ /**
1090
+ * Detect API versions from @Version() decorators on controllers.
1091
+ * @returns Array of unique API versions (e.g., ["v1", "v2"])
1092
+ * @private
1093
+ */
1094
+ detectApiVersions() {
1095
+ try {
1096
+ const controllers = getControllersFromMetadata();
1097
+ const versions = new Set();
1098
+ controllers.forEach((controllerTarget) => {
1099
+ // Cast DecoratorTarget to NewableFunction for metadata access
1100
+ const controllerConstructor = controllerTarget;
1101
+ // Check controller-level version
1102
+ const controllerMetadata = getControllerMetadata(controllerConstructor);
1103
+ if (controllerMetadata?.version) {
1104
+ const version = String(controllerMetadata.version);
1105
+ // Normalize version format (ensure "v" prefix)
1106
+ const normalizedVersion = version.startsWith("v") ? version : `v${version}`;
1107
+ versions.add(normalizedVersion);
1108
+ }
1109
+ // Check method-level versions
1110
+ const methodMetadata = getControllerMethodMetadata(controllerConstructor);
1111
+ if (methodMetadata) {
1112
+ methodMetadata.forEach((method) => {
1113
+ if (method.version) {
1114
+ const version = String(method.version);
1115
+ const normalizedVersion = version.startsWith("v") ? version : `v${version}`;
1116
+ versions.add(normalizedVersion);
1117
+ }
1118
+ });
1119
+ }
1120
+ });
1121
+ return Array.from(versions).sort();
1122
+ }
1123
+ catch (error) {
1124
+ // If metadata not available, return empty array
1125
+ return [];
1126
+ }
1127
+ }
1128
+ /**
1129
+ * Harvest provider + interceptor *names* from DI metadata so the
1130
+ * Studio Status page can drill down into the items behind the
1131
+ * "Providers" / "Interceptors" counters.
1132
+ *
1133
+ * Reads the same metadata that {@link MetricsCollector} uses for the
1134
+ * CLI banner — so what shows up here is the source of truth, not the
1135
+ * agent's static file scan (which can't see framework-registered
1136
+ * items like `Logger` or `LifecycleRegistry`).
1137
+ *
1138
+ * Returns `undefined` instead of an empty object so {@link
1139
+ * reportStudioRuntimeInfo} can skip forwarding when nothing was
1140
+ * harvested (keeps the WS payload small).
1141
+ */
1142
+ collectStudioRuntimeItems() {
1143
+ try {
1144
+ const providerMetadata = Reflect.getMetadata(PROVIDE_METADATA_KEY, Reflect) || [];
1145
+ const providers = [];
1146
+ for (const entry of providerMetadata) {
1147
+ const name = entry?.implementationType?.name;
1148
+ if (typeof name === "string" && name.length > 0) {
1149
+ providers.push({ name, source: "provide" });
1150
+ }
1151
+ }
1152
+ const interceptorMetadata = Reflect.getMetadata(INTERCEPTOR_METADATA_KEY.interceptor, Reflect) || [];
1153
+ const interceptors = [];
1154
+ for (const entry of interceptorMetadata) {
1155
+ const name = entry?.interceptor?.name;
1156
+ if (typeof name === "string" && name.length > 0) {
1157
+ interceptors.push({
1158
+ name,
1159
+ priority: entry?.priority,
1160
+ source: "metadata",
1161
+ });
1162
+ }
1163
+ }
1164
+ // Bail out if both lists are empty — nothing useful to forward.
1165
+ if (providers.length === 0 && interceptors.length === 0) {
1166
+ return undefined;
1167
+ }
1168
+ return { providers, interceptors };
1169
+ }
1170
+ catch {
1171
+ // Metadata reads should never break boot. Status page just falls
1172
+ // back to its static-scan list.
1173
+ return undefined;
1174
+ }
1175
+ }
1176
+ /**
1177
+ * Display middleware startup logs after the banner.
1178
+ * This makes startup logging transparent to the user - no need for manual code in postServerInitialization().
1179
+ * @private
1180
+ */
1181
+ displayMiddlewareStartupLogs() {
1182
+ const isDev = this.environment === "development";
1183
+ if (!isDev)
1184
+ return;
1185
+ const startupLogs = this.Middleware.getStartupLogs();
1186
+ if (startupLogs.length === 0)
1187
+ return;
1188
+ startupLogs.forEach((log) => {
1189
+ if (log.type === "warn") {
1190
+ this.logger.warn(log.message, "middleware");
1191
+ }
1192
+ else {
1193
+ this.logger.info(log.message, "middleware");
1194
+ }
1195
+ });
1196
+ this.Middleware.clearStartupLogs();
1197
+ }
1198
+ /**
1199
+ * Display startup banner with application metrics.
1200
+ * @param appInfo - Application info
1201
+ * @private
1202
+ */
1203
+ displayStartupBanner(appInfo) {
1204
+ if (!this.bannerGenerator) {
1205
+ // Fallback to old console message if banner generator not initialized
1206
+ this.console.messageServer(this.port, this.environment || "development", appInfo);
1207
+ // Log CI detection after banner, before middleware logs
1208
+ this.displayCIDetectionLogs(appInfo);
1209
+ // Still display middleware startup logs even in fallback mode
1210
+ this.displayMiddlewareStartupLogs();
1211
+ return;
1212
+ }
1213
+ try {
1214
+ let finalAppInfo = appInfo;
1215
+ if (!finalAppInfo?.apiVersions || finalAppInfo.apiVersions.length === 0) {
1216
+ const apiVersions = this.detectApiVersions();
1217
+ if (apiVersions.length > 0) {
1218
+ finalAppInfo = {
1219
+ ...appInfo,
1220
+ appName: appInfo?.appName || "App",
1221
+ appVersion: appInfo?.appVersion || "not provided",
1222
+ apiVersions,
1223
+ };
1224
+ }
1225
+ }
1226
+ // Detect API versions from controllers
1227
+ const detectedApiVersions = finalAppInfo?.apiVersions || [];
1228
+ // Collect metrics. Cache the result on the instance so the Studio
1229
+ // integration can forward the *runtime* counts (providers /
1230
+ // interceptors / middleware) to the agent — those values come from
1231
+ // DI metadata and registries, which our static file scanner can't
1232
+ // see, so without this the Studio Status page would disagree with
1233
+ // the CLI banner.
1234
+ const { metrics, features } = MetricsCollector.collect(this.appContainer.Container, {
1235
+ getControllersFromMetadata: () => getControllersFromMetadata(),
1236
+ getControllersFromContainer: () => getControllersFromContainer(this.appContainer.Container, false),
1237
+ getControllerMethodMetadata: (constructor) => getControllerMethodMetadata(constructor),
1238
+ getMiddlewareCount: () => this.Middleware.getMiddlewarePipeline().length,
1239
+ hasContentNegotiation: () => !!this.Middleware.getContentNegotiationService(),
1240
+ hasSmartValidation: () => !!this.Middleware.getValidationConfig(),
1241
+ hasAuthorization: () => this.appContainer.Container.isBound("IGuardCache"),
1242
+ hasExceptionFilters: () => !!this.Middleware.getErrorHandler(),
1243
+ hasApiVersioning: () => detectedApiVersions.length > 0,
1244
+ hasGlobalRoutePrefix: () => !!this.globalPrefix && this.globalPrefix !== "/",
1245
+ hasErrorHandler: () => !!this.Middleware.getErrorHandler(),
1246
+ hasRequestLogging: () => {
1247
+ // Check if any request logging middleware is in the pipeline
1248
+ const pipeline = this.Middleware.getPipelineInfo();
1249
+ return pipeline.entries.some((e) => e.category === "logging" || e.name.toLowerCase().includes("logging"));
1250
+ },
1251
+ });
1252
+ // Persist the metrics so `reportStudioRuntimeInfo` (called from the
1253
+ // listen callback) can forward live provider/interceptor counts.
1254
+ this.lastApplicationMetrics = metrics;
1255
+ // Discover providers for introspection
1256
+ this.Provider.discover();
1257
+ // Get middleware and provider views for banner
1258
+ const middlewareView = this.Middleware.getFormattedView();
1259
+ const providerView = this.Provider.getFormattedView();
1260
+ // Prepare banner data with extended info
1261
+ const bannerData = {
1262
+ appInfo: finalAppInfo,
1263
+ metrics,
1264
+ features,
1265
+ middlewareView,
1266
+ providerView,
1267
+ };
1268
+ // Display banner
1269
+ this.bannerGenerator.display(this.port, this.environment || "development", finalAppInfo, metrics, features, {
1270
+ "Global Prefix": this.globalPrefix || "/",
1271
+ "Node Version": process.version,
1272
+ Platform: process.platform,
1273
+ }, bannerData);
1274
+ // Log CI detection after banner, before middleware logs
1275
+ this.displayCIDetectionLogs(appInfo);
1276
+ // Automatically display middleware startup logs after banner (transparent to user)
1277
+ this.displayMiddlewareStartupLogs();
1278
+ }
1279
+ catch (error) {
1280
+ // Fallback to old console message on error
1281
+ this.logger.warn("Failed to display startup banner, using fallback", "adapter-express", error);
1282
+ this.console.messageServer(this.port, this.environment || "development", appInfo);
1283
+ // Log CI detection after banner, before middleware logs
1284
+ this.displayCIDetectionLogs(appInfo);
1285
+ // Still display middleware startup logs even in fallback mode
1286
+ this.displayMiddlewareStartupLogs();
1287
+ }
1288
+ }
1289
+ /**
1290
+ * Display CI detection logs after the banner but before middleware logs.
1291
+ * @param appInfo - Application info containing CI detection data
1292
+ * @private
1293
+ */
1294
+ displayCIDetectionLogs(appInfo) {
1295
+ if (appInfo?.ciDetection?.detected) {
1296
+ this.logger.info(`🔍 CI environment detected: ${appInfo.ciDetection.platform}`, "bootstrap");
1297
+ this.logger.info(`✅ Skipping .env file loading (using process.env)`, "bootstrap");
1298
+ }
1299
+ }
1300
+ }