@tdk-landscape/tdk-cli-core 1.3.49 → 1.3.50

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 (309) hide show
  1. package/.claude/skills/layer-autoresearch/SKILL.md +48 -0
  2. package/.claude/skills/tdk-troubleshoot/SKILL.md +222 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +40 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +18 -0
  6. package/.github/dependabot.yml +33 -0
  7. package/.github/pull_request_template.md +5 -0
  8. package/.github/workflows/ci.yml +187 -0
  9. package/.github/workflows/release-binaries.yml +19 -9
  10. package/.github/zizmor.yml +9 -0
  11. package/CODE_OF_CONDUCT.md +9 -0
  12. package/CONTRIBUTING.md +42 -0
  13. package/LICENSE +21 -0
  14. package/README.md +149 -0
  15. package/SECURITY.md +17 -0
  16. package/benchmarks/results/container-scale-2026-09-25T20-54-49-736Z.json +45 -0
  17. package/benchmarks/results/container-scale-2026-09-25T21-01-20-540Z.json +150 -0
  18. package/benchmarks/results/container-scale-2026-09-25T21-15-01-441Z.json +73 -0
  19. package/benchmarks/results/container-scale-2026-09-25T22-09-01-630Z.json +77 -0
  20. package/benchmarks/results/container-scale-2026-09-25T22-18-28-371Z.json +202 -0
  21. package/cli/README.md +25 -0
  22. package/cli/biome.json +1 -1
  23. package/cli/dist/cli.d.ts +3 -0
  24. package/cli/dist/cli.d.ts.map +1 -0
  25. package/cli/dist/cli.js +64 -0
  26. package/cli/dist/cli.js.map +1 -0
  27. package/cli/dist/commands/completion.d.ts +3 -0
  28. package/cli/dist/commands/completion.d.ts.map +1 -0
  29. package/cli/dist/commands/completion.js +294 -0
  30. package/cli/dist/commands/completion.js.map +1 -0
  31. package/cli/dist/commands/config.d.ts +3 -0
  32. package/cli/dist/commands/config.d.ts.map +1 -0
  33. package/cli/dist/commands/config.js +231 -0
  34. package/cli/dist/commands/config.js.map +1 -0
  35. package/cli/dist/commands/doctor.d.ts +19 -0
  36. package/cli/dist/commands/doctor.d.ts.map +1 -0
  37. package/cli/dist/commands/doctor.js +745 -0
  38. package/cli/dist/commands/doctor.js.map +1 -0
  39. package/cli/dist/commands/down.d.ts +3 -0
  40. package/cli/dist/commands/down.d.ts.map +1 -0
  41. package/cli/dist/commands/down.js +32 -0
  42. package/cli/dist/commands/down.js.map +1 -0
  43. package/cli/dist/commands/help.d.ts +2 -0
  44. package/cli/dist/commands/help.d.ts.map +1 -0
  45. package/cli/dist/commands/help.js +138 -0
  46. package/cli/dist/commands/help.js.map +1 -0
  47. package/cli/dist/commands/networks.d.ts +3 -0
  48. package/cli/dist/commands/networks.d.ts.map +1 -0
  49. package/cli/dist/commands/networks.js +260 -0
  50. package/cli/dist/commands/networks.js.map +1 -0
  51. package/cli/dist/commands/project.d.ts +3 -0
  52. package/cli/dist/commands/project.d.ts.map +1 -0
  53. package/cli/dist/commands/project.js +370 -0
  54. package/cli/dist/commands/project.js.map +1 -0
  55. package/cli/dist/commands/projects.d.ts +3 -0
  56. package/cli/dist/commands/projects.d.ts.map +1 -0
  57. package/cli/dist/commands/projects.js +71 -0
  58. package/cli/dist/commands/projects.js.map +1 -0
  59. package/cli/dist/commands/resource.d.ts +69 -0
  60. package/cli/dist/commands/resource.d.ts.map +1 -0
  61. package/cli/dist/commands/resource.js +579 -0
  62. package/cli/dist/commands/resource.js.map +1 -0
  63. package/cli/dist/commands/resources.d.ts +3 -0
  64. package/cli/dist/commands/resources.d.ts.map +1 -0
  65. package/cli/dist/commands/resources.js +73 -0
  66. package/cli/dist/commands/resources.js.map +1 -0
  67. package/cli/dist/commands/stack.d.ts +3 -0
  68. package/cli/dist/commands/stack.d.ts.map +1 -0
  69. package/cli/dist/commands/stack.js +97 -0
  70. package/cli/dist/commands/stack.js.map +1 -0
  71. package/cli/dist/commands/stacks.d.ts +3 -0
  72. package/cli/dist/commands/stacks.d.ts.map +1 -0
  73. package/cli/dist/commands/stacks.js +43 -0
  74. package/cli/dist/commands/stacks.js.map +1 -0
  75. package/cli/dist/commands/status.d.ts +3 -0
  76. package/cli/dist/commands/status.d.ts.map +1 -0
  77. package/cli/dist/commands/status.js +71 -0
  78. package/cli/dist/commands/status.js.map +1 -0
  79. package/cli/dist/commands/ui.d.ts +3 -0
  80. package/cli/dist/commands/ui.d.ts.map +1 -0
  81. package/cli/dist/commands/ui.js +445 -0
  82. package/cli/dist/commands/ui.js.map +1 -0
  83. package/cli/dist/commands/up.d.ts +3 -0
  84. package/cli/dist/commands/up.d.ts.map +1 -0
  85. package/cli/dist/commands/up.js +124 -0
  86. package/cli/dist/commands/up.js.map +1 -0
  87. package/cli/dist/commands/upgrade.d.ts +11 -0
  88. package/cli/dist/commands/upgrade.d.ts.map +1 -0
  89. package/cli/dist/commands/upgrade.js +496 -0
  90. package/cli/dist/commands/upgrade.js.map +1 -0
  91. package/cli/dist/commands/version.d.ts +3 -0
  92. package/cli/dist/commands/version.d.ts.map +1 -0
  93. package/cli/dist/commands/version.js +9 -0
  94. package/cli/dist/commands/version.js.map +1 -0
  95. package/cli/dist/components/Accessible.d.ts +8 -0
  96. package/cli/dist/components/Accessible.d.ts.map +1 -0
  97. package/cli/dist/components/Accessible.js +10 -0
  98. package/cli/dist/components/Accessible.js.map +1 -0
  99. package/cli/dist/components/BaseTooltip.d.ts +4 -0
  100. package/cli/dist/components/BaseTooltip.d.ts.map +1 -0
  101. package/cli/dist/components/BaseTooltip.js +28 -0
  102. package/cli/dist/components/BaseTooltip.js.map +1 -0
  103. package/cli/dist/components/DetailPanel.d.ts +4 -0
  104. package/cli/dist/components/DetailPanel.d.ts.map +1 -0
  105. package/cli/dist/components/DetailPanel.js +18 -0
  106. package/cli/dist/components/DetailPanel.js.map +1 -0
  107. package/cli/dist/components/FileTree.d.ts +4 -0
  108. package/cli/dist/components/FileTree.d.ts.map +1 -0
  109. package/cli/dist/components/FileTree.js +47 -0
  110. package/cli/dist/components/FileTree.js.map +1 -0
  111. package/cli/dist/components/ResourceSelectInput.d.ts +4 -0
  112. package/cli/dist/components/ResourceSelectInput.d.ts.map +1 -0
  113. package/cli/dist/components/ResourceSelectInput.js +7 -0
  114. package/cli/dist/components/ResourceSelectInput.js.map +1 -0
  115. package/cli/dist/components/ResourceTable.d.ts +4 -0
  116. package/cli/dist/components/ResourceTable.d.ts.map +1 -0
  117. package/cli/dist/components/ResourceTable.js +18 -0
  118. package/cli/dist/components/ResourceTable.js.map +1 -0
  119. package/cli/dist/components/TabBar.d.ts +4 -0
  120. package/cli/dist/components/TabBar.d.ts.map +1 -0
  121. package/cli/dist/components/TabBar.js +17 -0
  122. package/cli/dist/components/TabBar.js.map +1 -0
  123. package/cli/dist/components/Tooltip.d.ts +12 -0
  124. package/cli/dist/components/Tooltip.d.ts.map +1 -0
  125. package/cli/dist/components/Tooltip.js +12 -0
  126. package/cli/dist/components/Tooltip.js.map +1 -0
  127. package/cli/dist/components/index.d.ts +8 -0
  128. package/cli/dist/components/index.d.ts.map +1 -0
  129. package/cli/dist/components/index.js +8 -0
  130. package/cli/dist/components/index.js.map +1 -0
  131. package/cli/dist/config/platform-standards.d.ts +159 -0
  132. package/cli/dist/config/platform-standards.d.ts.map +1 -0
  133. package/cli/dist/config/platform-standards.js +181 -0
  134. package/cli/dist/config/platform-standards.js.map +1 -0
  135. package/cli/dist/generator/extension-fetch.d.ts +25 -0
  136. package/cli/dist/generator/extension-fetch.d.ts.map +1 -0
  137. package/cli/dist/generator/extension-fetch.js +259 -0
  138. package/cli/dist/generator/extension-fetch.js.map +1 -0
  139. package/cli/dist/generator/template-engine.d.ts +62 -0
  140. package/cli/dist/generator/template-engine.d.ts.map +1 -0
  141. package/cli/dist/generator/template-engine.js +623 -0
  142. package/cli/dist/generator/template-engine.js.map +1 -0
  143. package/cli/dist/index.d.ts +11 -0
  144. package/cli/dist/index.d.ts.map +1 -0
  145. package/cli/dist/index.js +11 -0
  146. package/cli/dist/index.js.map +1 -0
  147. package/cli/dist/types/index.d.ts +257 -0
  148. package/cli/dist/types/index.d.ts.map +1 -0
  149. package/cli/dist/types/index.js +17 -0
  150. package/cli/dist/types/index.js.map +1 -0
  151. package/cli/dist/utils/cache.d.ts +19 -0
  152. package/cli/dist/utils/cache.d.ts.map +1 -0
  153. package/cli/dist/utils/cache.js +64 -0
  154. package/cli/dist/utils/cache.js.map +1 -0
  155. package/cli/dist/utils/command-helpers.d.ts +17 -0
  156. package/cli/dist/utils/command-helpers.d.ts.map +1 -0
  157. package/cli/dist/utils/command-helpers.js +41 -0
  158. package/cli/dist/utils/command-helpers.js.map +1 -0
  159. package/cli/dist/utils/constants.d.ts +53 -0
  160. package/cli/dist/utils/constants.d.ts.map +1 -0
  161. package/cli/dist/utils/constants.js +115 -0
  162. package/cli/dist/utils/constants.js.map +1 -0
  163. package/cli/dist/utils/discovery-context.d.ts +4 -0
  164. package/cli/dist/utils/discovery-context.d.ts.map +1 -0
  165. package/cli/dist/utils/discovery-context.js +39 -0
  166. package/cli/dist/utils/discovery-context.js.map +1 -0
  167. package/cli/dist/utils/docker.d.ts +4 -0
  168. package/cli/dist/utils/docker.d.ts.map +1 -0
  169. package/cli/dist/utils/docker.js +19 -0
  170. package/cli/dist/utils/docker.js.map +1 -0
  171. package/cli/dist/utils/doctor-runtime.d.ts +81 -0
  172. package/cli/dist/utils/doctor-runtime.d.ts.map +1 -0
  173. package/cli/dist/utils/doctor-runtime.js +498 -0
  174. package/cli/dist/utils/doctor-runtime.js.map +1 -0
  175. package/cli/dist/utils/env-validator.d.ts +15 -0
  176. package/cli/dist/utils/env-validator.d.ts.map +1 -0
  177. package/cli/dist/utils/env-validator.js +109 -0
  178. package/cli/dist/utils/env-validator.js.map +1 -0
  179. package/cli/dist/utils/errors.d.ts +31 -0
  180. package/cli/dist/utils/errors.d.ts.map +1 -0
  181. package/cli/dist/utils/errors.js +140 -0
  182. package/cli/dist/utils/errors.js.map +1 -0
  183. package/cli/dist/utils/file-helpers.d.ts +14 -0
  184. package/cli/dist/utils/file-helpers.d.ts.map +1 -0
  185. package/cli/dist/utils/file-helpers.js +45 -0
  186. package/cli/dist/utils/file-helpers.js.map +1 -0
  187. package/cli/dist/utils/formatting.d.ts +30 -0
  188. package/cli/dist/utils/formatting.d.ts.map +1 -0
  189. package/cli/dist/utils/formatting.js +164 -0
  190. package/cli/dist/utils/formatting.js.map +1 -0
  191. package/cli/dist/utils/paths.d.ts +3 -0
  192. package/cli/dist/utils/paths.d.ts.map +1 -0
  193. package/cli/dist/utils/paths.js +41 -0
  194. package/cli/dist/utils/paths.js.map +1 -0
  195. package/cli/dist/utils/port-assignment.d.ts +6 -0
  196. package/cli/dist/utils/port-assignment.d.ts.map +1 -0
  197. package/cli/dist/utils/port-assignment.js +81 -0
  198. package/cli/dist/utils/port-assignment.js.map +1 -0
  199. package/cli/dist/utils/project-features.d.ts +23 -0
  200. package/cli/dist/utils/project-features.d.ts.map +1 -0
  201. package/cli/dist/utils/project-features.js +98 -0
  202. package/cli/dist/utils/project-features.js.map +1 -0
  203. package/cli/dist/utils/project-templates.d.ts +7 -0
  204. package/cli/dist/utils/project-templates.d.ts.map +1 -0
  205. package/cli/dist/utils/project-templates.js +24 -0
  206. package/cli/dist/utils/project-templates.js.map +1 -0
  207. package/cli/dist/utils/prompt.d.ts +28 -0
  208. package/cli/dist/utils/prompt.d.ts.map +1 -0
  209. package/cli/dist/utils/prompt.js +79 -0
  210. package/cli/dist/utils/prompt.js.map +1 -0
  211. package/cli/dist/utils/resource-features.d.ts +29 -0
  212. package/cli/dist/utils/resource-features.d.ts.map +1 -0
  213. package/cli/dist/utils/resource-features.js +175 -0
  214. package/cli/dist/utils/resource-features.js.map +1 -0
  215. package/cli/dist/utils/service-urls.d.ts +30 -0
  216. package/cli/dist/utils/service-urls.d.ts.map +1 -0
  217. package/cli/dist/utils/service-urls.js +93 -0
  218. package/cli/dist/utils/service-urls.js.map +1 -0
  219. package/cli/dist/utils/services.d.ts +18 -0
  220. package/cli/dist/utils/services.d.ts.map +1 -0
  221. package/cli/dist/utils/services.js +292 -0
  222. package/cli/dist/utils/services.js.map +1 -0
  223. package/cli/dist/utils/stack-features.d.ts +22 -0
  224. package/cli/dist/utils/stack-features.d.ts.map +1 -0
  225. package/cli/dist/utils/stack-features.js +13 -0
  226. package/cli/dist/utils/stack-features.js.map +1 -0
  227. package/cli/dist/utils/tar.d.ts +7 -0
  228. package/cli/dist/utils/tar.d.ts.map +1 -0
  229. package/cli/dist/utils/tar.js +21 -0
  230. package/cli/dist/utils/tar.js.map +1 -0
  231. package/cli/dist/utils/tilt.d.ts +20 -0
  232. package/cli/dist/utils/tilt.d.ts.map +1 -0
  233. package/cli/dist/utils/tilt.js +95 -0
  234. package/cli/dist/utils/tilt.js.map +1 -0
  235. package/cli/dist/utils/validation.d.ts +16 -0
  236. package/cli/dist/utils/validation.d.ts.map +1 -0
  237. package/cli/dist/utils/validation.js +58 -0
  238. package/cli/dist/utils/validation.js.map +1 -0
  239. package/cli/package.json +11 -12
  240. package/cli/src/commands/__tests__/always-enabled-infra.test.ts +2 -8
  241. package/cli/src/commands/__tests__/doctor.test.ts +181 -8
  242. package/cli/src/commands/__tests__/phases-model.test.ts +2 -4
  243. package/cli/src/commands/__tests__/project-resource-features-e2e.test.ts +4 -24
  244. package/cli/src/commands/__tests__/saas-starter-integration.test.ts +8 -10
  245. package/cli/src/commands/__tests__/stack-features-e2e.test.ts +10 -4
  246. package/cli/src/commands/__tests__/upgrade.test.ts +23 -13
  247. package/cli/src/commands/doctor.ts +136 -0
  248. package/cli/src/commands/help.ts +13 -3
  249. package/cli/src/commands/project.ts +11 -14
  250. package/cli/src/commands/projects.ts +2 -2
  251. package/cli/src/commands/up.ts +13 -4
  252. package/cli/src/commands/upgrade.ts +11 -4
  253. package/cli/src/config/platform-standards.test.ts +6 -6
  254. package/cli/src/generator/__tests__/extension-fetch.test.ts +36 -14
  255. package/cli/src/generator/extension-fetch.ts +12 -5
  256. package/cli/src/generator/template-engine.ts +19 -21
  257. package/cli/src/types/index.ts +2 -0
  258. package/cli/src/utils/__tests__/tilt.test.ts +29 -0
  259. package/cli/src/utils/constants.ts +4 -0
  260. package/cli/src/utils/doctor-runtime.ts +67 -27
  261. package/cli/src/utils/env-validator.ts +2 -2
  262. package/cli/src/utils/project-templates.ts +2 -1
  263. package/cli/src/utils/prompt.ts +2 -4
  264. package/cli/src/utils/resource-features.ts +10 -4
  265. package/cli/src/utils/services.ts +2 -1
  266. package/cli/src/utils/tilt.ts +20 -0
  267. package/engine/assets/docker/wake-gateway/Dockerfile +26 -0
  268. package/engine/assets/docker/wake-gateway/decide.test.ts +87 -0
  269. package/engine/assets/docker/wake-gateway/decide.ts +75 -0
  270. package/engine/assets/docker/wake-gateway/server.ts +220 -0
  271. package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +13 -23
  272. package/engine/topologies/platform/docker/compose/compose.star +7 -14
  273. package/engine/topologies/platform/docker/compose/compose_helpers.star +1 -1
  274. package/engine/topologies/platform/docker/compose/traefik_standalone.star +44 -6
  275. package/engine/topologies/platform/docker/config/healthcheck.star +56 -0
  276. package/engine/topologies/platform/docker/constants.star +10 -0
  277. package/engine/topologies/platform/docker/layers/l1_base_layers.star +1 -1
  278. package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +55 -20
  279. package/engine/topologies/platform/docker/networking/traefik_static_routes.star +107 -0
  280. package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +2 -2
  281. package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +1 -1
  282. package/engine/topologies/tilt/manifest/schema.star +6 -0
  283. package/engine/topologies/tilt/manifest/validator.star +16 -0
  284. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +107 -8
  285. package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +12 -6
  286. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +2 -1
  287. package/knip.json +1 -1
  288. package/openspec/changes/optimize-runtime-docker-layers/.openspec.yaml +3 -0
  289. package/openspec/changes/optimize-runtime-docker-layers/design.md +81 -0
  290. package/openspec/changes/optimize-runtime-docker-layers/proposal.md +44 -0
  291. package/openspec/changes/optimize-runtime-docker-layers/specs/runtime-container-footprint/spec.md +79 -0
  292. package/openspec/changes/optimize-runtime-docker-layers/tasks.md +36 -0
  293. package/openspec/changes/prioritized-cold-start/.openspec.yaml +4 -0
  294. package/openspec/changes/prioritized-cold-start/design.md +113 -0
  295. package/openspec/changes/prioritized-cold-start/proposal.md +40 -0
  296. package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +92 -0
  297. package/openspec/changes/prioritized-cold-start/tasks.md +46 -0
  298. package/openspec/config.yaml +1 -0
  299. package/package.json +35 -5
  300. package/scripts/autoresearch/layer-eval.sh +65 -0
  301. package/scripts/benchmark/README.md +25 -0
  302. package/scripts/benchmark/container-scale.ts +503 -0
  303. package/scripts/benchmark/pre-release-gate.sh +9 -0
  304. package/scripts/measure-idle-footprint.sh +58 -0
  305. package/tests/tilt-engine/test_runtime_container_footprint.py +162 -0
  306. package/tests/tilt-engine/test_sablier_defer_start.py +196 -0
  307. package/tests/tilt-engine/test_standalone_traefik.py +38 -0
  308. package/tests/tilt-engine/test_traefik_static_wake_route.py +119 -0
  309. package/bun.lock +0 -538
@@ -0,0 +1,745 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, join, normalize } from "node:path";
4
+ import chalk from "chalk";
5
+ import { Command } from "commander";
6
+ import { MASTER_CONFIG_FILES, QUICKSTART_DOCS_URL, REQUIRED_PACKAGE_SCRIPTS, } from "../utils/constants.js";
7
+ import { checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, } from "../utils/doctor-runtime.js";
8
+ import { validateEnvFile } from "../utils/env-validator.js";
9
+ import { formatCount } from "../utils/formatting.js";
10
+ import { findProjectRoot } from "../utils/paths.js";
11
+ import { buildHealthTargets, pingHealthTargets } from "../utils/service-urls.js";
12
+ import { discoverResourcesFromRoot } from "../utils/services.js";
13
+ export { checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, summarizeTiltBuildError, } from "../utils/doctor-runtime.js";
14
+ // A wedged Docker daemon makes `docker ps` block forever instead of failing,
15
+ // and doctor is exactly the tool people run when their environment is broken.
16
+ const EXEC_TIMEOUT_MS = 10_000;
17
+ function isTimeout(err) {
18
+ return err?.code === "ETIMEDOUT";
19
+ }
20
+ function createExecCheck(name, command, successMessage, failureMessage, fixInstructions) {
21
+ return () => {
22
+ try {
23
+ execSync(command, { stdio: "pipe", timeout: EXEC_TIMEOUT_MS });
24
+ return {
25
+ name,
26
+ didPass: true,
27
+ message: successMessage,
28
+ };
29
+ }
30
+ catch {
31
+ // Error details not needed - failure message tells user what to fix
32
+ return {
33
+ name,
34
+ didPass: false,
35
+ message: failureMessage,
36
+ fix: fixInstructions,
37
+ };
38
+ }
39
+ };
40
+ }
41
+ function checkDockerRuntime() {
42
+ // Check for Docker
43
+ try {
44
+ execSync("docker ps", { stdio: "pipe", timeout: EXEC_TIMEOUT_MS });
45
+ return {
46
+ name: "Container Runtime",
47
+ didPass: true,
48
+ message: "Docker daemon is running",
49
+ };
50
+ }
51
+ catch (err) {
52
+ if (isTimeout(err)) {
53
+ return {
54
+ name: "Container Runtime",
55
+ didPass: false,
56
+ message: `Docker daemon is not responding (\`docker ps\` hung for ${EXEC_TIMEOUT_MS / 1000}s)`,
57
+ fix: "Restart the runtime: quit and reopen Docker Desktop, or run `colima restart`",
58
+ };
59
+ }
60
+ // Docker not running, check for Colima
61
+ try {
62
+ execSync("colima status", { stdio: "pipe", timeout: EXEC_TIMEOUT_MS });
63
+ // Colima is running
64
+ return {
65
+ name: "Container Runtime",
66
+ didPass: true,
67
+ message: "Colima (Docker runtime) is running",
68
+ };
69
+ }
70
+ catch {
71
+ // Check if Colima is installed but not running
72
+ try {
73
+ execSync("which colima", { stdio: "pipe", timeout: EXEC_TIMEOUT_MS });
74
+ return {
75
+ name: "Container Runtime",
76
+ didPass: false,
77
+ message: "Colima is installed but not running",
78
+ fix: "Start Colima: colima start",
79
+ };
80
+ }
81
+ catch {
82
+ // Check for Podman
83
+ try {
84
+ execSync("podman ps", { stdio: "pipe", timeout: EXEC_TIMEOUT_MS });
85
+ return {
86
+ name: "Container Runtime",
87
+ didPass: true,
88
+ message: "Podman is running",
89
+ };
90
+ }
91
+ catch {
92
+ // No container runtime found
93
+ return {
94
+ name: "Container Runtime",
95
+ didPass: false,
96
+ message: "No container runtime (Docker/Colima/Podman) is running",
97
+ fix: `Start: colima start (recommended) OR open -a Docker (macOS) OR sudo systemctl start docker (Linux). Setup guide: ${QUICKSTART_DOCS_URL}`,
98
+ };
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ const checkDockerCompose = createExecCheck("Docker Compose", "docker compose version", "Docker Compose plugin available", "Docker Compose plugin not found", "Install Docker Compose: https://docs.docker.com/compose/install/");
105
+ // Generated healthchecks use `start_interval`, which older engines/compose reject.
106
+ const MIN_DOCKER_ENGINE_VERSION = [25, 0, 0];
107
+ const MIN_DOCKER_COMPOSE_VERSION = [2, 20, 2];
108
+ function parseVersion(raw) {
109
+ const match = raw.trim().match(/(\d+)\.(\d+)(?:\.(\d+))?/);
110
+ if (!match) {
111
+ return null;
112
+ }
113
+ return [Number(match[1]), Number(match[2]), Number(match[3] ?? 0)];
114
+ }
115
+ function isAtLeast(version, minimum) {
116
+ for (const [index, required] of minimum.entries()) {
117
+ const actual = version[index] ?? 0;
118
+ if (actual !== required) {
119
+ return actual > required;
120
+ }
121
+ }
122
+ return true;
123
+ }
124
+ export function checkDockerVersions(exec = execSync) {
125
+ const run = (command) => String(exec(command, { stdio: "pipe", encoding: "utf-8", timeout: EXEC_TIMEOUT_MS })).trim();
126
+ let engineRaw;
127
+ let composeRaw;
128
+ try {
129
+ engineRaw = run("docker version --format '{{.Server.Version}}'");
130
+ composeRaw = run("docker compose version --short");
131
+ }
132
+ catch {
133
+ return {
134
+ name: "Docker Versions",
135
+ didPass: true,
136
+ isSkipped: true,
137
+ message: "Could not read Docker Engine/Compose versions - skipped version check",
138
+ };
139
+ }
140
+ const engine = parseVersion(engineRaw);
141
+ const compose = parseVersion(composeRaw);
142
+ if (!engine || !compose) {
143
+ return {
144
+ name: "Docker Versions",
145
+ didPass: true,
146
+ isSkipped: true,
147
+ message: `Unrecognized Docker version output (engine "${engineRaw}", compose "${composeRaw}") - skipped version check`,
148
+ };
149
+ }
150
+ const problems = [];
151
+ if (!isAtLeast(engine, MIN_DOCKER_ENGINE_VERSION)) {
152
+ problems.push(`Docker Engine ${engineRaw} (need 25.0+)`);
153
+ }
154
+ if (!isAtLeast(compose, MIN_DOCKER_COMPOSE_VERSION)) {
155
+ problems.push(`Docker Compose ${composeRaw} (need 2.20.2+)`);
156
+ }
157
+ if (problems.length === 0) {
158
+ return {
159
+ name: "Docker Versions",
160
+ didPass: true,
161
+ message: `Docker Engine ${engineRaw} and Compose ${composeRaw} support generated healthchecks`,
162
+ };
163
+ }
164
+ return {
165
+ name: "Docker Versions",
166
+ didPass: false,
167
+ message: `Docker is too old for generated healthchecks (they use start_interval):\n ${problems.join("\n ")}`,
168
+ fix: "Update Docker Desktop, or on Linux update docker-ce and the docker-compose-plugin package",
169
+ };
170
+ }
171
+ const checkTilt = createExecCheck("Tilt CLI", "tilt version", "Tilt CLI installed", "Tilt CLI not found", `Install Tilt: brew install tilt (macOS) or see https://docs.tilt.dev/install.html. Setup guide: ${QUICKSTART_DOCS_URL}`);
172
+ function checkEnvironmentVariables() {
173
+ const projectRoot = findProjectRoot() ?? process.cwd();
174
+ const validation = validateEnvFile(projectRoot);
175
+ if (validation.missing.length > 0) {
176
+ return {
177
+ name: "Environment Variables",
178
+ didPass: false,
179
+ message: `Missing required env variables: ${validation.missing.join(", ")}`,
180
+ fix: `Edit .env and set these values:\n ${validation.missing.map((v) => `${v}=<value>`).join("\n ")}`,
181
+ };
182
+ }
183
+ if (validation.invalid.length > 0) {
184
+ return {
185
+ name: "Environment Variables",
186
+ didPass: false,
187
+ message: `Invalid env variables: ${validation.invalid.join(", ")}`,
188
+ fix: "Edit .env and provide values for empty variables",
189
+ };
190
+ }
191
+ return {
192
+ name: "Environment Variables",
193
+ didPass: true,
194
+ message: "All required environment variables are set",
195
+ };
196
+ }
197
+ function checkMasterConfigs() {
198
+ // `tdk project` writes generated master configs to .tdk/.tdk-out/, not the
199
+ // project root, so look there (and walk up to find the project root, same
200
+ // as every other command that reads project state).
201
+ const projectRoot = findProjectRoot() ?? process.cwd();
202
+ const outDir = join(projectRoot, ".tdk", ".tdk-out");
203
+ const missing = MASTER_CONFIG_FILES.filter((file) => !existsSync(join(outDir, file)));
204
+ if (missing.length === 0) {
205
+ return {
206
+ name: "Master Configs",
207
+ didPass: true,
208
+ message: `${MASTER_CONFIG_FILES.join(", ")} found`,
209
+ };
210
+ }
211
+ return {
212
+ name: "Master Configs",
213
+ didPass: false,
214
+ message: `Master configs missing: ${missing.join(", ")}`,
215
+ fix: "Run: tdk project",
216
+ };
217
+ }
218
+ const REQUIRED_DOCKER_TEMPLATE_FILES = [
219
+ "install-deps.sh",
220
+ "bun-hoisted-symlink-fix.sh",
221
+ "prisma-bun-client-link-fix.sh",
222
+ "prisma-normalize-client.sh",
223
+ "prisma-runtime-relink.sh",
224
+ "infisical-entrypoint.sh",
225
+ "migrate.sh",
226
+ ];
227
+ export function checkGeneratedProjectRuntimeAssets() {
228
+ const projectRoot = findProjectRoot() ?? process.cwd();
229
+ const problems = [];
230
+ if (!existsSync(join(projectRoot, "package.json"))) {
231
+ problems.push("package.json (workspace root manifest)");
232
+ }
233
+ const templateDir = join(projectRoot, "shared-platform-engineering", "docker-templates");
234
+ for (const file of REQUIRED_DOCKER_TEMPLATE_FILES) {
235
+ if (!existsSync(join(templateDir, file))) {
236
+ problems.push(`shared-platform-engineering/docker-templates/${file}`);
237
+ }
238
+ }
239
+ if (problems.length === 0) {
240
+ return {
241
+ name: "Generated Runtime Assets",
242
+ didPass: true,
243
+ message: "Generated Docker runtime assets are present",
244
+ };
245
+ }
246
+ return {
247
+ name: "Generated Runtime Assets",
248
+ didPass: false,
249
+ message: `Generated Dockerfiles reference missing project runtime assets:\n ${problems.join("\n ")}`,
250
+ fix: "Run `tdk project --yes` (or `tdk config regenerate`) with an updated TDK. `tdk up` also refreshes these assets before starting Tilt.",
251
+ };
252
+ }
253
+ function findPrivateStarlarkLoadExports(content) {
254
+ const privateExports = new Set();
255
+ const loadCallPattern = /^load\s*\(([\s\S]*?)\)/gm;
256
+ for (const loadMatch of content.matchAll(loadCallPattern)) {
257
+ const args = loadMatch[1] ?? "";
258
+ const quotedValuePattern = /["']([^"']+)["']/g;
259
+ const quotedValues = Array.from(args.matchAll(quotedValuePattern), (match) => match[1]);
260
+ // First quoted arg is the module path. Later quoted args are exported names
261
+ // or alias targets, and Starlark refuses to export names beginning with "_".
262
+ for (const value of quotedValues.slice(1)) {
263
+ if (value.startsWith("_")) {
264
+ privateExports.add(value);
265
+ }
266
+ }
267
+ }
268
+ return Array.from(privateExports).sort();
269
+ }
270
+ function resolveStarlarkLoadTarget(file, modulePath, projectRoot) {
271
+ if (modulePath === "ext://tdk-cli") {
272
+ return join(projectRoot, ".tdk", ".tdk-out", "tdk-cli-ext", "Tiltfile");
273
+ }
274
+ if (modulePath.startsWith(".") || (!modulePath.includes("://") && !modulePath.startsWith("@"))) {
275
+ return normalize(join(dirname(file), modulePath));
276
+ }
277
+ return null;
278
+ }
279
+ function findMissingRelativeStarlarkLoads(file, content, projectRoot) {
280
+ const missing = [];
281
+ const loadCallPattern = /^load\s*\(([\s\S]*?)\)/gm;
282
+ for (const loadMatch of content.matchAll(loadCallPattern)) {
283
+ const args = loadMatch[1] ?? "";
284
+ const moduleMatch = args.match(/["']([^"']+)["']/);
285
+ const modulePath = moduleMatch?.[1];
286
+ if (!modulePath) {
287
+ continue;
288
+ }
289
+ const resolved = resolveStarlarkLoadTarget(file, modulePath, projectRoot);
290
+ if (!resolved) {
291
+ continue;
292
+ }
293
+ if (!existsSync(resolved)) {
294
+ missing.push(`${file}: ${modulePath} -> ${resolved}`);
295
+ }
296
+ }
297
+ return missing;
298
+ }
299
+ function findRelativeStarlarkLoadTargets(file, content, projectRoot) {
300
+ const targets = [];
301
+ const loadCallPattern = /^load\s*\(([\s\S]*?)\)/gm;
302
+ for (const loadMatch of content.matchAll(loadCallPattern)) {
303
+ const args = loadMatch[1] ?? "";
304
+ const moduleMatch = args.match(/["']([^"']+)["']/);
305
+ const modulePath = moduleMatch?.[1];
306
+ if (modulePath) {
307
+ const target = resolveStarlarkLoadTarget(file, modulePath, projectRoot);
308
+ if (target) {
309
+ targets.push(target);
310
+ }
311
+ }
312
+ }
313
+ return targets;
314
+ }
315
+ function collectReachableStarlarkFiles(entryFile, projectRoot) {
316
+ const visited = new Set();
317
+ const pending = [entryFile];
318
+ while (pending.length > 0) {
319
+ const file = pending.pop();
320
+ if (!file || visited.has(file) || !existsSync(file)) {
321
+ continue;
322
+ }
323
+ visited.add(file);
324
+ const content = readFileSync(file, "utf-8");
325
+ for (const target of findRelativeStarlarkLoadTargets(file, content, projectRoot)) {
326
+ if (!visited.has(target)) {
327
+ pending.push(target);
328
+ }
329
+ }
330
+ }
331
+ return Array.from(visited).sort();
332
+ }
333
+ export function checkStarlarkLoadExports() {
334
+ const projectRoot = findProjectRoot() ?? process.cwd();
335
+ const entryFile = join(projectRoot, ".tdk", ".tdk-out", "Tiltfile");
336
+ if (!existsSync(entryFile)) {
337
+ return {
338
+ name: "Starlark Load Exports",
339
+ didPass: true,
340
+ isSkipped: true,
341
+ message: "No generated Tiltfile to check",
342
+ };
343
+ }
344
+ const privateExportProblems = [];
345
+ const missingLoadProblems = [];
346
+ const reachableFiles = collectReachableStarlarkFiles(entryFile, projectRoot);
347
+ for (const file of reachableFiles) {
348
+ const content = readFileSync(file, "utf-8");
349
+ const privateExports = findPrivateStarlarkLoadExports(content);
350
+ if (privateExports.length > 0) {
351
+ privateExportProblems.push(`${file}: ${privateExports.join(", ")}`);
352
+ }
353
+ for (const missingLoad of findMissingRelativeStarlarkLoads(file, content, projectRoot)) {
354
+ missingLoadProblems.push(missingLoad);
355
+ }
356
+ }
357
+ if (privateExportProblems.length === 0 && missingLoadProblems.length === 0) {
358
+ const fileCount = formatCount(reachableFiles.length, "reachable Starlark file");
359
+ return {
360
+ name: "Starlark Load Exports",
361
+ didPass: true,
362
+ message: `${fileCount} ${reachableFiles.length === 1 ? "has" : "have"} valid load exports and paths`,
363
+ };
364
+ }
365
+ const sections = [];
366
+ if (privateExportProblems.length > 0) {
367
+ sections.push(`Private exported symbols:\n ${privateExportProblems.join("\n ")}`);
368
+ }
369
+ if (missingLoadProblems.length > 0) {
370
+ sections.push(`Missing relative load targets:\n ${missingLoadProblems.join("\n ")}`);
371
+ }
372
+ return {
373
+ name: "Starlark Load Exports",
374
+ didPass: false,
375
+ message: `Starlark load() issues will stop Tilt before services start:\n ${sections.join("\n\n ")}`,
376
+ fix: "Regenerate with a current TDK (`tdk config regenerate`). If the issue remains, rename loaded symbols without leading underscores and update stale load paths.",
377
+ };
378
+ }
379
+ function checkStartupScripts() {
380
+ const projectRoot = findProjectRoot() ?? process.cwd();
381
+ const resources = discoverResourcesFromRoot(projectRoot);
382
+ const problems = [];
383
+ for (const resource of resources) {
384
+ const packageJsonPath = join(resource.path, "package.json");
385
+ if (!existsSync(packageJsonPath)) {
386
+ continue;
387
+ }
388
+ const appType = resource.config?.appType ?? "backend";
389
+ const requiredScripts = REQUIRED_PACKAGE_SCRIPTS[appType] ?? REQUIRED_PACKAGE_SCRIPTS.backend;
390
+ let scripts;
391
+ try {
392
+ scripts = JSON.parse(readFileSync(packageJsonPath, "utf-8")).scripts ?? {};
393
+ }
394
+ catch {
395
+ problems.push(`${resource.name} (package.json is invalid JSON)`);
396
+ continue;
397
+ }
398
+ const missing = requiredScripts.filter((script) => !scripts[script]);
399
+ if (missing.length > 0) {
400
+ problems.push(`${resource.name}: missing "${missing.join('", "')}"`);
401
+ }
402
+ }
403
+ if (problems.length === 0) {
404
+ return {
405
+ name: "Startup Scripts",
406
+ didPass: true,
407
+ message: "All services have required package.json scripts",
408
+ };
409
+ }
410
+ return {
411
+ name: "Startup Scripts",
412
+ didPass: false,
413
+ message: `Services missing required package.json scripts:\n ${problems.join("\n ")}`,
414
+ fix: 'Add the missing scripts to each service\'s package.json, e.g.:\n "dev": "bun run src/index.ts",\n "build": "tsc",\n "start": "bun run dist/index.js"',
415
+ };
416
+ }
417
+ /**
418
+ * Backend/frontend tsconfig generators set `"types": ["bun"]` on the
419
+ * assumption that `@types/bun` is a direct devDependency (`tdk resource`
420
+ * adds it for new services). Nothing re-adds it if a service's package.json
421
+ * loses that entry -- e.g. a checkout missing it entirely, or a manual edit
422
+ * -- and the failure only surfaces later as `tsc: TS2688: Cannot find type
423
+ * definition file for 'bun'` inside `bun run build`, mid Docker image build.
424
+ */
425
+ export function checkTypeScriptTypeDependencies() {
426
+ const projectRoot = findProjectRoot() ?? process.cwd();
427
+ const resources = discoverResourcesFromRoot(projectRoot);
428
+ const problems = [];
429
+ for (const resource of resources) {
430
+ const packageJsonPath = join(resource.path, "package.json");
431
+ const dockerTsconfigPath = join(resource.path, ".autogenerated", "tsconfig.docker.autogenerated.json");
432
+ if (!existsSync(packageJsonPath) || !existsSync(dockerTsconfigPath)) {
433
+ continue;
434
+ }
435
+ const tsconfig = readJsonFile(dockerTsconfigPath);
436
+ const compilerOptions = tsconfig?.compilerOptions;
437
+ const types = compilerOptions?.types;
438
+ if (!Array.isArray(types) || !types.includes("bun")) {
439
+ continue;
440
+ }
441
+ const pkg = readJsonFile(packageJsonPath);
442
+ const devDependencies = pkg?.devDependencies ?? {};
443
+ if (!devDependencies["@types/bun"]) {
444
+ problems.push(resource.name);
445
+ }
446
+ }
447
+ if (problems.length === 0) {
448
+ return {
449
+ name: "TypeScript Type Dependencies",
450
+ didPass: true,
451
+ message: "All services with a bun-typed tsconfig have @types/bun installed",
452
+ };
453
+ }
454
+ return {
455
+ name: "TypeScript Type Dependencies",
456
+ didPass: false,
457
+ message: `Services set "types": ["bun"] in their Docker tsconfig but are missing the "@types/bun" devDependency, so \`bun run build\` fails with TS2688:\n ${problems.join("\n ")}`,
458
+ fix: 'Add to each service\'s package.json devDependencies: "@types/bun": "^1.4.2"',
459
+ };
460
+ }
461
+ function readJsonFile(path) {
462
+ try {
463
+ return JSON.parse(readFileSync(path, "utf-8"));
464
+ }
465
+ catch {
466
+ return null;
467
+ }
468
+ }
469
+ function hasSyntheticDefaultImports(tsconfigPath) {
470
+ const config = readJsonFile(tsconfigPath);
471
+ const compilerOptions = config?.compilerOptions;
472
+ return (compilerOptions?.allowSyntheticDefaultImports === true ||
473
+ compilerOptions?.esModuleInterop === true);
474
+ }
475
+ export function checkFrontendDockerPreflight() {
476
+ const projectRoot = findProjectRoot() ?? process.cwd();
477
+ const resources = discoverResourcesFromRoot(projectRoot).filter((resource) => resource.config?.appType === "frontend");
478
+ if (resources.length === 0) {
479
+ return {
480
+ name: "Frontend Docker Preflight",
481
+ didPass: true,
482
+ isSkipped: true,
483
+ message: "No frontend services to check",
484
+ };
485
+ }
486
+ const problems = [];
487
+ for (const resource of resources) {
488
+ const dockerfilePath = join(resource.path, ".autogenerated", "Dockerfile.app.autogenerated");
489
+ const dockerTsconfigPath = join(resource.path, ".autogenerated", "tsconfig.docker.autogenerated.json");
490
+ if (existsSync(dockerfilePath)) {
491
+ const dockerfile = readFileSync(dockerfilePath, "utf-8");
492
+ const exposesNginxPort = /\bEXPOSE\s+80\b/.test(dockerfile);
493
+ const stack = resource.config?.stack;
494
+ const composePath = stack
495
+ ? join(projectRoot, "services", stack, ".autogenerated", "docker-compose.app.autogenerated.yml")
496
+ : "";
497
+ if (exposesNginxPort && composePath && existsSync(composePath)) {
498
+ const compose = readFileSync(composePath, "utf-8");
499
+ const escapedName = resource.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
500
+ const portPattern = new RegExp(`traefik\\.http\\.services\\.${escapedName}\\.loadbalancer\\.server\\.port=(\\d+)`);
501
+ const match = compose.match(portPattern);
502
+ const routedPort = match?.[1];
503
+ if (routedPort && routedPort !== "80") {
504
+ problems.push(`${resource.name}: nginx runtime exposes 80, but generated Traefik routes to ${routedPort}`);
505
+ }
506
+ }
507
+ }
508
+ if (existsSync(dockerTsconfigPath) && !hasSyntheticDefaultImports(dockerTsconfigPath)) {
509
+ const mainPath = join(resource.path, "src", "main.tsx");
510
+ if (existsSync(mainPath)) {
511
+ const main = readFileSync(mainPath, "utf-8");
512
+ const defaultImports = [
513
+ /import\s+\w+\s+from\s+["']react["']/,
514
+ /import\s+\w+\s+from\s+["']react-dom\/client["']/,
515
+ ]
516
+ .filter((pattern) => pattern.test(main))
517
+ .map((pattern) => (pattern.source.includes("react-dom") ? "react-dom/client" : "react"));
518
+ if (defaultImports.length > 0) {
519
+ problems.push(`${resource.name}: Docker tsconfig does not allow synthetic default imports, but src/main.tsx default-imports ${defaultImports.join(" and ")}`);
520
+ }
521
+ }
522
+ }
523
+ }
524
+ if (problems.length === 0) {
525
+ const frontendCount = formatCount(resources.length, "frontend");
526
+ return {
527
+ name: "Frontend Docker Preflight",
528
+ didPass: true,
529
+ message: `${frontendCount} ${resources.length === 1 ? "has" : "have"} compatible Docker build/routing metadata`,
530
+ };
531
+ }
532
+ return {
533
+ name: "Frontend Docker Preflight",
534
+ didPass: false,
535
+ message: `Frontend Docker metadata has issues that will make \`tdk up\` fail or route 404:\n ${problems.join("\n ")}`,
536
+ fix: 'Regenerate with a current TDK (`tdk config regenerate`) and ensure frontend nginx runtimes route to port 80. For React entrypoints, prefer named imports: `import { StrictMode } from "react"` and `import { createRoot } from "react-dom/client"`.',
537
+ };
538
+ }
539
+ const MEM_NEAR_LIMIT_PCT = 90;
540
+ const CPU_HANGING_PCT = 80;
541
+ function parseDockerStats(raw) {
542
+ return raw
543
+ .split("\n")
544
+ .map((line) => line.trim())
545
+ .filter(Boolean)
546
+ .map((line) => {
547
+ const [name, memPctRaw, cpuPctRaw] = line.split(",");
548
+ return {
549
+ name: name ?? "unknown",
550
+ memPct: Number.parseFloat((memPctRaw ?? "").replace("%", "")),
551
+ cpuPct: Number.parseFloat((cpuPctRaw ?? "").replace("%", "")),
552
+ };
553
+ })
554
+ .filter((stat) => Number.isFinite(stat.memPct) && Number.isFinite(stat.cpuPct));
555
+ }
556
+ /**
557
+ * Flags containers pinned near their memory limit while also burning CPU --
558
+ * the signature of a boot-time process stuck in a retry/fetch loop, not a
559
+ * genuine crash (which would just exit, not sit at ~100% resource usage).
560
+ *
561
+ * Caught in this workspace: a backend runtime image missing the local Prisma
562
+ * CLI fell back to `bunx prisma`, which re-fetched prisma@latest from npm on
563
+ * every container start. That pinned a 512MB container at ~100% memory and
564
+ * 100%+ CPU indefinitely, and it never became healthy. Neither `docker ps`
565
+ * (just shows "unhealthy") nor the healthcheck's own retry loop surfaces
566
+ * *why* -- only live resource stats catch this pattern before it's mistaken
567
+ * for a slow migration or a flaky healthcheck.
568
+ */
569
+ function checkContainerResourceHealth() {
570
+ let raw;
571
+ try {
572
+ raw = execSync('docker stats --no-stream --format "{{.Name}},{{.MemPerc}},{{.CPUPerc}}"', {
573
+ stdio: "pipe",
574
+ encoding: "utf-8",
575
+ timeout: EXEC_TIMEOUT_MS,
576
+ });
577
+ }
578
+ catch (err) {
579
+ return {
580
+ name: "Container Resource Health",
581
+ didPass: true,
582
+ isSkipped: true,
583
+ message: isTimeout(err)
584
+ ? "Docker not responding - skipped container resource check"
585
+ : "Docker not running - skipped container resource check",
586
+ };
587
+ }
588
+ const stats = parseDockerStats(raw);
589
+ if (stats.length === 0) {
590
+ return {
591
+ name: "Container Resource Health",
592
+ didPass: true,
593
+ isSkipped: true,
594
+ message: "No running containers to check",
595
+ };
596
+ }
597
+ const hangers = stats.filter((stat) => stat.memPct >= MEM_NEAR_LIMIT_PCT && stat.cpuPct >= CPU_HANGING_PCT);
598
+ if (hangers.length === 0) {
599
+ return {
600
+ name: "Container Resource Health",
601
+ didPass: true,
602
+ message: `${formatCount(stats.length, "container")} within normal resource usage`,
603
+ };
604
+ }
605
+ const details = hangers
606
+ .map((stat) => `${stat.name} (mem ${stat.memPct.toFixed(0)}%, cpu ${stat.cpuPct.toFixed(0)}%)`)
607
+ .join("\n ");
608
+ return {
609
+ name: "Container Resource Health",
610
+ didPass: false,
611
+ message: `${formatCount(hangers.length, "container")} pinned near its memory limit while burning CPU -- likely stuck in a boot-time retry/fetch loop, not a normal crash:\n ${details}`,
612
+ fix: "Check the container's boot logs for a network fetch loop (e.g. `bunx <pkg>` re-downloading a CLI on every start instead of using one already installed): docker logs <container>. If a Prisma auto-migration is the cause, set AUTO_MIGRATE=false in the project's .env as a temporary unblock.",
613
+ };
614
+ }
615
+ const DEFAULT_PING_TIMEOUT_MS = 5000;
616
+ /**
617
+ * Pings every routable service on the same /health URLs `tdk up` advertises.
618
+ *
619
+ * This is a runtime check, not an environment one: before `tdk up` there is
620
+ * nothing to ping, so a stopped stack is reported as skipped and doctor still
621
+ * passes. Once services are up, an unreachable endpoint is a real failure --
622
+ * it catches the case where a container is running but Traefik never routed
623
+ * to it, which `docker ps` alone will not show.
624
+ */
625
+ async function checkServiceHealth(timeoutMs) {
626
+ const projectRoot = findProjectRoot() ?? process.cwd();
627
+ const targets = buildHealthTargets(discoverResourcesFromRoot(projectRoot));
628
+ if (targets.length === 0) {
629
+ return {
630
+ name: "Service Health",
631
+ didPass: true,
632
+ isSkipped: true,
633
+ message: "No routable services to ping",
634
+ };
635
+ }
636
+ const probes = await pingHealthTargets(targets, timeoutMs);
637
+ const reachable = probes.filter((probe) => probe.ok);
638
+ const failed = probes.filter((probe) => !probe.ok);
639
+ // Nothing answered at all: either the stack was never started, or ingress
640
+ // never came up (Traefik port conflict). Prefer failing via
641
+ // checkTiltResourceHealth / checkIngressPorts when Tilt is up; here we only
642
+ // skip when there is truly nothing to probe yet.
643
+ if (reachable.length === 0) {
644
+ return {
645
+ name: "Service Health",
646
+ didPass: true,
647
+ isSkipped: true,
648
+ message: `No services responded - skipped ping of ${formatCount(targets.length, "service")} (stack may be down, or Traefik never bound :80)`,
649
+ fix: "If `tdk up` is already running, check Traefik/port 80 in the Tilt UI. Otherwise start with: tdk up",
650
+ };
651
+ }
652
+ if (failed.length === 0) {
653
+ return {
654
+ name: "Service Health",
655
+ didPass: true,
656
+ message: `All ${formatCount(targets.length, "service")} responding on /health`,
657
+ };
658
+ }
659
+ const details = failed
660
+ .map((probe) => {
661
+ const reason = probe.status ? `HTTP ${probe.status}` : (probe.error ?? "no response");
662
+ return `${probe.name} (${reason})\n ${probe.url}`;
663
+ })
664
+ .join("\n ");
665
+ return {
666
+ name: "Service Health",
667
+ didPass: false,
668
+ message: `${formatCount(failed.length, "service")} not responding (${reachable.length}/${probes.length} healthy):\n ${details}`,
669
+ fix: "Check container state and routing: docker ps, then tdk networks to compare the advertised URLs against Traefik's routers",
670
+ };
671
+ }
672
+ export const doctorCommand = new Command("doctor")
673
+ .description("Check environment readiness for TDK")
674
+ .option("--no-ping", "Skip pinging running services' /health endpoints")
675
+ .option("--ping-timeout <ms>", "Per-service ping timeout in milliseconds", String(DEFAULT_PING_TIMEOUT_MS))
676
+ .action(async (options) => {
677
+ console.log(`\n${chalk.bold("🔍 TDK Doctor")}\n`);
678
+ console.log("Checking environment...\n");
679
+ const pingTimeout = Number.parseInt(options.pingTimeout, 10);
680
+ if (!Number.isFinite(pingTimeout) || pingTimeout <= 0) {
681
+ console.log(`${chalk.red("✗")} --ping-timeout must be a positive number of milliseconds`);
682
+ process.exit(1);
683
+ }
684
+ const checks = [
685
+ checkDockerRuntime,
686
+ checkTilt,
687
+ checkDockerCompose,
688
+ checkDockerVersions,
689
+ checkMasterConfigs,
690
+ checkGeneratedProjectRuntimeAssets,
691
+ checkStarlarkLoadExports,
692
+ checkStartupScripts,
693
+ checkTypeScriptTypeDependencies,
694
+ checkFrontendDockerPreflight,
695
+ checkEnvironmentVariables,
696
+ // Preflight: catch "port 80 already allocated" BEFORE claiming ready.
697
+ checkIngressPorts,
698
+ // Preflight: Verdaccio down causes ImageBuild bun install ConnectionRefused.
699
+ checkPrivateNpmRegistry,
700
+ // Runtime checks: skip gracefully if the stack isn't started yet.
701
+ checkContainerResourceHealth,
702
+ // When Tilt is up, surface red resources (Traefik/apps never started).
703
+ checkTiltResourceHealth,
704
+ ];
705
+ // Runs last: needs routable services (and working Traefik) to mean anything.
706
+ if (options.ping) {
707
+ checks.push(() => checkServiceHealth(pingTimeout));
708
+ }
709
+ let allPassed = true;
710
+ for (const checkFn of checks) {
711
+ const result = await checkFn();
712
+ if (result.isSkipped) {
713
+ console.log(`${chalk.gray("○")} ${chalk.gray(result.message)}`);
714
+ if (result.fix) {
715
+ console.log(`${chalk.blue("ℹ")} ${result.fix}`);
716
+ }
717
+ }
718
+ else if (result.didPass) {
719
+ console.log(`${chalk.green("✓")} ${result.message}`);
720
+ }
721
+ else {
722
+ console.log(`${chalk.red("✗")} ${result.message}`);
723
+ if (result.fix) {
724
+ console.log(`${chalk.blue("ℹ")} Fix: ${result.fix}`);
725
+ }
726
+ allPassed = false;
727
+ break;
728
+ }
729
+ }
730
+ console.log("");
731
+ if (allPassed) {
732
+ console.log(`${chalk.green(chalk.bold("✓"))} Environment ready for TDK`);
733
+ console.log("");
734
+ console.log("Next steps:");
735
+ console.log(" 1. Run: tdk up");
736
+ console.log(" 2. Open: http://localhost:10350");
737
+ }
738
+ else {
739
+ console.log(`${chalk.red(chalk.bold("✗"))} Environment not ready`);
740
+ console.log("");
741
+ console.log("Fix the issues above, then run: tdk doctor");
742
+ process.exit(1);
743
+ }
744
+ });
745
+ //# sourceMappingURL=doctor.js.map