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
@@ -277,6 +277,7 @@ export const buildShellBody = (): string => {
277
277
  return [
278
278
  'zmodload zsh/stat 2>/dev/null',
279
279
  'zmodload zsh/datetime 2>/dev/null',
280
+ 'zmodload zsh/sched 2>/dev/null',
280
281
  // eslint-disable-next-line no-template-curly-in-string
281
282
  'if [[ -z "${INFRA_KIT_SESSION}" ]]; then',
282
283
  ' export INFRA_KIT_SESSION=$(head -c 4 /dev/urandom | xxd -p)',
@@ -287,11 +288,30 @@ export const buildShellBody = (): string => {
287
288
  ': ${_INFRA_KIT_LAST_CLEAR_MTIME:=0}',
288
289
  // eslint-disable-next-line no-template-curly-in-string
289
290
  ': ${_INFRA_KIT_SHELL_STARTED:=${EPOCHSECONDS:-0}}',
290
- 'export _INFRA_KIT_LAST_LOAD_MTIME _INFRA_KIT_LAST_CLEAR_MTIME _INFRA_KIT_SHELL_STARTED',
291
+ // Warm-cache source TTL (seconds); MUST equal DEFAULT_WARM_TTL_SECONDS (node
292
+ // eviction) — both 2h. A warm file older than this is not sourced at startup.
293
+ // eslint-disable-next-line no-template-curly-in-string
294
+ ': ${_INFRA_KIT_WARM_TTL:=7200}',
295
+ 'export _INFRA_KIT_LAST_LOAD_MTIME _INFRA_KIT_LAST_CLEAR_MTIME _INFRA_KIT_SHELL_STARTED _INFRA_KIT_WARM_TTL',
291
296
  `env-load() { local f m; f=$(${runCmd} env-load "$@") || return; m=$(zstat +mtime -- "$f" 2>/dev/null || echo 0); _INFRA_KIT_LAST_LOAD_MTIME=$m; source "$f"; ${runCmd} env-status; }`,
292
- `env-clear() { local f m; f=$(${runCmd} env-clear) || return; m=$(zstat +mtime -- "$f" 2>/dev/null || echo 0); _INFRA_KIT_LAST_CLEAR_MTIME=$m; source "$f"; ${runCmd} env-status; }`,
297
+ `env-clear() { local f m; f=$(${runCmd} env-clear "$@") || return; m=$(zstat +mtime -- "$f" 2>/dev/null || echo 0); _INFRA_KIT_LAST_CLEAR_MTIME=$m; source "$f"; ${runCmd} env-status; }`,
293
298
  `env-status() { ${runCmd} env-status; }`,
294
299
  `alias ik='${runCmd}'`,
300
+ // Print an async notice without corrupting an already-drawn prompt. The
301
+ // startup poll fires via `sched` at an IDLE prompt (ZLE active) which does
302
+ // NOT redraw the prompt, so a bare `print` lands appended to the visible
303
+ // prompt line and looks like un-removable typed input. When ZLE is active we
304
+ // erase the current line (CR + clear-to-EOL), print the message on its own
305
+ // line, then `zle reset-prompt` to redraw the prompt below it. In precmd /
306
+ // shell-startup (ZLE inactive) `zle` is false and this is a plain stderr
307
+ // print above the about-to-be-drawn prompt — unchanged behavior.
308
+ '_infra_kit_notify() {',
309
+ // When ZLE is active, erase the current prompt line first and redraw after;
310
+ // both are no-ops when ZLE is inactive so this stays a plain stderr print.
311
+ " zle && print -u2 -n $'\\r\\e[K'",
312
+ ' print -u2 -- "$1"',
313
+ ' zle && zle reset-prompt',
314
+ '}',
295
315
  '_infra_kit_autoload() {',
296
316
  ' [[ -z "$INFRA_KIT_SESSION" ]] && return',
297
317
  // eslint-disable-next-line no-template-curly-in-string
@@ -306,18 +326,73 @@ export const buildShellBody = (): string => {
306
326
  ' source "$load_file"',
307
327
  ' _INFRA_KIT_LAST_LOAD_MTIME=$load_mtime',
308
328
  // eslint-disable-next-line no-template-curly-in-string
309
- ' print -u2 "infra-kit: auto-loaded vars for ${INFRA_KIT_ENV_CONFIG:-?}"',
329
+ ' _infra_kit_notify "infra-kit: auto-loaded vars for ${INFRA_KIT_ENV_CONFIG:-?}"',
310
330
  ' fi',
311
331
  ' if (( clear_mtime > _INFRA_KIT_LAST_CLEAR_MTIME && clear_mtime >= _INFRA_KIT_SHELL_STARTED && clear_mtime > load_mtime )); then',
312
332
  ' source "$clear_file"',
313
333
  ' _INFRA_KIT_LAST_CLEAR_MTIME=$clear_mtime',
314
- ' print -u2 "infra-kit: auto-cleared env"',
334
+ ' _infra_kit_notify "infra-kit: auto-cleared env"',
315
335
  ' fi',
316
336
  '}',
317
337
  'autoload -Uz add-zsh-hook',
318
338
  'if (( _INFRA_KIT_SHELL_STARTED > 0 )); then',
319
339
  ' add-zsh-hook precmd _infra_kit_autoload',
320
340
  'fi',
341
+ // Non-blocking startup poll: the shell-startup spawn (below) writes env-load.sh
342
+ // asynchronously (node + Doppler fetch, ~0.5-2s), so the FIRST precmd — which
343
+ // fires before the first prompt — runs before the file lands and sources nothing;
344
+ // without this the vars only appear after the user's first command. This one-shot
345
+ // re-arming poll re-runs the precmd sourcer once per second, at the idle prompt
346
+ // with NO user command, until it sources the freshly written file (its mtime is
347
+ // >= _INFRA_KIT_SHELL_STARTED so the existing gate accepts it) OR the startup
348
+ // window closes. _infra_kit_autoload sets _INFRA_KIT_LAST_LOAD_MTIME on a
349
+ // successful source, which lifts it to >= _INFRA_KIT_SHELL_STARTED and stops the
350
+ // re-arm. Bounded to WINDOW seconds so an unconfigured project / down Doppler
351
+ // that never writes a file only costs a handful of no-op ticks, then falls back
352
+ // to the precmd hook. Sources the same freshly written file as precmd, so there
353
+ // is no stale/last-known-good secrets window.
354
+ '_INFRA_KIT_AUTOLOAD_WINDOW=6',
355
+ 'export _INFRA_KIT_AUTOLOAD_WINDOW',
356
+ '_infra_kit_poll_autoload() {',
357
+ ' _infra_kit_autoload',
358
+ // eslint-disable-next-line no-template-curly-in-string
359
+ ' if (( _INFRA_KIT_LAST_LOAD_MTIME < _INFRA_KIT_SHELL_STARTED )) && (( ${EPOCHSECONDS:-0} - _INFRA_KIT_SHELL_STARTED < _INFRA_KIT_AUTOLOAD_WINDOW )); then',
360
+ ' (( $+builtins[sched] )) && sched +1 _infra_kit_poll_autoload',
361
+ ' fi',
362
+ '}',
363
+ // Warm cache: source the project-scoped last-known-good env-load.sh INSTANTLY at
364
+ // startup (pure zsh, no node/Doppler) so vars are present at prompt-0, before the
365
+ // backgrounded refresh lands. `key` is sha256 of the canonical project dir passed
366
+ // in by the caller — byte-identical to node's warmCacheKey (printf %s | shasum,
367
+ // full 64-hex). Skips when: no sha tool (degrade to poll), no warm file, a project
368
+ // clear-marker is at least as new (clear wins ties), or the file is past TTL
369
+ // (bounds a rotated secret served at prompt-0). Deliberately does NOT set
370
+ // _INFRA_KIT_LAST_LOAD_MTIME (leaves it 0) so the poll/precmd still source the
371
+ // fresh SESSION file (different path, newer mtime) and upgrade warm->fresh.
372
+ '_infra_kit_warm_source() {',
373
+ ' local canon="$1" key',
374
+ ' if (( $+commands[shasum] )); then',
375
+ ' key=$(printf %s "$canon" | shasum -a 256 | cut -c1-64)',
376
+ ' elif (( $+commands[sha256sum] )); then',
377
+ ' key=$(printf %s "$canon" | sha256sum | cut -c1-64)',
378
+ ' else',
379
+ ' return',
380
+ ' fi',
381
+ // eslint-disable-next-line no-template-curly-in-string
382
+ ' local cache_root="${XDG_CACHE_HOME:-$HOME/.cache}/infra-kit"',
383
+ ' local warm="$cache_root/projects/$key/env-load.sh"',
384
+ ' local wclear="$cache_root/projects/$key/env-clear.sh"',
385
+ ' [[ -f "$warm" ]] || return',
386
+ ' local wm=0 cm=0',
387
+ ' wm=$(zstat +mtime -- "$warm" 2>/dev/null || echo 0)',
388
+ ' [[ -f "$wclear" ]] && cm=$(zstat +mtime -- "$wclear" 2>/dev/null || echo 0)',
389
+ ' (( cm >= wm )) && return',
390
+ // eslint-disable-next-line no-template-curly-in-string
391
+ ' (( ${EPOCHSECONDS:-0} - wm >= _INFRA_KIT_WARM_TTL )) && return',
392
+ ' source "$warm"',
393
+ // eslint-disable-next-line no-template-curly-in-string
394
+ ' _infra_kit_notify "infra-kit: loaded cached vars for ${INFRA_KIT_ENV_CONFIG:-?} (refreshing…)"',
395
+ '}',
321
396
  // One-shot env auto-load when a NEW shell opens inside an infra-kit project
322
397
  // or worktree (config: envAutoLoad.trigger "shell-startup"). Cheap pure-zsh
323
398
  // project gate (walk up for infra-kit.json) avoids spawning node in unrelated
@@ -325,17 +400,24 @@ export const buildShellBody = (): string => {
325
400
  // project root (INFRA_KIT_ENV_PROJECT_ROOT), so cd'ing into a DIFFERENT project
326
401
  // re-loads instead of silently keeping the previous project's secrets. The spawn
327
402
  // is DETACHED+backgrounded ( ... & ) so it never blocks the prompt; it only
328
- // WRITES env-load.sh and the precmd hook above is the sole sourcer (picked up on
329
- // a subsequent prompt). Trade-off: the gate can't read the merged JSON config, so
330
- // a fresh shell in a project WITHOUT envAutoLoad still spawns one background
331
- // process that resolves config, finds nothing, and exits.
403
+ // WRITES env-load.sh. The startup poll armed right after (and the precmd hook as
404
+ // a later-prompt fallback) sources the file once it lands. Trade-off: the gate
405
+ // can't read the merged JSON config, so a fresh shell in a project WITHOUT
406
+ // envAutoLoad still spawns one background process that resolves config, finds
407
+ // nothing, and exits.
332
408
  '_infra_kit_startup_autoload() {',
333
409
  ' [[ -z "$INFRA_KIT_SESSION" ]] && return',
334
410
  ' local dir="$PWD" prev=""',
335
411
  ' while [[ -n "$dir" && "$dir" != "$prev" ]]; do',
336
412
  ' if [[ -f "$dir/infra-kit.json" ]]; then',
337
413
  ' [[ -n "$INFRA_KIT_ENV_CONFIG" && "$dir" == "$INFRA_KIT_ENV_PROJECT_ROOT" ]] && return',
338
- ` ( ${runCmd} env-autoload & ) >/dev/null 2>&1`,
414
+ // canon = realpath of the project dir; the SAME string node hashes for the warm
415
+ // key (passed via --project-dir), so the two keys are byte-identical.
416
+ // eslint-disable-next-line no-template-curly-in-string
417
+ ' local canon="${dir:A}"',
418
+ ' _infra_kit_warm_source "$canon"',
419
+ ` ( ${runCmd} env-autoload --project-dir "$canon" & ) >/dev/null 2>&1`,
420
+ ' (( $+builtins[sched] )) && sched +1 _infra_kit_poll_autoload',
339
421
  ' return',
340
422
  ' fi',
341
423
  ' prev="$dir"',
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { toDevServerOptions } from 'src/entry/dev-server'
4
+ import type { DevCliOptions } from 'src/entry/dev-server'
5
+
6
+ /**
7
+ * Flag/option parsing for the dev-server CLI entry. `toDevServerOptions` maps the
8
+ * raw Commander flag object (comma-joined strings) to the typed `DevServerOptions`
9
+ * the orchestrator consumes. Pure function — no fixtures.
10
+ */
11
+ describe('toDevServerOptions — CLI flag parsing', () => {
12
+ it('splits --app into an include list', () => {
13
+ const opts = toDevServerOptions({ app: 'client,backoffice' } satisfies DevCliOptions)
14
+
15
+ expect(opts.include).toEqual(['client', 'backoffice'])
16
+ })
17
+
18
+ it('trims whitespace and drops empty segments in comma lists', () => {
19
+ const opts = toDevServerOptions({ app: ' client , , backoffice ,' } satisfies DevCliOptions)
20
+
21
+ expect(opts.include).toEqual(['client', 'backoffice'])
22
+ })
23
+
24
+ it('maps a missing --app to null (means "all apps")', () => {
25
+ const opts = toDevServerOptions({} satisfies DevCliOptions)
26
+
27
+ expect(opts.include).toBeNull()
28
+ })
29
+
30
+ it('maps an all-empty comma list to null rather than an empty array', () => {
31
+ const opts = toDevServerOptions({ app: ' , , ' } satisfies DevCliOptions)
32
+
33
+ expect(opts.include).toBeNull()
34
+ })
35
+
36
+ it('defaults watch to false when the flag is absent', () => {
37
+ expect(toDevServerOptions({} satisfies DevCliOptions).watch).toBe(false)
38
+ })
39
+
40
+ it('passes --watch through as true', () => {
41
+ expect(toDevServerOptions({ watch: true } satisfies DevCliOptions).watch).toBe(true)
42
+ })
43
+
44
+ it('parses a combined flag set (--app --watch) in one pass', () => {
45
+ const opts = toDevServerOptions({ app: 'a,b', watch: true } satisfies DevCliOptions)
46
+
47
+ expect(opts).toEqual({ watch: true, include: ['a', 'b'], ui: false })
48
+ })
49
+
50
+ it('defaults ui to false when --ui is absent (api-only, backward compatible)', () => {
51
+ expect(toDevServerOptions({} satisfies DevCliOptions).ui).toBe(false)
52
+ })
53
+
54
+ it('passes --ui through as true', () => {
55
+ expect(toDevServerOptions({ ui: true } satisfies DevCliOptions).ui).toBe(true)
56
+ })
57
+ })
@@ -0,0 +1,420 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+ import process from 'node:process'
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
5
+
6
+ import { DevServerRunner } from 'src/dev/dev-server'
7
+
8
+ import {
9
+ canBind,
10
+ createTempTracker,
11
+ getFreePort,
12
+ handlerSource,
13
+ makeMonorepo,
14
+ restoreCwd,
15
+ restoreEnv,
16
+ snapshotCwd,
17
+ snapshotEnv,
18
+ spyStdoutWrite,
19
+ } from './fixtures'
20
+
21
+ /**
22
+ * Deterministic orchestration tier: a real monorepo fixture + the REAL ServerlessLocalRun
23
+ * spawn, but the turbo build is replaced with an injected fake (2nd constructor arg) that
24
+ * records its calls and refreshes each app's dist output. No chokidar, no watch, no timers
25
+ * — start() then shutdown() is the whole lifecycle under test.
26
+ */
27
+ const temp = createTempTracker()
28
+ let envSnapshot: NodeJS.ProcessEnv
29
+ let cwdSnapshot: string
30
+
31
+ beforeEach(() => {
32
+ envSnapshot = snapshotEnv()
33
+ cwdSnapshot = snapshotCwd()
34
+ })
35
+
36
+ afterEach(() => {
37
+ restoreCwd(cwdSnapshot)
38
+ restoreEnv(envSnapshot)
39
+ temp.cleanup()
40
+ })
41
+
42
+ describe('devServerRunner — start/shutdown lifecycle (fake build, real spawn)', () => {
43
+ it('boots each discovered app on its resolved port and serves /__health, then frees every port on shutdown', async () => {
44
+ const root = temp.register(
45
+ makeMonorepo([
46
+ { name: 'alpha', packageName: 'alpha-api', withHandler: true },
47
+ { name: 'beta', packageName: 'beta-api', withHandler: true },
48
+ ]),
49
+ )
50
+
51
+ // Distinct free ports via env keep the conflict pre-check happy and avoid collisions.
52
+ const alphaPort = await getFreePort()
53
+ const betaPort = await getFreePort()
54
+
55
+ process.env.ALPHA_PORT = String(alphaPort)
56
+ process.env.BETA_PORT = String(betaPort)
57
+
58
+ // Fake build: record the command and (re)write fresh dist output for each app.
59
+ const buildCalls: string[] = []
60
+ const apiDirs = ['alpha', 'beta'].map((name) => {
61
+ return path.join(root, 'apps', name, 'api')
62
+ })
63
+ const fakeRunBuild = async (cmd: string): Promise<void> => {
64
+ buildCalls.push(cmd)
65
+ for (const dir of apiDirs) {
66
+ fs.writeFileSync(path.join(dir, 'dist', 'handler.js'), handlerSource(1))
67
+ }
68
+ }
69
+
70
+ // The runner resolves the monorepo root from cwd, so run from the fixture root.
71
+ process.chdir(root)
72
+ const runner = new DevServerRunner({}, fakeRunBuild)
73
+
74
+ await runner.start()
75
+
76
+ // The build seam was invoked (the initial turbo build).
77
+ expect(buildCalls.length).toBeGreaterThan(0)
78
+
79
+ // Both real servers are listening and answer their health route with their own port.
80
+ const alphaHealth = (await (await fetch(`http://127.0.0.1:${alphaPort}/__health`)).json()) as {
81
+ app: string
82
+ port: number
83
+ }
84
+ const betaHealth = (await (await fetch(`http://127.0.0.1:${betaPort}/__health`)).json()) as {
85
+ app: string
86
+ port: number
87
+ }
88
+
89
+ expect(alphaHealth).toMatchObject({ app: 'alpha', port: alphaPort })
90
+ expect(betaHealth).toMatchObject({ app: 'beta', port: betaPort })
91
+
92
+ // Ports are held while running.
93
+ expect(await canBind(alphaPort)).toBe(false)
94
+ expect(await canBind(betaPort)).toBe(false)
95
+
96
+ await runner.shutdown()
97
+
98
+ // shutdown() releases every port so a fresh listener can bind again.
99
+ expect(await canBind(alphaPort)).toBe(true)
100
+ expect(await canBind(betaPort)).toBe(true)
101
+ })
102
+ })
103
+
104
+ describe('start — port-conflict pre-check', () => {
105
+ it('throws a clear error naming the port when two apps resolve to the same port', async () => {
106
+ const root = temp.register(makeMonorepo([{ name: 'client' }, { name: 'backoffice' }]))
107
+
108
+ // No per-app config and no env override => both resolve to 3010.
109
+ delete process.env.PORT
110
+ process.chdir(root)
111
+ const runner = new DevServerRunner({})
112
+
113
+ // The duplicate-port guard runs before any turbo build, so start() rejects
114
+ // hermetically without ever shelling out.
115
+ await expect(runner.start()).rejects.toThrow(/Port conflict detected: 3010/)
116
+ })
117
+
118
+ it('does not throw the conflict error when apps resolve to distinct ports', async () => {
119
+ const root = temp.register(makeMonorepo([{ name: 'client' }, { name: 'backoffice' }]))
120
+
121
+ // Distinct per-app ports via env keep the pre-check happy; the build step then
122
+ // fails (no turbo in the fixture) — proving we got past the port guard.
123
+ process.env.CLIENT_PORT = '3010'
124
+ process.env.BACKOFFICE_PORT = '3011'
125
+ process.chdir(root)
126
+ const runner = new DevServerRunner({})
127
+
128
+ await expect(runner.start()).rejects.not.toThrow(/Port conflict/)
129
+ })
130
+ })
131
+
132
+ describe('start — --app selection + env-resolved port', () => {
133
+ it('runs only the --app subset (skipping the rest) and resolves its port from {APP}_PORT', async () => {
134
+ const root = temp.register(
135
+ makeMonorepo([
136
+ { name: 'client', packageName: 'client-api', withHandler: true },
137
+ { name: 'backoffice', packageName: 'backoffice-api', withHandler: true },
138
+ ]),
139
+ )
140
+ const apiDirs = ['client', 'backoffice'].map((name) => {
141
+ return path.join(root, 'apps', name, 'api')
142
+ })
143
+ const fakeRunBuild = async (): Promise<void> => {
144
+ for (const dir of apiDirs) {
145
+ fs.writeFileSync(path.join(dir, 'dist', 'handler.js'), handlerSource(1))
146
+ }
147
+ }
148
+
149
+ // `--app client` selects one app; its port comes purely from the CLIENT_PORT env var.
150
+ const clientPort = await getFreePort()
151
+ const backofficePort = await getFreePort()
152
+
153
+ delete process.env.PORT
154
+ process.env.CLIENT_PORT = String(clientPort)
155
+ process.env.BACKOFFICE_PORT = String(backofficePort)
156
+ process.chdir(root)
157
+ const runner = new DevServerRunner({ include: ['client'] }, fakeRunBuild)
158
+
159
+ await runner.start()
160
+
161
+ try {
162
+ // Only `client` booted, on its {APP}_PORT-resolved port.
163
+ const health = (await (await fetch(`http://127.0.0.1:${clientPort}/__health`)).json()) as {
164
+ app: string
165
+ port: number
166
+ }
167
+
168
+ expect(health).toMatchObject({ app: 'client', port: clientPort })
169
+
170
+ // `backoffice` was filtered out by --app, so its port never gets bound.
171
+ expect(await canBind(backofficePort)).toBe(true)
172
+ } finally {
173
+ await runner.shutdown()
174
+ }
175
+ })
176
+ })
177
+
178
+ /** Boot a single-app monorepo fixture, capturing stdout across start(); returns the output + runner. */
179
+ const bootAndCaptureStdout = async (
180
+ temp: ReturnType<typeof createTempTracker>,
181
+ appName: string,
182
+ ): Promise<{ out: string; runner: DevServerRunner }> => {
183
+ const root = temp.register(makeMonorepo([{ name: appName, packageName: `${appName}-api`, withHandler: true }]))
184
+ const port = await getFreePort()
185
+
186
+ process.env[`${appName.replace(/-/g, '_').toUpperCase()}_PORT`] = String(port)
187
+
188
+ const apiDir = path.join(root, 'apps', appName, 'api')
189
+ const fakeRunBuild = async (): Promise<void> => {
190
+ fs.writeFileSync(path.join(apiDir, 'dist', 'handler.js'), handlerSource(1))
191
+ }
192
+
193
+ const writes: string[] = []
194
+ const spy = spyStdoutWrite(writes)
195
+
196
+ process.chdir(root)
197
+ const runner = new DevServerRunner({}, fakeRunBuild)
198
+
199
+ try {
200
+ await runner.start()
201
+ } finally {
202
+ spy.mockRestore()
203
+ }
204
+
205
+ return { out: writes.join(''), runner }
206
+ }
207
+
208
+ describe('devServerRunner — startup server table', () => {
209
+ it('renders truthful, aligned rows: prefix in the base URL, a /__health URL, equal-width lines', async () => {
210
+ // A long app name is exactly what broke the old fixed-width padEnd(24) table.
211
+ const longName = 'super-long-application-name'
212
+ const { out, runner } = await bootAndCaptureStdout(temp, longName)
213
+
214
+ try {
215
+ // Truthful: the base URL carries the /api/v1 prefix and the health URL is shown.
216
+ expect(out).toContain('/api/v1')
217
+ expect(out).toContain('/__health')
218
+ expect(out).toContain(longName)
219
+
220
+ // Aligned: every box-drawn line shares one width (measured in UTF-16 units, as the
221
+ // renderer computes them — the title emoji is a surrogate pair, so code-point counts differ).
222
+ const tableLines = out.split('\n').filter((line) => {
223
+ return /^[┌│├└]/.test(line)
224
+ })
225
+
226
+ expect(tableLines.length).toBeGreaterThan(0)
227
+ expect(
228
+ new Set(
229
+ tableLines.map((line) => {
230
+ return line.length
231
+ }),
232
+ ).size,
233
+ ).toBe(1)
234
+ } finally {
235
+ await runner.shutdown()
236
+ }
237
+ }, 15000)
238
+ })
239
+
240
+ describe('devServerRunner — startup route dump', () => {
241
+ it('lists each app registered METHOD /path routes at startup', async () => {
242
+ const { out, runner } = await bootAndCaptureStdout(temp, 'epsilon')
243
+
244
+ try {
245
+ expect(out).toContain('Registered routes')
246
+ expect(out).toContain('epsilon')
247
+ // The monorepo fixture declares a single GET /api/v1/ping route.
248
+ expect(out).toContain('GET /api/v1/ping')
249
+ } finally {
250
+ await runner.shutdown()
251
+ }
252
+ }, 15000)
253
+ })
254
+
255
+ describe('devServerRunner — watch mode (dist-watch, build-less restart)', () => {
256
+ it('restarts on a dist change without rebuilding, spawns the turbo engine, and reaps it on shutdown', async () => {
257
+ const root = temp.register(makeMonorepo([{ name: 'omega', packageName: 'omega-api', withHandler: true }]))
258
+ const realRoot = fs.realpathSync(root)
259
+ const apiDir = path.join(realRoot, 'apps', 'omega', 'api')
260
+ const distHandler = path.join(apiDir, 'dist', 'handler.js')
261
+
262
+ const omegaPort = await getFreePort()
263
+
264
+ process.env.OMEGA_PORT = String(omegaPort)
265
+ // Hermetic polling so the dist watcher fires without relying on native fs events.
266
+ process.env.DEV_SERVER_CHOKIDAR_POLL = '1'
267
+
268
+ // Fake build: boot build only. In turbo mode the on-change rebuild is owned by the
269
+ // (faked) turbo engine, so runBuild must NOT be called again after boot.
270
+ const buildCalls: string[] = []
271
+ const fakeRunBuild = async (cmd: string): Promise<void> => {
272
+ buildCalls.push(cmd)
273
+ fs.writeFileSync(distHandler, handlerSource(1))
274
+ }
275
+
276
+ // No-op turbo engine: record that it was asked to start (+ with which packages) and
277
+ // that it was killed on shutdown, without spawning a real child.
278
+ let killed = false
279
+ let spawnedPackages: string[] | null = null
280
+ const fakeTurboWatch = (opts: { packageNames: string[] }): { kill: () => Promise<void> } => {
281
+ spawnedPackages = opts.packageNames
282
+
283
+ return {
284
+ kill: (): Promise<void> => {
285
+ killed = true
286
+
287
+ return Promise.resolve()
288
+ },
289
+ }
290
+ }
291
+
292
+ process.chdir(root)
293
+ const runner = new DevServerRunner({ watch: true }, fakeRunBuild, fakeTurboWatch)
294
+
295
+ await runner.start()
296
+
297
+ try {
298
+ const v1 = (await (await fetch(`http://127.0.0.1:${omegaPort}/api/v1/ping`)).json()) as { version: number }
299
+
300
+ expect(v1.version).toBe(1)
301
+
302
+ // The turbo engine was started, scoped to the app package.
303
+ expect(spawnedPackages).toEqual(['omega-api'])
304
+
305
+ // Exactly one build so far: the boot build. No rebuild happens on a dist change.
306
+ const buildsAfterBoot = buildCalls.length
307
+
308
+ expect(buildsAfterBoot).toBe(1)
309
+
310
+ // Let chokidar finish its initial scan, then simulate the turbo engine emitting a
311
+ // fresh dist build (v2) — the signal the dev-server restarts on.
312
+ await new Promise((r) => {
313
+ return setTimeout(r, 1200)
314
+ })
315
+ fs.writeFileSync(distHandler, handlerSource(2))
316
+
317
+ const deadline = Date.now() + 8000
318
+ let sawV2 = false
319
+
320
+ while (Date.now() < deadline && !sawV2) {
321
+ try {
322
+ const body = (await (await fetch(`http://127.0.0.1:${omegaPort}/api/v1/ping`)).json()) as { version: number }
323
+
324
+ if (body.version === 2) {
325
+ sawV2 = true
326
+ break
327
+ }
328
+ } catch {
329
+ // Server briefly down mid-restart; keep polling.
330
+ }
331
+ await new Promise((r) => {
332
+ return setTimeout(r, 100)
333
+ })
334
+ }
335
+
336
+ // Restarted and served the new dist on the same port…
337
+ expect(sawV2).toBe(true)
338
+ // …without any additional build (the restart is build-less in turbo mode).
339
+ expect(buildCalls).toHaveLength(buildsAfterBoot)
340
+ } finally {
341
+ await runner.shutdown()
342
+ }
343
+
344
+ // shutdown() reaped the turbo engine and freed the port.
345
+ expect(killed).toBe(true)
346
+ expect(await canBind(omegaPort)).toBe(true)
347
+ }, 15000)
348
+ })
349
+
350
+ describe('devServerRunner — --ui (frontends via delegated turbo run dev)', () => {
351
+ it('warms UI deps, spawns the ui-dev child scoped to ui packages, and reaps it on shutdown', async () => {
352
+ const root = temp.register(
353
+ makeMonorepo([{ name: 'shop', packageName: 'shop-api', withHandler: true, ui: { packageName: 'shop-ui' } }]),
354
+ )
355
+ const apiDir = path.join(root, 'apps', 'shop', 'api')
356
+ const shopPort = await getFreePort()
357
+
358
+ process.env.SHOP_PORT = String(shopPort)
359
+
360
+ const buildCalls: string[] = []
361
+ const fakeRunBuild = async (cmd: string): Promise<void> => {
362
+ buildCalls.push(cmd)
363
+ fs.writeFileSync(path.join(apiDir, 'dist', 'handler.js'), handlerSource(1))
364
+ }
365
+
366
+ // turbo watch isn't used without --watch; a no-op factory keeps the seam satisfied.
367
+ const noopTurboWatch = (): { kill: () => Promise<void> } => {
368
+ return {
369
+ kill: (): Promise<void> => {
370
+ return Promise.resolve()
371
+ },
372
+ }
373
+ }
374
+
375
+ let uiKilled = false
376
+ let uiPackages: string[] | null = null
377
+ let uiConcurrency = 0
378
+ const fakeUiDev = (opts: { packageNames: string[]; concurrency: number }): { kill: () => Promise<void> } => {
379
+ uiPackages = opts.packageNames
380
+ uiConcurrency = opts.concurrency
381
+
382
+ return {
383
+ kill: (): Promise<void> => {
384
+ uiKilled = true
385
+
386
+ return Promise.resolve()
387
+ },
388
+ }
389
+ }
390
+
391
+ process.chdir(root)
392
+ const runner = new DevServerRunner({ ui: true }, fakeRunBuild, noopTurboWatch, fakeUiDev)
393
+
394
+ await runner.start()
395
+
396
+ try {
397
+ // The API app still boots and serves (api + ui run together).
398
+ const health = (await (await fetch(`http://127.0.0.1:${shopPort}/__health`)).json()) as { app: string }
399
+
400
+ expect(health.app).toBe('shop')
401
+
402
+ // The ui-dev child was spawned, scoped to the UI package only, with adequate concurrency.
403
+ expect(uiPackages).toEqual(['shop-ui'])
404
+ expect(uiConcurrency).toBeGreaterThanOrEqual(1)
405
+
406
+ // A UI dependency-closure warm build ran (deps-only `^...`, not the UI's own build).
407
+ expect(
408
+ buildCalls.some((c) => {
409
+ return c.includes('shop-ui^...')
410
+ }),
411
+ ).toBe(true)
412
+ } finally {
413
+ await runner.shutdown()
414
+ }
415
+
416
+ // shutdown() reaped the ui-dev child and freed the API port.
417
+ expect(uiKilled).toBe(true)
418
+ expect(await canBind(shopPort)).toBe(true)
419
+ }, 15000)
420
+ })