@tdk-landscape/tdk-cli-core 1.3.44 → 1.3.48

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 (431) hide show
  1. package/.github/workflows/release-binaries.yml +54 -0
  2. package/.pre-commit-config.yaml +57 -0
  3. package/Makefile +92 -0
  4. package/bun.lock +538 -0
  5. package/cli/AGENTS.md +390 -0
  6. package/cli/DESIGN.md +735 -0
  7. package/cli/biome.json +44 -0
  8. package/cli/dependency-graph.svg +1135 -0
  9. package/cli/knip.json +6 -0
  10. package/cli/package.json +77 -0
  11. package/cli/pnpm-lock.yaml +2305 -0
  12. package/cli/pnpm-workspace.yaml +2 -0
  13. package/cli/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +23 -0
  14. package/cli/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +464 -0
  15. package/cli/shared-platform-engineering/docker-templates/install-deps.sh +152 -0
  16. package/cli/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +12 -0
  17. package/cli/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +56 -0
  18. package/{dist/cli.js → cli/src/cli.ts} +21 -13
  19. package/cli/src/commands/__tests__/always-enabled-infra.test.ts +115 -0
  20. package/cli/src/commands/__tests__/config.test.ts +146 -0
  21. package/cli/src/commands/__tests__/doctor.test.ts +557 -0
  22. package/cli/src/commands/__tests__/error-handling.test.ts +78 -0
  23. package/cli/src/commands/__tests__/phases-model.test.ts +163 -0
  24. package/cli/src/commands/__tests__/project-resource-features-e2e.test.ts +207 -0
  25. package/cli/src/commands/__tests__/project.test.ts +68 -0
  26. package/cli/src/commands/__tests__/resource.test.ts +226 -0
  27. package/cli/src/commands/__tests__/saas-starter-integration.test.ts +196 -0
  28. package/cli/src/commands/__tests__/stack-features-e2e.test.ts +129 -0
  29. package/cli/src/commands/__tests__/upgrade.test.ts +101 -0
  30. package/{dist/commands/completion.js → cli/src/commands/completion.ts} +78 -72
  31. package/cli/src/commands/config.ts +299 -0
  32. package/cli/src/commands/doctor.ts +790 -0
  33. package/cli/src/commands/down.ts +37 -0
  34. package/cli/src/commands/help.ts +150 -0
  35. package/cli/src/commands/networks-design.md +80 -0
  36. package/cli/src/commands/networks.ts +332 -0
  37. package/cli/src/commands/project.ts +439 -0
  38. package/cli/src/commands/projects.ts +82 -0
  39. package/cli/src/commands/resource.ts +652 -0
  40. package/cli/src/commands/resources.ts +95 -0
  41. package/cli/src/commands/stack.ts +128 -0
  42. package/cli/src/commands/stacks.ts +52 -0
  43. package/cli/src/commands/status.ts +88 -0
  44. package/cli/src/commands/ui.tsx +843 -0
  45. package/cli/src/commands/up.ts +150 -0
  46. package/cli/src/commands/upgrade.ts +570 -0
  47. package/{dist/commands/version.js → cli/src/commands/version.ts} +5 -5
  48. package/cli/src/components/Accessible.tsx +21 -0
  49. package/cli/src/components/BaseTooltip.tsx +66 -0
  50. package/cli/src/components/DetailPanel.tsx +146 -0
  51. package/cli/src/components/FileTree.tsx +89 -0
  52. package/cli/src/components/ResourceSelectInput.tsx +30 -0
  53. package/cli/src/components/ResourceTable.tsx +78 -0
  54. package/cli/src/components/TabBar.tsx +53 -0
  55. package/cli/src/components/Tooltip.tsx +11 -0
  56. package/{dist/components/index.js → cli/src/components/index.ts} +0 -1
  57. package/cli/src/config/platform-standards.test.ts +11 -0
  58. package/cli/src/config/platform-standards.ts +196 -0
  59. package/cli/src/generator/__tests__/extension-fetch.test.ts +220 -0
  60. package/cli/src/generator/__tests__/template-engine.test.ts +70 -0
  61. package/cli/src/generator/extension-fetch.ts +279 -0
  62. package/cli/src/generator/template-engine.ts +754 -0
  63. package/cli/src/index.ts +86 -0
  64. package/cli/src/templates.d.ts +4 -0
  65. package/cli/src/types/index.ts +351 -0
  66. package/cli/src/utils/__tests__/cache.test.ts +98 -0
  67. package/cli/src/utils/__tests__/formatting.test.ts +170 -0
  68. package/cli/src/utils/__tests__/paths.test.ts +180 -0
  69. package/cli/src/utils/__tests__/port-assignment.test.ts +121 -0
  70. package/cli/src/utils/__tests__/service-urls.test.ts +174 -0
  71. package/cli/src/utils/__tests__/services.test.ts +531 -0
  72. package/cli/src/utils/__tests__/tilt.test.ts +143 -0
  73. package/cli/src/utils/__tests__/validation.test.ts +148 -0
  74. package/cli/src/utils/cache.ts +76 -0
  75. package/cli/src/utils/command-helpers.ts +54 -0
  76. package/cli/src/utils/constants.ts +126 -0
  77. package/cli/src/utils/discovery-context.ts +49 -0
  78. package/cli/src/utils/docker.ts +20 -0
  79. package/cli/src/utils/doctor-runtime.ts +604 -0
  80. package/cli/src/utils/env-validator.ts +139 -0
  81. package/cli/src/utils/errors.ts +168 -0
  82. package/cli/src/utils/file-helpers.ts +62 -0
  83. package/cli/src/utils/formatting.ts +215 -0
  84. package/cli/src/utils/paths.ts +53 -0
  85. package/cli/src/utils/port-assignment.ts +106 -0
  86. package/cli/src/utils/project-features.ts +118 -0
  87. package/cli/src/utils/project-templates.ts +28 -0
  88. package/cli/src/utils/prompt.ts +123 -0
  89. package/cli/src/utils/resource-features.ts +209 -0
  90. package/cli/src/utils/service-urls.ts +124 -0
  91. package/cli/src/utils/services.ts +363 -0
  92. package/cli/src/utils/stack-features.ts +30 -0
  93. package/cli/src/utils/tar.ts +24 -0
  94. package/cli/src/utils/tilt.ts +113 -0
  95. package/cli/src/utils/validation.ts +67 -0
  96. package/{templates → cli/templates}/Tiltfile.hbs +1 -1
  97. package/cli/tsconfig.json +25 -0
  98. package/cli/tsconfig.typecheck.json +8 -0
  99. package/docs/FEATURES.md +279 -0
  100. package/engine/topologies/platform/docker/dockerfile/dockerfile.star +3 -3
  101. package/engine/topologies/platform/docker/generators/golden_docker_generator_v2.star +1 -1
  102. package/engine/topologies/platform/docker/layers/l2_dependency_layers.star +0 -3
  103. package/engine/topologies/platform/docker/layers/l3_builder_layers.star +1 -1
  104. package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +1 -1
  105. package/engine/topologies/platform/docker/secrets/infisical.star +19 -47
  106. package/engine/topologies/tilt/resources/orchestrator/apply_migrator_orchestration.star +1 -1
  107. package/install.sh +242 -0
  108. package/knip.json +10 -0
  109. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/.openspec.yaml +2 -0
  110. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/design.md +102 -0
  111. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/proposal.md +31 -0
  112. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-notifications/spec.md +48 -0
  113. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-orchestration/spec.md +63 -0
  114. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-troubleshooting/spec.md +56 -0
  115. package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/tasks.md +61 -0
  116. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/.openspec.yaml +2 -0
  117. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/design.md +114 -0
  118. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/proposal.md +32 -0
  119. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/manifest-field-migration/spec.md +20 -0
  120. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/resource-terminology-standard/spec.md +20 -0
  121. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/stack-terminology-standard/spec.md +20 -0
  122. package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/tasks.md +194 -0
  123. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/.openspec.yaml +2 -0
  124. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/design.md +111 -0
  125. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/proposal.md +33 -0
  126. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/function-naming-alignment/spec.md +12 -0
  127. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/optional-parameter-patterns/spec.md +18 -0
  128. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-documentation/spec.md +16 -0
  129. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-naming-consistency/spec.md +12 -0
  130. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-ordering-standard/spec.md +12 -0
  131. package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/tasks.md +194 -0
  132. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/.openspec.yaml +2 -0
  133. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/codebase-audit-report.md +62 -0
  134. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/design.md +84 -0
  135. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/proposal.md +31 -0
  136. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/codebase-audit/spec.md +27 -0
  137. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/dead-code-cleanup/spec.md +27 -0
  138. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/test-gap-fill/spec.md +23 -0
  139. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/todo-resolution/spec.md +27 -0
  140. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/tasks.md +39 -0
  141. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/.openspec.yaml +5 -0
  142. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/design.md +94 -0
  143. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/proposal.md +46 -0
  144. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CLEANUP_REPORT_8.md +174 -0
  145. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CRITICAL_ASSESSMENT_8.md +160 -0
  146. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/circular-deps/CRITICAL_ASSESSMENT_4.md +381 -0
  147. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/dead-code/CRITICAL_ASSESSMENT_3.md +316 -0
  148. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/CRITICAL_ASSESSMENT_1.md +290 -0
  149. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/IMPLEMENTATION_SUMMARY_1.md +233 -0
  150. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/defensive-cleanup/CRITICAL_ASSESSMENT_6.md +835 -0
  151. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/CRITICAL_ASSESSMENT_7.md +254 -0
  152. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/IMPLEMENTATION_SUMMARY_7.md +187 -0
  153. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/strong-typing/CRITICAL_ASSESSMENT_5.md +269 -0
  154. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/CRITICAL_ASSESSMENT_2.md +312 -0
  155. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/IMPLEMENTATION_SUMMARY.md +170 -0
  156. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/tasks.md +137 -0
  157. package/openspec/changes/archive/2026-06-25-landscape-consolidation/.openspec.yaml +2 -0
  158. package/openspec/changes/archive/2026-06-25-landscape-consolidation/design.md +80 -0
  159. package/openspec/changes/archive/2026-06-25-landscape-consolidation/proposal.md +40 -0
  160. package/openspec/changes/archive/2026-06-25-landscape-consolidation/spec.md +52 -0
  161. package/openspec/changes/archive/2026-06-25-landscape-consolidation/tasks.md +51 -0
  162. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/.openspec.yaml +2 -0
  163. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/design.md +43 -0
  164. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/proposal.md +29 -0
  165. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/specs/self-contained-generated-projects/spec.md +67 -0
  166. package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/tasks.md +27 -0
  167. package/openspec/specs/manifest-field-migration/spec.md +24 -0
  168. package/openspec/specs/resource-terminology-standard/spec.md +24 -0
  169. package/openspec/specs/self-contained-generated-projects/spec.md +67 -0
  170. package/openspec/specs/stack-terminology-standard/spec.md +24 -0
  171. package/package.json +27 -64
  172. package/platform/services/platform/proxy/service.yaml +14 -0
  173. package/scripts/audit-codebase.sh +213 -0
  174. package/scripts/install-git-hooks.sh +14 -0
  175. package/scripts/release-binaries.sh +94 -0
  176. package/scripts/setup-pre-commit.sh +33 -0
  177. package/scripts/stage-npm-engine.sh +36 -0
  178. package/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +23 -0
  179. package/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +464 -0
  180. package/shared-platform-engineering/docker-templates/install-deps.sh +152 -0
  181. package/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +12 -0
  182. package/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +56 -0
  183. package/tests/tilt-engine/__init__.py +5 -0
  184. package/tests/tilt-engine/conftest.py +182 -0
  185. package/tests/tilt-engine/fixtures/appointment-frontend-service.json +11 -0
  186. package/tests/tilt-engine/fixtures/identity-backend-service.json +12 -0
  187. package/tests/tilt-engine/fixtures/invalid-service.json +4 -0
  188. package/tests/tilt-engine/test_agents_md_generator.py +341 -0
  189. package/tests/tilt-engine/test_agents_md_integration.py +378 -0
  190. package/tests/tilt-engine/test_cache_operations.py +181 -0
  191. package/tests/tilt-engine/test_cache_persistence.py +142 -0
  192. package/tests/tilt-engine/test_code_executor.py +152 -0
  193. package/tests/tilt-engine/test_code_viewer.py +127 -0
  194. package/tests/tilt-engine/test_compose_port_anchors.py +310 -0
  195. package/tests/tilt-engine/test_config_inspector.py +162 -0
  196. package/tests/tilt-engine/test_daemon_focus_mode.py +89 -0
  197. package/tests/tilt-engine/test_daemon_polling.py +122 -0
  198. package/tests/tilt-engine/test_daemon_registration.py +115 -0
  199. package/tests/tilt-engine/test_daemon_validation.py +107 -0
  200. package/tests/tilt-engine/test_docker_generator_bugs.py +493 -0
  201. package/tests/tilt-engine/test_docker_prune.py +119 -0
  202. package/tests/tilt-engine/test_dockerignore_path_bug.py +82 -0
  203. package/tests/tilt-engine/test_file_browser.py +193 -0
  204. package/tests/tilt-engine/test_frontend_basepath_manifest.py +27 -0
  205. package/tests/tilt-engine/test_health_dependencies.py +130 -0
  206. package/tests/tilt-engine/test_health_endpoints.py +118 -0
  207. package/tests/tilt-engine/test_health_tilt_ui.py +85 -0
  208. package/tests/tilt-engine/test_safety_aliases.py +84 -0
  209. package/tests/tilt-engine/test_snapshot_diff.py +136 -0
  210. package/tests/tilt-engine/test_snapshot_persistence.py +163 -0
  211. package/tests/tilt-engine/test_snapshot_scanning.py +123 -0
  212. package/tests/tilt-engine/test_standalone_project_boot.py +61 -0
  213. package/tests/tilt-engine/test_standalone_traefik.py +108 -0
  214. package/dist/cli.d.ts +0 -3
  215. package/dist/cli.d.ts.map +0 -1
  216. package/dist/cli.js.map +0 -1
  217. package/dist/commands/completion.d.ts +0 -3
  218. package/dist/commands/completion.d.ts.map +0 -1
  219. package/dist/commands/completion.js.map +0 -1
  220. package/dist/commands/config.d.ts +0 -3
  221. package/dist/commands/config.d.ts.map +0 -1
  222. package/dist/commands/config.js +0 -231
  223. package/dist/commands/config.js.map +0 -1
  224. package/dist/commands/doctor.d.ts +0 -7
  225. package/dist/commands/doctor.d.ts.map +0 -1
  226. package/dist/commands/doctor.js +0 -597
  227. package/dist/commands/doctor.js.map +0 -1
  228. package/dist/commands/down.d.ts +0 -3
  229. package/dist/commands/down.d.ts.map +0 -1
  230. package/dist/commands/down.js +0 -32
  231. package/dist/commands/down.js.map +0 -1
  232. package/dist/commands/help.d.ts +0 -2
  233. package/dist/commands/help.d.ts.map +0 -1
  234. package/dist/commands/help.js +0 -130
  235. package/dist/commands/help.js.map +0 -1
  236. package/dist/commands/networks.d.ts +0 -3
  237. package/dist/commands/networks.d.ts.map +0 -1
  238. package/dist/commands/networks.js +0 -260
  239. package/dist/commands/networks.js.map +0 -1
  240. package/dist/commands/project.d.ts +0 -3
  241. package/dist/commands/project.d.ts.map +0 -1
  242. package/dist/commands/project.js +0 -373
  243. package/dist/commands/project.js.map +0 -1
  244. package/dist/commands/projects.d.ts +0 -3
  245. package/dist/commands/projects.d.ts.map +0 -1
  246. package/dist/commands/projects.js +0 -71
  247. package/dist/commands/projects.js.map +0 -1
  248. package/dist/commands/resource.d.ts +0 -69
  249. package/dist/commands/resource.d.ts.map +0 -1
  250. package/dist/commands/resource.js +0 -579
  251. package/dist/commands/resource.js.map +0 -1
  252. package/dist/commands/resources.d.ts +0 -3
  253. package/dist/commands/resources.d.ts.map +0 -1
  254. package/dist/commands/resources.js +0 -73
  255. package/dist/commands/resources.js.map +0 -1
  256. package/dist/commands/stack.d.ts +0 -3
  257. package/dist/commands/stack.d.ts.map +0 -1
  258. package/dist/commands/stack.js +0 -97
  259. package/dist/commands/stack.js.map +0 -1
  260. package/dist/commands/stacks.d.ts +0 -3
  261. package/dist/commands/stacks.d.ts.map +0 -1
  262. package/dist/commands/stacks.js +0 -43
  263. package/dist/commands/stacks.js.map +0 -1
  264. package/dist/commands/status.d.ts +0 -3
  265. package/dist/commands/status.d.ts.map +0 -1
  266. package/dist/commands/status.js +0 -71
  267. package/dist/commands/status.js.map +0 -1
  268. package/dist/commands/ui.d.ts +0 -3
  269. package/dist/commands/ui.d.ts.map +0 -1
  270. package/dist/commands/ui.js +0 -445
  271. package/dist/commands/ui.js.map +0 -1
  272. package/dist/commands/up.d.ts +0 -3
  273. package/dist/commands/up.d.ts.map +0 -1
  274. package/dist/commands/up.js +0 -110
  275. package/dist/commands/up.js.map +0 -1
  276. package/dist/commands/upgrade.d.ts +0 -11
  277. package/dist/commands/upgrade.d.ts.map +0 -1
  278. package/dist/commands/upgrade.js +0 -496
  279. package/dist/commands/upgrade.js.map +0 -1
  280. package/dist/commands/version.d.ts +0 -3
  281. package/dist/commands/version.d.ts.map +0 -1
  282. package/dist/commands/version.js.map +0 -1
  283. package/dist/components/Accessible.d.ts +0 -8
  284. package/dist/components/Accessible.d.ts.map +0 -1
  285. package/dist/components/Accessible.js +0 -10
  286. package/dist/components/Accessible.js.map +0 -1
  287. package/dist/components/BaseTooltip.d.ts +0 -4
  288. package/dist/components/BaseTooltip.d.ts.map +0 -1
  289. package/dist/components/BaseTooltip.js +0 -28
  290. package/dist/components/BaseTooltip.js.map +0 -1
  291. package/dist/components/DetailPanel.d.ts +0 -4
  292. package/dist/components/DetailPanel.d.ts.map +0 -1
  293. package/dist/components/DetailPanel.js +0 -18
  294. package/dist/components/DetailPanel.js.map +0 -1
  295. package/dist/components/FileTree.d.ts +0 -4
  296. package/dist/components/FileTree.d.ts.map +0 -1
  297. package/dist/components/FileTree.js +0 -47
  298. package/dist/components/FileTree.js.map +0 -1
  299. package/dist/components/ResourceSelectInput.d.ts +0 -4
  300. package/dist/components/ResourceSelectInput.d.ts.map +0 -1
  301. package/dist/components/ResourceSelectInput.js +0 -7
  302. package/dist/components/ResourceSelectInput.js.map +0 -1
  303. package/dist/components/ResourceTable.d.ts +0 -4
  304. package/dist/components/ResourceTable.d.ts.map +0 -1
  305. package/dist/components/ResourceTable.js +0 -18
  306. package/dist/components/ResourceTable.js.map +0 -1
  307. package/dist/components/TabBar.d.ts +0 -4
  308. package/dist/components/TabBar.d.ts.map +0 -1
  309. package/dist/components/TabBar.js +0 -17
  310. package/dist/components/TabBar.js.map +0 -1
  311. package/dist/components/Tooltip.d.ts +0 -12
  312. package/dist/components/Tooltip.d.ts.map +0 -1
  313. package/dist/components/Tooltip.js +0 -12
  314. package/dist/components/Tooltip.js.map +0 -1
  315. package/dist/components/index.d.ts +0 -8
  316. package/dist/components/index.d.ts.map +0 -1
  317. package/dist/components/index.js.map +0 -1
  318. package/dist/config/platform-standards.d.ts +0 -159
  319. package/dist/config/platform-standards.d.ts.map +0 -1
  320. package/dist/config/platform-standards.js +0 -181
  321. package/dist/config/platform-standards.js.map +0 -1
  322. package/dist/generator/extension-fetch.d.ts +0 -25
  323. package/dist/generator/extension-fetch.d.ts.map +0 -1
  324. package/dist/generator/extension-fetch.js +0 -252
  325. package/dist/generator/extension-fetch.js.map +0 -1
  326. package/dist/generator/template-engine.d.ts +0 -61
  327. package/dist/generator/template-engine.d.ts.map +0 -1
  328. package/dist/generator/template-engine.js +0 -557
  329. package/dist/generator/template-engine.js.map +0 -1
  330. package/dist/index.d.ts +0 -11
  331. package/dist/index.d.ts.map +0 -1
  332. package/dist/index.js +0 -11
  333. package/dist/index.js.map +0 -1
  334. package/dist/types/index.d.ts +0 -255
  335. package/dist/types/index.d.ts.map +0 -1
  336. package/dist/types/index.js +0 -17
  337. package/dist/types/index.js.map +0 -1
  338. package/dist/utils/cache.d.ts +0 -19
  339. package/dist/utils/cache.d.ts.map +0 -1
  340. package/dist/utils/cache.js +0 -64
  341. package/dist/utils/cache.js.map +0 -1
  342. package/dist/utils/command-helpers.d.ts +0 -17
  343. package/dist/utils/command-helpers.d.ts.map +0 -1
  344. package/dist/utils/command-helpers.js +0 -41
  345. package/dist/utils/command-helpers.js.map +0 -1
  346. package/dist/utils/constants.d.ts +0 -52
  347. package/dist/utils/constants.d.ts.map +0 -1
  348. package/dist/utils/constants.js +0 -112
  349. package/dist/utils/constants.js.map +0 -1
  350. package/dist/utils/discovery-context.d.ts +0 -4
  351. package/dist/utils/discovery-context.d.ts.map +0 -1
  352. package/dist/utils/discovery-context.js +0 -39
  353. package/dist/utils/discovery-context.js.map +0 -1
  354. package/dist/utils/docker.d.ts +0 -4
  355. package/dist/utils/docker.d.ts.map +0 -1
  356. package/dist/utils/docker.js +0 -19
  357. package/dist/utils/docker.js.map +0 -1
  358. package/dist/utils/doctor-runtime.d.ts +0 -66
  359. package/dist/utils/doctor-runtime.d.ts.map +0 -1
  360. package/dist/utils/doctor-runtime.js +0 -469
  361. package/dist/utils/doctor-runtime.js.map +0 -1
  362. package/dist/utils/env-validator.d.ts +0 -15
  363. package/dist/utils/env-validator.d.ts.map +0 -1
  364. package/dist/utils/env-validator.js +0 -109
  365. package/dist/utils/env-validator.js.map +0 -1
  366. package/dist/utils/errors.d.ts +0 -31
  367. package/dist/utils/errors.d.ts.map +0 -1
  368. package/dist/utils/errors.js +0 -140
  369. package/dist/utils/errors.js.map +0 -1
  370. package/dist/utils/file-helpers.d.ts +0 -14
  371. package/dist/utils/file-helpers.d.ts.map +0 -1
  372. package/dist/utils/file-helpers.js +0 -45
  373. package/dist/utils/file-helpers.js.map +0 -1
  374. package/dist/utils/formatting.d.ts +0 -30
  375. package/dist/utils/formatting.d.ts.map +0 -1
  376. package/dist/utils/formatting.js +0 -164
  377. package/dist/utils/formatting.js.map +0 -1
  378. package/dist/utils/paths.d.ts +0 -3
  379. package/dist/utils/paths.d.ts.map +0 -1
  380. package/dist/utils/paths.js +0 -41
  381. package/dist/utils/paths.js.map +0 -1
  382. package/dist/utils/port-assignment.d.ts +0 -6
  383. package/dist/utils/port-assignment.d.ts.map +0 -1
  384. package/dist/utils/port-assignment.js +0 -81
  385. package/dist/utils/port-assignment.js.map +0 -1
  386. package/dist/utils/project-features.d.ts +0 -23
  387. package/dist/utils/project-features.d.ts.map +0 -1
  388. package/dist/utils/project-features.js +0 -98
  389. package/dist/utils/project-features.js.map +0 -1
  390. package/dist/utils/project-templates.d.ts +0 -7
  391. package/dist/utils/project-templates.d.ts.map +0 -1
  392. package/dist/utils/project-templates.js +0 -24
  393. package/dist/utils/project-templates.js.map +0 -1
  394. package/dist/utils/prompt.d.ts +0 -28
  395. package/dist/utils/prompt.d.ts.map +0 -1
  396. package/dist/utils/prompt.js +0 -79
  397. package/dist/utils/prompt.js.map +0 -1
  398. package/dist/utils/resource-features.d.ts +0 -29
  399. package/dist/utils/resource-features.d.ts.map +0 -1
  400. package/dist/utils/resource-features.js +0 -171
  401. package/dist/utils/resource-features.js.map +0 -1
  402. package/dist/utils/service-urls.d.ts +0 -30
  403. package/dist/utils/service-urls.d.ts.map +0 -1
  404. package/dist/utils/service-urls.js +0 -93
  405. package/dist/utils/service-urls.js.map +0 -1
  406. package/dist/utils/services.d.ts +0 -18
  407. package/dist/utils/services.d.ts.map +0 -1
  408. package/dist/utils/services.js +0 -291
  409. package/dist/utils/services.js.map +0 -1
  410. package/dist/utils/stack-features.d.ts +0 -22
  411. package/dist/utils/stack-features.d.ts.map +0 -1
  412. package/dist/utils/stack-features.js +0 -13
  413. package/dist/utils/stack-features.js.map +0 -1
  414. package/dist/utils/tar.d.ts +0 -7
  415. package/dist/utils/tar.d.ts.map +0 -1
  416. package/dist/utils/tar.js +0 -21
  417. package/dist/utils/tar.js.map +0 -1
  418. package/dist/utils/tilt.d.ts +0 -19
  419. package/dist/utils/tilt.d.ts.map +0 -1
  420. package/dist/utils/tilt.js +0 -78
  421. package/dist/utils/tilt.js.map +0 -1
  422. package/dist/utils/validation.d.ts +0 -16
  423. package/dist/utils/validation.d.ts.map +0 -1
  424. package/dist/utils/validation.js +0 -58
  425. package/dist/utils/validation.js.map +0 -1
  426. /package/{README.md → cli/README.md} +0 -0
  427. /package/{bin → cli/bin}/tdk.js +0 -0
  428. /package/{templates → cli/templates}/.tiltignore.hbs +0 -0
  429. /package/{templates → cli/templates}/TILT_RESOURCE_DEFAULTS.star.hbs +0 -0
  430. /package/{templates → cli/templates}/TILT_TECH_STACK.star.hbs +0 -0
  431. /package/{templates → cli/templates}/spec.master.hbs +0 -0
@@ -0,0 +1,162 @@
1
+ """
2
+ Tests for config inspector component.
3
+ Tests the multi-pane comparison and editing features.
4
+ """
5
+
6
+ import os
7
+ import pytest
8
+ import hashlib
9
+ from pathlib import Path
10
+
11
+
12
+ @pytest.mark.ide
13
+ class TestConfigInspector:
14
+ """Tests for config inspector functionality."""
15
+
16
+ def test_single_pane_default_view(self):
17
+ """
18
+ Scenario: Single-pane default view
19
+ WHEN a user requests `GET /`
20
+ THEN it SHALL display a single pane with the first master config
21
+ """
22
+ # Simulate default view
23
+ panes = [{"name": "TILT_RESOURCE_DEFAULTS.star", "content": "# Content"}]
24
+
25
+ assert len(panes) == 1, "Default view should have 1 pane"
26
+ assert "TILT_RESOURCE_DEFAULTS.star" in panes[0]["name"]
27
+
28
+ def test_dual_pane_comparison(self):
29
+ """
30
+ Scenario: Dual-pane comparison
31
+ WHEN a user requests `GET /?master1=TILT_RESOURCE_DEFAULTS.star&master2=spec.master`
32
+ THEN it SHALL display two panes side-by-side
33
+ AND both configs SHALL be viewable simultaneously
34
+ """
35
+ # Simulate dual-pane request
36
+ requested_masters = ["TILT_RESOURCE_DEFAULTS.star", "spec.master"]
37
+
38
+ panes = [
39
+ {"name": name, "content": f"# {name} content"}
40
+ for name in requested_masters
41
+ ]
42
+
43
+ assert len(panes) == 2, "Dual-pane should have 2 panes"
44
+ assert all(p["name"] in requested_masters for p in panes)
45
+
46
+ def test_triple_pane_with_sync_scroll(self):
47
+ """
48
+ Scenario: Triple-pane with sync scroll
49
+ WHEN a user requests `GET /?master1=A.star&master2=B.star&master3=C.star&sync=true`
50
+ THEN it SHALL display three panes
51
+ AND scrolling one pane SHALL sync scroll the others
52
+ """
53
+ requested_masters = ["A.star", "B.star", "C.star"]
54
+ sync_enabled = True
55
+
56
+ panes = [
57
+ {"name": name, "content": f"# {name} content", "sync_scroll": sync_enabled}
58
+ for name in requested_masters
59
+ ]
60
+
61
+ assert len(panes) == 3, "Triple-pane should have 3 panes"
62
+ assert all(p.get("sync_scroll") for p in panes), "All panes should have sync scroll enabled"
63
+
64
+ def test_edit_mode_toggle(self, temp_dir):
65
+ """
66
+ Scenario: Edit mode toggle
67
+ WHEN a user clicks the "✏️ Edit Mode" button
68
+ THEN textareas SHALL become editable
69
+ AND save buttons SHALL appear for each pane
70
+ """
71
+ # Simulate edit mode toggle
72
+ edit_mode = True
73
+
74
+ pane = {
75
+ "name": "test.star",
76
+ "content": "# Original",
77
+ "editable": edit_mode,
78
+ "dirty": False
79
+ }
80
+
81
+ assert pane["editable"], "Pane should be editable in edit mode"
82
+
83
+ # Simulate edit
84
+ pane["content"] = "# Modified"
85
+ pane["dirty"] = True
86
+
87
+ assert pane["dirty"], "Pane should be marked as dirty after edit"
88
+
89
+ def test_safe_file_saving_with_backups(self, temp_dir):
90
+ """
91
+ Scenario: Safe file saving
92
+ WHEN a user saves changes in edit mode
93
+ THEN it SHALL create a backup in `.backups/` before writing
94
+ AND it SHALL use atomic write (temp file + rename)
95
+ """
96
+ test_file = temp_dir / "test.star"
97
+ original_content = "# Original content"
98
+ test_file.write_text(original_content)
99
+
100
+ # Create backup directory
101
+ backup_dir = temp_dir / ".backups"
102
+ backup_dir.mkdir(exist_ok=True)
103
+
104
+ # Simulate safe save
105
+ new_content = "# Modified content"
106
+ self._safe_save_file(test_file, new_content, backup_dir)
107
+
108
+ # Verify backup created (backup files are named like "test.star.0")
109
+ backup_files = list(backup_dir.glob(f"{test_file.name}.*"))
110
+ assert len(backup_files) > 0, f"Backup should be created in {backup_dir}"
111
+
112
+ # Verify new content written
113
+ assert test_file.read_text() == new_content, "File should have new content"
114
+
115
+ # Verify backup has original content
116
+ backup_content = backup_files[0].read_text()
117
+ assert backup_content == original_content, "Backup should have original content"
118
+
119
+ def test_conflict_detection(self, temp_dir):
120
+ """
121
+ Scenario: Conflict detection
122
+ WHEN a user attempts to save but the file was modified on disk since loading
123
+ THEN it SHALL return `409 Conflict` with current file hash
124
+ AND it SHALL ask user to refresh and retry
125
+ """
126
+ test_file = temp_dir / "test.star"
127
+ original_content = "# Version 1"
128
+ test_file.write_text(original_content)
129
+
130
+ # Load file and compute hash
131
+ loaded_content = test_file.read_text()
132
+ loaded_hash = hashlib.sha256(loaded_content.encode()).hexdigest()
133
+
134
+ # Simulate external modification
135
+ test_file.write_text("# Version 2 (external change)")
136
+
137
+ # Try to save with old hash
138
+ current_content = test_file.read_text()
139
+ current_hash = hashlib.sha256(current_content.encode()).hexdigest()
140
+
141
+ # Hash mismatch = conflict
142
+ assert loaded_hash != current_hash, "Should detect conflict (hash mismatch)"
143
+
144
+ def _safe_save_file(self, path: Path, content: str, backup_dir: Path):
145
+ """Simulate safe file save with backup."""
146
+ import tempfile
147
+ import shutil
148
+
149
+ # Create backup
150
+ if path.exists():
151
+ backup_path = backup_dir / f"{path.name}.{len(list(backup_dir.glob('*')))}"
152
+ shutil.copy2(path, backup_path)
153
+
154
+ # Atomic write using temp file
155
+ temp_fd, temp_path = tempfile.mkstemp(dir=path.parent)
156
+ try:
157
+ with os.fdopen(temp_fd, 'w') as f:
158
+ f.write(content)
159
+ os.replace(temp_path, path)
160
+ except Exception:
161
+ os.unlink(temp_path)
162
+ raise
@@ -0,0 +1,89 @@
1
+ """
2
+ Tests for daemon focus mode compliance.
3
+ Tests that discovery respects focus mode stack restrictions.
4
+ """
5
+
6
+ import pytest
7
+ from pathlib import Path
8
+ import json
9
+
10
+
11
+ @pytest.mark.daemon
12
+ class TestDaemonFocusMode:
13
+ """Tests for focus mode compliance."""
14
+
15
+ def test_focus_mode_restricts_discovery(self, temp_dir):
16
+ """
17
+ Scenario: Focus mode restricts discovery
18
+ WHEN `--focus identity` is enabled and a new resource appears in `services/product/billing/`
19
+ THEN the daemon SHALL NOT trigger registration (queue it for later)
20
+ AND it SHALL log "⏳ Resource queued (focus mode)"
21
+ """
22
+ # Simulate focus mode config
23
+ focus_config = {
24
+ "enabled": True,
25
+ "stacks": ["identity", "order"],
26
+ "excluded_paths": []
27
+ }
28
+
29
+ # Resource outside focus
30
+ resource_stack = "billing"
31
+ is_in_focus = resource_stack in focus_config["stacks"]
32
+
33
+ assert not is_in_focus, "billing should not be in focus mode"
34
+
35
+ # In real daemon, this would queue instead of registering
36
+ # For test, verify the filtering logic works
37
+ should_register = not focus_config["enabled"] or is_in_focus
38
+ assert not should_register, "Resource outside focus should not be registered immediately"
39
+
40
+ def test_focus_mode_allows_matching_resources(self, temp_dir):
41
+ """
42
+ Scenario: Focus mode allows matching resources
43
+ WHEN `--focus identity` is enabled and a new resource appears in `services/product/identity/`
44
+ THEN the daemon SHALL trigger registration normally
45
+ """
46
+ # Create resource in focus stack
47
+ focus_config = {
48
+ "enabled": True,
49
+ "stacks": ["identity", "order"],
50
+ "excluded_paths": []
51
+ }
52
+
53
+ resource_stack = "identity"
54
+ is_in_focus = resource_stack in focus_config["stacks"]
55
+
56
+ assert is_in_focus, "identity should be in focus mode"
57
+
58
+ # In focus - should register
59
+ should_register = not focus_config["enabled"] or is_in_focus
60
+ assert should_register, "Resource in focus should be registered"
61
+
62
+ def test_focus_mode_disabled_allows_all(self):
63
+ """Test that when focus mode is disabled, all resources are registered."""
64
+ focus_config = {
65
+ "enabled": False,
66
+ "stacks": ["identity"],
67
+ "excluded_paths": []
68
+ }
69
+
70
+ resource_stack = "billing"
71
+ is_in_focus = resource_stack in focus_config["stacks"]
72
+
73
+ # Focus disabled - should register regardless
74
+ should_register = not focus_config["enabled"] or is_in_focus
75
+ assert should_register, "When focus mode disabled, all resources should register"
76
+
77
+ def test_focus_mode_stack_matching(self):
78
+ """Test stack matching logic for focus mode."""
79
+ test_cases = [
80
+ ({"enabled": True, "stacks": ["identity"]}, "identity", True),
81
+ ({"enabled": True, "stacks": ["identity"]}, "billing", False),
82
+ ({"enabled": True, "stacks": ["identity", "order"]}, "order", True),
83
+ ({"enabled": False, "stacks": ["identity"]}, "billing", True),
84
+ ]
85
+
86
+ for config, stack, expected in test_cases:
87
+ is_in_focus = stack in config["stacks"]
88
+ should_register = not config["enabled"] or is_in_focus
89
+ assert should_register == expected, f"Failed for {stack} with config {config}"
@@ -0,0 +1,122 @@
1
+ """
2
+ Tests for discovery daemon polling behavior.
3
+ Tests the continuous monitoring and polling intervals.
4
+ """
5
+
6
+ import asyncio
7
+ import pytest
8
+ from pathlib import Path
9
+ from unittest.mock import Mock, patch, MagicMock
10
+
11
+
12
+ @pytest.mark.daemon
13
+ class TestDaemonPolling:
14
+ """Tests for daemon polling functionality."""
15
+
16
+ @pytest.mark.asyncio
17
+ async def test_daemon_polls_at_configured_interval(self):
18
+ """
19
+ Scenario: Daemon polls at configured interval
20
+ WHEN the daemon starts with `DISCOVERY_SCAN_INTERVAL=2`
21
+ THEN it SHALL call the scanner function every 2 seconds
22
+ """
23
+ mock_scanner = Mock(return_value=[])
24
+
25
+ # Mock the daemon loop to run only 3 iterations
26
+ call_count = 0
27
+ async def mock_daemon_loop():
28
+ nonlocal call_count
29
+ for _ in range(3):
30
+ mock_scanner()
31
+ call_count += 1
32
+ await asyncio.sleep(0.1) # Fast sleep for testing
33
+
34
+ # Run the mock daemon
35
+ await mock_daemon_loop()
36
+
37
+ assert call_count == 3, f"Expected 3 polling calls, got {call_count}"
38
+ assert mock_scanner.call_count == 3
39
+
40
+ @pytest.mark.asyncio
41
+ async def test_daemon_stops_cleanly_on_signal(self):
42
+ """
43
+ Scenario: Daemon can be stopped
44
+ WHEN the daemon receives a stop signal
45
+ THEN it SHALL cease polling and exit cleanly within 1 second
46
+ """
47
+ stop_event = asyncio.Event()
48
+ poll_count = [0] # Use list to allow mutation in nested function
49
+
50
+ async def polling_loop():
51
+ while not stop_event.is_set():
52
+ poll_count[0] += 1
53
+ try:
54
+ await asyncio.wait_for(stop_event.wait(), timeout=0.1)
55
+ except asyncio.TimeoutError:
56
+ pass
57
+
58
+ # Start polling
59
+ task = asyncio.create_task(polling_loop())
60
+
61
+ # Let it poll a few times
62
+ await asyncio.sleep(0.25)
63
+
64
+ # Signal stop
65
+ stop_event.set()
66
+
67
+ # Wait for clean exit with timeout
68
+ try:
69
+ await asyncio.wait_for(task, timeout=1.0)
70
+ except asyncio.TimeoutError:
71
+ task.cancel()
72
+
73
+ assert poll_count[0] >= 2, "Should have polled at least twice"
74
+ assert task.done(), "Task should be completed"
75
+
76
+ def test_polling_respects_interval_setting(self):
77
+ """Test that the daemon respects DISCOVERY_SCAN_INTERVAL."""
78
+ import os
79
+
80
+ # Default should be 5 seconds
81
+ interval = int(os.environ.get("DISCOVERY_SCAN_INTERVAL", "5"))
82
+
83
+ assert interval > 0, "Interval should be positive"
84
+ assert interval <= 60, "Interval should be reasonable (<= 60s)"
85
+
86
+ @pytest.mark.asyncio
87
+ async def test_polling_detects_changes_over_time(self, temp_dir):
88
+ """Test that daemon detects changes across multiple polling cycles."""
89
+ from pathlib import Path
90
+ import json
91
+
92
+ # Create initial service
93
+ services_dir = temp_dir / "services" / "product"
94
+ services_dir.mkdir(parents=True)
95
+
96
+ resource_dir = services_dir / "initial" / "service"
97
+ resource_dir.mkdir(parents=True)
98
+ (resource_dir / "service.json").write_text(json.dumps({"name": "initial"}))
99
+
100
+ poll_results = []
101
+
102
+ async def mock_scan_cycle():
103
+ # Simulate scanning
104
+ import sys
105
+ sys.path.insert(0, str(Path("discovery").resolve()))
106
+ from resource_snapshot import get_current_services
107
+
108
+ services = get_current_services(str(services_dir))
109
+ poll_results.append(len(services))
110
+
111
+ # First poll - should find 1 service
112
+ await mock_scan_cycle()
113
+ assert poll_results[-1] == 1
114
+
115
+ # Add new service
116
+ new_dir = services_dir / "new" / "service"
117
+ new_dir.mkdir(parents=True)
118
+ (new_dir / "service.json").write_text(json.dumps({"name": "new"}))
119
+
120
+ # Second poll - should find 2 services
121
+ await mock_scan_cycle()
122
+ assert poll_results[-1] == 2
@@ -0,0 +1,115 @@
1
+ """
2
+ Tests for daemon auto-registration functionality.
3
+ Tests the triggering of Tilt resource creation for new services.
4
+ """
5
+
6
+ import pytest
7
+ from pathlib import Path
8
+ from unittest.mock import Mock, patch, call
9
+ import json
10
+
11
+
12
+ @pytest.mark.daemon
13
+ class TestDaemonRegistration:
14
+ """Tests for daemon auto-registration."""
15
+
16
+ def test_new_resource_triggers_registration(self, temp_dir):
17
+ """
18
+ Scenario: New service triggers registration
19
+ WHEN the daemon detects a new service.json file not in the cache
20
+ THEN it SHALL emit a registration event with the service path and metadata
21
+ AND the Tilt resource for that service SHALL be created
22
+ """
23
+ from pathlib import Path
24
+ import sys
25
+ sys.path.insert(0, str(Path("discovery").resolve()))
26
+ from resource_snapshot import diff_snapshots, save_snapshot, load_snapshot
27
+
28
+ original_dir = Path.cwd()
29
+
30
+ try:
31
+ # Set up initial snapshot
32
+ services_dir = temp_dir / "services" / "product"
33
+ initial_service = services_dir / "existing" / "service"
34
+ initial_service.mkdir(parents=True)
35
+ (initial_service / "service.json").write_text(json.dumps({"name": "existing"}))
36
+
37
+ import os
38
+ os.chdir(temp_dir)
39
+
40
+ # Save initial snapshot
41
+ from resource_snapshot import get_current_services
42
+ initial_services = get_current_services("services/product")
43
+ save_snapshot(initial_services)
44
+
45
+ # Simulate adding new service
46
+ new_service = services_dir / "new" / "service"
47
+ new_service.mkdir(parents=True)
48
+ (new_service / "service.json").write_text(json.dumps({"name": "new"}))
49
+
50
+ # Get current services and diff
51
+ current_services = get_current_services("services/product")
52
+ old_snapshot = load_snapshot()
53
+
54
+ added, removed, modified = diff_snapshots(old_snapshot, current_services)
55
+
56
+ # Verify new service was detected
57
+ assert len(added) == 1, f"Expected 1 new service, got {len(added)}"
58
+ assert any("new" in s for s in added), "Should detect the new service"
59
+ finally:
60
+ os.chdir(original_dir)
61
+
62
+ def test_unchanged_services_do_not_trigger(self, temp_dir):
63
+ """
64
+ Scenario: No registration for unchanged services
65
+ WHEN the daemon scans and finds no changes from previous snapshot
66
+ THEN it SHALL NOT emit any registration events
67
+ """
68
+ from pathlib import Path
69
+ import sys
70
+ import os
71
+ sys.path.insert(0, str(Path("discovery").resolve()))
72
+ from resource_snapshot import save_snapshot, load_snapshot, diff_snapshots, get_current_services
73
+
74
+ original_dir = Path.cwd()
75
+
76
+ try:
77
+ # Set up services
78
+ services_dir = temp_dir / "services" / "product"
79
+ resource_dir = services_dir / "stable" / "service"
80
+ resource_dir.mkdir(parents=True)
81
+ (resource_dir / "service.json").write_text(json.dumps({"name": "stable"}))
82
+
83
+ os.chdir(temp_dir)
84
+
85
+ # Save snapshot
86
+ services = get_current_services("services/product")
87
+ save_snapshot(services)
88
+
89
+ # Scan again (no changes)
90
+ current_services = get_current_services("services/product")
91
+ old_snapshot = load_snapshot()
92
+
93
+ added, removed, modified = diff_snapshots(old_snapshot, current_services)
94
+
95
+ # Verify no changes detected
96
+ assert len(added) == 0, "Should have no added services"
97
+ assert len(removed) == 0, "Should have no removed services"
98
+ assert len(modified) == 0, "Should have no modified services"
99
+ finally:
100
+ os.chdir(original_dir)
101
+
102
+ def test_registration_includes_resource_metadata(self):
103
+ """Test that registration events include full service metadata."""
104
+ mock_registration_event = {
105
+ "path": "services/product/test/service.json",
106
+ "name": "test-service",
107
+ "type": "backend",
108
+ "stack": "test",
109
+ "port": 4000
110
+ }
111
+
112
+ assert "path" in mock_registration_event
113
+ assert "name" in mock_registration_event
114
+ assert "type" in mock_registration_event
115
+ assert "port" in mock_registration_event
@@ -0,0 +1,107 @@
1
+ """
2
+ Tests for daemon service validation.
3
+ Tests that invalid service.json files are rejected before registration.
4
+ """
5
+
6
+ import pytest
7
+ import json
8
+ from pathlib import Path
9
+
10
+
11
+ @pytest.mark.daemon
12
+ class TestDaemonValidation:
13
+ """Tests for service validation."""
14
+
15
+ def test_invalid_resource_json_rejected(self):
16
+ """
17
+ Scenario: Invalid service.json rejected
18
+ WHEN a new service.json file has missing required fields (e.g., no `name`)
19
+ THEN the daemon SHALL log "❌ Invalid service.json at {path}"
20
+ AND it SHALL NOT attempt registration
21
+ """
22
+ # Invalid service - missing required fields
23
+ invalid_service = {
24
+ "type": "backend",
25
+ "port": 4000
26
+ # Missing: name, domain, path
27
+ }
28
+
29
+ # Validate required fields
30
+ required_fields = ["name", "type", "domain", "path"]
31
+ missing_fields = [f for f in required_fields if f not in invalid_service]
32
+
33
+ assert len(missing_fields) > 0, "Should detect missing fields"
34
+ assert "name" in missing_fields, "Should detect missing 'name' field"
35
+
36
+ # In real daemon, this would be rejected
37
+ is_valid = len(missing_fields) == 0
38
+ assert not is_valid, "Invalid service should be rejected"
39
+
40
+ def test_valid_resource_json_accepted(self):
41
+ """
42
+ Scenario: Valid service.json accepted
43
+ WHEN a new service.json file has all required fields (`name`, `type`, `domain`, `port`)
44
+ THEN the daemon SHALL proceed with registration
45
+ """
46
+ # Valid service with all required fields
47
+ valid_service = {
48
+ "name": "test-service",
49
+ "type": "backend",
50
+ "stack": "test",
51
+ "port": 4000,
52
+ "path": "services/product/test/test-service"
53
+ }
54
+
55
+ required_fields = ["appName", "appType", "stack", "path"]
56
+ missing_fields = [f for f in required_fields if f not in valid_service]
57
+
58
+ is_valid = len(missing_fields) == 0
59
+ assert is_valid, "Valid service should be accepted"
60
+
61
+ def test_validation_detects_port_range(self):
62
+ """Test that port is validated to be in reasonable range."""
63
+ test_cases = [
64
+ ({"appName": "test", "appType": "backend", "stack": "test", "path": "p", "port": 3000}, True),
65
+ ({"appName": "test", "appType": "backend", "stack": "test", "path": "p", "port": 9999}, True),
66
+ ({"appName": "test", "appType": "backend", "stack": "test", "path": "p", "port": 80}, False), # Too low
67
+ ({"appName": "test", "appType": "backend", "stack": "test", "path": "p", "port": 100000}, False), # Too high
68
+ ]
69
+
70
+ for service, expected_valid in test_cases:
71
+ port = service.get("port", 0)
72
+ is_valid = 3000 <= port <= 9999
73
+ assert is_valid == expected_valid, f"Port {port} validation failed"
74
+
75
+ def test_validation_detects_invalid_type(self):
76
+ """Test that appType is validated against allowed values."""
77
+ valid_types = ["backend", "frontend", "library", "migrator", "sdk", "worker"]
78
+
79
+ test_cases = [
80
+ ("backend", True),
81
+ ("frontend", True),
82
+ ("microservice", False), # Invalid type
83
+ ("api", False), # Invalid type
84
+ ]
85
+
86
+ for app_type, expected_valid in test_cases:
87
+ is_valid = app_type in valid_types
88
+ assert is_valid == expected_valid, f"Type '{app_type}' validation failed"
89
+
90
+ def test_validation_allows_optional_fields(self):
91
+ """Test that optional fields don't cause validation failure."""
92
+ resource_with_optional = {
93
+ "name": "test-service",
94
+ "type": "backend",
95
+ "stack": "test",
96
+ "port": 4000,
97
+ "path": "services/product/test/test-service",
98
+ # Optional fields
99
+ "features": ["prisma", "nats"],
100
+ "replicas": 2,
101
+ "healthCheck": True
102
+ }
103
+
104
+ required_fields = ["name", "type", "domain", "path"]
105
+ has_all_required = all(f in resource_with_optional for f in required_fields)
106
+
107
+ assert has_all_required, "Service with optional fields should still be valid"