@warlock.js/core 5.24.0 → 5.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/CHANGELOG.md +64 -1
  2. package/bin/warlock.js +0 -0
  3. package/esm/application/application.d.mts +21 -0
  4. package/esm/application/application.d.mts.map +1 -1
  5. package/esm/application/application.mjs +29 -1
  6. package/esm/application/application.mjs.map +1 -1
  7. package/esm/application/boot-signal.d.mts +2 -1
  8. package/esm/application/boot-signal.d.mts.map +1 -1
  9. package/esm/application/boot-signal.mjs.map +1 -1
  10. package/esm/application/index.d.mts +1 -0
  11. package/esm/application/index.mjs +1 -0
  12. package/esm/application/roles.d.mts +72 -0
  13. package/esm/application/roles.d.mts.map +1 -0
  14. package/esm/application/roles.mjs +124 -0
  15. package/esm/application/roles.mjs.map +1 -0
  16. package/esm/cli/commands/add.command.mjs.map +1 -1
  17. package/esm/cli/commands/create-database.command.mjs.map +1 -1
  18. package/esm/cli/commands/dev-server.command.mjs.map +1 -1
  19. package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
  20. package/esm/cli/commands/doctor/checks/index.mjs +2 -0
  21. package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
  22. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +170 -0
  23. package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -0
  24. package/esm/cli/commands/doctor/doctor-command.action.mjs +7 -2
  25. package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
  26. package/esm/cli/commands/doctor.command.mjs +5 -0
  27. package/esm/cli/commands/doctor.command.mjs.map +1 -1
  28. package/esm/cli/commands/drop-tables.command.mjs.map +1 -1
  29. package/esm/cli/commands/generate/generators/module.generator.mjs +1 -12
  30. package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
  31. package/esm/cli/commands/generate/utils/path-resolver.mjs.map +1 -1
  32. package/esm/cli/commands/migrate.command.mjs.map +1 -1
  33. package/esm/cli/commands/routes.command.mjs.map +1 -1
  34. package/esm/cli/commands/seed.command.mjs.map +1 -1
  35. package/esm/cli/commands/start-production.command.mjs +54 -5
  36. package/esm/cli/commands/start-production.command.mjs.map +1 -1
  37. package/esm/cli/commands/storage-put.action.mjs.map +1 -1
  38. package/esm/cli/commands/storage-put.command.mjs.map +1 -1
  39. package/esm/cli/commands/typings-generator.command.mjs +20 -1
  40. package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
  41. package/esm/cli/commands/update.command.mjs.map +1 -1
  42. package/esm/cli/commands-loader.mjs.map +1 -1
  43. package/esm/config/config-loader.d.mts.map +1 -1
  44. package/esm/config/config-loader.mjs +3 -1
  45. package/esm/config/config-loader.mjs.map +1 -1
  46. package/esm/connectors/connectors-manager.mjs.map +1 -1
  47. package/esm/connectors/database-connector.mjs.map +1 -1
  48. package/esm/connectors/herald-connector.mjs.map +1 -1
  49. package/esm/connectors/http-connector.d.mts.map +1 -1
  50. package/esm/connectors/http-connector.mjs +7 -0
  51. package/esm/connectors/http-connector.mjs.map +1 -1
  52. package/esm/connectors/logger-connector.mjs.map +1 -1
  53. package/esm/connectors/mail-connector.mjs.map +1 -1
  54. package/esm/connectors/types.d.mts +8 -0
  55. package/esm/connectors/types.d.mts.map +1 -1
  56. package/esm/connectors/types.mjs.map +1 -1
  57. package/esm/database/create-database-action.mjs.map +1 -1
  58. package/esm/database/drop-tables-action.mjs.map +1 -1
  59. package/esm/database/migrate-action.mjs +4 -3
  60. package/esm/database/migrate-action.mjs.map +1 -1
  61. package/esm/database/models/database-log/database-log.mjs.map +1 -1
  62. package/esm/database/seed-command-action.mjs +2 -1
  63. package/esm/database/seed-command-action.mjs.map +1 -1
  64. package/esm/dev-server/dependency-graph.mjs.map +1 -1
  65. package/esm/dev-server/development-server.mjs +2 -0
  66. package/esm/dev-server/development-server.mjs.map +1 -1
  67. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  68. package/esm/dev-server/layer-executor.mjs.map +1 -1
  69. package/esm/dev-server/legacy-locales-warning.mjs +16 -0
  70. package/esm/dev-server/legacy-locales-warning.mjs.map +1 -0
  71. package/esm/dev-server/manifest-manager.mjs.map +1 -1
  72. package/esm/dev-server/module-loader.mjs +26 -5
  73. package/esm/dev-server/module-loader.mjs.map +1 -1
  74. package/esm/dev-server/register-installed-devtools.mjs +47 -0
  75. package/esm/dev-server/register-installed-devtools.mjs.map +1 -0
  76. package/esm/dev-server/run-connector-typings.mjs +28 -0
  77. package/esm/dev-server/run-connector-typings.mjs.map +1 -0
  78. package/esm/dev-server/special-file-patterns.mjs +5 -3
  79. package/esm/dev-server/special-file-patterns.mjs.map +1 -1
  80. package/esm/dev-server/special-files-collector.d.mts +2 -2
  81. package/esm/dev-server/special-files-collector.d.mts.map +1 -1
  82. package/esm/dev-server/special-files-collector.mjs +7 -4
  83. package/esm/dev-server/special-files-collector.mjs.map +1 -1
  84. package/esm/dev-server/translation-keys-sources.mjs +7 -3
  85. package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
  86. package/esm/dev-server/type-generator.mjs +27 -4
  87. package/esm/dev-server/type-generator.mjs.map +1 -1
  88. package/esm/encryption/password.mjs.map +1 -1
  89. package/esm/generations/add-command.action.mjs +3 -1
  90. package/esm/generations/add-command.action.mjs.map +1 -1
  91. package/esm/generations/features/access.feature.mjs +38 -18
  92. package/esm/generations/features/access.feature.mjs.map +1 -1
  93. package/esm/generations/features/ai-anthropic.feature.mjs.map +1 -1
  94. package/esm/generations/features/ai-bedrock.feature.mjs.map +1 -1
  95. package/esm/generations/features/ai-google.feature.mjs.map +1 -1
  96. package/esm/generations/features/ai-ollama.feature.mjs.map +1 -1
  97. package/esm/generations/features/ai-openai.feature.mjs.map +1 -1
  98. package/esm/generations/features/ai-panoptic.feature.mjs.map +1 -1
  99. package/esm/generations/features/ai-tools.feature.mjs.map +1 -1
  100. package/esm/generations/features/ai-workspace.feature.mjs.map +1 -1
  101. package/esm/generations/features/ai.feature.mjs.map +1 -1
  102. package/esm/generations/features/bull-board.feature.mjs +2 -2
  103. package/esm/generations/features/bull-board.feature.mjs.map +1 -1
  104. package/esm/generations/features/devtools.feature.mjs +23 -0
  105. package/esm/generations/features/devtools.feature.mjs.map +1 -0
  106. package/esm/generations/features/herald.feature.mjs.map +1 -1
  107. package/esm/generations/features/image.feature.mjs.map +1 -1
  108. package/esm/generations/features/index.mjs +2 -0
  109. package/esm/generations/features/index.mjs.map +1 -1
  110. package/esm/generations/features/mail.feature.mjs.map +1 -1
  111. package/esm/generations/features/mongodb.feature.mjs.map +1 -1
  112. package/esm/generations/features/notifications.feature.mjs.map +1 -1
  113. package/esm/generations/features/postgres.feature.mjs.map +1 -1
  114. package/esm/generations/features/react-email.feature.mjs.map +1 -1
  115. package/esm/generations/features/react.feature.mjs.map +1 -1
  116. package/esm/generations/features/redis.feature.mjs.map +1 -1
  117. package/esm/generations/features/s3.feature.mjs.map +1 -1
  118. package/esm/generations/features/scheduler.feature.mjs.map +1 -1
  119. package/esm/generations/features/ses.feature.mjs.map +1 -1
  120. package/esm/generations/features/socket.feature.mjs.map +1 -1
  121. package/esm/generations/features/test.feature.mjs +6 -2
  122. package/esm/generations/features/test.feature.mjs.map +1 -1
  123. package/esm/generations/features/types.mjs.map +1 -1
  124. package/esm/generations/features/web.feature.mjs.map +1 -1
  125. package/esm/http/build-rate-limit-options.d.mts +8 -2
  126. package/esm/http/build-rate-limit-options.d.mts.map +1 -1
  127. package/esm/http/build-rate-limit-options.mjs +1 -0
  128. package/esm/http/build-rate-limit-options.mjs.map +1 -1
  129. package/esm/http/database/RequestLog.mjs.map +1 -1
  130. package/esm/http/errors/errors.d.mts +1 -17
  131. package/esm/http/errors/errors.d.mts.map +1 -1
  132. package/esm/http/errors/errors.mjs +1 -20
  133. package/esm/http/errors/errors.mjs.map +1 -1
  134. package/esm/http/index.d.mts +4 -3
  135. package/esm/http/index.mjs +3 -2
  136. package/esm/http/middleware/inject-request-context.d.mts +1 -1
  137. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  138. package/esm/http/middleware/inject-request-context.mjs +9 -1
  139. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  140. package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
  141. package/esm/http/middleware/rate-limit.middleware.mjs +5 -1
  142. package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
  143. package/esm/http/request.d.mts +7 -24
  144. package/esm/http/request.d.mts.map +1 -1
  145. package/esm/http/request.mjs +1 -22
  146. package/esm/http/request.mjs.map +1 -1
  147. package/esm/http/response.d.mts +5 -5
  148. package/esm/http/response.d.mts.map +1 -1
  149. package/esm/http/response.mjs +4 -2
  150. package/esm/http/response.mjs.map +1 -1
  151. package/esm/http/server-rebuilt.d.mts +20 -0
  152. package/esm/http/server-rebuilt.d.mts.map +1 -0
  153. package/esm/http/server-rebuilt.mjs +21 -0
  154. package/esm/http/server-rebuilt.mjs.map +1 -0
  155. package/esm/http/tracing/index.mjs +1 -1
  156. package/esm/http/tracing/tracing-dispatcher.d.mts +13 -6
  157. package/esm/http/tracing/tracing-dispatcher.d.mts.map +1 -1
  158. package/esm/http/tracing/tracing-dispatcher.mjs +46 -14
  159. package/esm/http/tracing/tracing-dispatcher.mjs.map +1 -1
  160. package/esm/http/tracing/tracing.type.d.mts +2 -1
  161. package/esm/http/tracing/tracing.type.d.mts.map +1 -1
  162. package/esm/http/types.d.mts +9 -0
  163. package/esm/http/types.d.mts.map +1 -1
  164. package/esm/image/image.mjs.map +1 -1
  165. package/esm/index.d.mts +8 -4
  166. package/esm/index.mjs +8 -3
  167. package/esm/loader/app-module-importer.d.mts +17 -0
  168. package/esm/loader/app-module-importer.d.mts.map +1 -0
  169. package/esm/loader/app-module-importer.mjs +20 -0
  170. package/esm/loader/app-module-importer.mjs.map +1 -0
  171. package/esm/localization/index.mjs +4 -0
  172. package/esm/localization/parse-locale-dictionary.d.mts +23 -0
  173. package/esm/localization/parse-locale-dictionary.d.mts.map +1 -0
  174. package/esm/localization/parse-locale-dictionary.mjs +215 -0
  175. package/esm/localization/parse-locale-dictionary.mjs.map +1 -0
  176. package/esm/localization/register-locale-dictionary.d.mts +12 -0
  177. package/esm/localization/register-locale-dictionary.d.mts.map +1 -0
  178. package/esm/localization/register-locale-dictionary.mjs +58 -0
  179. package/esm/localization/register-locale-dictionary.mjs.map +1 -0
  180. package/esm/logger/logger.mjs.map +1 -1
  181. package/esm/mail/events.d.mts +7 -0
  182. package/esm/mail/events.d.mts.map +1 -1
  183. package/esm/mail/events.mjs +7 -0
  184. package/esm/mail/events.mjs.map +1 -1
  185. package/esm/mail/send-mail.d.mts.map +1 -1
  186. package/esm/mail/send-mail.mjs +23 -2
  187. package/esm/mail/send-mail.mjs.map +1 -1
  188. package/esm/mail/types.d.mts +5 -1
  189. package/esm/mail/types.d.mts.map +1 -1
  190. package/esm/mail/types.mjs.map +1 -1
  191. package/esm/manifest/manifest-manager.mjs.map +1 -1
  192. package/esm/production/build-contributions.mjs +19 -3
  193. package/esm/production/build-contributions.mjs.map +1 -1
  194. package/esm/production/production-builder.mjs +39 -10
  195. package/esm/production/production-builder.mjs.map +1 -1
  196. package/esm/production/production-supervisor.mjs +6 -0
  197. package/esm/production/production-supervisor.mjs.map +1 -1
  198. package/esm/production/resolve-build-config.d.mts +1 -1
  199. package/esm/production/resolve-build-config.mjs +15 -0
  200. package/esm/production/resolve-build-config.mjs.map +1 -1
  201. package/esm/production/route-registration-child.mjs +14 -2
  202. package/esm/production/route-registration-child.mjs.map +1 -1
  203. package/esm/production/route-registration-snapshot.mjs +9 -2
  204. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  205. package/esm/resource/resource-field-builder.mjs +1 -1
  206. package/esm/resource/resource-field-builder.mjs.map +1 -1
  207. package/esm/resource/resource.d.mts +5 -0
  208. package/esm/resource/resource.d.mts.map +1 -1
  209. package/esm/resource/resource.mjs +17 -6
  210. package/esm/resource/resource.mjs.map +1 -1
  211. package/esm/router/router.d.mts.map +1 -1
  212. package/esm/router/router.mjs +1 -0
  213. package/esm/router/router.mjs.map +1 -1
  214. package/esm/router/types.d.mts +10 -1
  215. package/esm/router/types.d.mts.map +1 -1
  216. package/esm/tests/index.d.mts +3 -2
  217. package/esm/tests/index.mjs +3 -2
  218. package/esm/tests/start-http-development-server.d.mts +10 -0
  219. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  220. package/esm/tests/start-http-development-server.mjs +16 -2
  221. package/esm/tests/start-http-development-server.mjs.map +1 -1
  222. package/esm/tests/vitest-setup.d.mts +16 -1
  223. package/esm/tests/vitest-setup.d.mts.map +1 -1
  224. package/esm/tests/vitest-setup.mjs +20 -2
  225. package/esm/tests/vitest-setup.mjs.map +1 -1
  226. package/esm/use-cases/use-case-events.mjs.map +1 -1
  227. package/esm/utils/is-test-file.mjs +15 -0
  228. package/esm/utils/is-test-file.mjs.map +1 -0
  229. package/esm/utils/normalized-path.d.mts +3 -1
  230. package/esm/utils/normalized-path.d.mts.map +1 -1
  231. package/esm/utils/normalized-path.mjs +4 -2
  232. package/esm/utils/normalized-path.mjs.map +1 -1
  233. package/esm/utils/paths.mjs.map +1 -1
  234. package/esm/validation/plugins/database-plugin.mjs.map +1 -1
  235. package/esm/validation/validateAll.mjs +57 -4
  236. package/esm/validation/validateAll.mjs.map +1 -1
  237. package/esm/warlock-config/default-configurations.mjs +1 -0
  238. package/esm/warlock-config/default-configurations.mjs.map +1 -1
  239. package/esm/warlock-config/types.d.mts +7 -0
  240. package/esm/warlock-config/types.d.mts.map +1 -1
  241. package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
  242. package/llms-full.txt +382 -214
  243. package/llms.txt +1 -1
  244. package/package.json +23 -19
  245. package/skills/README.md +1 -1
  246. package/skills/configure-app/SKILL.md +2 -0
  247. package/skills/create-controller/SKILL.md +4 -0
  248. package/skills/define-resource/SKILL.md +4 -0
  249. package/skills/request-tracing/SKILL.md +38 -15
  250. package/skills/run-app/SKILL.md +71 -34
  251. package/skills/send-mail/SKILL.md +55 -25
  252. package/skills/test-http/SKILL.md +28 -9
  253. package/skills/test-service/SKILL.md +61 -40
  254. package/skills/use-localization/SKILL.md +39 -26
  255. package/skills/warlock-conventions/SKILL.md +26 -18
  256. package/skills/write-cli-command/SKILL.md +50 -45
@@ -1 +1 @@
1
- {"version":3,"file":"start-production.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/start-production.command.ts"],"sourcesContent":["import { checkDistReadyToStartAsync } from \"../../production/assert-dist-ready-to-start\";\r\nimport { superviseProductionProcess } from \"../../production/production-supervisor\";\r\nimport { resolveBuildConfig } from \"../../production/resolve-build-config\";\r\nimport { command } from \"../../commands/cli-command\";\r\n\r\nconst NODE_FLAG_PATTERN =\r\n /^--(inspect(-brk|-wait)?(=.*)?|max-old-space-size=.*|enable-source-maps|trace-.*)$/;\r\n\r\nexport const startProductionCommand = command({\r\n name: \"start\",\r\n description: \"Start production server\",\r\n persistent: true,\r\n preload: {\r\n warlockConfig: true,\r\n },\r\n action: async () => {\r\n const { entryPath, sourcemap, outdir } = resolveBuildConfig();\r\n\r\n // Refuse a `dist` that did not come from a successful `warlock build` —\r\n // named explicitly, rather than failing later and incidentally because\r\n // some file the server happens to need (the client manifest, say) is\r\n // missing. See `assert-dist-ready-to-start.ts`.\r\n const readiness = await checkDistReadyToStartAsync(outdir);\r\n\r\n if (!readiness.ready) {\r\n console.error(`✖ ${readiness.reason}`);\r\n process.exit(1);\r\n return;\r\n }\r\n\r\n // Build node args\r\n const nodeArgs: string[] = [];\r\n\r\n // Enable source maps if configured\r\n if (sourcemap !== false) {\r\n nodeArgs.push(\"--enable-source-maps\");\r\n }\r\n\r\n // Recognised Node flags go BEFORE the entry file; everything else is app argv.\r\n // process.argv = [node, cli.ts, start, ...extra]\r\n const appArgs: string[] = [];\r\n const startIndex = process.argv.findIndex((arg) => arg === \"start\");\r\n if (startIndex !== -1 && startIndex < process.argv.length - 1) {\r\n for (const arg of process.argv.slice(startIndex + 1)) {\r\n if (NODE_FLAG_PATTERN.test(arg)) {\r\n nodeArgs.push(arg);\r\n } else {\r\n appArgs.push(arg);\r\n }\r\n }\r\n }\r\n\r\n nodeArgs.push(entryPath, ...appArgs);\r\n\r\n // Progress goes to stderr, never stdout. Stdout carries exactly one claim —\r\n // \"started\" — so that whatever greps it cannot mistake an intention for an\r\n // outcome. The banner that used to print here, in `preAction`, printed\r\n // before the child had even been spawned.\r\n console.error(`🚀 Starting production server...\\n`);\r\n\r\n const { exitCode } = await superviseProductionProcess({ nodeArgs });\r\n\r\n process.exit(exitCode);\r\n },\r\n});\r\n"],"mappings":";;;;;;AAKA,MAAM,oBACJ;AAEF,MAAa,yBAAyB,QAAQ;CAC5C,MAAM;CACN,aAAa;CACb,YAAY;CACZ,SAAS,EACP,eAAe,KACjB;CACA,QAAQ,YAAY;EAClB,MAAM,EAAE,WAAW,WAAW,WAAW,mBAAmB;EAM5D,MAAM,YAAY,MAAM,2BAA2B,MAAM;EAEzD,IAAI,CAAC,UAAU,OAAO;GACpB,QAAQ,MAAM,KAAK,UAAU,QAAQ;GACrC,QAAQ,KAAK,CAAC;GACd;EACF;EAGA,MAAM,WAAqB,CAAC;EAG5B,IAAI,cAAc,OAChB,SAAS,KAAK,sBAAsB;EAKtC,MAAM,UAAoB,CAAC;EAC3B,MAAM,aAAa,QAAQ,KAAK,WAAW,QAAQ,QAAQ,OAAO;EAClE,IAAI,eAAe,MAAM,aAAa,QAAQ,KAAK,SAAS,GAC1D,KAAK,MAAM,OAAO,QAAQ,KAAK,MAAM,aAAa,CAAC,GACjD,IAAI,kBAAkB,KAAK,GAAG,GAC5B,SAAS,KAAK,GAAG;OAEjB,QAAQ,KAAK,GAAG;EAKtB,SAAS,KAAK,WAAW,GAAG,OAAO;EAMnC,QAAQ,MAAM,oCAAoC;EAElD,MAAM,EAAE,aAAa,MAAM,2BAA2B,EAAE,SAAS,CAAC;EAElE,QAAQ,KAAK,QAAQ;CACvB;AACF,CAAC"}
1
+ {"version":3,"file":"start-production.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/start-production.command.ts"],"sourcesContent":["import { assertSitesRequireWebRole, parseRoles, parseSites } from \"../../application/roles\";\nimport { checkDistReadyToStartAsync } from \"../../production/assert-dist-ready-to-start\";\nimport { superviseProductionProcess } from \"../../production/production-supervisor\";\nimport { resolveBuildConfig } from \"../../production/resolve-build-config\";\nimport { command } from \"../../commands/cli-command\";\n\nconst NODE_FLAG_PATTERN =\n /^--(inspect(-brk|-wait)?(=.*)?|max-old-space-size=.*|enable-source-maps|trace-.*)$/;\n\n/** `--role`/`--sites`, in both `--role=x` and `--role x` forms — recognised\n * here so they never leak into the child's app argv (they become\n * `WARLOCK_ROLES`/`WARLOCK_SITES` env vars instead). */\nconst ROLE_OPTION_PATTERN = /^--(role|sites)(=.*)?$/;\n\nexport const startProductionCommand = command({\n name: \"start\",\n description: \"Start production server\",\n persistent: true,\n preload: {\n warlockConfig: true,\n },\n action: async (data) => {\n // Validated BEFORE the dist check and the spawn: an unknown role or a\n // `--sites` without `web` is an operator mistake, and the sooner it fails\n // the less it looks like the build (or the child) is at fault.\n const roleOption = typeof data.options.role === \"string\" ? data.options.role : undefined;\n const sitesOption = typeof data.options.sites === \"string\" ? data.options.sites : undefined;\n\n let roles;\n let sites;\n\n try {\n roles = parseRoles(roleOption);\n sites = parseSites(sitesOption);\n assertSitesRequireWebRole(roles, sites);\n } catch (error) {\n console.error(`✖ ${(error as Error).message}`);\n process.exit(1);\n return;\n }\n\n const { entryPath, sourcemap, outdir } = resolveBuildConfig();\n\n // Refuse a `dist` that did not come from a successful `warlock build` —\n // named explicitly, rather than failing later and incidentally because\n // some file the server happens to need (the client manifest, say) is\n // missing. See `assert-dist-ready-to-start.ts`.\n const readiness = await checkDistReadyToStartAsync(outdir);\n\n if (!readiness.ready) {\n console.error(`✖ ${readiness.reason}`);\n process.exit(1);\n return;\n }\n\n // Build node args\n const nodeArgs: string[] = [];\n\n // Enable source maps if configured\n if (sourcemap !== false) {\n nodeArgs.push(\"--enable-source-maps\");\n }\n\n // Recognised Node flags go BEFORE the entry file; everything else is app argv.\n // process.argv = [node, cli.ts, start, ...extra]\n const appArgs: string[] = [];\n const startIndex = process.argv.findIndex((arg) => arg === \"start\");\n if (startIndex !== -1 && startIndex < process.argv.length - 1) {\n const rest = process.argv.slice(startIndex + 1);\n\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i] as string;\n\n if (NODE_FLAG_PATTERN.test(arg)) {\n nodeArgs.push(arg);\n continue;\n }\n\n // `--role`/`--sites` are framework plumbing, resolved above into\n // `WARLOCK_ROLES`/`WARLOCK_SITES` — they must not also reach the\n // child as app argv.\n if (ROLE_OPTION_PATTERN.test(arg)) {\n const next = rest[i + 1];\n\n // `--role=x` already carries its value; `--role x` consumes the\n // following token too, unless it looks like another flag.\n if (!arg.includes(\"=\") && next !== undefined && !next.startsWith(\"-\")) {\n i++;\n }\n\n continue;\n }\n\n appArgs.push(arg);\n }\n }\n\n nodeArgs.push(entryPath, ...appArgs);\n\n // Progress goes to stderr, never stdout. Stdout carries exactly one claim —\n // \"started\" — so that whatever greps it cannot mistake an intention for an\n // outcome. The banner that used to print here, in `preAction`, printed\n // before the child had even been spawned.\n console.error(`🚀 Starting production server...\\n`);\n\n const env: NodeJS.ProcessEnv = { ...process.env, WARLOCK_ROLES: [...roles].join(\",\") };\n\n if (sites) {\n env.WARLOCK_SITES = [...sites].join(\",\");\n }\n\n const { exitCode } = await superviseProductionProcess({ nodeArgs, env });\n\n process.exit(exitCode);\n },\n options: [\n {\n text: \"--role\",\n description: \"Comma-separated roles to serve: api, web, worker. Defaults to every role.\",\n type: \"string\",\n },\n {\n text: \"--sites\",\n description: \"Comma-separated site keys to install pages for. Requires --role=web.\",\n type: \"string\",\n },\n ],\n});\n"],"mappings":";;;;;;;AAMA,MAAM,oBACJ;;;;AAKF,MAAM,sBAAsB;AAE5B,MAAa,yBAAyB,QAAQ;CAC5C,MAAM;CACN,aAAa;CACb,YAAY;CACZ,SAAS,EACP,eAAe,KACjB;CACA,QAAQ,OAAO,SAAS;EAItB,MAAM,aAAa,OAAO,KAAK,QAAQ,SAAS,WAAW,KAAK,QAAQ,OAAO;EAC/E,MAAM,cAAc,OAAO,KAAK,QAAQ,UAAU,WAAW,KAAK,QAAQ,QAAQ;EAElF,IAAI;EACJ,IAAI;EAEJ,IAAI;GACF,QAAQ,WAAW,UAAU;GAC7B,QAAQ,WAAW,WAAW;GAC9B,0BAA0B,OAAO,KAAK;EACxC,SAAS,OAAO;GACd,QAAQ,MAAM,KAAM,MAAgB,SAAS;GAC7C,QAAQ,KAAK,CAAC;GACd;EACF;EAEA,MAAM,EAAE,WAAW,WAAW,WAAW,mBAAmB;EAM5D,MAAM,YAAY,MAAM,2BAA2B,MAAM;EAEzD,IAAI,CAAC,UAAU,OAAO;GACpB,QAAQ,MAAM,KAAK,UAAU,QAAQ;GACrC,QAAQ,KAAK,CAAC;GACd;EACF;EAGA,MAAM,WAAqB,CAAC;EAG5B,IAAI,cAAc,OAChB,SAAS,KAAK,sBAAsB;EAKtC,MAAM,UAAoB,CAAC;EAC3B,MAAM,aAAa,QAAQ,KAAK,WAAW,QAAQ,QAAQ,OAAO;EAClE,IAAI,eAAe,MAAM,aAAa,QAAQ,KAAK,SAAS,GAAG;GAC7D,MAAM,OAAO,QAAQ,KAAK,MAAM,aAAa,CAAC;GAE9C,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;IACpC,MAAM,MAAM,KAAK;IAEjB,IAAI,kBAAkB,KAAK,GAAG,GAAG;KAC/B,SAAS,KAAK,GAAG;KACjB;IACF;IAKA,IAAI,oBAAoB,KAAK,GAAG,GAAG;KACjC,MAAM,OAAO,KAAK,IAAI;KAItB,IAAI,CAAC,IAAI,SAAS,GAAG,KAAK,SAAS,UAAa,CAAC,KAAK,WAAW,GAAG,GAClE;KAGF;IACF;IAEA,QAAQ,KAAK,GAAG;GAClB;EACF;EAEA,SAAS,KAAK,WAAW,GAAG,OAAO;EAMnC,QAAQ,MAAM,oCAAoC;EAElD,MAAM,MAAyB;GAAE,GAAG,QAAQ;GAAK,eAAe,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,GAAG;EAAE;EAErF,IAAI,OACF,IAAI,gBAAgB,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,GAAG;EAGzC,MAAM,EAAE,aAAa,MAAM,2BAA2B;GAAE;GAAU;EAAI,CAAC;EAEvE,QAAQ,KAAK,QAAQ;CACvB;CACA,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"storage-put.action.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/storage-put.action.ts"],"sourcesContent":["import fs from \"fs/promises\";\r\nimport path from \"path\";\r\nimport { config } from \"../../config\";\r\nimport { storage } from \"../../storage\";\r\nimport type { CommandActionData } from \"../../commands/types\";\r\n\r\n/**\r\n * Action for `warlock storage.put <localPath> [destination] [--driver <name>] [--concurrency <n>]`\r\n *\r\n * Uploads a local file or directory into any configured storage driver.\r\n * Auto-detects whether <localPath> is a file or directory and calls the\r\n * appropriate storage method.\r\n *\r\n * @example\r\n * # Upload entire uploads directory to R2 (root of bucket)\r\n * warlock storage.put ./uploads --driver r2\r\n *\r\n * @example\r\n * # Upload to a specific destination prefix\r\n * warlock storage.put ./uploads backups/2026 --driver r2\r\n *\r\n * @example\r\n * # Single file upload with custom concurrency\r\n * warlock storage.put ./public/logo.png assets/logo.png --driver s3\r\n */\r\nexport async function storagePutAction({ args, options }: CommandActionData): Promise<void> {\r\n const [localPath, destination = \"\"] = args;\r\n\r\n if (!localPath) {\r\n console.error(\"✖ Missing required argument: <localPath>\");\r\n console.error(\" Usage: warlock storage.put <localPath> [destination] [--driver <name>]\");\r\n process.exit(1);\r\n }\r\n\r\n // Resolve to absolute path relative to CWD\r\n const absolutePath = path.resolve(process.cwd(), localPath);\r\n const driverName = options.driver as string | undefined;\r\n const concurrency = options.concurrency ? Number(options.concurrency) : 5;\r\n\r\n // Verify the local path exists\r\n let stat: Awaited<ReturnType<typeof fs.stat>>;\r\n try {\r\n stat = await fs.stat(absolutePath);\r\n } catch {\r\n console.error(`✖ Path not found: ${absolutePath}`);\r\n process.exit(1);\r\n }\r\n\r\n // Select the driver — use named driver or fall back to default\r\n const store = driverName ? storage.use(driverName) : storage;\r\n const driverLabel = driverName ?? config.get(\"storage.default\");\r\n\r\n console.log(`\\n Driver : ${driverLabel}`);\r\n console.log(` Source : ${absolutePath}`);\r\n console.log(` Dest : ${destination || \"(root)\"}\\n`);\r\n\r\n if (stat.isDirectory()) {\r\n // ── Directory upload ────────────────────────────────────────────────────\r\n const result = await store.putDirectory(absolutePath, destination, {\r\n concurrency,\r\n // Skip hidden files (e.g. .DS_Store) by default\r\n filter: (_, rel) => !path.basename(rel).startsWith(\".\"),\r\n onProgress: (done, total, file) => {\r\n process.stdout.write(`\\r Progress : ${done}/${total} ${file.path}`);\r\n },\r\n });\r\n\r\n // Clear the progress line and print summary\r\n process.stdout.write(\"\\n\");\r\n console.log(`\\n ✔ ${result.uploaded.length} file(s) uploaded`);\r\n\r\n if (result.failed.length > 0) {\r\n console.warn(`\\n ⚠ ${result.failed.length} file(s) failed:`);\r\n for (const { localPath: fp, error } of result.failed) {\r\n console.warn(` - ${fp}: ${error.message}`);\r\n }\r\n\r\n process.exitCode = 1;\r\n }\r\n } else {\r\n // ── Single file upload ──────────────────────────────────────────────────\r\n // Destination is used as the storage path; if empty, fall back to filename\r\n const storagePath = destination || path.basename(absolutePath);\r\n\r\n console.log(` Uploading : ${path.basename(absolutePath)} → ${storagePath}`);\r\n\r\n const file = await store.putFromPath(absolutePath, storagePath);\r\n\r\n console.log(`\\n ✔ Uploaded: ${file.url}`);\r\n }\r\n\r\n console.log();\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,eAAsB,iBAAiB,EAAE,MAAM,WAA6C;CAC1F,MAAM,CAAC,WAAW,cAAc,MAAM;CAEtC,IAAI,CAAC,WAAW;EACd,QAAQ,MAAM,2CAA2C;EACzD,QAAQ,MAAM,2EAA2E;EACzF,QAAQ,KAAK,CAAC;CAChB;CAGA,MAAM,eAAe,KAAK,QAAQ,QAAQ,IAAI,GAAG,SAAS;CAC1D,MAAM,aAAa,QAAQ;CAC3B,MAAM,cAAc,QAAQ,cAAc,OAAO,QAAQ,WAAW,IAAI;CAGxE,IAAI;CACJ,IAAI;EACF,OAAO,MAAM,GAAG,KAAK,YAAY;CACnC,QAAQ;EACN,QAAQ,MAAM,sBAAsB,cAAc;EAClD,QAAQ,KAAK,CAAC;CAChB;CAGA,MAAM,QAAQ,aAAa,QAAQ,IAAI,UAAU,IAAI;CACrD,MAAM,cAAc,cAAc,OAAO,IAAI,iBAAiB;CAE9D,QAAQ,IAAI,mBAAmB,aAAa;CAC5C,QAAQ,IAAI,iBAAiB,cAAc;CAC3C,QAAQ,IAAI,iBAAiB,eAAe,SAAS,GAAG;CAExD,IAAI,KAAK,YAAY,GAAG;EAEtB,MAAM,SAAS,MAAM,MAAM,aAAa,cAAc,aAAa;GACjE;GAEA,SAAS,GAAG,QAAQ,CAAC,KAAK,SAAS,GAAG,CAAC,CAAC,WAAW,GAAG;GACtD,aAAa,MAAM,OAAO,SAAS;IACjC,QAAQ,OAAO,MAAM,mBAAmB,KAAK,GAAG,MAAM,IAAI,KAAK,MAAM;GACvE;EACF,CAAC;EAGD,QAAQ,OAAO,MAAM,IAAI;EACzB,QAAQ,IAAI,UAAU,OAAO,SAAS,OAAO,kBAAkB;EAE/D,IAAI,OAAO,OAAO,SAAS,GAAG;GAC5B,QAAQ,KAAK,UAAU,OAAO,OAAO,OAAO,iBAAiB;GAC7D,KAAK,MAAM,EAAE,WAAW,IAAI,WAAW,OAAO,QAC5C,QAAQ,KAAK,UAAU,GAAG,IAAI,MAAM,SAAS;GAG/C,QAAQ,WAAW;EACrB;CACF,OAAO;EAGL,MAAM,cAAc,eAAe,KAAK,SAAS,YAAY;EAE7D,QAAQ,IAAI,iBAAiB,KAAK,SAAS,YAAY,EAAE,KAAK,aAAa;EAE3E,MAAM,OAAO,MAAM,MAAM,YAAY,cAAc,WAAW;EAE9D,QAAQ,IAAI,oBAAoB,KAAK,KAAK;CAC5C;CAEA,QAAQ,IAAI;AACd"}
1
+ {"version":3,"file":"storage-put.action.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/storage-put.action.ts"],"sourcesContent":["import fs from \"fs/promises\";\nimport path from \"path\";\nimport { config } from \"../../config\";\nimport { storage } from \"../../storage\";\nimport type { CommandActionData } from \"../../commands/types\";\n\n/**\n * Action for `warlock storage.put <localPath> [destination] [--driver <name>] [--concurrency <n>]`\n *\n * Uploads a local file or directory into any configured storage driver.\n * Auto-detects whether <localPath> is a file or directory and calls the\n * appropriate storage method.\n *\n * @example\n * # Upload entire uploads directory to R2 (root of bucket)\n * warlock storage.put ./uploads --driver r2\n *\n * @example\n * # Upload to a specific destination prefix\n * warlock storage.put ./uploads backups/2026 --driver r2\n *\n * @example\n * # Single file upload with custom concurrency\n * warlock storage.put ./public/logo.png assets/logo.png --driver s3\n */\nexport async function storagePutAction({ args, options }: CommandActionData): Promise<void> {\n const [localPath, destination = \"\"] = args;\n\n if (!localPath) {\n console.error(\"✖ Missing required argument: <localPath>\");\n console.error(\" Usage: warlock storage.put <localPath> [destination] [--driver <name>]\");\n process.exit(1);\n }\n\n // Resolve to absolute path relative to CWD\n const absolutePath = path.resolve(process.cwd(), localPath);\n const driverName = options.driver as string | undefined;\n const concurrency = options.concurrency ? Number(options.concurrency) : 5;\n\n // Verify the local path exists\n let stat: Awaited<ReturnType<typeof fs.stat>>;\n try {\n stat = await fs.stat(absolutePath);\n } catch {\n console.error(`✖ Path not found: ${absolutePath}`);\n process.exit(1);\n }\n\n // Select the driver — use named driver or fall back to default\n const store = driverName ? storage.use(driverName) : storage;\n const driverLabel = driverName ?? config.get(\"storage.default\");\n\n console.log(`\\n Driver : ${driverLabel}`);\n console.log(` Source : ${absolutePath}`);\n console.log(` Dest : ${destination || \"(root)\"}\\n`);\n\n if (stat.isDirectory()) {\n // ── Directory upload ────────────────────────────────────────────────────\n const result = await store.putDirectory(absolutePath, destination, {\n concurrency,\n // Skip hidden files (e.g. .DS_Store) by default\n filter: (_, rel) => !path.basename(rel).startsWith(\".\"),\n onProgress: (done, total, file) => {\n process.stdout.write(`\\r Progress : ${done}/${total} ${file.path}`);\n },\n });\n\n // Clear the progress line and print summary\n process.stdout.write(\"\\n\");\n console.log(`\\n ✔ ${result.uploaded.length} file(s) uploaded`);\n\n if (result.failed.length > 0) {\n console.warn(`\\n ⚠ ${result.failed.length} file(s) failed:`);\n for (const { localPath: fp, error } of result.failed) {\n console.warn(` - ${fp}: ${error.message}`);\n }\n\n process.exitCode = 1;\n }\n } else {\n // ── Single file upload ──────────────────────────────────────────────────\n // Destination is used as the storage path; if empty, fall back to filename\n const storagePath = destination || path.basename(absolutePath);\n\n console.log(` Uploading : ${path.basename(absolutePath)} → ${storagePath}`);\n\n const file = await store.putFromPath(absolutePath, storagePath);\n\n console.log(`\\n ✔ Uploaded: ${file.url}`);\n }\n\n console.log();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,eAAsB,iBAAiB,EAAE,MAAM,WAA6C;CAC1F,MAAM,CAAC,WAAW,cAAc,MAAM;CAEtC,IAAI,CAAC,WAAW;EACd,QAAQ,MAAM,2CAA2C;EACzD,QAAQ,MAAM,2EAA2E;EACzF,QAAQ,KAAK,CAAC;CAChB;CAGA,MAAM,eAAe,KAAK,QAAQ,QAAQ,IAAI,GAAG,SAAS;CAC1D,MAAM,aAAa,QAAQ;CAC3B,MAAM,cAAc,QAAQ,cAAc,OAAO,QAAQ,WAAW,IAAI;CAGxE,IAAI;CACJ,IAAI;EACF,OAAO,MAAM,GAAG,KAAK,YAAY;CACnC,QAAQ;EACN,QAAQ,MAAM,sBAAsB,cAAc;EAClD,QAAQ,KAAK,CAAC;CAChB;CAGA,MAAM,QAAQ,aAAa,QAAQ,IAAI,UAAU,IAAI;CACrD,MAAM,cAAc,cAAc,OAAO,IAAI,iBAAiB;CAE9D,QAAQ,IAAI,mBAAmB,aAAa;CAC5C,QAAQ,IAAI,iBAAiB,cAAc;CAC3C,QAAQ,IAAI,iBAAiB,eAAe,SAAS,GAAG;CAExD,IAAI,KAAK,YAAY,GAAG;EAEtB,MAAM,SAAS,MAAM,MAAM,aAAa,cAAc,aAAa;GACjE;GAEA,SAAS,GAAG,QAAQ,CAAC,KAAK,SAAS,GAAG,CAAC,CAAC,WAAW,GAAG;GACtD,aAAa,MAAM,OAAO,SAAS;IACjC,QAAQ,OAAO,MAAM,mBAAmB,KAAK,GAAG,MAAM,IAAI,KAAK,MAAM;GACvE;EACF,CAAC;EAGD,QAAQ,OAAO,MAAM,IAAI;EACzB,QAAQ,IAAI,UAAU,OAAO,SAAS,OAAO,kBAAkB;EAE/D,IAAI,OAAO,OAAO,SAAS,GAAG;GAC5B,QAAQ,KAAK,UAAU,OAAO,OAAO,OAAO,iBAAiB;GAC7D,KAAK,MAAM,EAAE,WAAW,IAAI,WAAW,OAAO,QAC5C,QAAQ,KAAK,UAAU,GAAG,IAAI,MAAM,SAAS;GAG/C,QAAQ,WAAW;EACrB;CACF,OAAO;EAGL,MAAM,cAAc,eAAe,KAAK,SAAS,YAAY;EAE7D,QAAQ,IAAI,iBAAiB,KAAK,SAAS,YAAY,EAAE,KAAK,aAAa;EAE3E,MAAM,OAAO,MAAM,MAAM,YAAY,cAAc,WAAW;EAE9D,QAAQ,IAAI,oBAAoB,KAAK,KAAK;CAC5C;CAEA,QAAQ,IAAI;AACd"}
@@ -1 +1 @@
1
- {"version":3,"file":"storage-put.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/storage-put.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\r\nimport { storagePutAction } from \"./storage-put.action\";\r\n\r\n/**\r\n * `storage.put` CLI command\r\n *\r\n * Uploads a local file or directory to any configured storage driver.\r\n * Auto-detects file vs directory and streams each file (no full buffer in memory).\r\n *\r\n * @example\r\n * # Upload full uploads/ directory to R2 (migrating from local to cloud)\r\n * warlock storage.put ./uploads --driver r2\r\n *\r\n * @example\r\n * # Upload under a specific prefix\r\n * warlock storage.put ./uploads backups/2026 --driver r2\r\n *\r\n * @example\r\n * # Single file upload to default driver\r\n * warlock storage.put ./public/logo.png assets/logo.png\r\n */\r\nexport const storagePutCommand = command({\r\n name: \"storage.put <localPath> [destination]\",\r\n alias: \"sput\",\r\n action: storagePutAction,\r\n description: \"Upload a local file or directory to storage\",\r\n preload: {\r\n env: true,\r\n config: [\"storage\"],\r\n connectors: [\"storage\"],\r\n },\r\n options: [\r\n {\r\n text: \"--driver, -d\",\r\n description:\r\n \"Storage driver name (as defined in config). Defaults to the configured default driver.\",\r\n },\r\n {\r\n text: \"--concurrency, -c\",\r\n description: \"Number of concurrent uploads when uploading a directory\",\r\n type: \"number\",\r\n defaultValue: 5,\r\n },\r\n ],\r\n});\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,oBAAoB,QAAQ;CACvC,MAAM;CACN,OAAO;CACP,QAAQ;CACR,aAAa;CACb,SAAS;EACP,KAAK;EACL,QAAQ,CAAC,SAAS;EAClB,YAAY,CAAC,SAAS;CACxB;CACA,SAAS,CACP;EACE,MAAM;EACN,aACE;CACJ,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;EACN,cAAc;CAChB,CACF;AACF,CAAC"}
1
+ {"version":3,"file":"storage-put.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/storage-put.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\nimport { storagePutAction } from \"./storage-put.action\";\n\n/**\n * `storage.put` CLI command\n *\n * Uploads a local file or directory to any configured storage driver.\n * Auto-detects file vs directory and streams each file (no full buffer in memory).\n *\n * @example\n * # Upload full uploads/ directory to R2 (migrating from local to cloud)\n * warlock storage.put ./uploads --driver r2\n *\n * @example\n * # Upload under a specific prefix\n * warlock storage.put ./uploads backups/2026 --driver r2\n *\n * @example\n * # Single file upload to default driver\n * warlock storage.put ./public/logo.png assets/logo.png\n */\nexport const storagePutCommand = command({\n name: \"storage.put <localPath> [destination]\",\n alias: \"sput\",\n action: storagePutAction,\n description: \"Upload a local file or directory to storage\",\n preload: {\n env: true,\n config: [\"storage\"],\n connectors: [\"storage\"],\n },\n options: [\n {\n text: \"--driver, -d\",\n description:\n \"Storage driver name (as defined in config). Defaults to the configured default driver.\",\n },\n {\n text: \"--concurrency, -c\",\n description: \"Number of concurrent uploads when uploading a directory\",\n type: \"number\",\n defaultValue: 5,\n },\n ],\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,oBAAoB,QAAQ;CACvC,MAAM;CACN,OAAO;CACP,QAAQ;CACR,aAAa;CACb,SAAS;EACP,KAAK;EACL,QAAQ,CAAC,SAAS;EAClB,YAAY,CAAC,SAAS;CACxB;CACA,SAAS,CACP;EACE,MAAM;EACN,aACE;CACJ,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;EACN,cAAc;CAChB,CACF;AACF,CAAC"}
@@ -1,6 +1,12 @@
1
+ import { rootPath } from "../../utils/paths.mjs";
2
+ import "../../utils/index.mjs";
1
3
  import { command } from "../../commands/cli-command.mjs";
4
+ import { warlockConfigManager } from "../../warlock-config/warlock-config.manager.mjs";
2
5
  import { filesOrchestrator } from "../../dev-server/files-orchestrator.mjs";
6
+ import { collectRouteRegistrationSnapshot } from "../../production/route-registration-snapshot.mjs";
7
+ import { resolveBuildConfig } from "../../production/resolve-build-config.mjs";
3
8
  import { typeGenerator } from "../../dev-server/type-generator.mjs";
9
+ import { runConnectorTypings } from "../../dev-server/run-connector-typings.mjs";
4
10
 
5
11
  //#region ../core/src/cli/commands/typings-generator.command.ts
6
12
  /**
@@ -16,6 +22,9 @@ import { typeGenerator } from "../../dev-server/type-generator.mjs";
16
22
  * Not `filesOrchestrator.init()` — that also registers the ESM loader hook
17
23
  * for dynamically importing project code, which typings generation, being
18
24
  * purely static AST inspection, never does.
25
+ *
26
+ * Connectors then write their own typings through the `build.typings` hook
27
+ * (web's `web-routes.d.ts`), so a clean checkout typechecks before any build.
19
28
  */
20
29
  const typingsGeneratorCommand = command({
21
30
  name: "generate.typings",
@@ -23,7 +32,17 @@ const typingsGeneratorCommand = command({
23
32
  action: async () => {
24
33
  await filesOrchestrator.initializeAll();
25
34
  await typeGenerator.generateAll();
26
- }
35
+ await runConnectorTypings({
36
+ connectors: warlockConfigManager.isLoaded ? warlockConfigManager.get("connectors") ?? [] : [],
37
+ appRoot: rootPath(),
38
+ options: resolveBuildConfig,
39
+ collectRoutes: async () => (await collectRouteRegistrationSnapshot({
40
+ cwd: rootPath(),
41
+ timeoutMs: resolveBuildConfig().routeRegistrationTimeoutMs
42
+ })).routes
43
+ });
44
+ },
45
+ preload: { warlockConfig: true }
27
46
  });
28
47
 
29
48
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"typings-generator.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/typings-generator.command.ts"],"sourcesContent":["import { filesOrchestrator } from \"../../dev-server/files-orchestrator\";\nimport { typeGenerator } from \"../../dev-server/type-generator\";\nimport { command } from \"../../commands/cli-command\";\n\n/**\n * Standalone `warlock generate.typings`.\n *\n * Runs `filesOrchestrator.initializeAll()` before generating — the same\n * full-project discovery `warlock dev` performs at boot — so this produces\n * the identical registry from a fresh checkout instead of only whatever\n * config files a caller happened to list. A prior version added just the\n * config directory's files, which left `TranslationKeyRegistry` empty on any\n * app whose translation keys came from source outside `src/config`.\n *\n * Not `filesOrchestrator.init()` — that also registers the ESM loader hook\n * for dynamically importing project code, which typings generation, being\n * purely static AST inspection, never does.\n */\nexport const typingsGeneratorCommand = command({\n name: \"generate.typings\",\n description: \"Generate type definitions for the project\",\n action: async () => {\n await filesOrchestrator.initializeAll();\n\n await typeGenerator.generateAll();\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,MAAa,0BAA0B,QAAQ;CAC7C,MAAM;CACN,aAAa;CACb,QAAQ,YAAY;EAClB,MAAM,kBAAkB,cAAc;EAEtC,MAAM,cAAc,YAAY;CAClC;AACF,CAAC"}
1
+ {"version":3,"file":"typings-generator.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/typings-generator.command.ts"],"sourcesContent":["import { filesOrchestrator } from \"../../dev-server/files-orchestrator\";\nimport { runConnectorTypings } from \"../../dev-server/run-connector-typings\";\nimport { typeGenerator } from \"../../dev-server/type-generator\";\nimport { command } from \"../../commands/cli-command\";\nimport { collectRouteRegistrationSnapshot } from \"../../production/route-registration-snapshot\";\nimport { resolveBuildConfig } from \"../../production/resolve-build-config\";\nimport { rootPath } from \"../../utils\";\nimport { warlockConfigManager } from \"../../warlock-config/warlock-config.manager\";\n\n/**\n * Standalone `warlock generate.typings`.\n *\n * Runs `filesOrchestrator.initializeAll()` before generating — the same\n * full-project discovery `warlock dev` performs at boot — so this produces\n * the identical registry from a fresh checkout instead of only whatever\n * config files a caller happened to list. A prior version added just the\n * config directory's files, which left `TranslationKeyRegistry` empty on any\n * app whose translation keys came from source outside `src/config`.\n *\n * Not `filesOrchestrator.init()` — that also registers the ESM loader hook\n * for dynamically importing project code, which typings generation, being\n * purely static AST inspection, never does.\n *\n * Connectors then write their own typings through the `build.typings` hook\n * (web's `web-routes.d.ts`), so a clean checkout typechecks before any build.\n */\nexport const typingsGeneratorCommand = command({\n name: \"generate.typings\",\n description: \"Generate type definitions for the project\",\n action: async () => {\n await filesOrchestrator.initializeAll();\n\n await typeGenerator.generateAll();\n\n await runConnectorTypings({\n connectors: warlockConfigManager.isLoaded\n ? (warlockConfigManager.get(\"connectors\") ?? [])\n : [],\n appRoot: rootPath(),\n options: resolveBuildConfig,\n // Same timeout as `warlock build` (build.routeRegistrationTimeoutMs or\n // WARLOCK_ROUTE_REGISTRATION_TIMEOUT_MS): `generate.typings` runs first in\n // the usual build script, so a slower default here fails the whole build.\n collectRoutes: async () =>\n (\n await collectRouteRegistrationSnapshot({\n cwd: rootPath(),\n timeoutMs: resolveBuildConfig().routeRegistrationTimeoutMs,\n })\n ).routes,\n });\n },\n preload: {\n warlockConfig: true,\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,0BAA0B,QAAQ;CAC7C,MAAM;CACN,aAAa;CACb,QAAQ,YAAY;EAClB,MAAM,kBAAkB,cAAc;EAEtC,MAAM,cAAc,YAAY;EAEhC,MAAM,oBAAoB;GACxB,YAAY,qBAAqB,WAC5B,qBAAqB,IAAI,YAAY,KAAK,CAAC,IAC5C,CAAC;GACL,SAAS,SAAS;GAClB,SAAS;GAIT,eAAe,aAEX,MAAM,iCAAiC;IACrC,KAAK,SAAS;IACd,WAAW,mBAAmB,CAAC,CAAC;GAClC,CAAC,EAAC,CACF;EACN,CAAC;CACH;CACA,SAAS,EACP,eAAe,KACjB;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"update.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/update.command.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { updateWarlockPackages } from \"../../updater/update-warlock-packages\";\r\nimport { command } from \"../../commands/cli-command\";\r\n\r\nexport const updateCommand = command({\r\n name: \"update\",\r\n description: \"Update all @warlock.js packages in this project to their latest version\",\r\n action: async (data) => {\r\n // `parseCliArgs` camelCases every flag, so `--no-install` arrives as\r\n // `options.noInstall` (not `options[\"no-install\"]`).\r\n const check = Boolean(data.options.check);\r\n const dryRun = check || Boolean(data.options.dryRun);\r\n\r\n const result = await updateWarlockPackages({\r\n install: !data.options.noInstall,\r\n dryRun,\r\n });\r\n\r\n // `updateWarlockPackages` resolves on a failed install so the dev-server\r\n // shortcut can survive it; a one-shot CLI run must still exit non-zero.\r\n if (result.outcome === \"install-failed\") {\r\n throw result.error ?? new Error(\"Package manager install failed.\");\r\n }\r\n\r\n // `--check` must not pass blind: an unanswered registry is exit 2.\r\n if (check && result.outcome === \"registry-unreachable\") {\r\n process.exit(2);\r\n }\r\n\r\n // `--check` is a gate, so its exit code carries the answer: 0 current,\r\n // 1 behind, 2 registry unreachable. `--dry-run` only reports, and always succeeds.\r\n if (check && result.outcome === \"outdated\") {\r\n console.log(\r\n colors.dim(\"Run \") +\r\n colors.cyan(\"warlock update\") +\r\n colors.dim(\" to apply these, or --dry-run to preview without a gate.\"),\r\n );\r\n process.exit(1);\r\n }\r\n },\r\n options: [\r\n {\r\n text: \"--no-install\",\r\n description:\r\n \"Rewrite the @warlock.js versions in package.json without running the package manager install\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Show which packages would be updated without changing package.json\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--check\",\r\n description:\r\n \"Like --dry-run, but exits 1 when any package is behind — a CI gate for staying current\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n"],"mappings":";;;;;AAIA,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,aAAa;CACb,QAAQ,OAAO,SAAS;EAGtB,MAAM,QAAQ,QAAQ,KAAK,QAAQ,KAAK;EACxC,MAAM,SAAS,SAAS,QAAQ,KAAK,QAAQ,MAAM;EAEnD,MAAM,SAAS,MAAM,sBAAsB;GACzC,SAAS,CAAC,KAAK,QAAQ;GACvB;EACF,CAAC;EAID,IAAI,OAAO,YAAY,kBACrB,MAAM,OAAO,yBAAS,IAAI,MAAM,iCAAiC;EAInE,IAAI,SAAS,OAAO,YAAY,wBAC9B,QAAQ,KAAK,CAAC;EAKhB,IAAI,SAAS,OAAO,YAAY,YAAY;GAC1C,QAAQ,IACN,OAAO,IAAI,MAAM,IACf,OAAO,KAAK,gBAAgB,IAC5B,OAAO,IAAI,0DAA0D,CACzE;GACA,QAAQ,KAAK,CAAC;EAChB;CACF;CACA,SAAS;EACP;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"update.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/update.command.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { updateWarlockPackages } from \"../../updater/update-warlock-packages\";\nimport { command } from \"../../commands/cli-command\";\n\nexport const updateCommand = command({\n name: \"update\",\n description: \"Update all @warlock.js packages in this project to their latest version\",\n action: async (data) => {\n // `parseCliArgs` camelCases every flag, so `--no-install` arrives as\n // `options.noInstall` (not `options[\"no-install\"]`).\n const check = Boolean(data.options.check);\n const dryRun = check || Boolean(data.options.dryRun);\n\n const result = await updateWarlockPackages({\n install: !data.options.noInstall,\n dryRun,\n });\n\n // `updateWarlockPackages` resolves on a failed install so the dev-server\n // shortcut can survive it; a one-shot CLI run must still exit non-zero.\n if (result.outcome === \"install-failed\") {\n throw result.error ?? new Error(\"Package manager install failed.\");\n }\n\n // `--check` must not pass blind: an unanswered registry is exit 2.\n if (check && result.outcome === \"registry-unreachable\") {\n process.exit(2);\n }\n\n // `--check` is a gate, so its exit code carries the answer: 0 current,\n // 1 behind, 2 registry unreachable. `--dry-run` only reports, and always succeeds.\n if (check && result.outcome === \"outdated\") {\n console.log(\n colors.dim(\"Run \") +\n colors.cyan(\"warlock update\") +\n colors.dim(\" to apply these, or --dry-run to preview without a gate.\"),\n );\n process.exit(1);\n }\n },\n options: [\n {\n text: \"--no-install\",\n description:\n \"Rewrite the @warlock.js versions in package.json without running the package manager install\",\n type: \"boolean\",\n },\n {\n text: \"--dry-run\",\n description: \"Show which packages would be updated without changing package.json\",\n type: \"boolean\",\n },\n {\n text: \"--check\",\n description:\n \"Like --dry-run, but exits 1 when any package is behind — a CI gate for staying current\",\n type: \"boolean\",\n },\n ],\n});\n"],"mappings":";;;;;AAIA,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,aAAa;CACb,QAAQ,OAAO,SAAS;EAGtB,MAAM,QAAQ,QAAQ,KAAK,QAAQ,KAAK;EACxC,MAAM,SAAS,SAAS,QAAQ,KAAK,QAAQ,MAAM;EAEnD,MAAM,SAAS,MAAM,sBAAsB;GACzC,SAAS,CAAC,KAAK,QAAQ;GACvB;EACF,CAAC;EAID,IAAI,OAAO,YAAY,kBACrB,MAAM,OAAO,yBAAS,IAAI,MAAM,iCAAiC;EAInE,IAAI,SAAS,OAAO,YAAY,wBAC9B,QAAQ,KAAK,CAAC;EAKhB,IAAI,SAAS,OAAO,YAAY,YAAY;GAC1C,QAAQ,IACN,OAAO,IAAI,MAAM,IACf,OAAO,KAAK,gBAAgB,IAC5B,OAAO,IAAI,0DAA0D,CACzE;GACA,QAAQ,KAAK,CAAC;EAChB;CACF;CACA,SAAS;EACP;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;CACF;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"commands-loader.mjs","names":[],"sources":["../../../../../../../core/src/cli/commands-loader.ts"],"sourcesContent":["import { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { Path } from \"../utils/normalized-path\";\r\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\r\nimport { srcPath } from \"../utils\";\r\nimport { CLICommand } from \"../commands/cli-command\";\r\nimport { isMatchingCommandName } from \"./cli-commands.utils\";\r\n\r\nexport class CLICommandsLoader {\r\n /**\r\n * Locate command by name\r\n */\r\n public async locate(commandName: string): Promise<CLICommand | undefined> {\r\n const files = await getFilesFromDirectory(srcPath(\"app\"), \"**/commands/*.{ts,tsx}\");\r\n // now convert them into relative paths\r\n const relativeFiles = files.map((path) => Path.toRelative(path));\r\n\r\n for (const relativeFile of relativeFiles) {\r\n const command = await this.load(relativeFile);\r\n if (command) {\r\n command.$relativePath(relativeFile);\r\n if (isMatchingCommandName(command.name, commandName)) {\r\n return command;\r\n }\r\n }\r\n }\r\n\r\n return;\r\n }\r\n\r\n /**\r\n * Scan all project commands and return them\r\n * Used for warm cache functionality\r\n */\r\n public async scanAll(): Promise<CLICommand[]> {\r\n const files = await getFilesFromDirectory(srcPath(\"app\"), \"**/commands/*.{ts,tsx}\");\r\n const relativeFiles = files.map((path) => Path.toRelative(path));\r\n const commands: CLICommand[] = [];\r\n\r\n for (const relativeFile of relativeFiles) {\r\n const command = await this.load(relativeFile);\r\n if (command) {\r\n command.$relativePath(relativeFile);\r\n commands.push(command);\r\n }\r\n }\r\n\r\n return commands;\r\n }\r\n\r\n /**\r\n * Load command from a relative path\r\n */\r\n public async load(relativePath: string): Promise<CLICommand | undefined> {\r\n const output = await filesOrchestrator.load<{ default: CLICommand }>(relativePath);\r\n\r\n return output?.default;\r\n }\r\n}\r\n\r\nexport const cliCommandsLoader = new CLICommandsLoader();\r\n"],"mappings":";;;;;;;;AAOA,IAAa,oBAAb,MAA+B;;;;CAI7B,MAAa,OAAO,aAAsD;EAGxE,MAAM,iBAAgB,MAFF,sBAAsB,QAAQ,KAAK,GAAG,wBAAwB,EAEvD,CAAC,KAAK,SAAS,KAAK,WAAW,IAAI,CAAC;EAE/D,KAAK,MAAM,gBAAgB,eAAe;GACxC,MAAM,UAAU,MAAM,KAAK,KAAK,YAAY;GAC5C,IAAI,SAAS;IACX,QAAQ,cAAc,YAAY;IAClC,IAAI,sBAAsB,QAAQ,MAAM,WAAW,GACjD,OAAO;GAEX;EACF;CAGF;;;;;CAMA,MAAa,UAAiC;EAE5C,MAAM,iBAAgB,MADF,sBAAsB,QAAQ,KAAK,GAAG,wBAAwB,EACvD,CAAC,KAAK,SAAS,KAAK,WAAW,IAAI,CAAC;EAC/D,MAAM,WAAyB,CAAC;EAEhC,KAAK,MAAM,gBAAgB,eAAe;GACxC,MAAM,UAAU,MAAM,KAAK,KAAK,YAAY;GAC5C,IAAI,SAAS;IACX,QAAQ,cAAc,YAAY;IAClC,SAAS,KAAK,OAAO;GACvB;EACF;EAEA,OAAO;CACT;;;;CAKA,MAAa,KAAK,cAAuD;EAGvE,QAAO,MAFc,kBAAkB,KAA8B,YAAY,EAEpE,EAAE;CACjB;AACF;AAEA,MAAa,oBAAoB,IAAI,kBAAkB"}
1
+ {"version":3,"file":"commands-loader.mjs","names":[],"sources":["../../../../../../../core/src/cli/commands-loader.ts"],"sourcesContent":["import { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { Path } from \"../utils/normalized-path\";\nimport { getFilesFromDirectory } from \"../dev-server/utils\";\nimport { srcPath } from \"../utils\";\nimport { CLICommand } from \"../commands/cli-command\";\nimport { isMatchingCommandName } from \"./cli-commands.utils\";\n\nexport class CLICommandsLoader {\n /**\n * Locate command by name\n */\n public async locate(commandName: string): Promise<CLICommand | undefined> {\n const files = await getFilesFromDirectory(srcPath(\"app\"), \"**/commands/*.{ts,tsx}\");\n // now convert them into relative paths\n const relativeFiles = files.map((path) => Path.toRelative(path));\n\n for (const relativeFile of relativeFiles) {\n const command = await this.load(relativeFile);\n if (command) {\n command.$relativePath(relativeFile);\n if (isMatchingCommandName(command.name, commandName)) {\n return command;\n }\n }\n }\n\n return;\n }\n\n /**\n * Scan all project commands and return them\n * Used for warm cache functionality\n */\n public async scanAll(): Promise<CLICommand[]> {\n const files = await getFilesFromDirectory(srcPath(\"app\"), \"**/commands/*.{ts,tsx}\");\n const relativeFiles = files.map((path) => Path.toRelative(path));\n const commands: CLICommand[] = [];\n\n for (const relativeFile of relativeFiles) {\n const command = await this.load(relativeFile);\n if (command) {\n command.$relativePath(relativeFile);\n commands.push(command);\n }\n }\n\n return commands;\n }\n\n /**\n * Load command from a relative path\n */\n public async load(relativePath: string): Promise<CLICommand | undefined> {\n const output = await filesOrchestrator.load<{ default: CLICommand }>(relativePath);\n\n return output?.default;\n }\n}\n\nexport const cliCommandsLoader = new CLICommandsLoader();\n"],"mappings":";;;;;;;;AAOA,IAAa,oBAAb,MAA+B;;;;CAI7B,MAAa,OAAO,aAAsD;EAGxE,MAAM,iBAAgB,MAFF,sBAAsB,QAAQ,KAAK,GAAG,wBAAwB,EAEvD,CAAC,KAAK,SAAS,KAAK,WAAW,IAAI,CAAC;EAE/D,KAAK,MAAM,gBAAgB,eAAe;GACxC,MAAM,UAAU,MAAM,KAAK,KAAK,YAAY;GAC5C,IAAI,SAAS;IACX,QAAQ,cAAc,YAAY;IAClC,IAAI,sBAAsB,QAAQ,MAAM,WAAW,GACjD,OAAO;GAEX;EACF;CAGF;;;;;CAMA,MAAa,UAAiC;EAE5C,MAAM,iBAAgB,MADF,sBAAsB,QAAQ,KAAK,GAAG,wBAAwB,EACvD,CAAC,KAAK,SAAS,KAAK,WAAW,IAAI,CAAC;EAC/D,MAAM,WAAyB,CAAC;EAEhC,KAAK,MAAM,gBAAgB,eAAe;GACxC,MAAM,UAAU,MAAM,KAAK,KAAK,YAAY;GAC5C,IAAI,SAAS;IACX,QAAQ,cAAc,YAAY;IAClC,SAAS,KAAK,OAAO;GACvB;EACF;EAEA,OAAO;CACT;;;;CAKA,MAAa,KAAK,cAAuD;EAGvE,QAAO,MAFc,kBAAkB,KAA8B,YAAY,EAEpE,EAAE;CACjB;AACF;AAEA,MAAa,oBAAoB,IAAI,kBAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.mts","names":[],"sources":["../../../../../../../core/src/config/config-loader.ts"],"mappings":";AAeA;;;;AAAA,KAAY,oBAAA,IAAwB,WAAA,oBAA+B,OAAO"}
1
+ {"version":3,"file":"config-loader.d.mts","names":[],"sources":["../../../../../../../core/src/config/config-loader.ts"],"mappings":";AAgBA;;;;AAAA,KAAY,oBAAA,IAAwB,WAAA,oBAA+B,OAAO"}
@@ -1,4 +1,5 @@
1
1
  import { configSpecialHandlers } from "./config-special-handlers.mjs";
2
+ import { importAppModule } from "../loader/app-module-importer.mjs";
2
3
  import { configKeyFromPath } from "./config-key-from-path.mjs";
3
4
  import config from "@mongez/config";
4
5
  import { colors } from "@mongez/copper";
@@ -23,7 +24,8 @@ var ConfigLoader = class {
23
24
  async loadConfig(file) {
24
25
  const configName = this.getConfigName(file.relativePath);
25
26
  try {
26
- const configValue = (await import(pathToFileURL(file.absolutePath).href)).default;
27
+ const fileUrl = pathToFileURL(file.absolutePath).href;
28
+ const configValue = (await importAppModule(fileUrl)).default;
27
29
  if (configValue === void 0) {
28
30
  console.log(colors.red(`config error: `), `Config file ${colors.yellow(file.relativePath)} does not have a default export`);
29
31
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.mjs","names":[],"sources":["../../../../../../../core/src/config/config-loader.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { pathToFileURL } from \"node:url\";\r\nimport type { FileManager } from \"../dev-server/file-manager\";\r\nimport { configKeyFromPath } from \"./config-key-from-path\";\r\nimport { configSpecialHandlers } from \"./config-special-handlers\";\r\n\r\n/**\r\n * Special Config Handler\r\n * A function that handles special configuration loading\r\n */\r\n// Returns unknown, not void: ConfigSpecialHandlers.execute() RETURNS handler(config)\r\n// rather than discarding it, and a test pins that (\"returns the handler's resolved\r\n// value\"). The implementation and the test agreed; only this type said handlers\r\n// produce nothing, which made any value-returning handler fail to typecheck.\r\nexport type SpecialConfigHandler = (configValue: any) => unknown | Promise<unknown>;\r\n\r\n/**\r\n * Config Loader\r\n * Dynamically loads all configuration files and registers them with @mongez/config\r\n * Supports special handlers for configs that require additional processing\r\n */\r\nexport class ConfigLoader {\r\n public async loadAll(configFiles: FileManager[]): Promise<void> {\r\n for (const file of configFiles) {\r\n await this.loadConfig(file);\r\n }\r\n }\r\n\r\n /**\r\n * Load a single configuration file.\r\n *\r\n * The ESM loader hook stamps a version token onto the URL so that each HMR\r\n * cycle gets a fresh module — no manual cache-busting needed here.\r\n */\r\n public async loadConfig(file: FileManager): Promise<void> {\r\n const configName = this.getConfigName(file.relativePath);\r\n\r\n try {\r\n const fileUrl = pathToFileURL(file.absolutePath).href;\r\n const configModule = await import(fileUrl);\r\n const configValue = configModule.default;\r\n\r\n if (configValue === undefined) {\r\n console.log(\r\n colors.red(`config error: `),\r\n `Config file ${colors.yellow(file.relativePath)} does not have a default export`,\r\n );\r\n return;\r\n }\r\n\r\n config.set(configName, configValue);\r\n await configSpecialHandlers.execute(configName, configValue);\r\n } catch (error) {\r\n throw error;\r\n }\r\n }\r\n\r\n public async reloadConfig(file: FileManager): Promise<void> {\r\n await this.loadConfig(file);\r\n }\r\n\r\n private getConfigName(relativePath: string): string {\r\n const configName = configKeyFromPath(relativePath);\r\n\r\n if (!configName) {\r\n throw new Error(`Invalid config file path: ${relativePath}`);\r\n }\r\n\r\n return configName;\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AAsBA,IAAa,eAAb,MAA0B;CACxB,MAAa,QAAQ,aAA2C;EAC9D,KAAK,MAAM,QAAQ,aACjB,MAAM,KAAK,WAAW,IAAI;CAE9B;;;;;;;CAQA,MAAa,WAAW,MAAkC;EACxD,MAAM,aAAa,KAAK,cAAc,KAAK,YAAY;EAEvD,IAAI;GAGF,MAAM,eAAc,MADO,OADX,cAAc,KAAK,YAAY,CAAC,CAAC,MAEjB,CAAC;GAEjC,IAAI,gBAAgB,QAAW;IAC7B,QAAQ,IACN,OAAO,IAAI,gBAAgB,GAC3B,eAAe,OAAO,OAAO,KAAK,YAAY,EAAE,gCAClD;IACA;GACF;GAEA,OAAO,IAAI,YAAY,WAAW;GAClC,MAAM,sBAAsB,QAAQ,YAAY,WAAW;EAC7D,SAAS,OAAO;GACd,MAAM;EACR;CACF;CAEA,MAAa,aAAa,MAAkC;EAC1D,MAAM,KAAK,WAAW,IAAI;CAC5B;CAEA,AAAQ,cAAc,cAA8B;EAClD,MAAM,aAAa,kBAAkB,YAAY;EAEjD,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,6BAA6B,cAAc;EAG7D,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"config-loader.mjs","names":[],"sources":["../../../../../../../core/src/config/config-loader.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { colors } from \"@mongez/copper\";\nimport { pathToFileURL } from \"node:url\";\nimport type { FileManager } from \"../dev-server/file-manager\";\nimport { importAppModule } from \"../loader/app-module-importer\";\nimport { configKeyFromPath } from \"./config-key-from-path\";\nimport { configSpecialHandlers } from \"./config-special-handlers\";\n\n/**\n * Special Config Handler\n * A function that handles special configuration loading\n */\n// Returns unknown, not void: ConfigSpecialHandlers.execute() RETURNS handler(config)\n// rather than discarding it, and a test pins that (\"returns the handler's resolved\n// value\"). The implementation and the test agreed; only this type said handlers\n// produce nothing, which made any value-returning handler fail to typecheck.\nexport type SpecialConfigHandler = (configValue: any) => unknown | Promise<unknown>;\n\n/**\n * Config Loader\n * Dynamically loads all configuration files and registers them with @mongez/config\n * Supports special handlers for configs that require additional processing\n */\nexport class ConfigLoader {\n public async loadAll(configFiles: FileManager[]): Promise<void> {\n for (const file of configFiles) {\n await this.loadConfig(file);\n }\n }\n\n /**\n * Load a single configuration file.\n *\n * The ESM loader hook stamps a version token onto the URL so that each HMR\n * cycle gets a fresh module — no manual cache-busting needed here.\n */\n public async loadConfig(file: FileManager): Promise<void> {\n const configName = this.getConfigName(file.relativePath);\n\n try {\n const fileUrl = pathToFileURL(file.absolutePath).href;\n const configModule = await importAppModule(fileUrl);\n const configValue = configModule.default;\n\n if (configValue === undefined) {\n console.log(\n colors.red(`config error: `),\n `Config file ${colors.yellow(file.relativePath)} does not have a default export`,\n );\n return;\n }\n\n config.set(configName, configValue);\n await configSpecialHandlers.execute(configName, configValue);\n } catch (error) {\n throw error;\n }\n }\n\n public async reloadConfig(file: FileManager): Promise<void> {\n await this.loadConfig(file);\n }\n\n private getConfigName(relativePath: string): string {\n const configName = configKeyFromPath(relativePath);\n\n if (!configName) {\n throw new Error(`Invalid config file path: ${relativePath}`);\n }\n\n return configName;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAuBA,IAAa,eAAb,MAA0B;CACxB,MAAa,QAAQ,aAA2C;EAC9D,KAAK,MAAM,QAAQ,aACjB,MAAM,KAAK,WAAW,IAAI;CAE9B;;;;;;;CAQA,MAAa,WAAW,MAAkC;EACxD,MAAM,aAAa,KAAK,cAAc,KAAK,YAAY;EAEvD,IAAI;GACF,MAAM,UAAU,cAAc,KAAK,YAAY,CAAC,CAAC;GAEjD,MAAM,eAAc,MADO,gBAAgB,OAAO,EAClB,CAAC;GAEjC,IAAI,gBAAgB,QAAW;IAC7B,QAAQ,IACN,OAAO,IAAI,gBAAgB,GAC3B,eAAe,OAAO,OAAO,KAAK,YAAY,EAAE,gCAClD;IACA;GACF;GAEA,OAAO,IAAI,YAAY,WAAW;GAClC,MAAM,sBAAsB,QAAQ,YAAY,WAAW;EAC7D,SAAS,OAAO;GACd,MAAM;EACR;CACF;CAEA,MAAa,aAAa,MAAkC;EAC1D,MAAM,KAAK,WAAW,IAAI;CAC5B;CAEA,AAAQ,cAAc,cAA8B;EAClD,MAAM,aAAa,kBAAkB,YAAY;EAEjD,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,6BAA6B,cAAc;EAG7D,OAAO;CACT;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"connectors-manager.mjs","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { Application } from \"../application\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { AccessConnector } from \"./access-connector\";\r\nimport { AiConnector } from \"./ai-connector\";\r\nimport { CacheConnector } from \"./cache-connector\";\r\nimport { DatabaseConnector } from \"./database-connector\";\r\nimport { HeraldConnector } from \"./herald-connector\";\r\nimport { HttpConnector } from \"./http-connector\";\r\nimport { LoggerConnector } from \"./logger-connector\";\r\nimport { MailerConnector } from \"./mail-connector\";\r\nimport { NotificationsConnector } from \"./notifications-connector\";\r\nimport { SocketConnector } from \"./socket-connector\";\r\nimport { StorageConnector } from \"./storage.connector\";\r\nimport { ConnectorLifecyclePhase } from \"./types\";\r\nimport type { Connector, ConnectorName } from \"./types\";\r\n\r\n/**\r\n * Overall shutdown budget in ms. Override via `app.shutdownTimeout`.\r\n */\r\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\r\n\r\nexport class ConnectorsManager {\r\n /**\r\n * Connectors list\r\n */\r\n private readonly connectors: Connector[] = [];\r\n\r\n /**\r\n * The names the framework's own connectors hold.\r\n *\r\n * Filled by the constructor's own registration calls rather than declared\r\n * beside them, so there is exactly ONE place a built-in name exists: the\r\n * connector that owns it. A second list — in the builder, in a config\r\n * validator, in a test double — is a list that drifts the moment a built-in\r\n * is added or renamed, and it drifts silently, because nothing compares the\r\n * two. Both halves of a connector's life read this one through\r\n * {@link isBuiltInName}: the build refuses a `connectors` array that claims\r\n * a built-in name, and so does registration.\r\n */\r\n private readonly builtInNames = new Set<string>();\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor() {\r\n this.registerBuiltIn(new LoggerConnector());\r\n this.registerBuiltIn(new MailerConnector());\r\n this.registerBuiltIn(new HttpConnector());\r\n this.registerBuiltIn(new DatabaseConnector());\r\n this.registerBuiltIn(new HeraldConnector());\r\n this.registerBuiltIn(new CacheConnector());\r\n this.registerBuiltIn(new StorageConnector());\r\n this.registerBuiltIn(new SocketConnector());\r\n this.registerBuiltIn(new NotificationsConnector());\r\n this.registerBuiltIn(new AccessConnector());\r\n this.registerBuiltIn(new AiConnector());\r\n }\r\n\r\n /**\r\n * Register a framework-owned connector and reserve its name.\r\n *\r\n * Private on purpose: an app connector must not be able to reserve a name,\r\n * and the set is only correct if every entry in it came from a connector\r\n * this class constructed itself.\r\n */\r\n private registerBuiltIn(...connectors: Connector[]): void {\r\n for (const connector of connectors) {\r\n this.builtInNames.add(connector.name);\r\n }\r\n\r\n this.register(...connectors);\r\n }\r\n\r\n /**\r\n * Whether the given name belongs to a framework built-in connector.\r\n *\r\n * Reserved, not merely taken: a configured connector carrying one of these\r\n * names would be skipped by {@link has} at boot while the build had already\r\n * drained its contribution, so the artifact would carry a connector the\r\n * server never runs. Callers refuse the name instead of skipping it.\r\n */\r\n public isBuiltInName(name: string): boolean {\r\n return this.builtInNames.has(name);\r\n }\r\n\r\n /**\r\n * Register a connector\r\n */\r\n public register(...connectors: Connector[]): void {\r\n this.connectors.push(...connectors);\r\n // sort connectors by priority\r\n this.connectors.sort((a, b) => a.priority - b.priority);\r\n }\r\n\r\n /**\r\n * Get all connectors\r\n */\r\n public list(): Connector[] {\r\n return this.connectors;\r\n }\r\n\r\n /**\r\n * Whether a connector with the given name is already registered.\r\n *\r\n * Names are the identity of a connector here, which is what lets a caller\r\n * that may run more than once per process register idempotently instead of\r\n * stacking duplicates that would each boot and shut down.\r\n */\r\n public has(name: ConnectorName): boolean {\r\n return this.connectors.some((connector) => connector.name === name);\r\n }\r\n\r\n /**\r\n * start all connectors\r\n */\r\n public async start(connectorsNames?: ConnectorName[]): Promise<void> {\r\n const connectorsList = connectorsNames\r\n ? this.connectors.filter((connector) => connectorsNames.includes(connector.name))\r\n : this.connectors;\r\n\r\n for (const connector of connectorsList) {\r\n await connector.boot();\r\n }\r\n\r\n for (const connector of connectorsList) {\r\n await connector.start();\r\n }\r\n }\r\n\r\n /**\r\n * Start all connectors in the given lifecycle phase.\r\n *\r\n * The production builder and dev preload split startup around app\r\n * code: early phase before app imports, late phase after. Within a\r\n * phase, all connectors `boot()` first, then all `start()`, so\r\n * cross-connector wiring inside the phase still works (e.g. socket\r\n * reads http's instance during its own boot).\r\n */\r\n public async startPhase(phase: ConnectorLifecyclePhase): Promise<void> {\r\n const phaseConnectors = this.connectors.filter(\r\n (connector) => connector.lifecyclePhase === phase,\r\n );\r\n\r\n for (const connector of phaseConnectors) {\r\n await connector.boot();\r\n }\r\n\r\n for (const connector of phaseConnectors) {\r\n await connector.start();\r\n }\r\n }\r\n\r\n /**\r\n * Start all connectors except the given ones\r\n */\r\n public async startWithout(excludedConnectors: ConnectorName[]): Promise<void> {\r\n await this.start(\r\n this.connectors\r\n .filter((connector) => !excludedConnectors.includes(connector.name))\r\n .map((connector) => connector.name),\r\n );\r\n }\r\n\r\n /**\r\n * Shutdown all connectors\r\n */\r\n public async shutdown(): Promise<void> {\r\n // App-owned teardown runs first, while every connector (db, cache, http) is\r\n // still up, so cleanup hooks can use them. Idempotent + error-isolated.\r\n await Application.runShutdownHooks();\r\n\r\n // HTTP stops accepting and drains in-flight requests FIRST, while storage,\r\n // socket, etc. are still up; then the rest go in reverse priority. Copy the\r\n // list first — `reverse()` mutates in place, so reversing the live array\r\n // would corrupt the order on a second shutdown pass.\r\n const reversed = [...this.connectors].reverse();\r\n const ordered = [\r\n ...reversed.filter((connector) => connector.name === \"http\"),\r\n ...reversed.filter((connector) => connector.name !== \"http\"),\r\n ];\r\n\r\n const timeout = config.get(\"app.shutdownTimeout\", DEFAULT_SHUTDOWN_TIMEOUT);\r\n let current: string | undefined;\r\n\r\n const timer = setTimeout(() => {\r\n // Sync write: the process exits right after, so an async log would be lost.\r\n console.error(\r\n `Shutdown timed out after ${timeout}ms; connector still shutting down: ${current ?? \"none\"}`,\r\n );\r\n process.exit(1);\r\n }, timeout);\r\n timer.unref?.();\r\n\r\n try {\r\n for (const connector of ordered) {\r\n current = connector.name;\r\n await this.shutdownOne(connector);\r\n }\r\n current = undefined;\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n\r\n // `Logger.log()` hands each entry to `channel.log()` without awaiting it, so\r\n // awaiting the calls above is not enough on its own — a buffered or async\r\n // channel (file, Sentry) still loses the entry when the process exits.\r\n await log.flush();\r\n }\r\n\r\n /**\r\n * Shut one connector down, isolating and reporting its failure.\r\n */\r\n private async shutdownOne(connector: Connector): Promise<void> {\r\n try {\r\n await connector.shutdown();\r\n } catch (error) {\r\n try {\r\n // Awaited deliberately: the caller (`shutdownOnProcessKill`) calls\r\n // `process.exit(0)` as soon as this resolves, so an un-awaited log is\r\n // a log that never happens.\r\n await log.error(\"connectors\", \"shutdown\", error as Error, { connector: connector.name });\r\n } catch {\r\n // Reporting a failure must never become a worse failure than the one\r\n // being reported: a channel that throws synchronously rejects this call\r\n // INSIDE the catch, which would abort the remaining teardown. Swallowed\r\n // rather than re-reported: the only channel we could report it through\r\n // is the one that just threw.\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown connectors on process kill\r\n *\r\n * Registers signal handlers that run an idempotent graceful shutdown:\r\n * - SIGINT: Ctrl+C on Unix, also caught on Windows but unreliable in child processes\r\n * - SIGTERM: Termination signal (Unix primarily)\r\n * - SIGHUP: registered on Windows (`win32`) only, to catch Ctrl+C / console close\r\n *\r\n * Note: no `beforeExit` handler is registered — `beforeExit` cannot reliably\r\n * await async teardown (the process exits once the callback returns), so\r\n * shutdown is driven entirely by the signal handlers above.\r\n */\r\n public shutdownOnProcessKill(): void {\r\n let isShuttingDown = false;\r\n\r\n const gracefulShutdown = async (signal: string) => {\r\n if (isShuttingDown) return;\r\n isShuttingDown = true;\r\n\r\n console.log(`\\nExiting...`);\r\n await this.shutdown();\r\n process.exit(0);\r\n };\r\n\r\n // Unix signals\r\n process.on(\"SIGINT\", () => gracefulShutdown(\"SIGINT\"));\r\n process.on(\"SIGTERM\", () => gracefulShutdown(\"SIGTERM\"));\r\n\r\n // Windows-specific: handle when process is about to exit\r\n // This is more reliable on Windows for spawned child processes\r\n if (process.platform === \"win32\") {\r\n // Handle Ctrl+C on Windows specifically\r\n process.on(\"SIGHUP\", () => gracefulShutdown(\"SIGHUP\"));\r\n }\r\n }\r\n}\r\n\r\nexport const connectorsManager = new ConnectorsManager();\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"connectors-manager.mjs","names":[],"sources":["../../../../../../../core/src/connectors/connectors-manager.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { Application } from \"../application\";\nimport { log } from \"@warlock.js/logger\";\nimport { AccessConnector } from \"./access-connector\";\nimport { AiConnector } from \"./ai-connector\";\nimport { CacheConnector } from \"./cache-connector\";\nimport { DatabaseConnector } from \"./database-connector\";\nimport { HeraldConnector } from \"./herald-connector\";\nimport { HttpConnector } from \"./http-connector\";\nimport { LoggerConnector } from \"./logger-connector\";\nimport { MailerConnector } from \"./mail-connector\";\nimport { NotificationsConnector } from \"./notifications-connector\";\nimport { SocketConnector } from \"./socket-connector\";\nimport { StorageConnector } from \"./storage.connector\";\nimport { ConnectorLifecyclePhase } from \"./types\";\nimport type { Connector, ConnectorName } from \"./types\";\n\n/**\n * Overall shutdown budget in ms. Override via `app.shutdownTimeout`.\n */\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\n\nexport class ConnectorsManager {\n /**\n * Connectors list\n */\n private readonly connectors: Connector[] = [];\n\n /**\n * The names the framework's own connectors hold.\n *\n * Filled by the constructor's own registration calls rather than declared\n * beside them, so there is exactly ONE place a built-in name exists: the\n * connector that owns it. A second list — in the builder, in a config\n * validator, in a test double — is a list that drifts the moment a built-in\n * is added or renamed, and it drifts silently, because nothing compares the\n * two. Both halves of a connector's life read this one through\n * {@link isBuiltInName}: the build refuses a `connectors` array that claims\n * a built-in name, and so does registration.\n */\n private readonly builtInNames = new Set<string>();\n\n /**\n * Constructor\n */\n public constructor() {\n this.registerBuiltIn(new LoggerConnector());\n this.registerBuiltIn(new MailerConnector());\n this.registerBuiltIn(new HttpConnector());\n this.registerBuiltIn(new DatabaseConnector());\n this.registerBuiltIn(new HeraldConnector());\n this.registerBuiltIn(new CacheConnector());\n this.registerBuiltIn(new StorageConnector());\n this.registerBuiltIn(new SocketConnector());\n this.registerBuiltIn(new NotificationsConnector());\n this.registerBuiltIn(new AccessConnector());\n this.registerBuiltIn(new AiConnector());\n }\n\n /**\n * Register a framework-owned connector and reserve its name.\n *\n * Private on purpose: an app connector must not be able to reserve a name,\n * and the set is only correct if every entry in it came from a connector\n * this class constructed itself.\n */\n private registerBuiltIn(...connectors: Connector[]): void {\n for (const connector of connectors) {\n this.builtInNames.add(connector.name);\n }\n\n this.register(...connectors);\n }\n\n /**\n * Whether the given name belongs to a framework built-in connector.\n *\n * Reserved, not merely taken: a configured connector carrying one of these\n * names would be skipped by {@link has} at boot while the build had already\n * drained its contribution, so the artifact would carry a connector the\n * server never runs. Callers refuse the name instead of skipping it.\n */\n public isBuiltInName(name: string): boolean {\n return this.builtInNames.has(name);\n }\n\n /**\n * Register a connector\n */\n public register(...connectors: Connector[]): void {\n this.connectors.push(...connectors);\n // sort connectors by priority\n this.connectors.sort((a, b) => a.priority - b.priority);\n }\n\n /**\n * Get all connectors\n */\n public list(): Connector[] {\n return this.connectors;\n }\n\n /**\n * Whether a connector with the given name is already registered.\n *\n * Names are the identity of a connector here, which is what lets a caller\n * that may run more than once per process register idempotently instead of\n * stacking duplicates that would each boot and shut down.\n */\n public has(name: ConnectorName): boolean {\n return this.connectors.some((connector) => connector.name === name);\n }\n\n /**\n * start all connectors\n */\n public async start(connectorsNames?: ConnectorName[]): Promise<void> {\n const connectorsList = connectorsNames\n ? this.connectors.filter((connector) => connectorsNames.includes(connector.name))\n : this.connectors;\n\n for (const connector of connectorsList) {\n await connector.boot();\n }\n\n for (const connector of connectorsList) {\n await connector.start();\n }\n }\n\n /**\n * Start all connectors in the given lifecycle phase.\n *\n * The production builder and dev preload split startup around app\n * code: early phase before app imports, late phase after. Within a\n * phase, all connectors `boot()` first, then all `start()`, so\n * cross-connector wiring inside the phase still works (e.g. socket\n * reads http's instance during its own boot).\n */\n public async startPhase(phase: ConnectorLifecyclePhase): Promise<void> {\n const phaseConnectors = this.connectors.filter(\n (connector) => connector.lifecyclePhase === phase,\n );\n\n for (const connector of phaseConnectors) {\n await connector.boot();\n }\n\n for (const connector of phaseConnectors) {\n await connector.start();\n }\n }\n\n /**\n * Start all connectors except the given ones\n */\n public async startWithout(excludedConnectors: ConnectorName[]): Promise<void> {\n await this.start(\n this.connectors\n .filter((connector) => !excludedConnectors.includes(connector.name))\n .map((connector) => connector.name),\n );\n }\n\n /**\n * Shutdown all connectors\n */\n public async shutdown(): Promise<void> {\n // App-owned teardown runs first, while every connector (db, cache, http) is\n // still up, so cleanup hooks can use them. Idempotent + error-isolated.\n await Application.runShutdownHooks();\n\n // HTTP stops accepting and drains in-flight requests FIRST, while storage,\n // socket, etc. are still up; then the rest go in reverse priority. Copy the\n // list first — `reverse()` mutates in place, so reversing the live array\n // would corrupt the order on a second shutdown pass.\n const reversed = [...this.connectors].reverse();\n const ordered = [\n ...reversed.filter((connector) => connector.name === \"http\"),\n ...reversed.filter((connector) => connector.name !== \"http\"),\n ];\n\n const timeout = config.get(\"app.shutdownTimeout\", DEFAULT_SHUTDOWN_TIMEOUT);\n let current: string | undefined;\n\n const timer = setTimeout(() => {\n // Sync write: the process exits right after, so an async log would be lost.\n console.error(\n `Shutdown timed out after ${timeout}ms; connector still shutting down: ${current ?? \"none\"}`,\n );\n process.exit(1);\n }, timeout);\n timer.unref?.();\n\n try {\n for (const connector of ordered) {\n current = connector.name;\n await this.shutdownOne(connector);\n }\n current = undefined;\n } finally {\n clearTimeout(timer);\n }\n\n // `Logger.log()` hands each entry to `channel.log()` without awaiting it, so\n // awaiting the calls above is not enough on its own — a buffered or async\n // channel (file, Sentry) still loses the entry when the process exits.\n await log.flush();\n }\n\n /**\n * Shut one connector down, isolating and reporting its failure.\n */\n private async shutdownOne(connector: Connector): Promise<void> {\n try {\n await connector.shutdown();\n } catch (error) {\n try {\n // Awaited deliberately: the caller (`shutdownOnProcessKill`) calls\n // `process.exit(0)` as soon as this resolves, so an un-awaited log is\n // a log that never happens.\n await log.error(\"connectors\", \"shutdown\", error as Error, { connector: connector.name });\n } catch {\n // Reporting a failure must never become a worse failure than the one\n // being reported: a channel that throws synchronously rejects this call\n // INSIDE the catch, which would abort the remaining teardown. Swallowed\n // rather than re-reported: the only channel we could report it through\n // is the one that just threw.\n }\n }\n }\n\n /**\n * Shutdown connectors on process kill\n *\n * Registers signal handlers that run an idempotent graceful shutdown:\n * - SIGINT: Ctrl+C on Unix, also caught on Windows but unreliable in child processes\n * - SIGTERM: Termination signal (Unix primarily)\n * - SIGHUP: registered on Windows (`win32`) only, to catch Ctrl+C / console close\n *\n * Note: no `beforeExit` handler is registered — `beforeExit` cannot reliably\n * await async teardown (the process exits once the callback returns), so\n * shutdown is driven entirely by the signal handlers above.\n */\n public shutdownOnProcessKill(): void {\n let isShuttingDown = false;\n\n const gracefulShutdown = async (signal: string) => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n console.log(`\\nExiting...`);\n await this.shutdown();\n process.exit(0);\n };\n\n // Unix signals\n process.on(\"SIGINT\", () => gracefulShutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => gracefulShutdown(\"SIGTERM\"));\n\n // Windows-specific: handle when process is about to exit\n // This is more reliable on Windows for spawned child processes\n if (process.platform === \"win32\") {\n // Handle Ctrl+C on Windows specifically\n process.on(\"SIGHUP\", () => gracefulShutdown(\"SIGHUP\"));\n }\n }\n}\n\nexport const connectorsManager = new ConnectorsManager();\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"database-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/database-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { connectToDatabase, dataSourceRegistry } from \"@warlock.js/cascade\";\r\nimport { container } from \"../container\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Database Connector\r\n * Manages database connection lifecycle using @warlock.js/cascade\r\n */\r\nexport class DatabaseConnector extends BaseConnector {\r\n public readonly name = \"database\";\r\n public readonly priority = ConnectorPriority.DATABASE;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger database restart\r\n */\r\n protected readonly watchedFiles = [\"src/config/database.ts\", \"src/config/database.tsx\"];\r\n\r\n /**\r\n * Initialize database connection\r\n */\r\n public async start(): Promise<void> {\r\n const databaseConfig = config.get(\"database\");\r\n\r\n if (!databaseConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n const source = await connectToDatabase(databaseConfig);\r\n container.set(\"database.source\", source);\r\n this.active = true;\r\n } catch (error) {\r\n console.error(\"Failed to connect to database:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown database connection\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n // Disconnect all registered data sources\r\n const dataSources = dataSourceRegistry.getAllDataSources();\r\n\r\n for (const dataSource of dataSources) {\r\n if (dataSource.driver.isConnected) {\r\n await dataSource.driver.disconnect();\r\n }\r\n }\r\n\r\n this.active = false;\r\n } catch (error) {\r\n console.error(\"Failed to disconnect from database:\", error);\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;AAUA,IAAa,oBAAb,cAAuC,cAAc;;;cAC5B;;;sBAOW,CAAC,0BAA0B,yBAAyB;;;;;CAKtF,MAAa,QAAuB;EAClC,MAAM,iBAAiB,OAAO,IAAI,UAAU;EAE5C,IAAI,CAAC,gBACH;EAGF,IAAI;GACF,MAAM,SAAS,MAAM,kBAAkB,cAAc;GACrD,UAAU,IAAI,mBAAmB,MAAM;GACvC,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,kCAAkC,KAAK;GACrD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GAEF,MAAM,cAAc,mBAAmB,kBAAkB;GAEzD,KAAK,MAAM,cAAc,aACvB,IAAI,WAAW,OAAO,aACpB,MAAM,WAAW,OAAO,WAAW;GAIvC,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,uCAAuC,KAAK;GAC1D,MAAM;EACR;CACF;AACF"}
1
+ {"version":3,"file":"database-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/database-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { connectToDatabase, dataSourceRegistry } from \"@warlock.js/cascade\";\nimport { container } from \"../container\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Database Connector\n * Manages database connection lifecycle using @warlock.js/cascade\n */\nexport class DatabaseConnector extends BaseConnector {\n public readonly name = \"database\";\n public readonly priority = ConnectorPriority.DATABASE;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger database restart\n */\n protected readonly watchedFiles = [\"src/config/database.ts\", \"src/config/database.tsx\"];\n\n /**\n * Initialize database connection\n */\n public async start(): Promise<void> {\n const databaseConfig = config.get(\"database\");\n\n if (!databaseConfig) {\n return;\n }\n\n try {\n const source = await connectToDatabase(databaseConfig);\n container.set(\"database.source\", source);\n this.active = true;\n } catch (error) {\n console.error(\"Failed to connect to database:\", error);\n throw error;\n }\n }\n\n /**\n * Shutdown database connection\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n try {\n // Disconnect all registered data sources\n const dataSources = dataSourceRegistry.getAllDataSources();\n\n for (const dataSource of dataSources) {\n if (dataSource.driver.isConnected) {\n await dataSource.driver.disconnect();\n }\n }\n\n this.active = false;\n } catch (error) {\n console.error(\"Failed to disconnect from database:\", error);\n throw error;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAUA,IAAa,oBAAb,cAAuC,cAAc;;;cAC5B;;;sBAOW,CAAC,0BAA0B,yBAAyB;;;;;CAKtF,MAAa,QAAuB;EAClC,MAAM,iBAAiB,OAAO,IAAI,UAAU;EAE5C,IAAI,CAAC,gBACH;EAGF,IAAI;GACF,MAAM,SAAS,MAAM,kBAAkB,cAAc;GACrD,UAAU,IAAI,mBAAmB,MAAM;GACvC,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,kCAAkC,KAAK;GACrD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GAEF,MAAM,cAAc,mBAAmB,kBAAkB;GAEzD,KAAK,MAAM,cAAc,aACvB,IAAI,WAAW,OAAO,aACpB,MAAM,WAAW,OAAO,WAAW;GAIvC,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,uCAAuC,KAAK;GAC1D,MAAM;EACR;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"herald-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/herald-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Herald Connector\r\n * Manages message broker connection lifecycle using @warlock.js/herald\r\n */\r\nexport class HeraldConnector extends BaseConnector {\r\n public readonly name = \"herald\";\r\n public readonly priority = ConnectorPriority.COMMUNICATOR;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger herald restart\r\n */\r\n protected readonly watchedFiles = [\"src/config/herald.ts\"];\r\n\r\n /**\r\n * Initialize broker connection\r\n */\r\n public async start(): Promise<void> {\r\n const heraldConfig = config.get(\"herald\");\r\n\r\n if (!heraldConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n const { connectToBroker } = await import(\"@warlock.js/herald\");\r\n\r\n log.info(`herald.${heraldConfig.driver}`, \"connection\", \"Connecting to message broker\");\r\n await connectToBroker(heraldConfig);\r\n log.success(`herald.${heraldConfig.driver}`, \"connection\", \"Connected to message broker\");\r\n this.active = true;\r\n } catch (error) {\r\n // Boot-time broker connection failure is unrecoverable — `fatal` makes\r\n // \"page on fatal only\" alerting clean, aligned with the cascade and\r\n // cache drivers. Disconnect failures (shutdown path) stay at error.\r\n log.fatal(\r\n `herald.${heraldConfig.driver}`,\r\n \"connection\",\r\n \"Failed to connect to message broker\",\r\n );\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown broker connection\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n const { brokerRegistry } = await import(\"@warlock.js/herald\");\r\n\r\n // Disconnect all registered brokers\r\n const brokers = brokerRegistry.getAll();\r\n\r\n for (const broker of brokers) {\r\n if (broker.driver.isConnected) {\r\n await broker.driver.disconnect();\r\n }\r\n }\r\n\r\n // Clear the registry for clean restart\r\n brokerRegistry.clear();\r\n\r\n this.active = false;\r\n } catch (error) {\r\n log.error(\"herald\", \"shutdown\", \"Failed to disconnect from message broker\");\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;AASA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW,CAAC,sBAAsB;;;;;CAKzD,MAAa,QAAuB;EAClC,MAAM,eAAe,OAAO,IAAI,QAAQ;EAExC,IAAI,CAAC,cACH;EAGF,IAAI;GACF,MAAM,EAAE,oBAAoB,MAAM,OAAO;GAEzC,IAAI,KAAK,UAAU,aAAa,UAAU,cAAc,8BAA8B;GACtF,MAAM,gBAAgB,YAAY;GAClC,IAAI,QAAQ,UAAU,aAAa,UAAU,cAAc,6BAA6B;GACxF,KAAK,SAAS;EAChB,SAAS,OAAO;GAId,IAAI,MACF,UAAU,aAAa,UACvB,cACA,qCACF;GACA,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,MAAM,EAAE,mBAAmB,MAAM,OAAO;GAGxC,MAAM,UAAU,eAAe,OAAO;GAEtC,KAAK,MAAM,UAAU,SACnB,IAAI,OAAO,OAAO,aAChB,MAAM,OAAO,OAAO,WAAW;GAKnC,eAAe,MAAM;GAErB,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,IAAI,MAAM,UAAU,YAAY,0CAA0C;GAC1E,MAAM;EACR;CACF;AACF"}
1
+ {"version":3,"file":"herald-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/herald-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Herald Connector\n * Manages message broker connection lifecycle using @warlock.js/herald\n */\nexport class HeraldConnector extends BaseConnector {\n public readonly name = \"herald\";\n public readonly priority = ConnectorPriority.COMMUNICATOR;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger herald restart\n */\n protected readonly watchedFiles = [\"src/config/herald.ts\"];\n\n /**\n * Initialize broker connection\n */\n public async start(): Promise<void> {\n const heraldConfig = config.get(\"herald\");\n\n if (!heraldConfig) {\n return;\n }\n\n try {\n const { connectToBroker } = await import(\"@warlock.js/herald\");\n\n log.info(`herald.${heraldConfig.driver}`, \"connection\", \"Connecting to message broker\");\n await connectToBroker(heraldConfig);\n log.success(`herald.${heraldConfig.driver}`, \"connection\", \"Connected to message broker\");\n this.active = true;\n } catch (error) {\n // Boot-time broker connection failure is unrecoverable — `fatal` makes\n // \"page on fatal only\" alerting clean, aligned with the cascade and\n // cache drivers. Disconnect failures (shutdown path) stay at error.\n log.fatal(\n `herald.${heraldConfig.driver}`,\n \"connection\",\n \"Failed to connect to message broker\",\n );\n throw error;\n }\n }\n\n /**\n * Shutdown broker connection\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n try {\n const { brokerRegistry } = await import(\"@warlock.js/herald\");\n\n // Disconnect all registered brokers\n const brokers = brokerRegistry.getAll();\n\n for (const broker of brokers) {\n if (broker.driver.isConnected) {\n await broker.driver.disconnect();\n }\n }\n\n // Clear the registry for clean restart\n brokerRegistry.clear();\n\n this.active = false;\n } catch (error) {\n log.error(\"herald\", \"shutdown\", \"Failed to disconnect from message broker\");\n throw error;\n }\n }\n}\n"],"mappings":";;;;;;;;;;AASA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW,CAAC,sBAAsB;;;;;CAKzD,MAAa,QAAuB;EAClC,MAAM,eAAe,OAAO,IAAI,QAAQ;EAExC,IAAI,CAAC,cACH;EAGF,IAAI;GACF,MAAM,EAAE,oBAAoB,MAAM,OAAO;GAEzC,IAAI,KAAK,UAAU,aAAa,UAAU,cAAc,8BAA8B;GACtF,MAAM,gBAAgB,YAAY;GAClC,IAAI,QAAQ,UAAU,aAAa,UAAU,cAAc,6BAA6B;GACxF,KAAK,SAAS;EAChB,SAAS,OAAO;GAId,IAAI,MACF,UAAU,aAAa,UACvB,cACA,qCACF;GACA,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,MAAM,EAAE,mBAAmB,MAAM,OAAO;GAGxC,MAAM,UAAU,eAAe,OAAO;GAEtC,KAAK,MAAM,UAAU,SACnB,IAAI,OAAO,OAAO,aAChB,MAAM,OAAO,OAAO,WAAW;GAKnC,eAAe,MAAM;GAErB,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,IAAI,MAAM,UAAU,YAAY,0CAA0C;GAC1E,MAAM;EACR;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"http-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"mappings":";;;;;;;AAgDA;;cAAa,aAAA,SAAsB,aAAA;EAAA,SACjB,IAAA;EAAA,SACA,QAAA,GAAQ,iBAAA,CAAA,IAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,IAAA;EAwCb;;;;;EAAA,mBAjCE,YAAA;EAV2B;;;EAAA,UAepC,IAAA,GAAO,eAAA;EAbO;;;;;;;;;;;;;;;;;EAAA,cAgCV,iBAAA;EAyQP;;;AAAoC;;EAhQ9B,IAAA,IAAI,OAAA;;;;;;;;UAmCT,oBAAA;;;;;;EAgCK,KAAA,IAAS,OAAA;;;;;;EAwJT,OAAA,IAAW,OAAA;;;;EASX,QAAA,IAAY,OAAA;;;;;;EA4BlB,aAAA,CAAc,YAAA;AAAA"}
1
+ {"version":3,"file":"http-connector.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"mappings":";;;;;;;AAiDA;;cAAa,aAAA,SAAsB,aAAA;EAAA,SACjB,IAAA;EAAA,SACA,QAAA,GAAQ,iBAAA,CAAA,IAAA;EAAA,SACR,cAAA,GAAc,uBAAA,CAAA,IAAA;EAwCb;;;;;EAAA,mBAjCE,YAAA;EAV2B;;;EAAA,UAepC,IAAA,GAAO,eAAA;EAbO;;;;;;;;;;;;;;;;;EAAA,cAgCV,iBAAA;EAwRP;;;AAAoC;;EA/Q9B,IAAA,IAAI,OAAA;;;;;;;;UAmCT,oBAAA;;;;;;EAgCK,KAAA,IAAS,OAAA;;;;;;EAkKT,OAAA,IAAW,OAAA;;;;EAcX,QAAA,IAAY,OAAA;;;;;;EA4BlB,aAAA,CAAc,YAAA;AAAA"}
@@ -1,5 +1,6 @@
1
1
  import { setBaseUrl } from "../utils/urls.mjs";
2
2
  import { container } from "../container/index.mjs";
3
+ import { getRoles, servesHttp } from "../application/roles.mjs";
3
4
  import { Application } from "../application/application.mjs";
4
5
  import { router } from "../router/router.mjs";
5
6
  import { registerHttpPlugins } from "../http/plugins.mjs";
@@ -9,6 +10,7 @@ import { health } from "../http/health.mjs";
9
10
  import { assertPortIsAvailable } from "../http/port-preflight.mjs";
10
11
  import { isCanonicalPortValue, resolveBindPort } from "../http/resolve-bind-port.mjs";
11
12
  import { setHttpReadyReport } from "../http/ready-report.mjs";
13
+ import { notifyHttpServerRebuilt } from "../http/server-rebuilt.mjs";
12
14
  import { ConnectorLifecyclePhase, ConnectorPriority } from "./types.mjs";
13
15
  import { BaseConnector } from "./base-connector.mjs";
14
16
  import { ensureFatalIsVisible } from "../logger/fatal-visibility.mjs";
@@ -110,6 +112,10 @@ var HttpConnector = class extends BaseConnector {
110
112
  async start() {
111
113
  const httpConfig = config.get("http");
112
114
  if (!httpConfig || !this.http) return;
115
+ if (!servesHttp()) {
116
+ log.info("http", "connection", `http: not started (role: ${[...getRoles()].join(", ")})`);
117
+ return;
118
+ }
113
119
  const deferToReadyBlock = this.rendersReadyBlock;
114
120
  if (Application.runtimeStrategy === "development") router.scanDevServer(this.http);
115
121
  else router.scan(this.http);
@@ -156,6 +162,7 @@ var HttpConnector = class extends BaseConnector {
156
162
  async restart() {
157
163
  await this.shutdown();
158
164
  await this.boot();
165
+ if (this.http) notifyHttpServerRebuilt(this.http);
159
166
  await this.start();
160
167
  }
161
168
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"http-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { colors } from \"@mongez/copper\";\nimport { log } from \"@warlock.js/logger\";\nimport { Application } from \"../application\";\nimport { health } from \"../http/health\";\nimport { assertPortIsAvailable } from \"../http/port-preflight\";\nimport { registerHttpPlugins } from \"../http/plugins\";\nimport { setHttpReadyReport } from \"../http/ready-report\";\nimport { isCanonicalPortValue, resolveBindPort } from \"../http/resolve-bind-port\";\nimport {\n closeServerWithTimeout,\n type FastifyInstance,\n getHttpServer,\n startHttpServer,\n} from \"../http/server\";\nimport { ensureFatalIsVisible } from \"../logger/fatal-visibility\";\nimport { router } from \"../router/router\";\nimport { type Environment } from \"../utils\";\nimport { setBaseUrl } from \"../utils/urls\";\nimport { container } from \"./../container\";\nimport { BaseConnector } from \"./base-connector\";\nimport { describeServerAddress } from \"./describe-server-address\";\nimport { readBoundPort } from \"./read-bound-port\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\nfunction environmentColor(environment: Environment) {\n switch (environment) {\n case \"development\":\n return colors.magentaBright(environment);\n case \"test\":\n return colors.yellowBright(environment);\n case \"production\":\n return colors.greenBright(environment);\n default:\n return colors.white(environment);\n }\n}\n\n/**\n * Default time to wait for in-flight requests to drain on shutdown before the\n * HTTP server is force-closed. Override via `http.gracefulShutdown.timeout`.\n */\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\n\n/**\n * HTTP Connector\n * Manages HTTP server (Fastify) lifecycle\n */\nexport class HttpConnector extends BaseConnector {\n public readonly name = \"http\";\n public readonly priority = ConnectorPriority.HTTP;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\n\n /**\n * Files that trigger HTTP server restart\n * Note: routes.ts changes will be handled by HMR with wildcard routing\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\n */\n protected readonly watchedFiles = [\"src/config/http.ts\", \"src/config/http.tsx\"];\n\n /**\n * Fastify Server instance\n */\n protected http?: FastifyInstance;\n\n /**\n * Whether someone downstream will render the combined ready block.\n *\n * Only the dev server does (`dev-server/ready-block.ts`), and only it can:\n * the block reports facts — boot duration, the web surface — that this\n * connector does not own and must not wait for. When it will, this connector\n * records and stays quiet; otherwise it narrates as it always has.\n *\n * Keyed on the runtime strategy rather than on `NODE_ENV`, because that is\n * what actually distinguishes \"a dev server is driving this boot\" from \"an\n * app happens to be running with development config\".\n *\n * `!isBooted` is the second half and it is not a detail. The block summarises\n * a BOOT. A `restart()` after boot — an edited `src/config/http.ts` — is an\n * EVENT, and there is no block coming to carry it, so suppressing the lines\n * there would rebind the port in total silence.\n */\n protected get rendersReadyBlock(): boolean {\n return Application.runtimeStrategy === \"development\" && !Application.isBooted;\n }\n\n /**\n * Boot the connector — construction only (create Fastify, register\n * plugins, populate container). Route scanning is deferred to\n * `start()` so it reads the router after app code has registered.\n */\n public async boot() {\n const httpConfig = config.get(\"http\");\n\n if (!httpConfig) return;\n\n const port = httpConfig.port;\n log.info(\n `http`,\n \"connection\",\n `Starting http server on port ${port} in ${environmentColor(Application.environment)} mode`,\n );\n\n this.http = startHttpServer(httpConfig.serverOptions);\n\n container.set(\"http.server\", this.http);\n\n await registerHttpPlugins(this.http);\n\n const baseUrl = config.get(\"app.baseUrl\");\n\n // update base url\n setBaseUrl(baseUrl);\n\n // Liveness/readiness endpoints — registered on Fastify before start() scans\n // the app router, so they exist by the time the server listens.\n this.registerHealthRoutes();\n }\n\n /**\n * Register the built-in liveness (`/health`) and readiness (`/ready`)\n * endpoints directly on the Fastify instance — infra routes, kept off the app\n * router so they're immune to HMR and never collide with route scanning. Opt\n * out via `http.health.enabled = false`; rename via `http.health.path` /\n * `http.health.readinessPath`.\n */\n private registerHealthRoutes(): void {\n if (!this.http) {\n return;\n }\n\n const healthConfig = config.get(\"http.health\");\n\n if (healthConfig?.enabled === false) {\n return;\n }\n\n const livenessPath = healthConfig?.path ?? \"/health\";\n const readinessPath = healthConfig?.readinessPath ?? \"/ready\";\n\n this.http.get(livenessPath, async (_request: any, reply: any) => {\n const result = health.liveness();\n\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\n });\n\n this.http.get(readinessPath, async (_request: any, reply: any) => {\n const result = await health.readiness();\n\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\n });\n }\n\n /**\n * Initialize HTTP server — bind app-registered routes to Fastify\n * then listen. Scanning here (not in `boot`) lets HTTP boot before\n * app code without losing routes.\n */\n public async start(): Promise<void> {\n const httpConfig = config.get(\"http\");\n\n if (!httpConfig || !this.http) return;\n\n // Read once. `Application.isBooted` flips during the same boot this method\n // belongs to, and a predicate that answered differently before and after\n // `listen()` would print half a report.\n const deferToReadyBlock = this.rendersReadyBlock;\n\n if (Application.runtimeStrategy === \"development\") {\n router.scanDevServer(this.http);\n } else {\n router.scan(this.http);\n }\n\n // Surface the route table as a readiness signal + a boot log, so an empty\n // or partial route surface (e.g. a route module that failed to register) is\n // detectable instead of silently 404ing. `addRoutesRegisteredCheck` is\n // keyed by name, so re-running on restart is idempotent.\n health.addRoutesRegisteredCheck(() => router.routeCount());\n\n const routeCount = router.routeCount();\n\n // In development this fact is one line of the single ready block rendered\n // after boot completes (`dev-server/ready-block.ts`); printing it here as\n // well would put the same number on screen twice, in two formats. Outside\n // development the line is load-bearing — a supervisor or CI log greps it —\n // so it stays exactly as it was.\n if (!deferToReadyBlock) {\n log.info(\"http\", \"routes\", `${routeCount} route(s) registered`);\n }\n\n // Resolved ONCE, here, at the top of the bind sequence: `httpConfig.port`\n // may still be a string that round-tripped `env()`'s number coercion\n // (`\"03999\"`, `\" 3999\"`, `\"1e3\"`) — `net.Server.listen({ port })` binds\n // all of those without complaint, silently, sometimes to the WRONG port\n // (`\"1e3\"` -> 1000). Every downstream use of the port — the preflight\n // probe and `listen()` itself — must agree on the same resolved number.\n //\n // This is still the port ASKED for, not the port BOUND — `port: 0` asks\n // for \"any free port\" and this stays `0` for that case. What gets\n // published to `warlock:ready` is read back from `listen()`'s resolved\n // address further down, never this value.\n const configuredPort = resolveBindPort(httpConfig.port);\n\n if (!isCanonicalPortValue(httpConfig.port, configuredPort)) {\n log.info(\n \"http\",\n \"connection\",\n `Configured http.port ${JSON.stringify(httpConfig.port)} normalised to ${configuredPort}.`,\n );\n }\n\n try {\n // Preflight the bind before `listen()` so a collision surfaces as the\n // instruction in `PortInUseError.message` (which names the port) instead\n // of a raw `EADDRINUSE` from deep inside Fastify/libuv.\n //\n // Port 0 is exempt: it asks the OS for a free port, so probing it binds\n // some unrelated ephemeral port and \"passes\" while proving nothing about\n // the port this server will actually take.\n if (configuredPort !== 0) {\n await assertPortIsAvailable(configuredPort, httpConfig.host || \"localhost\");\n }\n\n // `listen()` RESOLVES with the address it actually bound — which is the\n // only address worth announcing. See `describe-server-address.ts` for why\n // announcing `app.baseUrl` here instead was a defect rather than a\n // shortcut.\n const boundAddress = await this.http.listen({\n port: configuredPort,\n host: httpConfig.host || \"localhost\",\n });\n\n // Read the ACTUALLY-bound port back out of what `listen()` resolved,\n // rather than republishing `configuredPort`. With `http.port: 0` the OS\n // picks the port, so `configuredPort` stays `0` forever — publishing it\n // instead of the real port is exactly the \"Server ready at a URL that\n // gives connection refused\" defect `describe-server-address.ts` already\n // fixed for the display string. `boundAddress` always carries an\n // explicit port (Fastify never omits one), so `configuredPort` is only\n // a defensive fallback for an address this parse cannot make sense of.\n const boundPort = readBoundPort(boundAddress, configuredPort);\n\n Application.setServedPort(boundPort);\n\n const address = describeServerAddress(\n boundAddress,\n config.get(\"app.baseUrl\"),\n // Passed because `listen()` does not report it back: Fastify resolves a\n // `0.0.0.0` bind to `http://127.0.0.1:<port>`, so the wildcard is only\n // recoverable from what we ASKED for.\n httpConfig.host || \"localhost\",\n );\n\n // Recorded unconditionally: the ready block reads it in development, and\n // in every other mode it is simply the one place that knows what was\n // bound, which is worth having whether or not anything renders it.\n setHttpReadyReport({\n boundAddress: address.boundAddress,\n url: address.url,\n wildcardBind: address.wildcardBind,\n port: boundPort,\n routeCount,\n publicUrl: address.publicUrl,\n });\n\n if (!deferToReadyBlock) {\n log.success(`http`, \"connection\", address.ready);\n\n if (address.publicUrl) {\n log.info(`http`, \"connection\", address.publicUrl);\n }\n }\n\n // NEVER conditional on the block. A base URL pointing at a port nothing\n // is listening on is the single most misleading state this connector can\n // be in, and a warning a status block swallows is a warning nobody reads\n // — so it is logged where it lands ABOVE the block, in both modes.\n if (address.warning) {\n log.warn(`http`, \"connection\", address.warning);\n }\n } catch (error) {\n // A failed listen()/port-bind at boot means the app can't serve — fatal.\n // `log.fatal` reaches Sentry/file; `await log.flush()` drains buffered and\n // async channels before the process exits.\n await log.fatal(\"http\", \"connection\", error);\n\n // …and `log.fatal` reaches NOBODY when the app's log config declares no\n // terminal channel — the ordinary production shape (`production: {\n // channels: [new FileLog()] }`), and the shape of every app with no `log`\n // config at all, since `LoggerConnector.start()` then returns before\n // `log.configure`. That is how an `EADDRINUSE` at boot became a process\n // that exited 1 in total silence: the error was constructed, thrown and\n // caught exactly as designed, and then written to zero channels. stderr\n // is the one destination that is always there.\n ensureFatalIsVisible(error);\n\n await log.flush();\n\n process.exit(1); // stop the process, exit with error\n }\n\n this.active = true;\n }\n\n /**\n * Restart — needs a fresh Fastify instance since `start()` now\n * re-runs `router.scan()`, and re-scanning the same Fastify would\n * register duplicate route handlers.\n */\n public async restart(): Promise<void> {\n await this.shutdown();\n await this.boot();\n await this.start();\n }\n\n /**\n * Shutdown HTTP server\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n const server = getHttpServer();\n\n if (server) {\n const timeout = config.get(\"http.gracefulShutdown.timeout\", DEFAULT_SHUTDOWN_TIMEOUT);\n const drained = await closeServerWithTimeout(server, timeout);\n\n if (!drained) {\n log.warn(\n \"http\",\n \"shutdown\",\n `In-flight requests did not drain within ${timeout}ms; closing anyway`,\n );\n }\n }\n\n this.active = false;\n }\n\n /**\n * Override shouldRestart to handle routes.ts specially\n * routes.ts changes should NOT restart the server (use HMR instead)\n * Now receives config file paths directly from layer-executor\n */\n public shouldRestart(changedFiles: string[]): boolean {\n // Only restart for config changes, not routes\n return changedFiles.some((file) => {\n const relativePath = file.replace(/\\\\/g, \"/\");\n return this.watchedFiles.includes(relativePath);\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"http-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/http-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { colors } from \"@mongez/copper\";\nimport { log } from \"@warlock.js/logger\";\nimport { Application, getRoles, servesHttp } from \"../application\";\nimport { health } from \"../http/health\";\nimport { assertPortIsAvailable } from \"../http/port-preflight\";\nimport { registerHttpPlugins } from \"../http/plugins\";\nimport { notifyHttpServerRebuilt } from \"../http/server-rebuilt\";\nimport { setHttpReadyReport } from \"../http/ready-report\";\nimport { isCanonicalPortValue, resolveBindPort } from \"../http/resolve-bind-port\";\nimport {\n closeServerWithTimeout,\n type FastifyInstance,\n getHttpServer,\n startHttpServer,\n} from \"../http/server\";\nimport { ensureFatalIsVisible } from \"../logger/fatal-visibility\";\nimport { router } from \"../router/router\";\nimport { type Environment } from \"../utils\";\nimport { setBaseUrl } from \"../utils/urls\";\nimport { container } from \"./../container\";\nimport { BaseConnector } from \"./base-connector\";\nimport { describeServerAddress } from \"./describe-server-address\";\nimport { readBoundPort } from \"./read-bound-port\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\nfunction environmentColor(environment: Environment) {\n switch (environment) {\n case \"development\":\n return colors.magentaBright(environment);\n case \"test\":\n return colors.yellowBright(environment);\n case \"production\":\n return colors.greenBright(environment);\n default:\n return colors.white(environment);\n }\n}\n\n/**\n * Default time to wait for in-flight requests to drain on shutdown before the\n * HTTP server is force-closed. Override via `http.gracefulShutdown.timeout`.\n */\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\n\n/**\n * HTTP Connector\n * Manages HTTP server (Fastify) lifecycle\n */\nexport class HttpConnector extends BaseConnector {\n public readonly name = \"http\";\n public readonly priority = ConnectorPriority.HTTP;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\n\n /**\n * Files that trigger HTTP server restart\n * Note: routes.ts changes will be handled by HMR with wildcard routing\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\n */\n protected readonly watchedFiles = [\"src/config/http.ts\", \"src/config/http.tsx\"];\n\n /**\n * Fastify Server instance\n */\n protected http?: FastifyInstance;\n\n /**\n * Whether someone downstream will render the combined ready block.\n *\n * Only the dev server does (`dev-server/ready-block.ts`), and only it can:\n * the block reports facts — boot duration, the web surface — that this\n * connector does not own and must not wait for. When it will, this connector\n * records and stays quiet; otherwise it narrates as it always has.\n *\n * Keyed on the runtime strategy rather than on `NODE_ENV`, because that is\n * what actually distinguishes \"a dev server is driving this boot\" from \"an\n * app happens to be running with development config\".\n *\n * `!isBooted` is the second half and it is not a detail. The block summarises\n * a BOOT. A `restart()` after boot — an edited `src/config/http.ts` — is an\n * EVENT, and there is no block coming to carry it, so suppressing the lines\n * there would rebind the port in total silence.\n */\n protected get rendersReadyBlock(): boolean {\n return Application.runtimeStrategy === \"development\" && !Application.isBooted;\n }\n\n /**\n * Boot the connector — construction only (create Fastify, register\n * plugins, populate container). Route scanning is deferred to\n * `start()` so it reads the router after app code has registered.\n */\n public async boot() {\n const httpConfig = config.get(\"http\");\n\n if (!httpConfig) return;\n\n const port = httpConfig.port;\n log.info(\n `http`,\n \"connection\",\n `Starting http server on port ${port} in ${environmentColor(Application.environment)} mode`,\n );\n\n this.http = startHttpServer(httpConfig.serverOptions);\n\n container.set(\"http.server\", this.http);\n\n await registerHttpPlugins(this.http);\n\n const baseUrl = config.get(\"app.baseUrl\");\n\n // update base url\n setBaseUrl(baseUrl);\n\n // Liveness/readiness endpoints — registered on Fastify before start() scans\n // the app router, so they exist by the time the server listens.\n this.registerHealthRoutes();\n }\n\n /**\n * Register the built-in liveness (`/health`) and readiness (`/ready`)\n * endpoints directly on the Fastify instance — infra routes, kept off the app\n * router so they're immune to HMR and never collide with route scanning. Opt\n * out via `http.health.enabled = false`; rename via `http.health.path` /\n * `http.health.readinessPath`.\n */\n private registerHealthRoutes(): void {\n if (!this.http) {\n return;\n }\n\n const healthConfig = config.get(\"http.health\");\n\n if (healthConfig?.enabled === false) {\n return;\n }\n\n const livenessPath = healthConfig?.path ?? \"/health\";\n const readinessPath = healthConfig?.readinessPath ?? \"/ready\";\n\n this.http.get(livenessPath, async (_request: any, reply: any) => {\n const result = health.liveness();\n\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\n });\n\n this.http.get(readinessPath, async (_request: any, reply: any) => {\n const result = await health.readiness();\n\n return reply.code(result.status === \"ok\" ? 200 : 503).send(result);\n });\n }\n\n /**\n * Initialize HTTP server — bind app-registered routes to Fastify\n * then listen. Scanning here (not in `boot`) lets HTTP boot before\n * app code without losing routes.\n */\n public async start(): Promise<void> {\n const httpConfig = config.get(\"http\");\n\n if (!httpConfig || !this.http) return;\n\n // Neither `api` nor `web` is active — an http-only listener would serve\n // nothing this process is meant to answer, so it never binds. `worker`\n // is the only role that reaches here today; named generically so a\n // future role composition still reads correctly.\n if (!servesHttp()) {\n log.info(\"http\", \"connection\", `http: not started (role: ${[...getRoles()].join(\", \")})`);\n\n return;\n }\n\n // Read once. `Application.isBooted` flips during the same boot this method\n // belongs to, and a predicate that answered differently before and after\n // `listen()` would print half a report.\n const deferToReadyBlock = this.rendersReadyBlock;\n\n if (Application.runtimeStrategy === \"development\") {\n router.scanDevServer(this.http);\n } else {\n router.scan(this.http);\n }\n\n // Surface the route table as a readiness signal + a boot log, so an empty\n // or partial route surface (e.g. a route module that failed to register) is\n // detectable instead of silently 404ing. `addRoutesRegisteredCheck` is\n // keyed by name, so re-running on restart is idempotent.\n health.addRoutesRegisteredCheck(() => router.routeCount());\n\n const routeCount = router.routeCount();\n\n // In development this fact is one line of the single ready block rendered\n // after boot completes (`dev-server/ready-block.ts`); printing it here as\n // well would put the same number on screen twice, in two formats. Outside\n // development the line is load-bearing — a supervisor or CI log greps it —\n // so it stays exactly as it was.\n if (!deferToReadyBlock) {\n log.info(\"http\", \"routes\", `${routeCount} route(s) registered`);\n }\n\n // Resolved ONCE, here, at the top of the bind sequence: `httpConfig.port`\n // may still be a string that round-tripped `env()`'s number coercion\n // (`\"03999\"`, `\" 3999\"`, `\"1e3\"`) — `net.Server.listen({ port })` binds\n // all of those without complaint, silently, sometimes to the WRONG port\n // (`\"1e3\"` -> 1000). Every downstream use of the port — the preflight\n // probe and `listen()` itself — must agree on the same resolved number.\n //\n // This is still the port ASKED for, not the port BOUND — `port: 0` asks\n // for \"any free port\" and this stays `0` for that case. What gets\n // published to `warlock:ready` is read back from `listen()`'s resolved\n // address further down, never this value.\n const configuredPort = resolveBindPort(httpConfig.port);\n\n if (!isCanonicalPortValue(httpConfig.port, configuredPort)) {\n log.info(\n \"http\",\n \"connection\",\n `Configured http.port ${JSON.stringify(httpConfig.port)} normalised to ${configuredPort}.`,\n );\n }\n\n try {\n // Preflight the bind before `listen()` so a collision surfaces as the\n // instruction in `PortInUseError.message` (which names the port) instead\n // of a raw `EADDRINUSE` from deep inside Fastify/libuv.\n //\n // Port 0 is exempt: it asks the OS for a free port, so probing it binds\n // some unrelated ephemeral port and \"passes\" while proving nothing about\n // the port this server will actually take.\n if (configuredPort !== 0) {\n await assertPortIsAvailable(configuredPort, httpConfig.host || \"localhost\");\n }\n\n // `listen()` RESOLVES with the address it actually bound — which is the\n // only address worth announcing. See `describe-server-address.ts` for why\n // announcing `app.baseUrl` here instead was a defect rather than a\n // shortcut.\n const boundAddress = await this.http.listen({\n port: configuredPort,\n host: httpConfig.host || \"localhost\",\n });\n\n // Read the ACTUALLY-bound port back out of what `listen()` resolved,\n // rather than republishing `configuredPort`. With `http.port: 0` the OS\n // picks the port, so `configuredPort` stays `0` forever — publishing it\n // instead of the real port is exactly the \"Server ready at a URL that\n // gives connection refused\" defect `describe-server-address.ts` already\n // fixed for the display string. `boundAddress` always carries an\n // explicit port (Fastify never omits one), so `configuredPort` is only\n // a defensive fallback for an address this parse cannot make sense of.\n const boundPort = readBoundPort(boundAddress, configuredPort);\n\n Application.setServedPort(boundPort);\n\n const address = describeServerAddress(\n boundAddress,\n config.get(\"app.baseUrl\"),\n // Passed because `listen()` does not report it back: Fastify resolves a\n // `0.0.0.0` bind to `http://127.0.0.1:<port>`, so the wildcard is only\n // recoverable from what we ASKED for.\n httpConfig.host || \"localhost\",\n );\n\n // Recorded unconditionally: the ready block reads it in development, and\n // in every other mode it is simply the one place that knows what was\n // bound, which is worth having whether or not anything renders it.\n setHttpReadyReport({\n boundAddress: address.boundAddress,\n url: address.url,\n wildcardBind: address.wildcardBind,\n port: boundPort,\n routeCount,\n publicUrl: address.publicUrl,\n });\n\n if (!deferToReadyBlock) {\n log.success(`http`, \"connection\", address.ready);\n\n if (address.publicUrl) {\n log.info(`http`, \"connection\", address.publicUrl);\n }\n }\n\n // NEVER conditional on the block. A base URL pointing at a port nothing\n // is listening on is the single most misleading state this connector can\n // be in, and a warning a status block swallows is a warning nobody reads\n // — so it is logged where it lands ABOVE the block, in both modes.\n if (address.warning) {\n log.warn(`http`, \"connection\", address.warning);\n }\n } catch (error) {\n // A failed listen()/port-bind at boot means the app can't serve — fatal.\n // `log.fatal` reaches Sentry/file; `await log.flush()` drains buffered and\n // async channels before the process exits.\n await log.fatal(\"http\", \"connection\", error);\n\n // …and `log.fatal` reaches NOBODY when the app's log config declares no\n // terminal channel — the ordinary production shape (`production: {\n // channels: [new FileLog()] }`), and the shape of every app with no `log`\n // config at all, since `LoggerConnector.start()` then returns before\n // `log.configure`. That is how an `EADDRINUSE` at boot became a process\n // that exited 1 in total silence: the error was constructed, thrown and\n // caught exactly as designed, and then written to zero channels. stderr\n // is the one destination that is always there.\n ensureFatalIsVisible(error);\n\n await log.flush();\n\n process.exit(1); // stop the process, exit with error\n }\n\n this.active = true;\n }\n\n /**\n * Restart — needs a fresh Fastify instance since `start()` now\n * re-runs `router.scan()`, and re-scanning the same Fastify would\n * register duplicate route handlers.\n */\n public async restart(): Promise<void> {\n await this.shutdown();\n await this.boot();\n\n // Subscribers (the web connector's Vite middleware) re-attach to the new\n // instance here: after it exists, before `start()` listens.\n if (this.http) notifyHttpServerRebuilt(this.http);\n\n await this.start();\n }\n\n /**\n * Shutdown HTTP server\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n const server = getHttpServer();\n\n if (server) {\n const timeout = config.get(\"http.gracefulShutdown.timeout\", DEFAULT_SHUTDOWN_TIMEOUT);\n const drained = await closeServerWithTimeout(server, timeout);\n\n if (!drained) {\n log.warn(\n \"http\",\n \"shutdown\",\n `In-flight requests did not drain within ${timeout}ms; closing anyway`,\n );\n }\n }\n\n this.active = false;\n }\n\n /**\n * Override shouldRestart to handle routes.ts specially\n * routes.ts changes should NOT restart the server (use HMR instead)\n * Now receives config file paths directly from layer-executor\n */\n public shouldRestart(changedFiles: string[]): boolean {\n // Only restart for config changes, not routes\n return changedFiles.some((file) => {\n const relativePath = file.replace(/\\\\/g, \"/\");\n return this.watchedFiles.includes(relativePath);\n });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"logger-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/logger-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { setLogConfigurations } from \"../logger\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Logger Connector\r\n * Manages logger lifecycle and ensures synchronous flushing on termination\r\n */\r\nexport class LoggerConnector extends BaseConnector {\r\n public readonly name = \"logger\";\r\n public readonly priority = ConnectorPriority.LOGGER;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger logger restart\r\n */\r\n protected readonly watchedFiles = [\"src/config/log.ts\", \"src/config/log.tsx\"];\r\n\r\n /**\r\n * Initialize logger configurations\r\n */\r\n public async start(): Promise<void> {\r\n const logConfig = config.get(\"log\");\r\n\r\n if (!logConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n setLogConfigurations(logConfig);\r\n this.active = true;\r\n } catch (error) {\r\n console.error(\"Failed to initialize logger:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown logger and flush messages synchronously\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n log.flushSync();\r\n this.active = false;\r\n } catch (error) {\r\n console.error(\"Failed to flush logger:\", error);\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AAUA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW,CAAC,qBAAqB,oBAAoB;;;;;CAK5E,MAAa,QAAuB;EAClC,MAAM,YAAY,OAAO,IAAI,KAAK;EAElC,IAAI,CAAC,WACH;EAGF,IAAI;GACF,qBAAqB,SAAS;GAC9B,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,IAAI,UAAU;GACd,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,2BAA2B,KAAK;GAC9C,MAAM;EACR;CACF;AACF"}
1
+ {"version":3,"file":"logger-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/logger-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { setLogConfigurations } from \"../logger\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Logger Connector\n * Manages logger lifecycle and ensures synchronous flushing on termination\n */\nexport class LoggerConnector extends BaseConnector {\n public readonly name = \"logger\";\n public readonly priority = ConnectorPriority.LOGGER;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger logger restart\n */\n protected readonly watchedFiles = [\"src/config/log.ts\", \"src/config/log.tsx\"];\n\n /**\n * Initialize logger configurations\n */\n public async start(): Promise<void> {\n const logConfig = config.get(\"log\");\n\n if (!logConfig) {\n return;\n }\n\n try {\n setLogConfigurations(logConfig);\n this.active = true;\n } catch (error) {\n console.error(\"Failed to initialize logger:\", error);\n throw error;\n }\n }\n\n /**\n * Shutdown logger and flush messages synchronously\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n try {\n log.flushSync();\n this.active = false;\n } catch (error) {\n console.error(\"Failed to flush logger:\", error);\n throw error;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;AAUA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW,CAAC,qBAAqB,oBAAoB;;;;;CAK5E,MAAa,QAAuB;EAClC,MAAM,YAAY,OAAO,IAAI,KAAK;EAElC,IAAI,CAAC,WACH;EAGF,IAAI;GACF,qBAAqB,SAAS;GAC9B,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,IAAI,UAAU;GACd,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,2BAA2B,KAAK;GAC9C,MAAM;EACR;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"mail-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/mail-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport { closeAllMailers, setMailConfigurations } from \"../mail\";\r\nimport { BaseConnector } from \"./base-connector\";\r\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\r\n\r\n/**\r\n * Mailer Connector\r\n * Manages mailer lifecycle and ensures graceful pool shutdown\r\n */\r\nexport class MailerConnector extends BaseConnector {\r\n public readonly name = \"mailer\";\r\n public readonly priority = ConnectorPriority.MAILER;\r\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\r\n\r\n /**\r\n * Files that trigger mailer restart\r\n */\r\n protected readonly watchedFiles = [\".env\", \"src/config/mail.ts\", \"src/config/mail.tsx\"];\r\n\r\n /**\r\n * Initialize mailer configurations\r\n */\r\n public async start(): Promise<void> {\r\n const mailConfig = config.get(\"mail\");\r\n\r\n if (!mailConfig) {\r\n return;\r\n }\r\n\r\n try {\r\n setMailConfigurations(mailConfig);\r\n this.active = true;\r\n } catch (error) {\r\n console.error(\"Failed to initialize mailer:\", error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown mailer pool\r\n */\r\n public async shutdown(): Promise<void> {\r\n if (!this.active) {\r\n return;\r\n }\r\n\r\n try {\r\n closeAllMailers();\r\n this.active = false;\r\n } catch (error) {\r\n console.error(\"Failed to close all mailers:\", error);\r\n throw error;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;AASA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW;GAAC;GAAQ;GAAsB;EAAqB;;;;;CAKtF,MAAa,QAAuB;EAClC,MAAM,aAAa,OAAO,IAAI,MAAM;EAEpC,IAAI,CAAC,YACH;EAGF,IAAI;GACF,sBAAsB,UAAU;GAChC,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,gBAAgB;GAChB,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;AACF"}
1
+ {"version":3,"file":"mail-connector.mjs","names":[],"sources":["../../../../../../../core/src/connectors/mail-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { closeAllMailers, setMailConfigurations } from \"../mail\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, ConnectorPriority } from \"./types\";\n\n/**\n * Mailer Connector\n * Manages mailer lifecycle and ensures graceful pool shutdown\n */\nexport class MailerConnector extends BaseConnector {\n public readonly name = \"mailer\";\n public readonly priority = ConnectorPriority.MAILER;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Early;\n\n /**\n * Files that trigger mailer restart\n */\n protected readonly watchedFiles = [\".env\", \"src/config/mail.ts\", \"src/config/mail.tsx\"];\n\n /**\n * Initialize mailer configurations\n */\n public async start(): Promise<void> {\n const mailConfig = config.get(\"mail\");\n\n if (!mailConfig) {\n return;\n }\n\n try {\n setMailConfigurations(mailConfig);\n this.active = true;\n } catch (error) {\n console.error(\"Failed to initialize mailer:\", error);\n throw error;\n }\n }\n\n /**\n * Shutdown mailer pool\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n try {\n closeAllMailers();\n this.active = false;\n } catch (error) {\n console.error(\"Failed to close all mailers:\", error);\n throw error;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;AASA,IAAa,kBAAb,cAAqC,cAAc;;;cAC1B;;;sBAOW;GAAC;GAAQ;GAAsB;EAAqB;;;;;CAKtF,MAAa,QAAuB;EAClC,MAAM,aAAa,OAAO,IAAI,MAAM;EAEpC,IAAI,CAAC,YACH;EAGF,IAAI;GACF,sBAAsB,UAAU;GAChC,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;;;;CAKA,MAAa,WAA0B;EACrC,IAAI,CAAC,KAAK,QACR;EAGF,IAAI;GACF,gBAAgB;GAChB,KAAK,SAAS;EAChB,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,KAAK;GACnD,MAAM;EACR;CACF;AACF"}
@@ -125,6 +125,14 @@ type ConnectorBuildContribution = {
125
125
  * Runs after esbuild and before `.warlock/production` is removed.
126
126
  */
127
127
  emit?(context: ConnectorBuildContext): Promise<void> | void;
128
+ /**
129
+ * Write the connector's generated `.d.ts` files under `.warlock/typings`.
130
+ *
131
+ * Runs from `warlock generate.typings` with the same context `generate`
132
+ * receives (`productionDir` is not written to), so a clean checkout can
133
+ * typecheck before any build has run.
134
+ */
135
+ typings?(context: ConnectorBuildContext): Promise<void> | void;
128
136
  };
129
137
  type ConnectorName = "logger" | "mailer" | "http" | "database" | "cache" | "storage" | "herald" | "socket" | "notifications" | "access" | "ai" | (string & {});
130
138
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/types.ts"],"mappings":";;;;;;;AAQA;;UAAiB,SAAA;EAIA;;;EAAA,SAAN,IAAA,EAAM,aAAA;EAmCJ;;;;EAAA,SA7BF,QAAA;EANA;;;;EAAA,SAYA,cAAA,EAAgB,uBAAA;EAKzB;;;EAAA,QAAA;EAYS;;;;EANT,IAAA,WAAe,OAAA;EAyBf;;;;EAnBA,KAAA,IAAS,OAAA;EA8BkC;AAO7C;;;EA/BE,OAAA,IAAW,OAAA;EAiCX;;;;EA3BA,QAAA,IAAY,OAAA;EAiCuB;;AAAa;AAalD;;EAvCE,aAAA,CAAc,YAAA;EAuCoB;AACtB;AAOd;;;;;;;EARoC,SA5BzB,KAAA,GAAQ,0BAAA;AAAA;AA+DnB;;;;AAAA,KAxDY,qBAAA;EAgEP,uEA9DH,aAAA,UAqEe;EAnEf,OAAA,UAmE8C;EAjE9C,OAAA,EAAS,mBAAA,EAwDT;EAAA,SAtDS,cAAA,YAA0B,aAAa;AAAA;;;;;;;;;AA+DF;AAGhD;KArDY,qBAAA,GAAwB,IAAI,CACtC,YAAA;;;AAoDuB;KA7Cb,4BAAA;EAoEuB;;;AAE7B;AAMN;;;EApEE,YAAA,aAqEA;EAnEA,OAAA,GAAU,qBAAqB;AAAA;;;;;;;;;;AA6E7B;;;;;KA5DQ,0BAAA;;;;;;EAMV,QAAA,EACE,OAAA,EAAS,qBAAA,GACR,OAAA,CAAQ,4BAAA,WAAuC,4BAAA;;;;;;EAOlD,IAAA,EAAM,OAAA,EAAS,qBAAA,GAAwB,OAAA;AAAA;AAAA,KAG7B,aAAA;;;;;;;;;;aAuBA,uBAAA;EACV,KAAA;EACA,IAAI;AAAA;;;;aAMM,iBAAA;EACV,MAAA;EACA,MAAA;EACA,QAAA;EACA,YAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;EACA,MAAA;EACA,aAAA;EACA,MAAA;EACA,EAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/connectors/types.ts"],"mappings":";;;;;;;AAQA;;UAAiB,SAAA;EAIA;;;EAAA,SAAN,IAAA,EAAM,aAAA;EAmCJ;;;;EAAA,SA7BF,QAAA;EANA;;;;EAAA,SAYA,cAAA,EAAgB,uBAAA;EAKzB;;;EAAA,QAAA;EAYS;;;;EANT,IAAA,WAAe,OAAA;EAyBf;;;;EAnBA,KAAA,IAAS,OAAA;EA8BkC;AAO7C;;;EA/BE,OAAA,IAAW,OAAA;EAiCX;;;;EA3BA,QAAA,IAAY,OAAA;EAiCuB;;AAAa;AAalD;;EAvCE,aAAA,CAAc,YAAA;EAuCoB;AACtB;AAOd;;;;;;;EARoC,SA5BzB,KAAA,GAAQ,0BAAA;AAAA;AA+DnB;;;;AAAA,KAxDY,qBAAA;EAgEP,uEA9DH,aAAA,UAqEe;EAnEf,OAAA,UA4EkB;EA1ElB,OAAA,EAAS,mBAAA,EA0EwC;EAAA,SAxExC,cAAA,YAA0B,aAAa;AAAA;;;;;;;;;;;KAatC,qBAAA,GAAwB,IAAI,CACtC,YAAA;;;;KAOU,4BAAA;EAsDA;;;;AAAa;AAuBzB;;EArEE,YAAA,aAsEA;EApEA,OAAA,GAAU,qBAAqB;AAAA;;;;;;;;;;;;;;;KAiBrB,0BAAA;EAqER;;;;;EA/DF,QAAA,EACE,OAAA,EAAS,qBAAA,GACR,OAAA,CAAQ,4BAAA,WAAuC,4BAAA;;;;;;EAOlD,IAAA,EAAM,OAAA,EAAS,qBAAA,GAAwB,OAAA;;;;;;;;EASvC,OAAA,EAAS,OAAA,EAAS,qBAAA,GAAwB,OAAA;AAAA;AAAA,KAGhC,aAAA;;;;;;;;;;aAuBA,uBAAA;EACV,KAAA;EACA,IAAI;AAAA;;;;aAMM,iBAAA;EACV,MAAA;EACA,MAAA;EACA,QAAA;EACA,YAAA;EACA,KAAA;EACA,IAAA;EACA,OAAA;EACA,MAAA;EACA,aAAA;EACA,MAAA;EACA,EAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","names":[],"sources":["../../../../../../../core/src/connectors/types.ts"],"sourcesContent":["import type { BuildOptions } from \"esbuild\";\nimport type { NamedApiRoute } from \"../router/types\";\nimport type { ResolvedBuildConfig } from \"../production/resolve-build-config\";\n\n/**\n * Connector Interface\n * All service connectors (Database, HTTP, Cache, etc.) must implement this interface\n */\nexport interface Connector {\n /**\n * Unique name of the connector\n */\n readonly name: ConnectorName;\n\n /**\n * Priority for initialization order\n * Lower numbers initialize first (e.g., Database: 1, HTTP: 3)\n */\n readonly priority: number;\n\n /**\n * Phase the connector boots in, relative to user code.\n * See `ConnectorLifecyclePhase` for semantics.\n */\n readonly lifecyclePhase: ConnectorLifecyclePhase;\n\n /**\n * Whether the connector is currently active/connected\n */\n isActive(): boolean;\n\n /**\n * Boot the connector\n * Called during server startup before starting the connector\n */\n boot(): void | Promise<void>;\n\n /**\n * Initialize the connector\n * Called during server startup\n */\n start(): Promise<void>;\n\n /**\n * Restart the connector\n * Called when watched files change\n */\n restart(): Promise<void>;\n\n /**\n * Gracefully shutdown the connector\n * Called during server shutdown\n */\n shutdown(): Promise<void>;\n\n /**\n * Determine if this connector needs restart based on changed files\n * @param changedFiles Array of relative file paths that changed\n * @returns true if connector should restart\n */\n shouldRestart(changedFiles: string[]): boolean;\n\n /**\n * What this connector contributes to `warlock build`.\n *\n * Read STATICALLY from `warlock.config.ts > connectors` — `warlock build`\n * never calls `boot()`/`start()`, so nothing here may assume a running\n * connector. The same `connectors` key drives both halves of a connector's\n * life — build-time contribution and runtime boot; see\n * {@link ConnectorBuildContribution}.\n */\n readonly build?: ConnectorBuildContribution;\n}\n\n/**\n * What a build contribution is handed. Everything it needs to write files\n * and shape the bundle, and nothing that would let it reach the runtime.\n */\nexport type ConnectorBuildContext = {\n /** Absolute path to `.warlock/production` — deleted after the build */\n productionDir: string;\n /** Absolute path to the application root (where `package.json` lives) */\n appRoot: string;\n /** The build config after framework defaults and user overrides */\n options: ResolvedBuildConfig;\n /** Fresh registration-only snapshot, present only when the Web connector is configured. */\n readonly namedApiRoutes?: readonly NamedApiRoute[];\n};\n\n/**\n * The esbuild options a contributor may patch.\n *\n * Deliberately a narrow `Pick`, not `Partial<BuildOptions>`: `entryPoints`,\n * `tsconfig` and `tsconfigRaw` stay forbidden here for the same reason they\n * are forbidden in the user's `build` block — they re-compile every file in\n * the bundle, workspace packages included (see `ForbiddenBuildOptionError`).\n * A contributor reaching them would reintroduce the failure the user-level\n * guard exists to prevent.\n */\nexport type ConnectorEsbuildPatch = Pick<\n BuildOptions,\n \"jsx\" | \"jsxImportSource\" | \"define\" | \"external\" | \"loader\" | \"plugins\"\n>;\n\n/**\n * What `generate` may hand back to the builder.\n */\nexport type ConnectorBuildGenerateResult = {\n /**\n * Lines appended verbatim to section 4 of the generated entry, AFTER the\n * `./routes` import — page registration must see the routes app code\n * collected, mirroring dev's ordering.\n *\n * @example ['await import(\"./pages\");']\n */\n entryImports?: string[];\n /** Merged into the esbuild call: defaults < this patch < user `build` */\n esbuild?: ConnectorEsbuildPatch;\n};\n\n/**\n * A connector's build-time half: two hooks, both awaited, drained in\n * `connectors` array order. A hook that throws fails the build.\n *\n * **This type is closed on purpose**: exactly these two optional function properties and ZERO data-bearing\n * fields. There is deliberately no property where a Vite/babel plugin\n * instance, an alias map or a compiled pipeline could live, so\n * `build: { plugins: [...] }` is a type error rather than something a\n * reviewer has to catch. Anything heavy must be constructed INSIDE a hook,\n * after that hook dynamically imports it — that is what keeps a connector's\n * static import graph out of the runtime closure. The drain mirrors this at\n * read time by rejecting unknown enumerable keys (`assertClosedContribution`):\n * the type narrows, the runtime asserts.\n */\nexport type ConnectorBuildContribution = {\n /**\n * Write files into `ctx.productionDir` and/or patch the bundle.\n *\n * Runs after the generated-imports check and before esbuild bundles.\n */\n generate?(\n context: ConnectorBuildContext,\n ): Promise<ConnectorBuildGenerateResult | void> | ConnectorBuildGenerateResult | void;\n\n /**\n * Produce artifacts esbuild does not — e.g. a client bundle.\n *\n * Runs after esbuild and before `.warlock/production` is removed.\n */\n emit?(context: ConnectorBuildContext): Promise<void> | void;\n};\n\nexport type ConnectorName =\n | \"logger\"\n | \"mailer\"\n | \"http\"\n | \"database\"\n | \"cache\"\n | \"storage\"\n | \"herald\"\n | \"socket\"\n | \"notifications\"\n | \"access\"\n | \"ai\"\n | (string & {});\n\n/**\n * When a connector boots relative to user code (main, routes, events).\n *\n * - `Early`: before user code is imported. For services user code\n * needs at import time (database, cache, logger, storage, mailer).\n * - `Late`: after user code is imported. For services that bind to\n * things user code registered (http scans the router; socket\n * depends on http's instance).\n */\nexport enum ConnectorLifecyclePhase {\n Early = \"early\",\n Late = \"late\",\n}\n\n/**\n * Connector priority constants\n */\nexport enum ConnectorPriority {\n LOGGER = 0,\n MAILER = 1,\n DATABASE = 2,\n COMMUNICATOR = 3,\n CACHE = 4,\n HTTP = 5,\n STORAGE = 6,\n SOCKET = 7,\n NOTIFICATIONS = 8,\n ACCESS = 9,\n AI = 10,\n}\n"],"mappings":";;;;;;;;;;AA+KA,IAAY,0BAAL;CACL;CACA;;AACF;;;;AAKA,IAAY,oBAAL;CACL;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;AACF"}
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../../../../../../../core/src/connectors/types.ts"],"sourcesContent":["import type { BuildOptions } from \"esbuild\";\nimport type { NamedApiRoute } from \"../router/types\";\nimport type { ResolvedBuildConfig } from \"../production/resolve-build-config\";\n\n/**\n * Connector Interface\n * All service connectors (Database, HTTP, Cache, etc.) must implement this interface\n */\nexport interface Connector {\n /**\n * Unique name of the connector\n */\n readonly name: ConnectorName;\n\n /**\n * Priority for initialization order\n * Lower numbers initialize first (e.g., Database: 1, HTTP: 3)\n */\n readonly priority: number;\n\n /**\n * Phase the connector boots in, relative to user code.\n * See `ConnectorLifecyclePhase` for semantics.\n */\n readonly lifecyclePhase: ConnectorLifecyclePhase;\n\n /**\n * Whether the connector is currently active/connected\n */\n isActive(): boolean;\n\n /**\n * Boot the connector\n * Called during server startup before starting the connector\n */\n boot(): void | Promise<void>;\n\n /**\n * Initialize the connector\n * Called during server startup\n */\n start(): Promise<void>;\n\n /**\n * Restart the connector\n * Called when watched files change\n */\n restart(): Promise<void>;\n\n /**\n * Gracefully shutdown the connector\n * Called during server shutdown\n */\n shutdown(): Promise<void>;\n\n /**\n * Determine if this connector needs restart based on changed files\n * @param changedFiles Array of relative file paths that changed\n * @returns true if connector should restart\n */\n shouldRestart(changedFiles: string[]): boolean;\n\n /**\n * What this connector contributes to `warlock build`.\n *\n * Read STATICALLY from `warlock.config.ts > connectors` — `warlock build`\n * never calls `boot()`/`start()`, so nothing here may assume a running\n * connector. The same `connectors` key drives both halves of a connector's\n * life — build-time contribution and runtime boot; see\n * {@link ConnectorBuildContribution}.\n */\n readonly build?: ConnectorBuildContribution;\n}\n\n/**\n * What a build contribution is handed. Everything it needs to write files\n * and shape the bundle, and nothing that would let it reach the runtime.\n */\nexport type ConnectorBuildContext = {\n /** Absolute path to `.warlock/production` — deleted after the build */\n productionDir: string;\n /** Absolute path to the application root (where `package.json` lives) */\n appRoot: string;\n /** The build config after framework defaults and user overrides */\n options: ResolvedBuildConfig;\n /** Fresh registration-only snapshot, present only when the Web connector is configured. */\n readonly namedApiRoutes?: readonly NamedApiRoute[];\n};\n\n/**\n * The esbuild options a contributor may patch.\n *\n * Deliberately a narrow `Pick`, not `Partial<BuildOptions>`: `entryPoints`,\n * `tsconfig` and `tsconfigRaw` stay forbidden here for the same reason they\n * are forbidden in the user's `build` block — they re-compile every file in\n * the bundle, workspace packages included (see `ForbiddenBuildOptionError`).\n * A contributor reaching them would reintroduce the failure the user-level\n * guard exists to prevent.\n */\nexport type ConnectorEsbuildPatch = Pick<\n BuildOptions,\n \"jsx\" | \"jsxImportSource\" | \"define\" | \"external\" | \"loader\" | \"plugins\"\n>;\n\n/**\n * What `generate` may hand back to the builder.\n */\nexport type ConnectorBuildGenerateResult = {\n /**\n * Lines appended verbatim to section 4 of the generated entry, AFTER the\n * `./routes` import — page registration must see the routes app code\n * collected, mirroring dev's ordering.\n *\n * @example ['await import(\"./pages\");']\n */\n entryImports?: string[];\n /** Merged into the esbuild call: defaults < this patch < user `build` */\n esbuild?: ConnectorEsbuildPatch;\n};\n\n/**\n * A connector's build-time half: two hooks, both awaited, drained in\n * `connectors` array order. A hook that throws fails the build.\n *\n * **This type is closed on purpose**: exactly these two optional function properties and ZERO data-bearing\n * fields. There is deliberately no property where a Vite/babel plugin\n * instance, an alias map or a compiled pipeline could live, so\n * `build: { plugins: [...] }` is a type error rather than something a\n * reviewer has to catch. Anything heavy must be constructed INSIDE a hook,\n * after that hook dynamically imports it — that is what keeps a connector's\n * static import graph out of the runtime closure. The drain mirrors this at\n * read time by rejecting unknown enumerable keys (`assertClosedContribution`):\n * the type narrows, the runtime asserts.\n */\nexport type ConnectorBuildContribution = {\n /**\n * Write files into `ctx.productionDir` and/or patch the bundle.\n *\n * Runs after the generated-imports check and before esbuild bundles.\n */\n generate?(\n context: ConnectorBuildContext,\n ): Promise<ConnectorBuildGenerateResult | void> | ConnectorBuildGenerateResult | void;\n\n /**\n * Produce artifacts esbuild does not — e.g. a client bundle.\n *\n * Runs after esbuild and before `.warlock/production` is removed.\n */\n emit?(context: ConnectorBuildContext): Promise<void> | void;\n\n /**\n * Write the connector's generated `.d.ts` files under `.warlock/typings`.\n *\n * Runs from `warlock generate.typings` with the same context `generate`\n * receives (`productionDir` is not written to), so a clean checkout can\n * typecheck before any build has run.\n */\n typings?(context: ConnectorBuildContext): Promise<void> | void;\n};\n\nexport type ConnectorName =\n | \"logger\"\n | \"mailer\"\n | \"http\"\n | \"database\"\n | \"cache\"\n | \"storage\"\n | \"herald\"\n | \"socket\"\n | \"notifications\"\n | \"access\"\n | \"ai\"\n | (string & {});\n\n/**\n * When a connector boots relative to user code (main, routes, events).\n *\n * - `Early`: before user code is imported. For services user code\n * needs at import time (database, cache, logger, storage, mailer).\n * - `Late`: after user code is imported. For services that bind to\n * things user code registered (http scans the router; socket\n * depends on http's instance).\n */\nexport enum ConnectorLifecyclePhase {\n Early = \"early\",\n Late = \"late\",\n}\n\n/**\n * Connector priority constants\n */\nexport enum ConnectorPriority {\n LOGGER = 0,\n MAILER = 1,\n DATABASE = 2,\n COMMUNICATOR = 3,\n CACHE = 4,\n HTTP = 5,\n STORAGE = 6,\n SOCKET = 7,\n NOTIFICATIONS = 8,\n ACCESS = 9,\n AI = 10,\n}\n"],"mappings":";;;;;;;;;;AAwLA,IAAY,0BAAL;CACL;CACA;;AACF;;;;AAKA,IAAY,oBAAL;CACL;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-database-action.mjs","names":[],"sources":["../../../../../../../core/src/database/create-database-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { createDatabase } from \"@warlock.js/cascade\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type { CommandActionData } from \"../commands/types\";\r\nimport { config } from \"../config\";\r\n\r\nexport async function createDatabaseAction(command: CommandActionData) {\r\n const name = command.args[0];\r\n const { connection = \"default\" } = command.options;\r\n\r\n const connectionName =\r\n connection === \"default\" || connection === true ? undefined : String(connection);\r\n\r\n let databaseName = name;\r\n\r\n if (!databaseName) {\r\n if (connection === \"default\") {\r\n databaseName = config.get(\"database\")?.database;\r\n }\r\n }\r\n\r\n if (!databaseName) {\r\n log.error(\r\n \"database\",\r\n \"create\",\r\n \"Database name is required. Please provide a name or configure it in the database config.\",\r\n );\r\n return;\r\n }\r\n\r\n log.info(\"database\", \"create\", `Creating database ${colors.cyan(databaseName)}...`);\r\n\r\n try {\r\n const { created } = await createDatabase(databaseName, { connection: connectionName });\r\n\r\n if (created) {\r\n log.success(\r\n \"database\",\r\n \"create\",\r\n `Database ${colors.green(databaseName)} created successfully.`,\r\n );\r\n } else {\r\n log.warn(\"database\", \"create\", `Database ${colors.yellow(databaseName)} already exists.`);\r\n }\r\n } catch (error: any) {\r\n log.error(\r\n \"database\",\r\n \"create\",\r\n `Failed to create database ${colors.red(databaseName)}: ${error.message}`,\r\n );\r\n }\r\n}\r\n"],"mappings":";;;;;;;AAMA,eAAsB,qBAAqB,SAA4B;CACrE,MAAM,OAAO,QAAQ,KAAK;CAC1B,MAAM,EAAE,aAAa,cAAc,QAAQ;CAE3C,MAAM,iBACJ,eAAe,aAAa,eAAe,OAAO,SAAY,OAAO,UAAU;CAEjF,IAAI,eAAe;CAEnB,IAAI,CAAC,cACH;MAAI,eAAe,WACjB,eAAe,OAAO,IAAI,UAAU,CAAC,EAAE;CACzC;CAGF,IAAI,CAAC,cAAc;EACjB,IAAI,MACF,YACA,UACA,0FACF;EACA;CACF;CAEA,IAAI,KAAK,YAAY,UAAU,qBAAqB,OAAO,KAAK,YAAY,EAAE,IAAI;CAElF,IAAI;EACF,MAAM,EAAE,YAAY,MAAM,eAAe,cAAc,EAAE,YAAY,eAAe,CAAC;EAErF,IAAI,SACF,IAAI,QACF,YACA,UACA,YAAY,OAAO,MAAM,YAAY,EAAE,uBACzC;OAEA,IAAI,KAAK,YAAY,UAAU,YAAY,OAAO,OAAO,YAAY,EAAE,iBAAiB;CAE5F,SAAS,OAAY;EACnB,IAAI,MACF,YACA,UACA,6BAA6B,OAAO,IAAI,YAAY,EAAE,IAAI,MAAM,SAClE;CACF;AACF"}
1
+ {"version":3,"file":"create-database-action.mjs","names":[],"sources":["../../../../../../../core/src/database/create-database-action.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { createDatabase } from \"@warlock.js/cascade\";\nimport { log } from \"@warlock.js/logger\";\nimport type { CommandActionData } from \"../commands/types\";\nimport { config } from \"../config\";\n\nexport async function createDatabaseAction(command: CommandActionData) {\n const name = command.args[0];\n const { connection = \"default\" } = command.options;\n\n const connectionName =\n connection === \"default\" || connection === true ? undefined : String(connection);\n\n let databaseName = name;\n\n if (!databaseName) {\n if (connection === \"default\") {\n databaseName = config.get(\"database\")?.database;\n }\n }\n\n if (!databaseName) {\n log.error(\n \"database\",\n \"create\",\n \"Database name is required. Please provide a name or configure it in the database config.\",\n );\n return;\n }\n\n log.info(\"database\", \"create\", `Creating database ${colors.cyan(databaseName)}...`);\n\n try {\n const { created } = await createDatabase(databaseName, { connection: connectionName });\n\n if (created) {\n log.success(\n \"database\",\n \"create\",\n `Database ${colors.green(databaseName)} created successfully.`,\n );\n } else {\n log.warn(\"database\", \"create\", `Database ${colors.yellow(databaseName)} already exists.`);\n }\n } catch (error: any) {\n log.error(\n \"database\",\n \"create\",\n `Failed to create database ${colors.red(databaseName)}: ${error.message}`,\n );\n }\n}\n"],"mappings":";;;;;;;AAMA,eAAsB,qBAAqB,SAA4B;CACrE,MAAM,OAAO,QAAQ,KAAK;CAC1B,MAAM,EAAE,aAAa,cAAc,QAAQ;CAE3C,MAAM,iBACJ,eAAe,aAAa,eAAe,OAAO,SAAY,OAAO,UAAU;CAEjF,IAAI,eAAe;CAEnB,IAAI,CAAC,cACH;MAAI,eAAe,WACjB,eAAe,OAAO,IAAI,UAAU,CAAC,EAAE;CACzC;CAGF,IAAI,CAAC,cAAc;EACjB,IAAI,MACF,YACA,UACA,0FACF;EACA;CACF;CAEA,IAAI,KAAK,YAAY,UAAU,qBAAqB,OAAO,KAAK,YAAY,EAAE,IAAI;CAElF,IAAI;EACF,MAAM,EAAE,YAAY,MAAM,eAAe,cAAc,EAAE,YAAY,eAAe,CAAC;EAErF,IAAI,SACF,IAAI,QACF,YACA,UACA,YAAY,OAAO,MAAM,YAAY,EAAE,uBACzC;OAEA,IAAI,KAAK,YAAY,UAAU,YAAY,OAAO,OAAO,YAAY,EAAE,iBAAiB;CAE5F,SAAS,OAAY;EACnB,IAAI,MACF,YACA,UACA,6BAA6B,OAAO,IAAI,YAAY,EAAE,IAAI,MAAM,SAClE;CACF;AACF"}