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