infra-kit 0.1.126 β 0.1.127
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.
- package/.eslintcache +1 -1
- package/.omc/state/agent-replay-036daa78-5fee-4c7d-9b44-53584acb0d87.jsonl +26 -0
- package/.omc/state/agent-replay-0a65ec83-9de7-458d-9d0f-e56e1a26c68e.jsonl +2 -0
- package/.omc/state/agent-replay-1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a.jsonl +17 -0
- package/.omc/state/agent-replay-2ac0dcd0-01f7-41a8-a8c9-f2195599817b.jsonl +5 -0
- package/.omc/state/agent-replay-d02e6565-5168-40c8-8629-de219a29fa43.jsonl +2 -0
- package/.omc/state/idle-notif-cooldown.json +1 -1
- package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/mission-state.json +129 -0
- package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/pre-tool-advisory-throttle.json +34 -0
- package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/ralph-state.json +13 -0
- package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/ralplan-state.json +11 -0
- package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/subagent-tracking-state.json +80 -0
- package/.omc/state/sessions/036daa78-5fee-4c7d-9b44-53584acb0d87/ultrawork-state.json +11 -0
- package/.omc/state/sessions/0a65ec83-9de7-458d-9d0f-e56e1a26c68e/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/0a65ec83-9de7-458d-9d0f-e56e1a26c68e/skill-active-state.json +15 -0
- package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/mission-state.json +139 -0
- package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/pre-tool-advisory-throttle.json +50 -0
- package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/ralplan-state.json +11 -0
- package/.omc/state/sessions/1b6ffc57-2d9b-4af4-b603-f1cef7e01b0a/subagent-tracking-state.json +90 -0
- package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/mission-state.json +79 -0
- package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/pre-tool-advisory-throttle.json +10 -0
- package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/skill-active-state.json +15 -0
- package/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/subagent-tracking-state.json +26 -0
- package/.omc/state/sessions/7772f972-4ec1-4287-bf42-a92c14cf3ffc/pre-tool-advisory-throttle.json +10 -0
- package/.omc/state/sessions/d02e6565-5168-40c8-8629-de219a29fa43/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/d02e6565-5168-40c8-8629-de219a29fa43/mission-state.json +53 -0
- package/.omc/state/sessions/d02e6565-5168-40c8-8629-de219a29fa43/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/d02e6565-5168-40c8-8629-de219a29fa43/subagent-tracking-state.json +17 -0
- package/.turbo/turbo-build.log +7 -6
- package/.turbo/turbo-eslint-check.log +2 -1
- package/.turbo/turbo-prettier-check.log +1 -1
- package/.turbo/turbo-test.log +406 -128
- package/.turbo/turbo-ts-check.log +1 -1
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/cli/index.html +116 -0
- package/coverage/cli/package.json.html +283 -0
- package/coverage/cli/src/commands/audit/audit.ts.html +616 -0
- package/coverage/cli/src/commands/audit/index.html +131 -0
- package/coverage/cli/src/commands/audit/index.ts.html +88 -0
- package/coverage/cli/src/commands/doctor/doctor.ts.html +1654 -0
- package/coverage/cli/src/commands/doctor/index.html +131 -0
- package/coverage/cli/src/commands/doctor/index.ts.html +88 -0
- package/coverage/cli/src/commands/env-clear/env-clear.ts.html +370 -0
- package/coverage/cli/src/commands/env-clear/index.html +131 -0
- package/coverage/cli/src/commands/env-clear/index.ts.html +88 -0
- package/coverage/cli/src/commands/env-list/env-list.ts.html +229 -0
- package/coverage/cli/src/commands/env-list/index.html +131 -0
- package/coverage/cli/src/commands/env-list/index.ts.html +88 -0
- package/coverage/cli/src/commands/env-load/env-load.ts.html +1201 -0
- package/coverage/cli/src/commands/env-load/index.html +131 -0
- package/coverage/cli/src/commands/env-load/index.ts.html +91 -0
- package/coverage/cli/src/commands/env-status/env-status.ts.html +409 -0
- package/coverage/cli/src/commands/env-status/index.html +131 -0
- package/coverage/cli/src/commands/env-status/index.ts.html +88 -0
- package/coverage/cli/src/commands/gh-merge-dev/gh-merge-dev.ts.html +694 -0
- package/coverage/cli/src/commands/gh-merge-dev/index.html +131 -0
- package/coverage/cli/src/commands/gh-merge-dev/index.ts.html +88 -0
- package/coverage/cli/src/commands/gh-release-deliver/gh-release-deliver.ts.html +1495 -0
- package/coverage/cli/src/commands/gh-release-deliver/index.html +131 -0
- package/coverage/cli/src/commands/gh-release-deliver/index.ts.html +88 -0
- package/coverage/cli/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts.html +706 -0
- package/coverage/cli/src/commands/gh-release-deploy-all/index.html +131 -0
- package/coverage/cli/src/commands/gh-release-deploy-all/index.ts.html +88 -0
- package/coverage/cli/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts.html +994 -0
- package/coverage/cli/src/commands/gh-release-deploy-selected/index.html +131 -0
- package/coverage/cli/src/commands/gh-release-deploy-selected/index.ts.html +88 -0
- package/coverage/cli/src/commands/gh-release-list/gh-release-list.ts.html +355 -0
- package/coverage/cli/src/commands/gh-release-list/index.html +131 -0
- package/coverage/cli/src/commands/gh-release-list/index.ts.html +88 -0
- package/coverage/cli/src/commands/init/agent-files.ts.html +742 -0
- package/coverage/cli/src/commands/init/index.html +146 -0
- package/coverage/cli/src/commands/init/init.ts.html +1159 -0
- package/coverage/cli/src/commands/init/migrate-config.ts.html +1219 -0
- package/coverage/cli/src/commands/release-create/index.html +131 -0
- package/coverage/cli/src/commands/release-create/index.ts.html +88 -0
- package/coverage/cli/src/commands/release-create/release-create.ts.html +1426 -0
- package/coverage/cli/src/commands/release-desc-edit/index.html +131 -0
- package/coverage/cli/src/commands/release-desc-edit/index.ts.html +88 -0
- package/coverage/cli/src/commands/release-desc-edit/release-desc-edit.ts.html +766 -0
- package/coverage/cli/src/commands/vendor-check/index.html +131 -0
- package/coverage/cli/src/commands/vendor-check/index.ts.html +88 -0
- package/coverage/cli/src/commands/vendor-check/vendor-check.ts.html +466 -0
- package/coverage/cli/src/commands/vendor-config/index.html +116 -0
- package/coverage/cli/src/commands/vendor-config/vendor-config.ts.html +571 -0
- package/coverage/cli/src/commands/vendor-diff/index.html +131 -0
- package/coverage/cli/src/commands/vendor-diff/index.ts.html +88 -0
- package/coverage/cli/src/commands/vendor-diff/vendor-diff.ts.html +316 -0
- package/coverage/cli/src/commands/vendor-manifest/index.html +131 -0
- package/coverage/cli/src/commands/vendor-manifest/index.ts.html +88 -0
- package/coverage/cli/src/commands/vendor-manifest/vendor-manifest.ts.html +265 -0
- package/coverage/cli/src/commands/vendor-sync/index.html +131 -0
- package/coverage/cli/src/commands/vendor-sync/index.ts.html +88 -0
- package/coverage/cli/src/commands/vendor-sync/vendor-sync.ts.html +355 -0
- package/coverage/cli/src/commands/version/index.html +131 -0
- package/coverage/cli/src/commands/version/index.ts.html +88 -0
- package/coverage/cli/src/commands/version/version.ts.html +184 -0
- package/coverage/cli/src/commands/worktrees-add/index.html +131 -0
- package/coverage/cli/src/commands/worktrees-add/index.ts.html +88 -0
- package/coverage/cli/src/commands/worktrees-add/worktrees-add.ts.html +1183 -0
- package/coverage/cli/src/commands/worktrees-list/index.html +131 -0
- package/coverage/cli/src/commands/worktrees-list/index.ts.html +88 -0
- package/coverage/cli/src/commands/worktrees-list/worktrees-list.ts.html +400 -0
- package/coverage/cli/src/commands/worktrees-reload/index.html +131 -0
- package/coverage/cli/src/commands/worktrees-reload/index.ts.html +88 -0
- package/coverage/cli/src/commands/worktrees-reload/worktrees-reload.ts.html +775 -0
- package/coverage/cli/src/commands/worktrees-remove/index.html +131 -0
- package/coverage/cli/src/commands/worktrees-remove/index.ts.html +88 -0
- package/coverage/cli/src/commands/worktrees-remove/worktrees-remove.ts.html +682 -0
- package/coverage/cli/src/commands/worktrees-sync/index.html +131 -0
- package/coverage/cli/src/commands/worktrees-sync/index.ts.html +88 -0
- package/coverage/cli/src/commands/worktrees-sync/worktrees-sync.ts.html +679 -0
- package/coverage/cli/src/dev/__tests__/fixtures.ts.html +787 -0
- package/coverage/cli/src/dev/__tests__/index.html +116 -0
- package/coverage/cli/src/dev/dev-server.ts.html +1855 -0
- package/coverage/cli/src/dev/discovery.ts.html +598 -0
- package/coverage/cli/src/dev/index.html +191 -0
- package/coverage/cli/src/dev/interfaces.ts.html +223 -0
- package/coverage/cli/src/dev/logger.ts.html +1102 -0
- package/coverage/cli/src/dev/ports.ts.html +430 -0
- package/coverage/cli/src/dev/serverless-local-run.ts.html +916 -0
- package/coverage/cli/src/entry/dev-server.ts.html +409 -0
- package/coverage/cli/src/entry/index.html +116 -0
- package/coverage/cli/src/index.html +116 -0
- package/coverage/cli/src/integrations/cmux/canonicalize-cmux-title.ts.html +178 -0
- package/coverage/cli/src/integrations/cmux/close-workspace-by-title.ts.html +262 -0
- package/coverage/cli/src/integrations/cmux/index.html +191 -0
- package/coverage/cli/src/integrations/cmux/index.ts.html +100 -0
- package/coverage/cli/src/integrations/cmux/list-workspace-titles.ts.html +226 -0
- package/coverage/cli/src/integrations/cmux/open-workspace-with-layout.ts.html +325 -0
- package/coverage/cli/src/integrations/cmux/workspace-title.ts.html +154 -0
- package/coverage/cli/src/integrations/cursor/add-folders-to-workspace.ts.html +337 -0
- package/coverage/cli/src/integrations/cursor/index.html +206 -0
- package/coverage/cli/src/integrations/cursor/index.ts.html +103 -0
- package/coverage/cli/src/integrations/cursor/launch-cursor.ts.html +121 -0
- package/coverage/cli/src/integrations/cursor/open-cursor-workspace.ts.html +277 -0
- package/coverage/cli/src/integrations/cursor/reconcile-workspace-folders.ts.html +355 -0
- package/coverage/cli/src/integrations/cursor/remove-folders-from-workspace.ts.html +364 -0
- package/coverage/cli/src/integrations/cursor/resolve-workspace-path.ts.html +124 -0
- package/coverage/cli/src/integrations/doppler/doppler-cli-auth.ts.html +178 -0
- package/coverage/cli/src/integrations/doppler/doppler-errors.ts.html +349 -0
- package/coverage/cli/src/integrations/doppler/doppler-project.ts.html +355 -0
- package/coverage/cli/src/integrations/doppler/index.html +161 -0
- package/coverage/cli/src/integrations/doppler/index.ts.html +97 -0
- package/coverage/cli/src/integrations/gh/gh-cli-auth/gh-cli-auth.ts.html +172 -0
- package/coverage/cli/src/integrations/gh/gh-cli-auth/index.html +131 -0
- package/coverage/cli/src/integrations/gh/gh-cli-auth/index.ts.html +88 -0
- package/coverage/cli/src/integrations/gh/gh-release-prs/gh-release-prs.ts.html +703 -0
- package/coverage/cli/src/integrations/gh/gh-release-prs/index.html +131 -0
- package/coverage/cli/src/integrations/gh/gh-release-prs/index.ts.html +91 -0
- package/coverage/cli/src/integrations/gh/index.html +116 -0
- package/coverage/cli/src/integrations/gh/index.ts.html +94 -0
- package/coverage/cli/src/integrations/ide/add-ide-worktree-folders.ts.html +316 -0
- package/coverage/cli/src/integrations/ide/index.html +191 -0
- package/coverage/cli/src/integrations/ide/index.ts.html +103 -0
- package/coverage/cli/src/integrations/ide/open-ide-workspace.ts.html +238 -0
- package/coverage/cli/src/integrations/ide/provider-label.ts.html +157 -0
- package/coverage/cli/src/integrations/ide/remove-ide-worktree-folders.ts.html +520 -0
- package/coverage/cli/src/integrations/ide/types.ts.html +208 -0
- package/coverage/cli/src/integrations/jira/api.ts.html +1060 -0
- package/coverage/cli/src/integrations/jira/index.html +131 -0
- package/coverage/cli/src/integrations/jira/index.ts.html +142 -0
- package/coverage/cli/src/integrations/zed/add-folders-to-zed-workspace.ts.html +175 -0
- package/coverage/cli/src/integrations/zed/index.html +161 -0
- package/coverage/cli/src/integrations/zed/index.ts.html +94 -0
- package/coverage/cli/src/integrations/zed/open-zed-workspace.ts.html +238 -0
- package/coverage/cli/src/integrations/zed/reuse-zed-workspace.ts.html +256 -0
- package/coverage/cli/src/lib/assert-never/assert-never.ts.html +130 -0
- package/coverage/cli/src/lib/assert-never/index.html +131 -0
- package/coverage/cli/src/lib/assert-never/index.ts.html +88 -0
- package/coverage/cli/src/lib/command-catalog/command-catalog.ts.html +523 -0
- package/coverage/cli/src/lib/command-catalog/index.html +116 -0
- package/coverage/cli/src/lib/command-echo/command-echo.ts.html +319 -0
- package/coverage/cli/src/lib/command-echo/index.html +131 -0
- package/coverage/cli/src/lib/command-echo/index.ts.html +88 -0
- package/coverage/cli/src/lib/constants/constants.ts.html +472 -0
- package/coverage/cli/src/lib/constants/index.html +131 -0
- package/coverage/cli/src/lib/constants/index.ts.html +139 -0
- package/coverage/cli/src/lib/env-autoload/env-autoload.ts.html +1111 -0
- package/coverage/cli/src/lib/env-autoload/index.html +116 -0
- package/coverage/cli/src/lib/errors/format-zx-error.ts.html +247 -0
- package/coverage/cli/src/lib/errors/index.html +146 -0
- package/coverage/cli/src/lib/errors/is-prompt-cancellation.ts.html +199 -0
- package/coverage/cli/src/lib/errors/operation-error.ts.html +325 -0
- package/coverage/cli/src/lib/git-guard/git-guard.ts.html +250 -0
- package/coverage/cli/src/lib/git-guard/index.html +131 -0
- package/coverage/cli/src/lib/git-guard/index.ts.html +91 -0
- package/coverage/cli/src/lib/git-utils/git-utils.ts.html +622 -0
- package/coverage/cli/src/lib/git-utils/index.html +131 -0
- package/coverage/cli/src/lib/git-utils/index.ts.html +115 -0
- package/coverage/cli/src/lib/infra-kit-config/index.html +131 -0
- package/coverage/cli/src/lib/infra-kit-config/index.ts.html +142 -0
- package/coverage/cli/src/lib/infra-kit-config/infra-kit-config.ts.html +1447 -0
- package/coverage/cli/src/lib/json-output/index.html +116 -0
- package/coverage/cli/src/lib/json-output/json-output.ts.html +226 -0
- package/coverage/cli/src/lib/logger/index.html +116 -0
- package/coverage/cli/src/lib/logger/index.ts.html +205 -0
- package/coverage/cli/src/lib/managed-block/index.html +131 -0
- package/coverage/cli/src/lib/managed-block/index.ts.html +109 -0
- package/coverage/cli/src/lib/managed-block/managed-block.ts.html +520 -0
- package/coverage/cli/src/lib/package-config/index.html +146 -0
- package/coverage/cli/src/lib/package-config/index.ts.html +94 -0
- package/coverage/cli/src/lib/package-config/package-config-schema.ts.html +142 -0
- package/coverage/cli/src/lib/package-config/package-config.ts.html +376 -0
- package/coverage/cli/src/lib/package-validator/checks/config-check.ts.html +175 -0
- package/coverage/cli/src/lib/package-validator/checks/files-check.ts.html +172 -0
- package/coverage/cli/src/lib/package-validator/checks/index.html +176 -0
- package/coverage/cli/src/lib/package-validator/checks/index.ts.html +97 -0
- package/coverage/cli/src/lib/package-validator/checks/scripts-check.ts.html +154 -0
- package/coverage/cli/src/lib/package-validator/checks/turbo-check.ts.html +226 -0
- package/coverage/cli/src/lib/package-validator/fs-utils.ts.html +139 -0
- package/coverage/cli/src/lib/package-validator/index.html +146 -0
- package/coverage/cli/src/lib/package-validator/index.ts.html +94 -0
- package/coverage/cli/src/lib/package-validator/loader/config-loader.ts.html +316 -0
- package/coverage/cli/src/lib/package-validator/loader/index.html +146 -0
- package/coverage/cli/src/lib/package-validator/loader/index.ts.html +91 -0
- package/coverage/cli/src/lib/package-validator/loader/package-discovery.ts.html +379 -0
- package/coverage/cli/src/lib/package-validator/package-validator.ts.html +229 -0
- package/coverage/cli/src/lib/path-display/index.html +131 -0
- package/coverage/cli/src/lib/path-display/index.ts.html +88 -0
- package/coverage/cli/src/lib/path-display/path-display.ts.html +211 -0
- package/coverage/cli/src/lib/release-id/index.html +131 -0
- package/coverage/cli/src/lib/release-id/index.ts.html +130 -0
- package/coverage/cli/src/lib/release-id/release-id.ts.html +859 -0
- package/coverage/cli/src/lib/release-utils/index.html +131 -0
- package/coverage/cli/src/lib/release-utils/index.ts.html +130 -0
- package/coverage/cli/src/lib/release-utils/release-utils.ts.html +778 -0
- package/coverage/cli/src/lib/render/index.html +116 -0
- package/coverage/cli/src/lib/render/render.ts.html +157 -0
- package/coverage/cli/src/lib/vendor/__tests__/index.html +116 -0
- package/coverage/cli/src/lib/vendor/__tests__/zx-mock.ts.html +181 -0
- package/coverage/cli/src/lib/vendor/config-schema.ts.html +319 -0
- package/coverage/cli/src/lib/vendor/config.ts.html +259 -0
- package/coverage/cli/src/lib/vendor/factory-config-schema.ts.html +190 -0
- package/coverage/cli/src/lib/vendor/factory-config.ts.html +358 -0
- package/coverage/cli/src/lib/vendor/hash.ts.html +133 -0
- package/coverage/cli/src/lib/vendor/index.html +251 -0
- package/coverage/cli/src/lib/vendor/index.ts.html +154 -0
- package/coverage/cli/src/lib/vendor/manifest.ts.html +538 -0
- package/coverage/cli/src/lib/vendor/skip-sets.ts.html +229 -0
- package/coverage/cli/src/lib/vendor/sync-ops.ts.html +955 -0
- package/coverage/cli/src/lib/vendor/walk.ts.html +253 -0
- package/coverage/cli/src/lib/version-utils/index.html +161 -0
- package/coverage/cli/src/lib/version-utils/index.ts.html +139 -0
- package/coverage/cli/src/lib/version-utils/load-existing-versions.ts.html +343 -0
- package/coverage/cli/src/lib/version-utils/next-version.ts.html +934 -0
- package/coverage/cli/src/lib/version-utils/version-utils.ts.html +178 -0
- package/coverage/cli/src/lib/worktrees/index.html +131 -0
- package/coverage/cli/src/lib/worktrees/index.ts.html +88 -0
- package/coverage/cli/src/lib/worktrees/remove-worktrees.ts.html +283 -0
- package/coverage/cli/src/tui/boot.tsx.html +265 -0
- package/coverage/cli/src/tui/index.html +116 -0
- package/coverage/cli/src/tui/screens/command-palette.tsx.html +562 -0
- package/coverage/cli/src/tui/screens/index.html +116 -0
- package/coverage/cli/src/types.ts.html +283 -0
- package/coverage/clover.xml +4488 -0
- package/coverage/coverage-final.json +156 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +1076 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/chunk-DB6CTNBS.js +3 -0
- package/dist/chunk-DB6CTNBS.js.map +7 -0
- package/dist/chunk-SGR2XAVR.js +164 -0
- package/dist/chunk-SGR2XAVR.js.map +7 -0
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +3 -3
- package/dist/dev-server.js +13 -0
- package/dist/dev-server.js.map +7 -0
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/package.json +6 -1
- package/scripts/build.js +4 -4
- package/src/.omc/state/agent-replay-2ac0dcd0-01f7-41a8-a8c9-f2195599817b.jsonl +5 -0
- package/src/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/mission-state.json +79 -0
- package/src/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/pre-tool-advisory-throttle.json +26 -0
- package/src/.omc/state/sessions/2ac0dcd0-01f7-41a8-a8c9-f2195599817b/subagent-tracking-state.json +26 -0
- package/src/commands/doctor/doctor.ts +53 -0
- package/src/commands/env-autoload/env-autoload.ts +14 -2
- package/src/commands/env-clear/env-clear.ts +48 -7
- package/src/commands/env-load/env-load.ts +23 -1
- package/src/commands/init/__tests__/shell-body.test.ts +73 -3
- package/src/commands/init/init.ts +74 -7
- package/src/dev/__tests__/dev-server-options.test.ts +57 -0
- package/src/dev/__tests__/dev-server.test.ts +420 -0
- package/src/dev/__tests__/discovery.test.ts +181 -0
- package/src/dev/__tests__/fixtures.ts +257 -0
- package/src/dev/__tests__/ports.test.ts +98 -0
- package/src/dev/__tests__/serverless-local-run.test.ts +533 -0
- package/src/dev/dev-server.ts +796 -0
- package/src/dev/discovery.ts +229 -0
- package/src/dev/interfaces.ts +11 -0
- package/src/dev/managed-child.ts +73 -0
- package/src/dev/ports.ts +100 -0
- package/src/dev/serverless-local-run.ts +329 -0
- package/src/dev/turbo-watch.ts +62 -0
- package/src/dev/ui-dev.ts +62 -0
- package/src/entry/cli.ts +25 -6
- package/src/entry/dev-server.ts +103 -0
- package/src/lib/command-catalog/__tests__/__snapshots__/command-catalog.test.ts.snap +1 -0
- package/src/lib/command-catalog/command-catalog.ts +4 -0
- package/src/lib/constants/constants.ts +38 -0
- package/src/lib/constants/index.ts +4 -0
- package/src/lib/env-autoload/__tests__/env-autoload.test.ts +25 -0
- package/src/lib/env-autoload/env-autoload.ts +35 -4
- package/src/lib/infra-kit-config/index.ts +2 -0
- package/src/lib/infra-kit-config/infra-kit-config.ts +22 -0
- package/src/lib/warm-cache/__tests__/warm-cache.test.ts +164 -0
- package/src/lib/warm-cache/index.ts +7 -0
- package/src/lib/warm-cache/warm-cache.ts +136 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/chunk-EY5LDHXC.js +0 -164
- package/dist/chunk-EY5LDHXC.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
|
+
}
|