@tdk-landscape/tdk-cli-core 1.3.15 → 1.3.26

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 (274) hide show
  1. package/Tiltfile +144 -0
  2. package/discovery/AGENTS.md +362 -0
  3. package/discovery/TILT_DISCOVERY.star +424 -0
  4. package/discovery/Tiltfile +58 -0
  5. package/discovery/config.star +258 -0
  6. package/discovery/constants.star +127 -0
  7. package/discovery/deterministic.star +211 -0
  8. package/discovery/discovery_daemon.star +191 -0
  9. package/discovery/discovery_orchestrator.star +460 -0
  10. package/discovery/filtering.star +5 -0
  11. package/discovery/identity-management-backend/service.yaml +26 -0
  12. package/discovery/identity-management-frontend/service.yaml +29 -0
  13. package/discovery/identity-sdk/service.yaml +9 -0
  14. package/discovery/incremental_discovery.star +213 -0
  15. package/discovery/indexing.star +7 -0
  16. package/discovery/json_manifest_scanner.star +51 -0
  17. package/discovery/libraries.star +46 -0
  18. package/discovery/loading.star +23 -0
  19. package/discovery/manifest/constants.star +104 -0
  20. package/discovery/manifest/loading.star +386 -0
  21. package/discovery/manifest/normalize.star +281 -0
  22. package/discovery/manifest/validation.star +108 -0
  23. package/discovery/normalization.star +11 -0
  24. package/discovery/registry.star +544 -0
  25. package/discovery/resource_snapshot.py +382 -0
  26. package/discovery/resource_snapshot.star +143 -0
  27. package/discovery/schemas.star +1 -0
  28. package/discovery/services/platform/cdc/debezium/nats-connector/service.yaml +9 -0
  29. package/discovery/services/platform/cdc/nats-http-bridge/service.yaml +9 -0
  30. package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +26 -0
  31. package/discovery/services/platform/synthetic-monitor/service.yaml +9 -0
  32. package/discovery/test_resource_snapshot.py +251 -0
  33. package/discovery/validation.star +78 -0
  34. package/discovery/versioning.star +1 -0
  35. package/dist/commands/config.d.ts.map +1 -1
  36. package/dist/commands/config.js +11 -0
  37. package/dist/commands/config.js.map +1 -1
  38. package/dist/commands/doctor.d.ts.map +1 -1
  39. package/dist/commands/doctor.js +1 -2
  40. package/dist/commands/doctor.js.map +1 -1
  41. package/dist/commands/project.d.ts.map +1 -1
  42. package/dist/commands/project.js +36 -41
  43. package/dist/commands/project.js.map +1 -1
  44. package/dist/commands/resource.d.ts.map +1 -1
  45. package/dist/commands/resource.js +51 -55
  46. package/dist/commands/resource.js.map +1 -1
  47. package/dist/commands/stack.d.ts.map +1 -1
  48. package/dist/commands/stack.js +22 -24
  49. package/dist/commands/stack.js.map +1 -1
  50. package/dist/commands/ui.d.ts.map +1 -1
  51. package/dist/commands/ui.js +1 -2
  52. package/dist/commands/ui.js.map +1 -1
  53. package/dist/commands/up.d.ts.map +1 -1
  54. package/dist/commands/up.js +1 -2
  55. package/dist/commands/up.js.map +1 -1
  56. package/dist/commands/upgrade.d.ts +8 -0
  57. package/dist/commands/upgrade.d.ts.map +1 -1
  58. package/dist/commands/upgrade.js +59 -18
  59. package/dist/commands/upgrade.js.map +1 -1
  60. package/dist/generator/extension-fetch.d.ts +19 -0
  61. package/dist/generator/extension-fetch.d.ts.map +1 -1
  62. package/dist/generator/extension-fetch.js +71 -5
  63. package/dist/generator/extension-fetch.js.map +1 -1
  64. package/dist/generator/template-engine.d.ts +1 -0
  65. package/dist/generator/template-engine.d.ts.map +1 -1
  66. package/dist/generator/template-engine.js +50 -6
  67. package/dist/generator/template-engine.js.map +1 -1
  68. package/dist/utils/command-helpers.d.ts.map +1 -1
  69. package/dist/utils/command-helpers.js +7 -9
  70. package/dist/utils/command-helpers.js.map +1 -1
  71. package/dist/utils/constants.d.ts +1 -0
  72. package/dist/utils/constants.d.ts.map +1 -1
  73. package/dist/utils/constants.js +1 -0
  74. package/dist/utils/constants.js.map +1 -1
  75. package/dist/utils/errors.d.ts +2 -0
  76. package/dist/utils/errors.d.ts.map +1 -1
  77. package/dist/utils/errors.js +6 -1
  78. package/dist/utils/errors.js.map +1 -1
  79. package/dist/utils/project-features.d.ts.map +1 -1
  80. package/dist/utils/project-features.js +1 -1
  81. package/dist/utils/project-features.js.map +1 -1
  82. package/dist/utils/resource-features.d.ts.map +1 -1
  83. package/dist/utils/resource-features.js +2 -2
  84. package/dist/utils/resource-features.js.map +1 -1
  85. package/dist/utils/tar.js +4 -4
  86. package/dist/utils/tar.js.map +1 -1
  87. package/dist/utils/validation.d.ts +7 -0
  88. package/dist/utils/validation.d.ts.map +1 -1
  89. package/dist/utils/validation.js +9 -0
  90. package/dist/utils/validation.js.map +1 -1
  91. package/engine/AGENTS.md +705 -0
  92. package/engine/README.md +29 -0
  93. package/engine/REFACTORING_MANIFEST_SYSTEM.md +974 -0
  94. package/engine/Tiltfile +80 -0
  95. package/engine/assets/docker/golden-layers.Dockerfile +221 -0
  96. package/engine/assets/scripts/cleanup-completed-migrator-images.sh +37 -0
  97. package/engine/assets/scripts/cleanup-tilt-images.sh +120 -0
  98. package/engine/assets/scripts/mac-m1-resource-monitor.sh +121 -0
  99. package/engine/config.star +3 -0
  100. package/engine/docs/AUTODISCOVERY_TEST.md +182 -0
  101. package/engine/docs/C4-ARCHITECTURE-operations.md +396 -0
  102. package/engine/docs/C4-ARCHITECTURE-overview.md +284 -0
  103. package/engine/docs/C4-ARCHITECTURE.md +17 -0
  104. package/engine/docs/GOLDEN_IMAGE_INTEGRATION.md +349 -0
  105. package/engine/docs/MANIFEST_DRIVEN_ARCHITECTURE.md +357 -0
  106. package/engine/docs/README.md +284 -0
  107. package/engine/entrypoint.star +97 -0
  108. package/engine/lifecycle/orchestrator.star +260 -0
  109. package/engine/paths.star +717 -0
  110. package/engine/schemas/manifest-schema.json +301 -0
  111. package/engine/schemas/service-schema.json +301 -0
  112. package/engine/scripts/README.md +89 -0
  113. package/engine/scripts/build-and-publish-lib.sh +205 -0
  114. package/engine/scripts/check-lib-publish.sh +85 -0
  115. package/engine/scripts/debug-container.sh +258 -0
  116. package/engine/scripts/generate-manifest.sh +197 -0
  117. package/engine/spec.master +702 -0
  118. package/engine/spec.master.example +341 -0
  119. package/engine/topologies/README.md +34 -0
  120. package/engine/topologies/platform/docker/AGENTS.md +59 -0
  121. package/engine/topologies/platform/docker/auth/auth.star +167 -0
  122. package/engine/topologies/platform/docker/build/golden_image.star +37 -0
  123. package/engine/topologies/platform/docker/build/golden_image_build.star +82 -0
  124. package/engine/topologies/platform/docker/build/golden_image_constants.star +93 -0
  125. package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +291 -0
  126. package/engine/topologies/platform/docker/compose/compose.star +636 -0
  127. package/engine/topologies/platform/docker/compose/compose_helpers.star +52 -0
  128. package/engine/topologies/platform/docker/compose/traefik_standalone.star +53 -0
  129. package/engine/topologies/platform/docker/config/bunignore.star +142 -0
  130. package/engine/topologies/platform/docker/config/dockerignore.star +150 -0
  131. package/engine/topologies/platform/docker/constants.star +170 -0
  132. package/engine/topologies/platform/docker/dockerfile/dockerfile.star +54 -0
  133. package/engine/topologies/platform/docker/generators/golden_docker_generator_v2.star +73 -0
  134. package/engine/topologies/platform/docker/index.star +27 -0
  135. package/engine/topologies/platform/docker/layers/bun_helpers.star +23 -0
  136. package/engine/topologies/platform/docker/layers/infisical/index.star +40 -0
  137. package/engine/topologies/platform/docker/layers/infisical/infisical_docker.star +262 -0
  138. package/engine/topologies/platform/docker/layers/l1_base_layers.star +87 -0
  139. package/engine/topologies/platform/docker/layers/l2_dependency_layers.star +125 -0
  140. package/engine/topologies/platform/docker/layers/l3_builder_layers.star +209 -0
  141. package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +214 -0
  142. package/engine/topologies/platform/docker/layers/prisma/constants.star +135 -0
  143. package/engine/topologies/platform/docker/layers/prisma/db_readiness.star +96 -0
  144. package/engine/topologies/platform/docker/layers/prisma/filesystem_setup.star +109 -0
  145. package/engine/topologies/platform/docker/layers/prisma/migrate_script.star +98 -0
  146. package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +126 -0
  147. package/engine/topologies/platform/docker/layers/prisma/prisma_runtime.star +161 -0
  148. package/engine/topologies/platform/docker/layers/prisma/secret_resolution.star +119 -0
  149. package/engine/topologies/platform/docker/networking/api_path_constants.star +230 -0
  150. package/engine/topologies/platform/docker/networking/sablier_container_cycle.star +27 -0
  151. package/engine/topologies/platform/docker/networking/traefik.star +232 -0
  152. package/engine/topologies/platform/docker/networking/traefik_constants.star +146 -0
  153. package/engine/topologies/platform/docker/networking/traefik_helpers.star +160 -0
  154. package/engine/topologies/platform/docker/secrets/infisical.star +135 -0
  155. package/engine/topologies/platform/net/proxy.star +4 -0
  156. package/engine/topologies/platform/registries/local.star +4 -0
  157. package/engine/topologies/platform/security/secrets.star +239 -0
  158. package/engine/topologies/platform/state/relational.star +4 -0
  159. package/engine/topologies/tilt/common/AGENTS.md +59 -0
  160. package/engine/topologies/tilt/common/utils.star +364 -0
  161. package/engine/topologies/tilt/common/utils_debug.star +69 -0
  162. package/engine/topologies/tilt/common/utils_deterministic.star +168 -0
  163. package/engine/topologies/tilt/common/utils_docker_networks.star +46 -0
  164. package/engine/topologies/tilt/common/utils_env.star +43 -0
  165. package/engine/topologies/tilt/common/utils_output.star +97 -0
  166. package/engine/topologies/tilt/config/AGENTS.md +68 -0
  167. package/engine/topologies/tilt/config/defaults.star +3 -0
  168. package/engine/topologies/tilt/config/feature_flags.star +10 -0
  169. package/engine/topologies/tilt/config/global.star +55 -0
  170. package/engine/topologies/tilt/config/profiles.star +382 -0
  171. package/engine/topologies/tilt/generators/agents_md.star +62 -0
  172. package/engine/topologies/tilt/generators/api_gateway.star +145 -0
  173. package/engine/topologies/tilt/generators/index.star +124 -0
  174. package/engine/topologies/tilt/generators/infisical/README.md +424 -0
  175. package/engine/topologies/tilt/generators/infisical/examples.star +312 -0
  176. package/engine/topologies/tilt/generators/infisical/index.star +57 -0
  177. package/engine/topologies/tilt/generators/infisical/machine_identity.star +420 -0
  178. package/engine/topologies/tilt/generators/infisical/organization.star +534 -0
  179. package/engine/topologies/tilt/generators/infisical/path_manager.star +476 -0
  180. package/engine/topologies/tilt/generators/infisical/secrets_generator.star +420 -0
  181. package/engine/topologies/tilt/generators/nginx_conf.star +143 -0
  182. package/engine/topologies/tilt/generators/npmrc.star +111 -0
  183. package/engine/topologies/tilt/generators/tsconfig.star +23 -0
  184. package/engine/topologies/tilt/generators/typescript/backend_library_tsconfig.star +71 -0
  185. package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +124 -0
  186. package/engine/topologies/tilt/generators/typescript/common.star +7 -0
  187. package/engine/topologies/tilt/generators/typescript/frontend_library_tsconfig.star +76 -0
  188. package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +110 -0
  189. package/engine/topologies/tilt/generators/typescript/include.star +23 -0
  190. package/engine/topologies/tilt/generators/typescript/playwright_config.star +40 -0
  191. package/engine/topologies/tilt/generators/typescript/prisma_config.star +49 -0
  192. package/engine/topologies/tilt/generators/validators.star +853 -0
  193. package/engine/topologies/tilt/generators/vite/backend.star +78 -0
  194. package/engine/topologies/tilt/generators/vite/frontend.star +98 -0
  195. package/engine/topologies/tilt/generators/vite/generators.star +43 -0
  196. package/engine/topologies/tilt/generators/vite/helpers.star +244 -0
  197. package/engine/topologies/tilt/generators/vite/library.star +65 -0
  198. package/engine/topologies/tilt/generators/vite/sdk.star +73 -0
  199. package/engine/topologies/tilt/generators/vite/templates.star +737 -0
  200. package/engine/topologies/tilt/generators/vite_config.star +22 -0
  201. package/engine/topologies/tilt/manifest/AGENTS.md +61 -0
  202. package/engine/topologies/tilt/manifest/README.md +693 -0
  203. package/engine/topologies/tilt/manifest/__init__.star +218 -0
  204. package/engine/topologies/tilt/manifest/constants.star +252 -0
  205. package/engine/topologies/tilt/manifest/errors.star +266 -0
  206. package/engine/topologies/tilt/manifest/integration.star +339 -0
  207. package/engine/topologies/tilt/manifest/json_to_yaml.py +54 -0
  208. package/engine/topologies/tilt/manifest/loader.star +214 -0
  209. package/engine/topologies/tilt/manifest/mapper.star +227 -0
  210. package/engine/topologies/tilt/manifest/parser.star +400 -0
  211. package/engine/topologies/tilt/manifest/schema.star +462 -0
  212. package/engine/topologies/tilt/manifest/tests/test_integration.star +276 -0
  213. package/engine/topologies/tilt/manifest/tests/test_loader_parser.star +397 -0
  214. package/engine/topologies/tilt/manifest/tests/test_manifest.star +316 -0
  215. package/engine/topologies/tilt/manifest/tests/test_validator.star +470 -0
  216. package/engine/topologies/tilt/manifest/validator.star +717 -0
  217. package/engine/topologies/tilt/manifest/yaml_to_json.py +18 -0
  218. package/engine/topologies/tilt/resources/AGENTS.md +71 -0
  219. package/engine/topologies/tilt/resources/conditions.star +3 -0
  220. package/engine/topologies/tilt/resources/databases.star +244 -0
  221. package/engine/topologies/tilt/resources/declaration.star +3 -0
  222. package/engine/topologies/tilt/resources/deps.star +3 -0
  223. package/engine/topologies/tilt/resources/infra-loader.star +437 -0
  224. package/engine/topologies/tilt/resources/libs.star +30 -0
  225. package/engine/topologies/tilt/resources/libs_discovery.star +102 -0
  226. package/engine/topologies/tilt/resources/libs_paths.star +79 -0
  227. package/engine/topologies/tilt/resources/libs_resource_definition.star +151 -0
  228. package/engine/topologies/tilt/resources/libs_setup.star +164 -0
  229. package/engine/topologies/tilt/resources/libs_tsconfig.star +37 -0
  230. package/engine/topologies/tilt/resources/orchestrator/apply.star +77 -0
  231. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +431 -0
  232. package/engine/topologies/tilt/resources/orchestrator/apply_manifest_orchestration.star +81 -0
  233. package/engine/topologies/tilt/resources/orchestrator/apply_migrator_orchestration.star +148 -0
  234. package/engine/topologies/tilt/resources/orchestrator/apply_resource_validation.star +36 -0
  235. package/engine/topologies/tilt/resources/orchestrator/apply_runtime_flags.star +44 -0
  236. package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +164 -0
  237. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/AGENTS.md +75 -0
  238. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/index.star +21 -0
  239. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/resolver.star +134 -0
  240. package/engine/topologies/tilt/resources/orchestrator/generators/env.star +258 -0
  241. package/engine/topologies/tilt/resources/orchestrator/generators/frontend.star +169 -0
  242. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +524 -0
  243. package/engine/topologies/tilt/resources/orchestrator/helpers.star +33 -0
  244. package/engine/topologies/tilt/resources/orchestrator.star +54 -0
  245. package/engine/topologies/tilt/resources/ordering.star +3 -0
  246. package/engine/topologies/tilt/resources/triggers.star +3 -0
  247. package/engine/topologies/tilt/resources/watchers.star +299 -0
  248. package/engine/where-things-live.star +717 -0
  249. package/ext/Tiltfile +66 -0
  250. package/ext/ide-components/README.md +219 -0
  251. package/ext/ide-components/code_executor/server.py +268 -0
  252. package/ext/ide-components/code_viewer/server.py +206 -0
  253. package/ext/ide-components/config_inspector/server.py +1050 -0
  254. package/ext/ide-components/file_browser/server.py +285 -0
  255. package/ext/ide-components/shared/config_service.py +469 -0
  256. package/ext/ide-components/shared/file_utils.py +298 -0
  257. package/ext/ide-components/shared/http_utils.py +232 -0
  258. package/ext/ide-components/shared/static/styles.css +1334 -0
  259. package/ext/ide-components/shared/syntax_highlighter.py +140 -0
  260. package/ext/ide-components/shared/template_engine.py +123 -0
  261. package/ext/ide-components/shared/templates/base.html +121 -0
  262. package/ext/ide-components/shared/test_config_service.py +326 -0
  263. package/ext/ide-components/shared/tilt_integration.py +158 -0
  264. package/ext/ui-enhancements/cron-status-server.sh +49 -0
  265. package/ext/ui-enhancements/cron-status-tracker.sh +146 -0
  266. package/ext/ui-enhancements/extension.yaml +36 -0
  267. package/ext/ui-enhancements/tiltfile.py +99 -0
  268. package/ext/ui-enhancements/ui/cron-jobs-tab.css +396 -0
  269. package/ext/ui-enhancements/ui/help-panel.css +279 -0
  270. package/ext/ui-enhancements/ui/icons.css +125 -0
  271. package/ext/ui-enhancements/ui/tooltips.css +114 -0
  272. package/package.json +13 -9
  273. package/specs/specs/TILT_SERVICE_DEFAULTS.star +205 -0
  274. package/specs/specs/TILT_TECH_STACK.star +159 -0
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Automated tests for ConfigService
4
+ Tests path validation, file operations, backups, and security features.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import tempfile
10
+ import shutil
11
+ from pathlib import Path
12
+
13
+ # Add parent to path
14
+ sys.path.insert(0, str(Path(__file__).parent))
15
+ from config_service import ConfigService, SaveResult
16
+
17
+
18
+ class TestConfigService:
19
+ """Test suite for ConfigService"""
20
+
21
+ def __init__(self):
22
+ self.passed = 0
23
+ self.failed = 0
24
+ self.temp_dir = None
25
+
26
+ def setup(self):
27
+ """Create temporary test directory"""
28
+ self.temp_dir = tempfile.mkdtemp()
29
+ # Create structure mimicking project
30
+ (Path(self.temp_dir) / '.tilt-engine' / 'topologies').mkdir(parents=True)
31
+ (Path(self.temp_dir) / '.tilt-engine' / 'topologies' / 'test.star').write_text('# test')
32
+
33
+ # Create root master configs
34
+ (Path(self.temp_dir) / 'TILT_RESOURCE_DEFAULTS.star').write_text('# defaults')
35
+ (Path(self.temp_dir) / 'spec.master').write_text('# spec')
36
+
37
+ # Override PROJECT_ROOT for testing by patching the module directly
38
+ import config_service
39
+ self.original_root = config_service.PROJECT_ROOT
40
+ config_service.PROJECT_ROOT = Path(self.temp_dir)
41
+ # Also patch in file_utils
42
+ import file_utils
43
+ self.original_file_utils_root = file_utils.PROJECT_ROOT
44
+ file_utils.PROJECT_ROOT = Path(self.temp_dir)
45
+
46
+ def teardown(self):
47
+ """Cleanup temporary directory"""
48
+ # Restore original
49
+ import config_service
50
+ config_service.PROJECT_ROOT = self.original_root
51
+ import file_utils
52
+ file_utils.PROJECT_ROOT = self.original_file_utils_root
53
+
54
+ if self.temp_dir and os.path.exists(self.temp_dir):
55
+ shutil.rmtree(self.temp_dir)
56
+
57
+ def assert_true(self, condition, msg):
58
+ """Assert condition is true"""
59
+ if condition:
60
+ self.passed += 1
61
+ print(f" ✓ {msg}")
62
+ else:
63
+ self.failed += 1
64
+ print(f" ✗ {msg}")
65
+
66
+ def assert_false(self, condition, msg):
67
+ """Assert condition is false"""
68
+ self.assert_true(not condition, msg)
69
+
70
+ def assert_equals(self, a, b, msg):
71
+ """Assert two values are equal"""
72
+ if a == b:
73
+ self.passed += 1
74
+ print(f" ✓ {msg}")
75
+ else:
76
+ self.failed += 1
77
+ print(f" ✗ {msg}: expected {b}, got {a}")
78
+
79
+ def run_all_tests(self):
80
+ """Run all test methods"""
81
+ print("\n" + "="*60)
82
+ print("ConfigService Test Suite")
83
+ print("="*60)
84
+
85
+ self.setup()
86
+ try:
87
+ self.test_path_validation()
88
+ self.test_file_type_validation()
89
+ self.test_hash_computation()
90
+ self.test_backup_rotation()
91
+ self.test_conflict_detection()
92
+ self.test_atomic_write()
93
+ self.test_csrf_validation()
94
+ self.test_content_length_validation()
95
+ finally:
96
+ self.teardown()
97
+
98
+ print("\n" + "="*60)
99
+ print(f"Results: {self.passed} passed, {self.failed} failed")
100
+ print("="*60)
101
+ return self.failed == 0
102
+
103
+ def test_path_validation(self):
104
+ """Test 7.4, 7.10, 7.11: Path validation and traversal prevention"""
105
+ print("\n📁 Testing Path Validation (Tasks 7.4, 7.10, 7.11)")
106
+
107
+ # Valid paths - topologies
108
+ valid_paths = [
109
+ '.tilt-engine/topologies/test.star',
110
+ '.tilt-engine/topologies/tilt/discovery/registry.star',
111
+ ]
112
+
113
+ for path in valid_paths:
114
+ result = ConfigService.validate_write_path(path)
115
+ self.assert_true(result is not None, f"Valid path accepted: {path}")
116
+
117
+ # Valid paths - project root master configs (Task 7.11)
118
+ root_configs = [
119
+ 'TILT_RESOURCE_DEFAULTS.star',
120
+ 'spec.master',
121
+ '.tilt-engine/spec.master',
122
+ ]
123
+
124
+ for path in root_configs:
125
+ result = ConfigService.validate_write_path(path)
126
+ self.assert_true(result is not None, f"Root config accepted: {path}")
127
+
128
+ # Invalid paths - traversal attempts
129
+ invalid_paths = [
130
+ '../outside.star',
131
+ '.tilt-engine/../../etc/passwd',
132
+ '%2e%2e%2fsecret.star',
133
+ '/etc/passwd',
134
+ 'random/file.star',
135
+ 'README.md',
136
+ ]
137
+
138
+ for path in invalid_paths:
139
+ result = ConfigService.validate_write_path(path)
140
+ self.assert_true(result is None, f"Invalid path rejected: {path}")
141
+
142
+ def test_file_type_validation(self):
143
+ """Test 7.12: File extension validation"""
144
+ print("\n📄 Testing File Type Validation (Task 7.12)")
145
+
146
+ # Valid file types
147
+ valid_files = [
148
+ 'test.star',
149
+ 'spec.master',
150
+ '.tilt-engine/spec.master',
151
+ 'TILT_RESOURCE_DEFAULTS.star',
152
+ ]
153
+
154
+ for path in valid_files:
155
+ is_valid, _ = ConfigService.validate_file_type(path)
156
+ self.assert_true(is_valid, f"Valid file type: {path}")
157
+
158
+ # Invalid file types
159
+ invalid_files = [
160
+ 'README.md',
161
+ 'file.txt',
162
+ 'script.py',
163
+ 'config.json',
164
+ ]
165
+
166
+ for path in invalid_files:
167
+ is_valid, _ = ConfigService.validate_file_type(path)
168
+ self.assert_false(is_valid, f"Invalid file type rejected: {path}")
169
+
170
+ def test_hash_computation(self):
171
+ """Test 7.9: Hash computation for conflict detection"""
172
+ print("\n🔐 Testing Hash Computation (Task 7.9)")
173
+
174
+ # Same content produces same hash
175
+ content1 = "Hello, World!"
176
+ hash1 = ConfigService.compute_content_hash(content1)
177
+ hash2 = ConfigService.compute_content_hash(content1)
178
+ self.assert_equals(hash1, hash2, "Same content produces same hash")
179
+
180
+ # Different content produces different hash
181
+ content2 = "Hello, World"
182
+ hash3 = ConfigService.compute_content_hash(content2)
183
+ self.assert_true(hash1 != hash3, "Different content produces different hash")
184
+
185
+ # Hash is SHA-256 (64 hex characters)
186
+ self.assert_equals(len(hash1), 64, "Hash is 64 hex chars (SHA-256)")
187
+
188
+ def test_backup_rotation(self):
189
+ """Test 7.8: Backup creation and rotation"""
190
+ print("\n💾 Testing Backup Rotation (Task 7.8)")
191
+
192
+ import time
193
+
194
+ # Create test file
195
+ test_file = Path(self.temp_dir) / '.tilt-engine' / 'topologies' / 'backup_test.star'
196
+ test_file.write_text('version 1')
197
+
198
+ # Create multiple backups with small delays to ensure different timestamps
199
+ for i in range(5):
200
+ time.sleep(0.01) # Small delay to ensure different timestamps
201
+ test_file.write_text(f'version {i+2}')
202
+ backup_path, msg = ConfigService.create_backup_with_rotation(test_file, max_backups=3)
203
+ self.assert_true(backup_path is not None, f"Backup {i+1} created: {msg}")
204
+
205
+ # Check only 3 backups exist
206
+ backup_dir = test_file.parent / '.backups'
207
+ if backup_dir.exists():
208
+ backups = list(backup_dir.glob('backup_test.star.*.backup'))
209
+ self.assert_true(len(backups) <= 3, f"Rotation keeps max 3 backups (found {len(backups)})")
210
+ else:
211
+ self.assert_true(False, "Backup directory should exist")
212
+
213
+ def test_conflict_detection(self):
214
+ """Test 7.9: File content hash conflict detection"""
215
+ print("\n⚠️ Testing Conflict Detection (Task 7.9)")
216
+
217
+ # Create test file
218
+ test_file = Path(self.temp_dir) / '.tilt-engine' / 'topologies' / 'conflict_test.star'
219
+ original_content = "Original content"
220
+ test_file.write_text(original_content)
221
+
222
+ # Get original hash
223
+ original_hash = ConfigService.compute_content_hash(original_content)
224
+
225
+ # No conflict when file unchanged
226
+ has_conflict, msg = ConfigService.check_conflict(test_file, original_hash)
227
+ self.assert_false(has_conflict, "No conflict when file unchanged")
228
+
229
+ # Modify file
230
+ test_file.write_text("Modified content")
231
+
232
+ # Conflict detected after modification
233
+ has_conflict, msg = ConfigService.check_conflict(test_file, original_hash)
234
+ self.assert_true(has_conflict, "Conflict detected after file modification")
235
+
236
+ def test_atomic_write(self):
237
+ """Test 7.7, 7.20: Atomic write and failure recovery"""
238
+ print("\n📝 Testing Atomic Write (Tasks 7.7, 7.20)")
239
+
240
+ # Use relative path that will pass validation
241
+ rel_path = '.tilt-engine/topologies/atomic_test.star'
242
+ abs_path = Path(self.temp_dir) / rel_path
243
+
244
+ # Verify path validation works
245
+ validated = ConfigService.validate_write_path(rel_path)
246
+ self.assert_true(validated is not None, "Path validation for atomic write")
247
+
248
+ # Test successful write (using relative path)
249
+ result = ConfigService.atomic_write(validated, "Test content", create_backup=False)
250
+ self.assert_true(result.success, f"Atomic write succeeds: {result.message}")
251
+ self.assert_true(abs_path.exists(), "File created")
252
+ self.assert_equals(abs_path.read_text(), "Test content", "Content written correctly")
253
+
254
+ # Test write with backup
255
+ result = ConfigService.atomic_write(validated, "Updated content", create_backup=True)
256
+ self.assert_true(result.success, f"Atomic write with backup succeeds: {result.message}")
257
+ self.assert_true(result.backup_path is not None, "Backup created")
258
+
259
+ # Test conflict detection during write
260
+ original_hash = ConfigService.compute_content_hash("Wrong hash")
261
+ result = ConfigService.atomic_write(
262
+ validated,
263
+ "New content",
264
+ original_hash=original_hash,
265
+ create_backup=False
266
+ )
267
+ self.assert_false(result.success, "Write fails with hash conflict")
268
+
269
+ def test_csrf_validation(self):
270
+ """Test 7.13: CSRF protection"""
271
+ print("\n🛡️ Testing CSRF Protection (Task 7.13)")
272
+
273
+ # Valid token with localhost origin
274
+ is_valid, _ = ConfigService.validate_csrf_token(
275
+ "valid_token_12345",
276
+ referer="http://localhost:9767/",
277
+ origin="http://localhost:9767"
278
+ )
279
+ self.assert_true(is_valid, "Valid CSRF token with localhost origin")
280
+
281
+ # Invalid token (too short)
282
+ is_valid, _ = ConfigService.validate_csrf_token(
283
+ "short",
284
+ referer="http://localhost:9767/",
285
+ origin="http://localhost:9767"
286
+ )
287
+ self.assert_false(is_valid, "Short token rejected")
288
+
289
+ # Valid token but wrong origin
290
+ is_valid, _ = ConfigService.validate_csrf_token(
291
+ "valid_token_12345",
292
+ referer="https://evil.com/",
293
+ origin="https://evil.com"
294
+ )
295
+ self.assert_false(is_valid, "Wrong origin rejected")
296
+
297
+ def test_content_length_validation(self):
298
+ """Test content length limits"""
299
+ print("\n📏 Testing Content Length Validation")
300
+
301
+ # Valid content length
302
+ is_valid, _ = ConfigService.validate_request_content_length("1024")
303
+ self.assert_true(is_valid, "Valid content length accepted")
304
+
305
+ # Too large
306
+ is_valid, _ = ConfigService.validate_request_content_length(str(2 * 1024 * 1024))
307
+ self.assert_false(is_valid, "Oversized content rejected")
308
+
309
+ # Missing
310
+ is_valid, _ = ConfigService.validate_request_content_length(None)
311
+ self.assert_false(is_valid, "Missing content length rejected")
312
+
313
+ # Invalid format
314
+ is_valid, _ = ConfigService.validate_request_content_length("invalid")
315
+ self.assert_false(is_valid, "Invalid content length rejected")
316
+
317
+
318
+ def run_tests():
319
+ """Run all tests and return exit code"""
320
+ tester = TestConfigService()
321
+ success = tester.run_all_tests()
322
+ return 0 if success else 1
323
+
324
+
325
+ if __name__ == '__main__':
326
+ sys.exit(run_tests())
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Tilt integration utilities for IDE components
4
+ """
5
+
6
+ import subprocess
7
+ import json
8
+
9
+ from file_utils import PROJECT_ROOT
10
+
11
+
12
+ class TiltIntegration:
13
+ """Utilities for calling Tilt commands from Python"""
14
+
15
+ @staticmethod
16
+ def trigger_resource(resource_name: str) -> dict:
17
+ """
18
+ Trigger a Tilt resource (e.g., config-gen for a service)
19
+
20
+ Returns: {'success': bool, 'output': str, 'error': str}
21
+ """
22
+ try:
23
+ result = subprocess.run(
24
+ ['tilt', 'trigger', resource_name],
25
+ cwd=PROJECT_ROOT,
26
+ capture_output=True,
27
+ text=True,
28
+ timeout=30
29
+ )
30
+
31
+ return {
32
+ 'success': result.returncode == 0,
33
+ 'output': result.stdout,
34
+ 'error': result.stderr if result.returncode != 0 else None
35
+ }
36
+ except subprocess.TimeoutExpired:
37
+ return {'success': False, 'output': '', 'error': 'Command timed out'}
38
+ except FileNotFoundError:
39
+ return {'success': False, 'output': '', 'error': 'tilt command not found. Is Tilt installed?'}
40
+ except Exception as e:
41
+ return {'success': False, 'output': '', 'error': str(e)}
42
+
43
+ @staticmethod
44
+ def get_resources() -> list:
45
+ """
46
+ Get list of all Tilt resources
47
+
48
+ Returns: List of resource names
49
+ """
50
+ try:
51
+ result = subprocess.run(
52
+ ['tilt', 'get', 'uiresources', '-o', 'json'],
53
+ cwd=PROJECT_ROOT,
54
+ capture_output=True,
55
+ text=True,
56
+ timeout=10
57
+ )
58
+
59
+ if result.returncode != 0:
60
+ return []
61
+
62
+ data = json.loads(result.stdout)
63
+ resources = []
64
+
65
+ for item in data.get('items', []):
66
+ name = item.get('metadata', {}).get('name', '')
67
+ if name:
68
+ resources.append(name)
69
+
70
+ return resources
71
+ except Exception:
72
+ return []
73
+
74
+ @staticmethod
75
+ def get_resource_status(resource_name: str) -> dict:
76
+ """
77
+ Get status of a specific Tilt resource
78
+
79
+ Returns: {'exists': bool, 'status': str, 'error': str}
80
+ """
81
+ try:
82
+ result = subprocess.run(
83
+ ['tilt', 'get', 'uiresources', resource_name, '-o', 'json'],
84
+ cwd=PROJECT_ROOT,
85
+ capture_output=True,
86
+ text=True,
87
+ timeout=10
88
+ )
89
+
90
+ if result.returncode != 0:
91
+ return {'exists': False, 'status': 'unknown', 'error': result.stderr}
92
+
93
+ data = json.loads(result.stdout)
94
+ status = data.get('status', {}).get('runtimeStatus', 'unknown')
95
+
96
+ return {'exists': True, 'status': status, 'error': None}
97
+ except Exception as e:
98
+ return {'exists': False, 'status': 'unknown', 'error': str(e)}
99
+
100
+ @staticmethod
101
+ def regenerate_resource_configs(resource_name: str) -> dict:
102
+ """
103
+ Trigger config regeneration for a service
104
+
105
+ Tries common config-gen resource naming patterns:
106
+ - <service>-config-gen
107
+ - <service>-yaml
108
+
109
+ Returns: {'success': bool, 'message': str}
110
+ """
111
+ # Try different resource name patterns
112
+ patterns = [
113
+ f'{resource_name}-config-gen',
114
+ f'{resource_name}-yaml',
115
+ f'{resource_name}-config',
116
+ ]
117
+
118
+ for pattern in patterns:
119
+ result = TiltIntegration.trigger_resource(pattern)
120
+ if result['success']:
121
+ return {
122
+ 'success': True,
123
+ 'message': f'Regenerated configs for {resource_name} via {pattern}'
124
+ }
125
+
126
+ return {
127
+ 'success': False,
128
+ 'message': f'Could not find config-gen resource for {resource_name}. Tried: {", ".join(patterns)}'
129
+ }
130
+
131
+ @staticmethod
132
+ def is_tilt_running() -> bool:
133
+ """Check if Tilt is currently running"""
134
+ try:
135
+ result = subprocess.run(
136
+ ['tilt', 'status'],
137
+ cwd=PROJECT_ROOT,
138
+ capture_output=True,
139
+ text=True,
140
+ timeout=5
141
+ )
142
+ return result.returncode == 0
143
+ except Exception:
144
+ return False
145
+
146
+
147
+ if __name__ == '__main__':
148
+ # Test
149
+ print(f"Project root: {PROJECT_ROOT}")
150
+ print(f"Tilt running: {TiltIntegration.is_tilt_running()}")
151
+
152
+ resources = TiltIntegration.get_resources()
153
+ print(f"Found {len(resources)} resources")
154
+
155
+ # Show first 5
156
+ for name in resources[:5]:
157
+ status = TiltIntegration.get_resource_status(name)
158
+ print(f" - {name}: {status['status']}")
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+ # ============================================================================
3
+ # Cron Jobs Status HTTP Server
4
+ # Serves cron job status as JSON for the UI extension
5
+ # ============================================================================
6
+
7
+ STATUS_FILE="/tmp/cron-jobs-status.json"
8
+ PORT=10352
9
+
10
+ echo "🌐 Cron Jobs Status Server starting on port ${PORT}..."
11
+
12
+ # Simple HTTP server using Python
13
+ python3 << 'PYEOF'
14
+ import http.server
15
+ import socketserver
16
+ import json
17
+ import os
18
+
19
+ STATUS_FILE = "/tmp/cron-jobs-status.json"
20
+ PORT = 10352
21
+
22
+ class Handler(http.server.SimpleHTTPRequestHandler):
23
+ def do_GET(self):
24
+ if self.path == '/api/cron-jobs-status':
25
+ self.send_response(200)
26
+ self.send_header('Content-Type', 'application/json')
27
+ self.send_header('Access-Control-Allow-Origin', '*')
28
+ self.end_headers()
29
+
30
+ # Read status file or return default
31
+ if os.path.exists(STATUS_FILE):
32
+ with open(STATUS_FILE, 'r') as f:
33
+ data = f.read()
34
+ self.wfile.write(data.encode())
35
+ else:
36
+ default = {"jobs": [], "updatedAt": None}
37
+ self.wfile.write(json.dumps(default).encode())
38
+ else:
39
+ self.send_response(404)
40
+ self.end_headers()
41
+
42
+ def log_message(self, format, *args):
43
+ # Suppress log messages
44
+ pass
45
+
46
+ with socketserver.TCPServer(("", PORT), Handler) as httpd:
47
+ print(f"Cron Jobs Status Server running on port {PORT}")
48
+ httpd.serve_forever()
49
+ PYEOF
@@ -0,0 +1,146 @@
1
+ #!/bin/bash
2
+ # ============================================================================
3
+ # Cron Jobs Status Tracker
4
+ # Tracks execution status of all cron jobs
5
+ # ============================================================================
6
+
7
+ STATUS_FILE="/tmp/cron-jobs-status.json"
8
+ CONTROL_DIR="/tmp/cron-jobs-control"
9
+ LOG_DIR="/tmp/cron-jobs-logs"
10
+
11
+ # Ensure directories exist
12
+ mkdir -p "$CONTROL_DIR" "$LOG_DIR"
13
+
14
+ # Initialize status file
15
+ init_status() {
16
+ cat > "$STATUS_FILE" << 'EOF'
17
+ {
18
+ "jobs": [
19
+ {
20
+ "name": "cron-lint",
21
+ "status": "idle",
22
+ "lastRun": null,
23
+ "nextRun": null,
24
+ "history": []
25
+ },
26
+ {
27
+ "name": "cron-format",
28
+ "status": "idle",
29
+ "lastRun": null,
30
+ "nextRun": null,
31
+ "history": []
32
+ },
33
+ {
34
+ "name": "auto-prune-dangling",
35
+ "status": "running",
36
+ "lastRun": null,
37
+ "nextRun": null,
38
+ "history": []
39
+ },
40
+ {
41
+ "name": "auto-prune-migrator-images",
42
+ "status": "running",
43
+ "lastRun": null,
44
+ "nextRun": null,
45
+ "history": []
46
+ }
47
+ ],
48
+ "updatedAt": null
49
+ }
50
+ EOF
51
+ }
52
+
53
+ # Update job status
54
+ update_job() {
55
+ local name=$1
56
+ local status=$2
57
+ local duration=$3
58
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
59
+
60
+ # Read current status
61
+ local current=$(cat "$STATUS_FILE" 2>/dev/null || echo '{"jobs":[]}')
62
+
63
+ # Calculate next run (60 seconds from now for cron jobs)
64
+ local nextRun=$(date -u -v+60S +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d '+60 seconds' +"%Y-%m-%dT%H:%M:%SZ")
65
+
66
+ # Update using Python for reliable JSON manipulation
67
+ python3 << PYEOF
68
+ import json
69
+ import sys
70
+ from datetime import datetime, timedelta
71
+
72
+ data = json.loads('''$current''')
73
+ job_name = '''$name'''
74
+ status = '''$status'''
75
+ timestamp = '''$timestamp'''
76
+ duration = '''$duration'''
77
+ next_run = '''$nextRun'''
78
+
79
+ # Find and update job
80
+ for job in data.get('jobs', []):
81
+ if job['name'] == job_name:
82
+ job['status'] = status
83
+ job['lastRun'] = timestamp
84
+ job['nextRun'] = next_run
85
+
86
+ # Add to history
87
+ if 'history' not in job:
88
+ job['history'] = []
89
+
90
+ job['history'].insert(0, {
91
+ 'time': timestamp,
92
+ 'status': status,
93
+ 'duration': duration if duration else 'N/A'
94
+ })
95
+
96
+ # Keep only last 10 entries
97
+ job['history'] = job['history'][:10]
98
+ break
99
+
100
+ data['updatedAt'] = timestamp
101
+ print(json.dumps(data, indent=2))
102
+ PYEOF
103
+ }
104
+
105
+ # Check if job should pause
106
+ check_pause() {
107
+ local name=$1
108
+ if [[ -f "$CONTROL_DIR/$name.pause" ]]; then
109
+ return 0
110
+ fi
111
+ return 1
112
+ }
113
+
114
+ # Check if manual trigger requested
115
+ check_trigger() {
116
+ local name=$1
117
+ if [[ -f "$CONTROL_DIR/$name.trigger" ]]; then
118
+ rm "$CONTROL_DIR/$name.trigger"
119
+ return 0
120
+ fi
121
+ return 1
122
+ }
123
+
124
+ # Main execution
125
+ command=$1
126
+ shift
127
+
128
+ case $command in
129
+ init)
130
+ init_status
131
+ echo "Status file initialized at $STATUS_FILE"
132
+ ;;
133
+ update)
134
+ update_job "$1" "$2" "$3" > "${STATUS_FILE}.tmp" && mv "${STATUS_FILE}.tmp" "$STATUS_FILE"
135
+ ;;
136
+ pause-check)
137
+ check_pause "$1"
138
+ ;;
139
+ trigger-check)
140
+ check_trigger "$1"
141
+ ;;
142
+ *)
143
+ echo "Usage: $0 {init|update <name> <status> [duration]|pause-check <name>|trigger-check <name>}"
144
+ exit 1
145
+ ;;
146
+ esac