infra-kit 0.1.126 β†’ 0.1.128

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 (335) hide show
  1. package/.eslintcache +1 -1
  2. package/.omc/state/agent-replay-036daa78-5fee-4c7d-9b44-53584acb0d87.jsonl +26 -0
  3. package/.omc/state/agent-replay-0a65ec83-9de7-458d-9d0f-e56e1a26c68e.jsonl +2 -0
  4. package/.omc/state/agent-replay-1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a.jsonl +17 -0
  5. package/.omc/state/agent-replay-2ac0dcd0-01f7-41a8-a8c9-f2195599817b.jsonl +5 -0
  6. package/.omc/state/agent-replay-d02e6565-5168-40c8-8629-de219a29fa43.jsonl +2 -0
  7. package/.omc/state/idle-notif-cooldown.json +1 -1
  8. package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/last-tool-error-state.json +7 -0
  9. package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/mission-state.json +129 -0
  10. package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/pre-tool-advisory-throttle.json +34 -0
  11. package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/ralph-state.json +13 -0
  12. package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/ralplan-state.json +11 -0
  13. package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/subagent-tracking-state.json +80 -0
  14. package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/ultrawork-state.json +11 -0
  15. package/.omc/state/sessions/0a65ec83-9de7-458d-9d0f-e56e1a26c68e/pre-tool-advisory-throttle.json +18 -0
  16. package/.omc/state/sessions/0a65ec83-9de7-458d-9d0f-e56e1a26c68e/skill-active-state.json +15 -0
  17. package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/last-tool-error-state.json +7 -0
  18. package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/mission-state.json +139 -0
  19. package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/pre-tool-advisory-throttle.json +50 -0
  20. package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/ralplan-state.json +11 -0
  21. package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/subagent-tracking-state.json +90 -0
  22. package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/last-tool-error-state.json +7 -0
  23. package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/mission-state.json +79 -0
  24. package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/pre-tool-advisory-throttle.json +10 -0
  25. package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/skill-active-state.json +15 -0
  26. package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/subagent-tracking-state.json +26 -0
  27. package/.omc/state/sessions/7772f972-4ec1-4287-bf42-a92c14cf3ffc/pre-tool-advisory-throttle.json +10 -0
  28. package/.omc/state/sessions/d02e6565-5168-40c8-8629-de219a29fa43/last-tool-error-state.json +7 -0
  29. package/.omc/state/sessions/d02e6565-5168-40c8-8629-de219a29fa43/mission-state.json +53 -0
  30. package/.omc/state/sessions/d02e6565-5168-40c8-8629-de219a29fa43/pre-tool-advisory-throttle.json +18 -0
  31. package/.omc/state/sessions/d02e6565-5168-40c8-8629-de219a29fa43/subagent-tracking-state.json +17 -0
  32. package/.omc/state/sessions/f9c17a0e-2dd8-4f7d-badf-631f266c5d4c/pre-tool-advisory-throttle.json +10 -0
  33. package/.turbo/turbo-build.log +7 -6
  34. package/.turbo/turbo-check.log +1 -1
  35. package/.turbo/turbo-eslint-check.log +2 -1
  36. package/.turbo/turbo-infra-kit-check.log +14 -0
  37. package/.turbo/turbo-prettier-check.log +1 -1
  38. package/.turbo/turbo-test.log +406 -128
  39. package/.turbo/turbo-ts-check.log +1 -1
  40. package/coverage/base.css +224 -0
  41. package/coverage/block-navigation.js +87 -0
  42. package/coverage/cli/index.html +116 -0
  43. package/coverage/cli/package.json.html +283 -0
  44. package/coverage/cli/src/commands/audit/audit.ts.html +616 -0
  45. package/coverage/cli/src/commands/audit/index.html +131 -0
  46. package/coverage/cli/src/commands/audit/index.ts.html +88 -0
  47. package/coverage/cli/src/commands/doctor/doctor.ts.html +1654 -0
  48. package/coverage/cli/src/commands/doctor/index.html +131 -0
  49. package/coverage/cli/src/commands/doctor/index.ts.html +88 -0
  50. package/coverage/cli/src/commands/env-clear/env-clear.ts.html +370 -0
  51. package/coverage/cli/src/commands/env-clear/index.html +131 -0
  52. package/coverage/cli/src/commands/env-clear/index.ts.html +88 -0
  53. package/coverage/cli/src/commands/env-list/env-list.ts.html +229 -0
  54. package/coverage/cli/src/commands/env-list/index.html +131 -0
  55. package/coverage/cli/src/commands/env-list/index.ts.html +88 -0
  56. package/coverage/cli/src/commands/env-load/env-load.ts.html +1201 -0
  57. package/coverage/cli/src/commands/env-load/index.html +131 -0
  58. package/coverage/cli/src/commands/env-load/index.ts.html +91 -0
  59. package/coverage/cli/src/commands/env-status/env-status.ts.html +409 -0
  60. package/coverage/cli/src/commands/env-status/index.html +131 -0
  61. package/coverage/cli/src/commands/env-status/index.ts.html +88 -0
  62. package/coverage/cli/src/commands/gh-merge-dev/gh-merge-dev.ts.html +694 -0
  63. package/coverage/cli/src/commands/gh-merge-dev/index.html +131 -0
  64. package/coverage/cli/src/commands/gh-merge-dev/index.ts.html +88 -0
  65. package/coverage/cli/src/commands/gh-release-deliver/gh-release-deliver.ts.html +1495 -0
  66. package/coverage/cli/src/commands/gh-release-deliver/index.html +131 -0
  67. package/coverage/cli/src/commands/gh-release-deliver/index.ts.html +88 -0
  68. package/coverage/cli/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts.html +706 -0
  69. package/coverage/cli/src/commands/gh-release-deploy-all/index.html +131 -0
  70. package/coverage/cli/src/commands/gh-release-deploy-all/index.ts.html +88 -0
  71. package/coverage/cli/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts.html +994 -0
  72. package/coverage/cli/src/commands/gh-release-deploy-selected/index.html +131 -0
  73. package/coverage/cli/src/commands/gh-release-deploy-selected/index.ts.html +88 -0
  74. package/coverage/cli/src/commands/gh-release-list/gh-release-list.ts.html +355 -0
  75. package/coverage/cli/src/commands/gh-release-list/index.html +131 -0
  76. package/coverage/cli/src/commands/gh-release-list/index.ts.html +88 -0
  77. package/coverage/cli/src/commands/init/agent-files.ts.html +742 -0
  78. package/coverage/cli/src/commands/init/index.html +146 -0
  79. package/coverage/cli/src/commands/init/init.ts.html +1159 -0
  80. package/coverage/cli/src/commands/init/migrate-config.ts.html +1219 -0
  81. package/coverage/cli/src/commands/release-create/index.html +131 -0
  82. package/coverage/cli/src/commands/release-create/index.ts.html +88 -0
  83. package/coverage/cli/src/commands/release-create/release-create.ts.html +1426 -0
  84. package/coverage/cli/src/commands/release-desc-edit/index.html +131 -0
  85. package/coverage/cli/src/commands/release-desc-edit/index.ts.html +88 -0
  86. package/coverage/cli/src/commands/release-desc-edit/release-desc-edit.ts.html +766 -0
  87. package/coverage/cli/src/commands/vendor-check/index.html +131 -0
  88. package/coverage/cli/src/commands/vendor-check/index.ts.html +88 -0
  89. package/coverage/cli/src/commands/vendor-check/vendor-check.ts.html +466 -0
  90. package/coverage/cli/src/commands/vendor-config/index.html +116 -0
  91. package/coverage/cli/src/commands/vendor-config/vendor-config.ts.html +571 -0
  92. package/coverage/cli/src/commands/vendor-diff/index.html +131 -0
  93. package/coverage/cli/src/commands/vendor-diff/index.ts.html +88 -0
  94. package/coverage/cli/src/commands/vendor-diff/vendor-diff.ts.html +316 -0
  95. package/coverage/cli/src/commands/vendor-manifest/index.html +131 -0
  96. package/coverage/cli/src/commands/vendor-manifest/index.ts.html +88 -0
  97. package/coverage/cli/src/commands/vendor-manifest/vendor-manifest.ts.html +265 -0
  98. package/coverage/cli/src/commands/vendor-sync/index.html +131 -0
  99. package/coverage/cli/src/commands/vendor-sync/index.ts.html +88 -0
  100. package/coverage/cli/src/commands/vendor-sync/vendor-sync.ts.html +355 -0
  101. package/coverage/cli/src/commands/version/index.html +131 -0
  102. package/coverage/cli/src/commands/version/index.ts.html +88 -0
  103. package/coverage/cli/src/commands/version/version.ts.html +184 -0
  104. package/coverage/cli/src/commands/worktrees-add/index.html +131 -0
  105. package/coverage/cli/src/commands/worktrees-add/index.ts.html +88 -0
  106. package/coverage/cli/src/commands/worktrees-add/worktrees-add.ts.html +1183 -0
  107. package/coverage/cli/src/commands/worktrees-list/index.html +131 -0
  108. package/coverage/cli/src/commands/worktrees-list/index.ts.html +88 -0
  109. package/coverage/cli/src/commands/worktrees-list/worktrees-list.ts.html +400 -0
  110. package/coverage/cli/src/commands/worktrees-reload/index.html +131 -0
  111. package/coverage/cli/src/commands/worktrees-reload/index.ts.html +88 -0
  112. package/coverage/cli/src/commands/worktrees-reload/worktrees-reload.ts.html +775 -0
  113. package/coverage/cli/src/commands/worktrees-remove/index.html +131 -0
  114. package/coverage/cli/src/commands/worktrees-remove/index.ts.html +88 -0
  115. package/coverage/cli/src/commands/worktrees-remove/worktrees-remove.ts.html +682 -0
  116. package/coverage/cli/src/commands/worktrees-sync/index.html +131 -0
  117. package/coverage/cli/src/commands/worktrees-sync/index.ts.html +88 -0
  118. package/coverage/cli/src/commands/worktrees-sync/worktrees-sync.ts.html +679 -0
  119. package/coverage/cli/src/dev/__tests__/fixtures.ts.html +787 -0
  120. package/coverage/cli/src/dev/__tests__/index.html +116 -0
  121. package/coverage/cli/src/dev/dev-server.ts.html +1855 -0
  122. package/coverage/cli/src/dev/discovery.ts.html +598 -0
  123. package/coverage/cli/src/dev/index.html +191 -0
  124. package/coverage/cli/src/dev/interfaces.ts.html +223 -0
  125. package/coverage/cli/src/dev/logger.ts.html +1102 -0
  126. package/coverage/cli/src/dev/ports.ts.html +430 -0
  127. package/coverage/cli/src/dev/serverless-local-run.ts.html +916 -0
  128. package/coverage/cli/src/entry/dev-server.ts.html +409 -0
  129. package/coverage/cli/src/entry/index.html +116 -0
  130. package/coverage/cli/src/index.html +116 -0
  131. package/coverage/cli/src/integrations/cmux/canonicalize-cmux-title.ts.html +178 -0
  132. package/coverage/cli/src/integrations/cmux/close-workspace-by-title.ts.html +262 -0
  133. package/coverage/cli/src/integrations/cmux/index.html +191 -0
  134. package/coverage/cli/src/integrations/cmux/index.ts.html +100 -0
  135. package/coverage/cli/src/integrations/cmux/list-workspace-titles.ts.html +226 -0
  136. package/coverage/cli/src/integrations/cmux/open-workspace-with-layout.ts.html +325 -0
  137. package/coverage/cli/src/integrations/cmux/workspace-title.ts.html +154 -0
  138. package/coverage/cli/src/integrations/cursor/add-folders-to-workspace.ts.html +337 -0
  139. package/coverage/cli/src/integrations/cursor/index.html +206 -0
  140. package/coverage/cli/src/integrations/cursor/index.ts.html +103 -0
  141. package/coverage/cli/src/integrations/cursor/launch-cursor.ts.html +121 -0
  142. package/coverage/cli/src/integrations/cursor/open-cursor-workspace.ts.html +277 -0
  143. package/coverage/cli/src/integrations/cursor/reconcile-workspace-folders.ts.html +355 -0
  144. package/coverage/cli/src/integrations/cursor/remove-folders-from-workspace.ts.html +364 -0
  145. package/coverage/cli/src/integrations/cursor/resolve-workspace-path.ts.html +124 -0
  146. package/coverage/cli/src/integrations/doppler/doppler-cli-auth.ts.html +178 -0
  147. package/coverage/cli/src/integrations/doppler/doppler-errors.ts.html +349 -0
  148. package/coverage/cli/src/integrations/doppler/doppler-project.ts.html +355 -0
  149. package/coverage/cli/src/integrations/doppler/index.html +161 -0
  150. package/coverage/cli/src/integrations/doppler/index.ts.html +97 -0
  151. package/coverage/cli/src/integrations/gh/gh-cli-auth/gh-cli-auth.ts.html +172 -0
  152. package/coverage/cli/src/integrations/gh/gh-cli-auth/index.html +131 -0
  153. package/coverage/cli/src/integrations/gh/gh-cli-auth/index.ts.html +88 -0
  154. package/coverage/cli/src/integrations/gh/gh-release-prs/gh-release-prs.ts.html +703 -0
  155. package/coverage/cli/src/integrations/gh/gh-release-prs/index.html +131 -0
  156. package/coverage/cli/src/integrations/gh/gh-release-prs/index.ts.html +91 -0
  157. package/coverage/cli/src/integrations/gh/index.html +116 -0
  158. package/coverage/cli/src/integrations/gh/index.ts.html +94 -0
  159. package/coverage/cli/src/integrations/ide/add-ide-worktree-folders.ts.html +316 -0
  160. package/coverage/cli/src/integrations/ide/index.html +191 -0
  161. package/coverage/cli/src/integrations/ide/index.ts.html +103 -0
  162. package/coverage/cli/src/integrations/ide/open-ide-workspace.ts.html +238 -0
  163. package/coverage/cli/src/integrations/ide/provider-label.ts.html +157 -0
  164. package/coverage/cli/src/integrations/ide/remove-ide-worktree-folders.ts.html +520 -0
  165. package/coverage/cli/src/integrations/ide/types.ts.html +208 -0
  166. package/coverage/cli/src/integrations/jira/api.ts.html +1060 -0
  167. package/coverage/cli/src/integrations/jira/index.html +131 -0
  168. package/coverage/cli/src/integrations/jira/index.ts.html +142 -0
  169. package/coverage/cli/src/integrations/zed/add-folders-to-zed-workspace.ts.html +175 -0
  170. package/coverage/cli/src/integrations/zed/index.html +161 -0
  171. package/coverage/cli/src/integrations/zed/index.ts.html +94 -0
  172. package/coverage/cli/src/integrations/zed/open-zed-workspace.ts.html +238 -0
  173. package/coverage/cli/src/integrations/zed/reuse-zed-workspace.ts.html +256 -0
  174. package/coverage/cli/src/lib/assert-never/assert-never.ts.html +130 -0
  175. package/coverage/cli/src/lib/assert-never/index.html +131 -0
  176. package/coverage/cli/src/lib/assert-never/index.ts.html +88 -0
  177. package/coverage/cli/src/lib/command-catalog/command-catalog.ts.html +523 -0
  178. package/coverage/cli/src/lib/command-catalog/index.html +116 -0
  179. package/coverage/cli/src/lib/command-echo/command-echo.ts.html +319 -0
  180. package/coverage/cli/src/lib/command-echo/index.html +131 -0
  181. package/coverage/cli/src/lib/command-echo/index.ts.html +88 -0
  182. package/coverage/cli/src/lib/constants/constants.ts.html +472 -0
  183. package/coverage/cli/src/lib/constants/index.html +131 -0
  184. package/coverage/cli/src/lib/constants/index.ts.html +139 -0
  185. package/coverage/cli/src/lib/env-autoload/env-autoload.ts.html +1111 -0
  186. package/coverage/cli/src/lib/env-autoload/index.html +116 -0
  187. package/coverage/cli/src/lib/errors/format-zx-error.ts.html +247 -0
  188. package/coverage/cli/src/lib/errors/index.html +146 -0
  189. package/coverage/cli/src/lib/errors/is-prompt-cancellation.ts.html +199 -0
  190. package/coverage/cli/src/lib/errors/operation-error.ts.html +325 -0
  191. package/coverage/cli/src/lib/git-guard/git-guard.ts.html +250 -0
  192. package/coverage/cli/src/lib/git-guard/index.html +131 -0
  193. package/coverage/cli/src/lib/git-guard/index.ts.html +91 -0
  194. package/coverage/cli/src/lib/git-utils/git-utils.ts.html +622 -0
  195. package/coverage/cli/src/lib/git-utils/index.html +131 -0
  196. package/coverage/cli/src/lib/git-utils/index.ts.html +115 -0
  197. package/coverage/cli/src/lib/infra-kit-config/index.html +131 -0
  198. package/coverage/cli/src/lib/infra-kit-config/index.ts.html +142 -0
  199. package/coverage/cli/src/lib/infra-kit-config/infra-kit-config.ts.html +1447 -0
  200. package/coverage/cli/src/lib/json-output/index.html +116 -0
  201. package/coverage/cli/src/lib/json-output/json-output.ts.html +226 -0
  202. package/coverage/cli/src/lib/logger/index.html +116 -0
  203. package/coverage/cli/src/lib/logger/index.ts.html +205 -0
  204. package/coverage/cli/src/lib/managed-block/index.html +131 -0
  205. package/coverage/cli/src/lib/managed-block/index.ts.html +109 -0
  206. package/coverage/cli/src/lib/managed-block/managed-block.ts.html +520 -0
  207. package/coverage/cli/src/lib/package-config/index.html +146 -0
  208. package/coverage/cli/src/lib/package-config/index.ts.html +94 -0
  209. package/coverage/cli/src/lib/package-config/package-config-schema.ts.html +142 -0
  210. package/coverage/cli/src/lib/package-config/package-config.ts.html +376 -0
  211. package/coverage/cli/src/lib/package-validator/checks/config-check.ts.html +175 -0
  212. package/coverage/cli/src/lib/package-validator/checks/files-check.ts.html +172 -0
  213. package/coverage/cli/src/lib/package-validator/checks/index.html +176 -0
  214. package/coverage/cli/src/lib/package-validator/checks/index.ts.html +97 -0
  215. package/coverage/cli/src/lib/package-validator/checks/scripts-check.ts.html +154 -0
  216. package/coverage/cli/src/lib/package-validator/checks/turbo-check.ts.html +226 -0
  217. package/coverage/cli/src/lib/package-validator/fs-utils.ts.html +139 -0
  218. package/coverage/cli/src/lib/package-validator/index.html +146 -0
  219. package/coverage/cli/src/lib/package-validator/index.ts.html +94 -0
  220. package/coverage/cli/src/lib/package-validator/loader/config-loader.ts.html +316 -0
  221. package/coverage/cli/src/lib/package-validator/loader/index.html +146 -0
  222. package/coverage/cli/src/lib/package-validator/loader/index.ts.html +91 -0
  223. package/coverage/cli/src/lib/package-validator/loader/package-discovery.ts.html +379 -0
  224. package/coverage/cli/src/lib/package-validator/package-validator.ts.html +229 -0
  225. package/coverage/cli/src/lib/path-display/index.html +131 -0
  226. package/coverage/cli/src/lib/path-display/index.ts.html +88 -0
  227. package/coverage/cli/src/lib/path-display/path-display.ts.html +211 -0
  228. package/coverage/cli/src/lib/release-id/index.html +131 -0
  229. package/coverage/cli/src/lib/release-id/index.ts.html +130 -0
  230. package/coverage/cli/src/lib/release-id/release-id.ts.html +859 -0
  231. package/coverage/cli/src/lib/release-utils/index.html +131 -0
  232. package/coverage/cli/src/lib/release-utils/index.ts.html +130 -0
  233. package/coverage/cli/src/lib/release-utils/release-utils.ts.html +778 -0
  234. package/coverage/cli/src/lib/render/index.html +116 -0
  235. package/coverage/cli/src/lib/render/render.ts.html +157 -0
  236. package/coverage/cli/src/lib/vendor/__tests__/index.html +116 -0
  237. package/coverage/cli/src/lib/vendor/__tests__/zx-mock.ts.html +181 -0
  238. package/coverage/cli/src/lib/vendor/config-schema.ts.html +319 -0
  239. package/coverage/cli/src/lib/vendor/config.ts.html +259 -0
  240. package/coverage/cli/src/lib/vendor/factory-config-schema.ts.html +190 -0
  241. package/coverage/cli/src/lib/vendor/factory-config.ts.html +358 -0
  242. package/coverage/cli/src/lib/vendor/hash.ts.html +133 -0
  243. package/coverage/cli/src/lib/vendor/index.html +251 -0
  244. package/coverage/cli/src/lib/vendor/index.ts.html +154 -0
  245. package/coverage/cli/src/lib/vendor/manifest.ts.html +538 -0
  246. package/coverage/cli/src/lib/vendor/skip-sets.ts.html +229 -0
  247. package/coverage/cli/src/lib/vendor/sync-ops.ts.html +955 -0
  248. package/coverage/cli/src/lib/vendor/walk.ts.html +253 -0
  249. package/coverage/cli/src/lib/version-utils/index.html +161 -0
  250. package/coverage/cli/src/lib/version-utils/index.ts.html +139 -0
  251. package/coverage/cli/src/lib/version-utils/load-existing-versions.ts.html +343 -0
  252. package/coverage/cli/src/lib/version-utils/next-version.ts.html +934 -0
  253. package/coverage/cli/src/lib/version-utils/version-utils.ts.html +178 -0
  254. package/coverage/cli/src/lib/worktrees/index.html +131 -0
  255. package/coverage/cli/src/lib/worktrees/index.ts.html +88 -0
  256. package/coverage/cli/src/lib/worktrees/remove-worktrees.ts.html +283 -0
  257. package/coverage/cli/src/tui/boot.tsx.html +265 -0
  258. package/coverage/cli/src/tui/index.html +116 -0
  259. package/coverage/cli/src/tui/screens/command-palette.tsx.html +562 -0
  260. package/coverage/cli/src/tui/screens/index.html +116 -0
  261. package/coverage/cli/src/types.ts.html +283 -0
  262. package/coverage/clover.xml +4488 -0
  263. package/coverage/coverage-final.json +156 -0
  264. package/coverage/favicon.png +0 -0
  265. package/coverage/index.html +1076 -0
  266. package/coverage/prettify.css +1 -0
  267. package/coverage/prettify.js +2 -0
  268. package/coverage/sort-arrow-sprite.png +0 -0
  269. package/coverage/sorter.js +210 -0
  270. package/dist/chunk-DB6CTNBS.js +3 -0
  271. package/dist/chunk-DB6CTNBS.js.map +7 -0
  272. package/dist/chunk-QBHCWAAG.js +164 -0
  273. package/dist/chunk-QBHCWAAG.js.map +7 -0
  274. package/dist/chunk-YSBF5C7C.js +2 -0
  275. package/dist/chunk-YSBF5C7C.js.map +7 -0
  276. package/dist/cli.js +5 -5
  277. package/dist/cli.js.map +3 -3
  278. package/dist/dev-server.js +13 -0
  279. package/dist/dev-server.js.map +7 -0
  280. package/dist/index.js +1 -1
  281. package/dist/mcp.js +1 -1
  282. package/dist/mcp.js.map +1 -1
  283. package/package.json +7 -2
  284. package/scripts/build.js +4 -4
  285. package/src/.omc/state/agent-replay-2ac0dcd0-01f7-41a8-a8c9-f2195599817b.jsonl +5 -0
  286. package/src/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/mission-state.json +79 -0
  287. package/src/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/pre-tool-advisory-throttle.json +26 -0
  288. package/src/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/subagent-tracking-state.json +26 -0
  289. package/src/commands/audit/.omc/state/agent-replay-f9c17a0e-2dd8-4f7d-badf-631f266c5d4c.jsonl +4 -0
  290. package/src/commands/audit/.omc/state/sessions/f9c17a0e-2dd8-4f7d-badf-631f266c5d4c/mission-state.json +79 -0
  291. package/src/commands/audit/.omc/state/sessions/f9c17a0e-2dd8-4f7d-badf-631f266c5d4c/pre-tool-advisory-throttle.json +22 -0
  292. package/src/commands/audit/.omc/state/sessions/f9c17a0e-2dd8-4f7d-badf-631f266c5d4c/subagent-tracking-state.json +26 -0
  293. package/src/commands/audit/audit.ts +1 -1
  294. package/src/commands/doctor/doctor.ts +53 -0
  295. package/src/commands/env-autoload/env-autoload.ts +14 -2
  296. package/src/commands/env-clear/env-clear.ts +48 -7
  297. package/src/commands/env-load/env-load.ts +23 -1
  298. package/src/commands/init/__tests__/shell-body.test.ts +104 -3
  299. package/src/commands/init/init.ts +91 -9
  300. package/src/dev/__tests__/dev-server-options.test.ts +57 -0
  301. package/src/dev/__tests__/dev-server.test.ts +420 -0
  302. package/src/dev/__tests__/discovery.test.ts +181 -0
  303. package/src/dev/__tests__/fixtures.ts +257 -0
  304. package/src/dev/__tests__/ports.test.ts +98 -0
  305. package/src/dev/__tests__/serverless-local-run.test.ts +533 -0
  306. package/src/dev/dev-server.ts +796 -0
  307. package/src/dev/discovery.ts +229 -0
  308. package/src/dev/interfaces.ts +11 -0
  309. package/src/dev/managed-child.ts +73 -0
  310. package/src/dev/ports.ts +100 -0
  311. package/src/dev/serverless-local-run.ts +329 -0
  312. package/src/dev/turbo-watch.ts +62 -0
  313. package/src/dev/ui-dev.ts +62 -0
  314. package/src/entry/cli.ts +25 -6
  315. package/src/entry/dev-server.ts +103 -0
  316. package/src/lib/.omc/state/sessions/f9c17a0e-2dd8-4f7d-badf-631f266c5d4c/pre-tool-advisory-throttle.json +18 -0
  317. package/src/lib/command-catalog/__tests__/__snapshots__/command-catalog.test.ts.snap +1 -0
  318. package/src/lib/command-catalog/command-catalog.ts +4 -0
  319. package/src/lib/constants/constants.ts +38 -0
  320. package/src/lib/constants/index.ts +4 -0
  321. package/src/lib/env-autoload/__tests__/env-autoload.test.ts +25 -0
  322. package/src/lib/env-autoload/env-autoload.ts +35 -4
  323. package/src/lib/infra-kit-config/index.ts +2 -0
  324. package/src/lib/infra-kit-config/infra-kit-config.ts +22 -0
  325. package/src/lib/package-config/package-config.ts +2 -2
  326. package/src/lib/package-validator/.omc/state/sessions/f9c17a0e-2dd8-4f7d-badf-631f266c5d4c/pre-tool-advisory-throttle.json +10 -0
  327. package/src/lib/package-validator/__tests__/package-validator.test.ts +9 -2
  328. package/src/lib/warm-cache/__tests__/warm-cache.test.ts +164 -0
  329. package/src/lib/warm-cache/index.ts +7 -0
  330. package/src/lib/warm-cache/warm-cache.ts +136 -0
  331. package/tsconfig.tsbuildinfo +1 -1
  332. package/dist/chunk-EY5LDHXC.js +0 -164
  333. package/dist/chunk-EY5LDHXC.js.map +0 -7
  334. package/dist/chunk-WA4BQRDC.js +0 -2
  335. package/dist/chunk-WA4BQRDC.js.map +0 -7
@@ -0,0 +1,796 @@
1
+ /**
2
+ * Unified Development Server Runner
3
+ *
4
+ * Discovers and runs API apps under each `apps/<app>/api` folder that contains `serverless.yml`.
5
+ *
6
+ * Ports: `{APP}_PORT`, then `process.env.PORT`, then `dev.<app>.port` from infra-kit.json,
7
+ * else 3010. URL prefix: `dev.<app>.prefixUrl`, else `/api/v1`.
8
+ * Env vars should be provided via secrets manager (e.g. `doppler run -- pnpm dev-server`) or shell.
9
+ *
10
+ * This module is side-effect free on import: call `run()` (or construct `DevServerRunner`
11
+ * directly) to start. Signal handling and process exit are the entry point's responsibility.
12
+ *
13
+ * Runner messages append to `<cwd>/.infra-kit/dev-server.log`. Lambda / Powertools logs from
14
+ * handlers go to stdout.
15
+ */
16
+ import chokidar from 'chokidar'
17
+ import type { FSWatcher } from 'chokidar'
18
+ import { exec } from 'node:child_process'
19
+ import * as fs from 'node:fs'
20
+ import * as path from 'node:path'
21
+ import process from 'node:process'
22
+ import util from 'node:util'
23
+
24
+ import type { DevConfig } from 'src/lib/infra-kit-config'
25
+ import { getInfraKitConfig } from 'src/lib/infra-kit-config'
26
+
27
+ import {
28
+ classifyDistChange,
29
+ discoverApiApps as discoverApiAppsBare,
30
+ discoverUiApps as discoverUiAppsBare,
31
+ findMonorepoRoot,
32
+ getAppDistDirs,
33
+ getPackageDistDirs,
34
+ normalizeAppInclude as normalizeAppIncludePure,
35
+ } from './discovery.js'
36
+ import type { DiscoveredUiApp } from './discovery.js'
37
+ import { findPortConflicts, resolvePort as resolvePortPure, resolvePrefixUrl as resolvePrefixUrlPure } from './ports.js'
38
+ import { ServerlessLocalRun } from './serverless-local-run.js'
39
+ import { defaultTurboWatchFactory } from './turbo-watch.js'
40
+ import type { TurboWatchFactory, TurboWatchHandle } from './turbo-watch.js'
41
+ import { defaultUiDevFactory } from './ui-dev.js'
42
+ import type { UiDevFactory, UiDevHandle } from './ui-dev.js'
43
+
44
+ /** Runner-only log file, resolved under the consumer repo's `.infra-kit/` dir at startup. */
45
+ let LOG_FILE_PATH = path.join(process.cwd(), '.infra-kit', 'dev-server.log')
46
+
47
+ /** Resolve `<cwd>/.infra-kit/dev-server.log`, ensure the dir exists, and clear the file. */
48
+ function initLogFile(): void {
49
+ LOG_FILE_PATH = path.join(process.cwd(), '.infra-kit', 'dev-server.log')
50
+ fs.mkdirSync(path.dirname(LOG_FILE_PATH), { recursive: true })
51
+ fs.writeFileSync(LOG_FILE_PATH, `=== Dev Server Started: ${new Date().toISOString()} ===\n\n`)
52
+ }
53
+
54
+ const execFn = util.promisify(exec)
55
+
56
+ type LogFn = (msg: string, level?: 'info' | 'warn' | 'error' | 'debug') => void
57
+
58
+ /**
59
+ * Build seam: shells out to turbo by default ({@link launchScript}); injectable so
60
+ * tests can run the orchestrator without a real build. `logFn` is optional so both
61
+ * the verbose initial build and the terse restart builds keep their current output.
62
+ */
63
+ export type BuildRunner = (cmd: string, logFn?: LogFn) => Promise<void>
64
+
65
+ const launchScript = async (script: string, logFn?: LogFn): Promise<void> => {
66
+ try {
67
+ const { stderr } = await execFn(script)
68
+
69
+ if (stderr && logFn) logFn(` (build) ${stderr.trim()}`, 'debug')
70
+ if (stderr && !logFn) console.error('stderr:', stderr)
71
+ } catch (error) {
72
+ const err = error as { stdout?: string; stderr?: string; message?: string }
73
+
74
+ if (logFn && (err.stdout || err.stderr)) {
75
+ if (err.stdout) logFn(` stdout: ${err.stdout.trim()}`, 'error')
76
+ if (err.stderr) logFn(` stderr: ${err.stderr.trim()}`, 'error')
77
+ }
78
+
79
+ throw error
80
+ }
81
+ }
82
+
83
+ /** Append raw text to the runner-only log file (the single tee-to-file seam). */
84
+ function appendLogFile(text: string): void {
85
+ fs.appendFileSync(LOG_FILE_PATH, text)
86
+ }
87
+
88
+ /** Write a message to the console (routed by level) and tee it to the log file. */
89
+ function log(message: string, level: 'info' | 'warn' | 'error' | 'debug' = 'info'): void {
90
+ if (level === 'error') {
91
+ console.error(message)
92
+ } else if (level === 'warn') {
93
+ console.warn(message)
94
+ } else {
95
+ process.stdout.write(`${message}\n`)
96
+ }
97
+
98
+ appendLogFile(`[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}\n`)
99
+ }
100
+
101
+ /** Write pre-formatted table lines to stdout and tee them to the log file. */
102
+ function logTable(lines: string[]): void {
103
+ const output = `${lines.join('\n')}\n`
104
+
105
+ process.stdout.write(`${output}\n`)
106
+ appendLogFile(`${output}\n`)
107
+ }
108
+
109
+ /** Center `s` within `width` columns (clamped, so a too-long string is never truncated). */
110
+ function center(s: string, width: number): string {
111
+ const pad = Math.max(0, width - s.length)
112
+ const left = Math.floor(pad / 2)
113
+
114
+ return `${' '.repeat(left)}${s}${' '.repeat(pad - left)}`
115
+ }
116
+
117
+ /**
118
+ * Render a box-drawn table whose column widths are computed from the longest cell in
119
+ * each column, so every emitted line shares one width regardless of app-name length
120
+ * (the old fixed `padEnd` misaligned on long names). Borders are generated from the
121
+ * same widths, so they always match the rows.
122
+ */
123
+ function renderTable(title: string, headers: string[], rows: string[][]): string[] {
124
+ const widths = headers.map((header, i) => {
125
+ return Math.max(
126
+ header.length,
127
+ ...rows.map((row) => {
128
+ return (row[i] ?? '').length
129
+ }),
130
+ )
131
+ })
132
+ // Grow the last column if the title is wider than the columns, so the title band never clips.
133
+ const columnsInner =
134
+ widths.reduce((sum, w) => {
135
+ return sum + w + 2
136
+ }, 0) +
137
+ (widths.length - 1)
138
+
139
+ const lastIdx = widths.length - 1
140
+
141
+ if (lastIdx >= 0 && title.length + 2 > columnsInner) {
142
+ widths[lastIdx] = (widths[lastIdx] ?? 0) + (title.length + 2 - columnsInner)
143
+ }
144
+
145
+ const inner =
146
+ widths.reduce((sum, w) => {
147
+ return sum + w + 2
148
+ }, 0) +
149
+ (widths.length - 1)
150
+ const blocks = widths.map((w) => {
151
+ return '─'.repeat(w + 2)
152
+ })
153
+ const rowLine = (cells: string[]): string => {
154
+ return `β”‚ ${cells
155
+ .map((c, i) => {
156
+ return (c ?? '').padEnd(widths[i]!)
157
+ })
158
+ .join(' β”‚ ')} β”‚`
159
+ }
160
+
161
+ return [
162
+ '',
163
+ `β”Œ${'─'.repeat(inner)}┐`,
164
+ `β”‚${center(title, inner)}β”‚`,
165
+ `β”œ${blocks.join('┬')}─`,
166
+ rowLine(headers),
167
+ `β”œ${blocks.join('β”Ό')}─`,
168
+ ...rows.map(rowLine),
169
+ `β””${blocks.join('β”΄')}β”˜`,
170
+ '',
171
+ ]
172
+ }
173
+
174
+ interface IApiAppConfig {
175
+ /** App folder name (e.g. backoffice, client) */
176
+ name: string
177
+ /** Package name from package.json (e.g. sls-trvl-client) */
178
+ packageName: string
179
+ path: string
180
+ port: number
181
+ prefixUrl: string
182
+ }
183
+
184
+ /**
185
+ * Runner options, parsed by the CLI entry point (`--watch`, `--app`) and threaded
186
+ * through `run()`. The entry owns flag parsing; the runner never reads `process.argv`
187
+ * itself. App selection is `--app` only; ports come from env/config (see `resolvePort`).
188
+ */
189
+ export interface DevServerOptions {
190
+ /**
191
+ * Watch mode: start a long-lived `turbo watch build` engine (incremental rebuilds
192
+ * + dependency fan-out) and restart the affected server(s) when compiled `dist/`
193
+ * changes. Without it, `dev` builds and serves once and exits on signal.
194
+ */
195
+ watch?: boolean
196
+ /** Only run these app folder names (null/empty = all discovered). Filters BOTH api and ui apps. */
197
+ include?: string[] | null
198
+ /**
199
+ * Also run frontends: discover `apps/<app>/ui` with a `dev` script and start them via one
200
+ * delegated `turbo run dev` child (streamed to the terminal). Default (unset) = api-only,
201
+ * preserving today's behavior.
202
+ */
203
+ ui?: boolean
204
+ }
205
+
206
+ interface IAppServer {
207
+ app: IApiAppConfig
208
+ server: ServerlessLocalRun
209
+ }
210
+
211
+ export class DevServerRunner {
212
+ private readonly monorepoRoot: string
213
+ private readonly appServers: IAppServer[] = []
214
+ private watchDebounceTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
215
+ /** Active chokidar watcher in `--watch` mode; closed on {@link shutdown}. */
216
+ private watcher: FSWatcher | null = null
217
+ private static readonly WATCH_DEBOUNCE_MS = 400
218
+ /** Serialized restarts so rapid saves never bind :port while the previous server is still shutting down. */
219
+ private restartWorkChain: Promise<void> = Promise.resolve()
220
+ private static readonly PORT_RELEASE_DELAY_MS = 200
221
+ private readonly options: DevServerOptions
222
+ /** Build runner seam β€” real turbo shell-out by default, injectable for tests. */
223
+ private readonly runBuild: BuildRunner
224
+ /** `turbo watch` spawn seam β€” real detached child by default, injectable for tests. */
225
+ private readonly turboWatchFactory: TurboWatchFactory
226
+ /** Live `turbo watch` engine in `--watch` mode; reaped on {@link shutdown}. */
227
+ private turboWatch: TurboWatchHandle | null = null
228
+ /** `turbo run dev` (frontends) spawn seam β€” real detached child by default, injectable for tests. */
229
+ private readonly uiDevFactory: UiDevFactory
230
+ /** Live `turbo run dev` frontend engine in `--ui` mode; reaped on {@link shutdown}. */
231
+ private uiDev: UiDevHandle | null = null
232
+
233
+ constructor(
234
+ options: DevServerOptions = {},
235
+ runBuild: BuildRunner = launchScript,
236
+ turboWatchFactory: TurboWatchFactory = defaultTurboWatchFactory,
237
+ uiDevFactory: UiDevFactory = defaultUiDevFactory,
238
+ ) {
239
+ this.options = options
240
+ this.runBuild = runBuild
241
+ this.turboWatchFactory = turboWatchFactory
242
+ this.uiDevFactory = uiDevFactory
243
+ initLogFile()
244
+
245
+ // Walk up from the consumer repo cwd to the monorepo root.
246
+ this.monorepoRoot = findMonorepoRoot(process.cwd())
247
+ log(`Monorepo root: ${this.monorepoRoot}`)
248
+
249
+ if (process.env.DOPPLER_PROJECT != null || process.env.DOPPLER_ENVIRONMENT != null) {
250
+ log('πŸ” Doppler env detected (DOPPLER_PROJECT / DOPPLER_ENVIRONMENT)', 'debug')
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Discover API apps and resolve each app's port + URL prefix. Delegates bare
256
+ * filesystem discovery to {@link discoverApiAppsBare} and per-app resolution to
257
+ * the pure `resolvePort` / `resolvePrefixUrl`, preserving the original behavior.
258
+ */
259
+ private discoverApiApps(devConfig: DevConfig): IApiAppConfig[] {
260
+ return discoverApiAppsBare(this.monorepoRoot).map((app) => {
261
+ return {
262
+ ...app,
263
+ port: this.resolvePort(app.name, devConfig),
264
+ prefixUrl: this.resolvePrefixUrl(app.name, devConfig),
265
+ }
266
+ })
267
+ }
268
+
269
+ /**
270
+ * Read the `dev` section from the resolved infra-kit config. Defensive: any
271
+ * failure (not in an infra-kit project, missing/invalid config) resolves to an
272
+ * empty map so the dev-server always falls back to env vars + built-in defaults
273
+ * rather than refusing to start.
274
+ */
275
+ private async loadDevConfig(): Promise<DevConfig> {
276
+ try {
277
+ const config = await getInfraKitConfig()
278
+
279
+ return config.dev ?? {}
280
+ } catch {
281
+ return {}
282
+ }
283
+ }
284
+
285
+ /** Thin delegator to the pure {@link normalizeAppIncludePure} over the runner's `--app` list. */
286
+ private normalizeAppInclude(): string[] | null {
287
+ return normalizeAppIncludePure(this.options.include)
288
+ }
289
+
290
+ /** Thin delegator to the pure {@link resolvePortPure}, threading env + config. */
291
+ private resolvePort(appName: string, devConfig: DevConfig): number {
292
+ return resolvePortPure(appName, process.env, devConfig)
293
+ }
294
+
295
+ /** Thin delegator to the pure {@link resolvePrefixUrlPure}. */
296
+ private resolvePrefixUrl(appName: string, devConfig: DevConfig): string {
297
+ return resolvePrefixUrlPure(appName, devConfig)
298
+ }
299
+
300
+ public async start(): Promise<void> {
301
+ const include = this.normalizeAppInclude()
302
+ const watch = this.options.watch ?? false
303
+ const devConfig = await this.loadDevConfig()
304
+
305
+ process.env.POWERTOOLS_DEV ??= 'true'
306
+ process.env.LOG_LEVEL ??= 'DEBUG'
307
+
308
+ log('πŸš€ Starting Development Server Runner')
309
+
310
+ if (watch) {
311
+ log('πŸ‘€ Watch mode: will rebuild and restart on file save')
312
+ }
313
+ log(`πŸ“‚ Monorepo root: ${this.monorepoRoot}`)
314
+
315
+ const apps = this.selectApps(this.discoverApiApps(devConfig), include)
316
+ const uiApps = (this.options.ui ?? false) ? this.selectUiApps(include) : []
317
+
318
+ if (apps.length === 0 && uiApps.length === 0) {
319
+ log(this.options.ui ? '⚠️ No API or UI apps found to run' : '⚠️ No API apps found to run', 'warn')
320
+
321
+ return
322
+ }
323
+
324
+ // Build the API boot closure (dist must exist before servers import handlers).
325
+ if (apps.length > 0) {
326
+ log(`πŸ“¦ Discovered ${apps.length} API app(s): ${this.formatAppList(apps)}`)
327
+ this.assertNoPortConflicts(apps)
328
+ await this.buildApps(apps, watch)
329
+ }
330
+
331
+ // Warm the UI dependency closure BEFORE spawning any persistent child (turbo watch / turbo run
332
+ // dev), so both see a warm cache and the cold-cache double-build race can't corrupt shared dist.
333
+ if (uiApps.length > 0) {
334
+ await this.buildUiApps(uiApps)
335
+ }
336
+
337
+ if (apps.length > 0) {
338
+ await this.startAllApps(apps)
339
+ log('πŸŽ‰ All servers started!')
340
+ this.printServerTable(apps)
341
+ this.printRouteDump()
342
+ log(
343
+ `πŸ“ Handler logs (AWS Powertools, logger.info/debug, etc.) β†’ this terminal. Runner-only file: ${LOG_FILE_PATH}`,
344
+ )
345
+ }
346
+
347
+ if (watch && this.appServers.length > 0) {
348
+ this.setupWatch(apps)
349
+ }
350
+
351
+ // Frontends last: their delegated `turbo run dev` streams raw to the terminal below the BE table.
352
+ if (uiApps.length > 0) {
353
+ this.startUiDev(uiApps)
354
+ }
355
+ }
356
+
357
+ /** Discover `apps/<app>/ui` frontends (with a `dev` script), applying the `--app` include filter. */
358
+ private selectUiApps(include: string[] | null): DiscoveredUiApp[] {
359
+ const uiApps = discoverUiAppsBare(this.monorepoRoot)
360
+ const selected = include
361
+ ? uiApps.filter((a) => {
362
+ return include.includes(a.name)
363
+ })
364
+ : uiApps
365
+
366
+ if (selected.length > 0) {
367
+ log(
368
+ `🎨 Discovered ${selected.length} UI app(s): ${selected
369
+ .map((a) => {
370
+ return a.name
371
+ })
372
+ .join(', ')}`,
373
+ )
374
+ }
375
+
376
+ return selected
377
+ }
378
+
379
+ /**
380
+ * Warm ONLY each UI's dependency closure (`<pkg>^...` β€” deps, excluding the UI itself, so no full
381
+ * production `vite build`) with a cache-friendly (non-`--force`) turbo build. Non-fatal: if it
382
+ * fails, `turbo run dev`'s own `^build` retries and surfaces the error in its streamed output.
383
+ */
384
+ private async buildUiApps(uiApps: DiscoveredUiApp[]): Promise<void> {
385
+ const filters = uiApps
386
+ .map((a) => {
387
+ return `--filter=${a.packageName}^...`
388
+ })
389
+ .join(' ')
390
+
391
+ log('πŸ”¨ Warming UI dependency build cache (turbo)...')
392
+ try {
393
+ await this.runBuild(`pnpm exec turbo run build ${filters} --env-mode=loose`, log)
394
+ log('βœ… UI deps built')
395
+ } catch (error) {
396
+ log(`⚠️ UI dep warm build failed (continuing; turbo run dev will build): ${String(error)}`, 'warn')
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Start the frontends via ONE delegated `turbo run dev` child (streamed to the terminal). Reaped
402
+ * on {@link shutdown}. Concurrency β‰₯ the persistent UI `dev` task count (turbo hard-errors otherwise).
403
+ */
404
+ private startUiDev(uiApps: DiscoveredUiApp[]): void {
405
+ const names = uiApps
406
+ .map((a) => {
407
+ return a.name
408
+ })
409
+ .join(', ')
410
+
411
+ log(`🎨 Starting ${uiApps.length} UI dev server(s) via \`turbo run dev\`: ${names}`)
412
+ log(' (framework dev output streams below; each prints its own local URL)')
413
+
414
+ this.uiDev = this.uiDevFactory({
415
+ packageNames: uiApps.map((a) => {
416
+ return a.packageName
417
+ }),
418
+ cwd: process.cwd(),
419
+ concurrency: Math.max(uiApps.length + 4, 12),
420
+ })
421
+ }
422
+
423
+ /** Render an app list as `name:port, name:port` for log lines. */
424
+ private formatAppList(apps: Array<{ name: string; port: number }>): string {
425
+ return apps
426
+ .map((a) => {
427
+ return `${a.name}:${a.port}`
428
+ })
429
+ .join(', ')
430
+ }
431
+
432
+ /** Apply the `--app` include filter (logging it when applied). */
433
+ private selectApps(apps: IApiAppConfig[], include: string[] | null): IApiAppConfig[] {
434
+ if (!include) {
435
+ return apps
436
+ }
437
+
438
+ log(`πŸ” Filtering to apps: ${include.join(', ')}`)
439
+
440
+ return apps.filter((app) => {
441
+ return include.includes(app.name)
442
+ })
443
+ }
444
+
445
+ /** Throw (after logging remediation tips) when two apps resolve to the same port. */
446
+ private assertNoPortConflicts(apps: IApiAppConfig[]): void {
447
+ const { duplicatePorts, conflictingApps } = findPortConflicts(apps)
448
+
449
+ if (duplicatePorts.length === 0) {
450
+ return
451
+ }
452
+
453
+ log(`⚠️ Port conflict detected! ${duplicatePorts.join(', ')}`, 'error')
454
+ log(`Conflicting apps: ${this.formatAppList(conflictingApps)}`, 'error')
455
+ log('\nπŸ’‘ Tip: give each app a distinct port via `{APP}_PORT` env (e.g. `CLIENT_PORT=`,', 'error')
456
+ log(' `SEARCH_ENGINE_PORT=`) or `dev.<app>.port` in infra-kit.json; or run a subset with', 'error')
457
+ log(' `--app=<name>,<name>`.\n', 'error')
458
+ throw new Error(`Port conflict detected: ${duplicatePorts.join(', ')}`)
459
+ }
460
+
461
+ /** Build every app via turbo; rethrows the build error after logging stdout/stderr. */
462
+ private async buildApps(apps: IApiAppConfig[], watch: boolean): Promise<void> {
463
+ const filters = apps
464
+ .map((a) => {
465
+ return `--filter=${a.packageName}`
466
+ })
467
+ .join(' ')
468
+ // With `--watch`, always bypass Turbo cache so `tsc` runs and `dist/` matches disk (otherwise watch restarts can be no-ops).
469
+ const buildCmd = `pnpm exec turbo run build ${filters} --env-mode=loose${watch ? ' --force' : ''}`
470
+
471
+ log('πŸ”¨ Building API apps (turbo)...')
472
+ try {
473
+ await this.runBuild(buildCmd, log)
474
+ log('βœ… Build complete')
475
+ } catch (buildError) {
476
+ log(`❌ Build failed: ${String(buildError)}`, 'error')
477
+ if (buildError instanceof Error && buildError.message) {
478
+ log(` ${buildError.message}`, 'error')
479
+ }
480
+ const err = buildError as { stdout?: string; stderr?: string }
481
+
482
+ if (err.stdout) log(` stdout: ${err.stdout.trim()}`, 'error')
483
+ if (err.stderr) log(` stderr: ${err.stderr.trim()}`, 'error')
484
+ throw buildError
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Start every app concurrently, collecting the ones that boot; per-app failures are logged,
490
+ * not fatal. Safe to parallelize because each app has a distinct port (guarded up-front) and a
491
+ * distinct `ServerlessLocalRun`, and `startOneApp` no longer mutates cwd. Push order into
492
+ * `appServers` is non-deterministic but nothing depends on it (the table renders from `apps`).
493
+ */
494
+ private async startAllApps(apps: IApiAppConfig[]): Promise<void> {
495
+ await Promise.all(
496
+ apps.map(async (app) => {
497
+ try {
498
+ const server = await this.startOneApp(app)
499
+
500
+ if (server) {
501
+ this.appServers.push({ app, server })
502
+ }
503
+ } catch (error) {
504
+ log(`❌ Failed to start ${app.name}: ${String(error)}`, 'error')
505
+ }
506
+ }),
507
+ )
508
+ }
509
+
510
+ private async startOneApp(app: IApiAppConfig): Promise<ServerlessLocalRun | null> {
511
+ log(`πŸ”„ Starting ${app.name}...`)
512
+
513
+ // No `process.chdir` here: `ServerlessLocalRun` reads `serverless.yml` and imports the
514
+ // compiled handler from `controllersPath` (absolute), so the runner never mutates cwd β€”
515
+ // which is what makes concurrent boot/restart safe.
516
+ const server = new ServerlessLocalRun({
517
+ controllersPath: app.path,
518
+ prefixUrl: app.prefixUrl,
519
+ port: app.port,
520
+ appName: app.name,
521
+ })
522
+
523
+ await server.start()
524
+ log(`βœ… ${app.name} started on port ${app.port}`)
525
+
526
+ return server
527
+ }
528
+
529
+ /** Run restart jobs one after another (watch can fire faster than close + listen). */
530
+ private scheduleRestartWork(work: () => Promise<void>): Promise<void> {
531
+ const run = this.restartWorkChain.then(
532
+ () => {
533
+ return work()
534
+ },
535
+ () => {
536
+ return work()
537
+ },
538
+ )
539
+
540
+ this.restartWorkChain = run.catch(() => {})
541
+
542
+ return run
543
+ }
544
+
545
+ private async delayPortRelease(): Promise<void> {
546
+ await new Promise((r) => {
547
+ return setTimeout(r, DevServerRunner.PORT_RELEASE_DELAY_MS)
548
+ })
549
+ }
550
+
551
+ /**
552
+ * Schedule a restart of the given apps (1 or N), serialized against other restarts via
553
+ * {@link scheduleRestartWork}. A single-app dist change passes `[app]`; a dependency-package
554
+ * dist change passes every running app. The `turbo watch` engine has already rebuilt `dist/`,
555
+ * so the runner only bounces the fastify server(s) β€” no build here.
556
+ */
557
+ private restart(apps: IApiAppConfig[]): Promise<void> {
558
+ return this.scheduleRestartWork(() => {
559
+ return this.runRestart(apps)
560
+ })
561
+ }
562
+
563
+ /** Resolve the requested apps to their live server slots (dropping any not running). */
564
+ private resolveRestartTargets(apps: IApiAppConfig[]): Array<{ idx: number; app: IApiAppConfig }> {
565
+ return apps
566
+ .map((app) => {
567
+ return {
568
+ idx: this.appServers.findIndex((e) => {
569
+ return e.app.name === app.name
570
+ }),
571
+ app,
572
+ }
573
+ })
574
+ .filter((t) => {
575
+ return t.idx >= 0
576
+ })
577
+ }
578
+
579
+ private async runRestart(apps: IApiAppConfig[]): Promise<void> {
580
+ const targets = this.resolveRestartTargets(apps)
581
+
582
+ if (targets.length === 0) return
583
+
584
+ const label = targets.length === 1 ? targets[0]!.app.name : `${targets.length} apps`
585
+
586
+ log(`πŸ”„ Restarting ${label}...`)
587
+ await Promise.all(
588
+ targets.map(async ({ idx }) => {
589
+ try {
590
+ await this.appServers[idx]!.server.close()
591
+ } catch (err) {
592
+ log(` Close warning: ${String(err)}`, 'debug')
593
+ }
594
+ }),
595
+ )
596
+
597
+ await this.delayPortRelease()
598
+
599
+ await Promise.all(
600
+ targets.map(async ({ idx, app }) => {
601
+ try {
602
+ const newServer = await this.startOneApp(app)
603
+
604
+ if (newServer) {
605
+ this.appServers[idx] = { app, server: newServer }
606
+ }
607
+ } catch (error) {
608
+ log(`❌ Failed to restart ${app.name}: ${String(error)}`, 'error')
609
+ }
610
+ }),
611
+ )
612
+ log(`βœ… Restarted ${label}`)
613
+ }
614
+
615
+ /**
616
+ * Start the long-lived `turbo watch build` engine, then watch compiled `dist/` output to
617
+ * trigger restarts. A change under an app's `dist` restarts that app; a change under any
618
+ * `packages/<pkg>/dist` restarts every app (editing a shared lib rewrites only the lib's
619
+ * `dist`, so a package-dist change is the lib-rebuild signal). Restarts are build-less β€”
620
+ * the engine already rebuilt `dist/`.
621
+ */
622
+ private setupWatch(apps: IApiAppConfig[]): void {
623
+ this.turboWatch = this.turboWatchFactory({
624
+ packageNames: apps.map((a) => {
625
+ return a.packageName
626
+ }),
627
+ cwd: process.cwd(),
628
+ logFile: LOG_FILE_PATH,
629
+ })
630
+ log('πŸ‘€ Watch mode: started `turbo watch build` engine; watching dist output')
631
+
632
+ const appDistDirs = getAppDistDirs(apps)
633
+ const packageDistDirs = getPackageDistDirs(this.monorepoRoot)
634
+ const allDistDirs = [...appDistDirs, ...packageDistDirs]
635
+
636
+ if (allDistDirs.length === 0) {
637
+ log('⚠️ No app or package dist directories found to watch (were they built?)', 'warn')
638
+
639
+ return
640
+ }
641
+
642
+ const usePoll = process.env.DEV_SERVER_CHOKIDAR_POLL === '1'
643
+
644
+ const watcher = chokidar.watch(allDistDirs, {
645
+ ignoreInitial: true,
646
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 },
647
+ // Ignore tsc's incremental bookkeeping + sourcemaps: they rewrite on every build
648
+ // (even content-identical ones) and would bounce fastify onto no real change.
649
+ ignored: (p: string): boolean => {
650
+ return p.endsWith('.tsbuildinfo') || p.endsWith('.map')
651
+ },
652
+ ...(usePoll ? { usePolling: true, interval: 400 } : {}),
653
+ })
654
+
655
+ this.watcher = watcher
656
+
657
+ if (usePoll) {
658
+ log('πŸ‘€ chokidar: usePolling enabled (DEV_SERVER_CHOKIDAR_POLL=1)', 'debug')
659
+ }
660
+
661
+ watcher.on('change', (filePath: string) => {
662
+ log(`πŸ‘€ dist change detected: ${filePath}`, 'debug')
663
+
664
+ const change = classifyDistChange(filePath, appDistDirs, packageDistDirs)
665
+
666
+ if (change.kind === 'package') {
667
+ // A dependency package's dist was rebuilt β€” restart every app.
668
+ this.scheduleDebounced('__packages__', () => {
669
+ return this.restart(apps)
670
+ })
671
+
672
+ return
673
+ }
674
+
675
+ const app = apps.find((a) => {
676
+ return path.join(a.path, 'dist') === change.app
677
+ })
678
+
679
+ if (!app) return
680
+
681
+ this.scheduleDebounced(app.name, () => {
682
+ return this.restart([app])
683
+ })
684
+ })
685
+
686
+ log(`πŸ‘€ Watching ${appDistDirs.length} app dist + ${packageDistDirs.length} package dist dir(s) for changes...`)
687
+ }
688
+
689
+ /**
690
+ * Debounce a restart under `key`: cancel any pending timer for the same key and start
691
+ * a fresh {@link DevServerRunner.WATCH_DEBOUNCE_MS} timer, so a burst of saves collapses
692
+ * into one restart. Errors from the scheduled work are logged, never thrown.
693
+ */
694
+ private scheduleDebounced(key: string, work: () => Promise<void>): void {
695
+ const existing = this.watchDebounceTimers.get(key)
696
+
697
+ if (existing) clearTimeout(existing)
698
+
699
+ const timer = setTimeout(() => {
700
+ this.watchDebounceTimers.delete(key)
701
+ work().catch((err) => {
702
+ log(`Restart error (${key}): ${String(err)}`, 'error')
703
+ })
704
+ }, DevServerRunner.WATCH_DEBOUNCE_MS)
705
+
706
+ this.watchDebounceTimers.set(key, timer)
707
+ }
708
+
709
+ /**
710
+ * Print the running-server table. The `Base URL` column carries each app's
711
+ * `prefixUrl` (e.g. `/api/v1`) so it is copy-pasteable β€” the old table showed a bare
712
+ * `http://localhost:<port>` that 404s against prefixed handler routes. A `Health`
713
+ * column surfaces the unprefixed `/__health` liveness URL.
714
+ */
715
+ private printServerTable(apps: IApiAppConfig[]): void {
716
+ const rows = apps.map((app) => {
717
+ return [
718
+ app.name,
719
+ String(app.port),
720
+ `http://localhost:${app.port}${app.prefixUrl}`,
721
+ `http://localhost:${app.port}/__health`,
722
+ ]
723
+ })
724
+
725
+ logTable(renderTable('πŸ–₯️ Running Servers', ['App', 'Port', 'Base URL', 'Health'], rows))
726
+ }
727
+
728
+ /**
729
+ * Dump each running app's registered `METHOD /path` routes at startup so the
730
+ * emulator is self-describing (no need to open `serverless.yml` to learn the routes).
731
+ * Reads the live route set via {@link ServerlessLocalRun.getRegisteredRoutes}.
732
+ */
733
+ private printRouteDump(): void {
734
+ if (this.appServers.length === 0) return
735
+
736
+ log('πŸ—ΊοΈ Registered routes:')
737
+ for (const { app, server } of this.appServers) {
738
+ const routes = server.getRegisteredRoutes()
739
+
740
+ log(` ${app.name} (${routes.length}): ${routes.length > 0 ? routes.join(', ') : '(none)'}`)
741
+ }
742
+ }
743
+
744
+ /**
745
+ * Stop watching, cancel any pending debounced restart, and close all running servers.
746
+ * Does not exit the process β€” the entry point owns exit.
747
+ */
748
+ public async shutdown(): Promise<void> {
749
+ log('πŸ›‘ Shutting down all servers...')
750
+
751
+ // Reap the long-lived engines FIRST (group SIGTERM→SIGKILL) so neither writes fresh dist nor
752
+ // holds a port mid-teardown. Reaped here (not only in the entry signal handler) because tests
753
+ // and any non-signal caller invoke shutdown() directly. Awaited so the SIGKILL escalation
754
+ // completes before the entry point's `process.exit`.
755
+ if (this.turboWatch) {
756
+ await this.turboWatch.kill()
757
+ this.turboWatch = null
758
+ }
759
+
760
+ if (this.uiDev) {
761
+ await this.uiDev.kill()
762
+ this.uiDev = null
763
+ }
764
+
765
+ for (const timer of this.watchDebounceTimers.values()) {
766
+ clearTimeout(timer)
767
+ }
768
+ this.watchDebounceTimers.clear()
769
+
770
+ if (this.watcher) {
771
+ await this.watcher.close()
772
+ this.watcher = null
773
+ }
774
+
775
+ for (const { server } of this.appServers) {
776
+ try {
777
+ await server.close()
778
+ } catch {
779
+ // ignore
780
+ }
781
+ }
782
+ log(`πŸ“ Logs saved to: ${LOG_FILE_PATH}`)
783
+ }
784
+ }
785
+
786
+ /**
787
+ * Construct a {@link DevServerRunner}, start it, and return the instance so the caller
788
+ * (the CLI entry point) can wire signal handlers to `shutdown()` and own process exit.
789
+ */
790
+ export async function run(options: DevServerOptions = {}): Promise<DevServerRunner> {
791
+ const runner = new DevServerRunner(options)
792
+
793
+ await runner.start()
794
+
795
+ return runner
796
+ }