@tdk-landscape/tdk-cli-core 1.3.14 → 1.3.22

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 (277) hide show
  1. package/README.md +3 -3
  2. package/Tiltfile +144 -0
  3. package/discovery/AGENTS.md +362 -0
  4. package/discovery/TILT_DISCOVERY.star +424 -0
  5. package/discovery/Tiltfile +58 -0
  6. package/discovery/config.star +258 -0
  7. package/discovery/constants.star +127 -0
  8. package/discovery/deterministic.star +211 -0
  9. package/discovery/discovery_daemon.star +191 -0
  10. package/discovery/discovery_orchestrator.star +460 -0
  11. package/discovery/filtering.star +5 -0
  12. package/discovery/identity-management-backend/service.yaml +26 -0
  13. package/discovery/identity-management-frontend/service.yaml +29 -0
  14. package/discovery/identity-sdk/service.yaml +9 -0
  15. package/discovery/incremental_discovery.star +213 -0
  16. package/discovery/indexing.star +7 -0
  17. package/discovery/json_manifest_scanner.star +51 -0
  18. package/discovery/libraries.star +46 -0
  19. package/discovery/loading.star +23 -0
  20. package/discovery/manifest/constants.star +104 -0
  21. package/discovery/manifest/loading.star +386 -0
  22. package/discovery/manifest/normalize.star +281 -0
  23. package/discovery/manifest/validation.star +108 -0
  24. package/discovery/normalization.star +11 -0
  25. package/discovery/registry.star +544 -0
  26. package/discovery/resource_snapshot.py +382 -0
  27. package/discovery/resource_snapshot.star +143 -0
  28. package/discovery/schemas.star +1 -0
  29. package/discovery/services/platform/cdc/debezium/nats-connector/service.yaml +9 -0
  30. package/discovery/services/platform/cdc/nats-http-bridge/service.yaml +9 -0
  31. package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +26 -0
  32. package/discovery/services/platform/synthetic-monitor/service.yaml +9 -0
  33. package/discovery/test_resource_snapshot.py +251 -0
  34. package/discovery/validation.star +78 -0
  35. package/discovery/versioning.star +1 -0
  36. package/dist/commands/config.d.ts.map +1 -1
  37. package/dist/commands/config.js +11 -0
  38. package/dist/commands/config.js.map +1 -1
  39. package/dist/commands/doctor.d.ts.map +1 -1
  40. package/dist/commands/doctor.js +1 -2
  41. package/dist/commands/doctor.js.map +1 -1
  42. package/dist/commands/project.d.ts.map +1 -1
  43. package/dist/commands/project.js +36 -41
  44. package/dist/commands/project.js.map +1 -1
  45. package/dist/commands/resource.d.ts.map +1 -1
  46. package/dist/commands/resource.js +51 -55
  47. package/dist/commands/resource.js.map +1 -1
  48. package/dist/commands/stack.d.ts.map +1 -1
  49. package/dist/commands/stack.js +22 -24
  50. package/dist/commands/stack.js.map +1 -1
  51. package/dist/commands/ui.d.ts.map +1 -1
  52. package/dist/commands/ui.js +1 -2
  53. package/dist/commands/ui.js.map +1 -1
  54. package/dist/commands/up.d.ts.map +1 -1
  55. package/dist/commands/up.js +1 -2
  56. package/dist/commands/up.js.map +1 -1
  57. package/dist/commands/upgrade.d.ts +8 -0
  58. package/dist/commands/upgrade.d.ts.map +1 -1
  59. package/dist/commands/upgrade.js +62 -21
  60. package/dist/commands/upgrade.js.map +1 -1
  61. package/dist/generator/extension-fetch.d.ts +19 -0
  62. package/dist/generator/extension-fetch.d.ts.map +1 -1
  63. package/dist/generator/extension-fetch.js +71 -5
  64. package/dist/generator/extension-fetch.js.map +1 -1
  65. package/dist/generator/template-engine.d.ts +1 -0
  66. package/dist/generator/template-engine.d.ts.map +1 -1
  67. package/dist/generator/template-engine.js +50 -6
  68. package/dist/generator/template-engine.js.map +1 -1
  69. package/dist/utils/command-helpers.d.ts.map +1 -1
  70. package/dist/utils/command-helpers.js +7 -9
  71. package/dist/utils/command-helpers.js.map +1 -1
  72. package/dist/utils/constants.d.ts +1 -0
  73. package/dist/utils/constants.d.ts.map +1 -1
  74. package/dist/utils/constants.js +1 -0
  75. package/dist/utils/constants.js.map +1 -1
  76. package/dist/utils/errors.d.ts +2 -0
  77. package/dist/utils/errors.d.ts.map +1 -1
  78. package/dist/utils/errors.js +6 -1
  79. package/dist/utils/errors.js.map +1 -1
  80. package/dist/utils/paths.js +1 -1
  81. package/dist/utils/paths.js.map +1 -1
  82. package/dist/utils/project-features.d.ts.map +1 -1
  83. package/dist/utils/project-features.js +1 -1
  84. package/dist/utils/project-features.js.map +1 -1
  85. package/dist/utils/resource-features.d.ts.map +1 -1
  86. package/dist/utils/resource-features.js +2 -2
  87. package/dist/utils/resource-features.js.map +1 -1
  88. package/dist/utils/tar.js +4 -4
  89. package/dist/utils/tar.js.map +1 -1
  90. package/dist/utils/validation.d.ts +7 -0
  91. package/dist/utils/validation.d.ts.map +1 -1
  92. package/dist/utils/validation.js +9 -0
  93. package/dist/utils/validation.js.map +1 -1
  94. package/engine/AGENTS.md +705 -0
  95. package/engine/README.md +29 -0
  96. package/engine/REFACTORING_MANIFEST_SYSTEM.md +974 -0
  97. package/engine/Tiltfile +80 -0
  98. package/engine/assets/docker/golden-layers.Dockerfile +221 -0
  99. package/engine/assets/scripts/cleanup-completed-migrator-images.sh +37 -0
  100. package/engine/assets/scripts/cleanup-tilt-images.sh +120 -0
  101. package/engine/assets/scripts/mac-m1-resource-monitor.sh +121 -0
  102. package/engine/config.star +3 -0
  103. package/engine/docs/AUTODISCOVERY_TEST.md +182 -0
  104. package/engine/docs/C4-ARCHITECTURE-operations.md +396 -0
  105. package/engine/docs/C4-ARCHITECTURE-overview.md +284 -0
  106. package/engine/docs/C4-ARCHITECTURE.md +17 -0
  107. package/engine/docs/GOLDEN_IMAGE_INTEGRATION.md +349 -0
  108. package/engine/docs/MANIFEST_DRIVEN_ARCHITECTURE.md +357 -0
  109. package/engine/docs/README.md +284 -0
  110. package/engine/entrypoint.star +97 -0
  111. package/engine/lifecycle/orchestrator.star +260 -0
  112. package/engine/paths.star +717 -0
  113. package/engine/schemas/manifest-schema.json +301 -0
  114. package/engine/schemas/service-schema.json +301 -0
  115. package/engine/scripts/README.md +89 -0
  116. package/engine/scripts/build-and-publish-lib.sh +205 -0
  117. package/engine/scripts/check-lib-publish.sh +85 -0
  118. package/engine/scripts/debug-container.sh +258 -0
  119. package/engine/scripts/generate-manifest.sh +197 -0
  120. package/engine/spec.master +702 -0
  121. package/engine/spec.master.example +341 -0
  122. package/engine/topologies/README.md +34 -0
  123. package/engine/topologies/platform/docker/AGENTS.md +59 -0
  124. package/engine/topologies/platform/docker/auth/auth.star +167 -0
  125. package/engine/topologies/platform/docker/build/golden_image.star +37 -0
  126. package/engine/topologies/platform/docker/build/golden_image_build.star +82 -0
  127. package/engine/topologies/platform/docker/build/golden_image_constants.star +93 -0
  128. package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +291 -0
  129. package/engine/topologies/platform/docker/compose/compose.star +636 -0
  130. package/engine/topologies/platform/docker/compose/compose_helpers.star +52 -0
  131. package/engine/topologies/platform/docker/compose/traefik_standalone.star +53 -0
  132. package/engine/topologies/platform/docker/config/bunignore.star +142 -0
  133. package/engine/topologies/platform/docker/config/dockerignore.star +150 -0
  134. package/engine/topologies/platform/docker/constants.star +170 -0
  135. package/engine/topologies/platform/docker/dockerfile/dockerfile.star +54 -0
  136. package/engine/topologies/platform/docker/generators/golden_docker_generator_v2.star +73 -0
  137. package/engine/topologies/platform/docker/index.star +27 -0
  138. package/engine/topologies/platform/docker/layers/bun_helpers.star +23 -0
  139. package/engine/topologies/platform/docker/layers/infisical/index.star +40 -0
  140. package/engine/topologies/platform/docker/layers/infisical/infisical_docker.star +262 -0
  141. package/engine/topologies/platform/docker/layers/l1_base_layers.star +87 -0
  142. package/engine/topologies/platform/docker/layers/l2_dependency_layers.star +125 -0
  143. package/engine/topologies/platform/docker/layers/l3_builder_layers.star +209 -0
  144. package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +214 -0
  145. package/engine/topologies/platform/docker/layers/prisma/constants.star +135 -0
  146. package/engine/topologies/platform/docker/layers/prisma/db_readiness.star +96 -0
  147. package/engine/topologies/platform/docker/layers/prisma/filesystem_setup.star +109 -0
  148. package/engine/topologies/platform/docker/layers/prisma/migrate_script.star +98 -0
  149. package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +126 -0
  150. package/engine/topologies/platform/docker/layers/prisma/prisma_runtime.star +161 -0
  151. package/engine/topologies/platform/docker/layers/prisma/secret_resolution.star +119 -0
  152. package/engine/topologies/platform/docker/networking/api_path_constants.star +230 -0
  153. package/engine/topologies/platform/docker/networking/sablier_container_cycle.star +27 -0
  154. package/engine/topologies/platform/docker/networking/traefik.star +232 -0
  155. package/engine/topologies/platform/docker/networking/traefik_constants.star +146 -0
  156. package/engine/topologies/platform/docker/networking/traefik_helpers.star +160 -0
  157. package/engine/topologies/platform/docker/secrets/infisical.star +135 -0
  158. package/engine/topologies/platform/net/proxy.star +4 -0
  159. package/engine/topologies/platform/registries/local.star +4 -0
  160. package/engine/topologies/platform/security/secrets.star +239 -0
  161. package/engine/topologies/platform/state/relational.star +4 -0
  162. package/engine/topologies/tilt/common/AGENTS.md +59 -0
  163. package/engine/topologies/tilt/common/utils.star +364 -0
  164. package/engine/topologies/tilt/common/utils_debug.star +69 -0
  165. package/engine/topologies/tilt/common/utils_deterministic.star +168 -0
  166. package/engine/topologies/tilt/common/utils_docker_networks.star +46 -0
  167. package/engine/topologies/tilt/common/utils_env.star +43 -0
  168. package/engine/topologies/tilt/common/utils_output.star +97 -0
  169. package/engine/topologies/tilt/config/AGENTS.md +68 -0
  170. package/engine/topologies/tilt/config/defaults.star +3 -0
  171. package/engine/topologies/tilt/config/feature_flags.star +10 -0
  172. package/engine/topologies/tilt/config/global.star +55 -0
  173. package/engine/topologies/tilt/config/profiles.star +382 -0
  174. package/engine/topologies/tilt/generators/agents_md.star +62 -0
  175. package/engine/topologies/tilt/generators/api_gateway.star +145 -0
  176. package/engine/topologies/tilt/generators/index.star +124 -0
  177. package/engine/topologies/tilt/generators/infisical/README.md +424 -0
  178. package/engine/topologies/tilt/generators/infisical/examples.star +312 -0
  179. package/engine/topologies/tilt/generators/infisical/index.star +57 -0
  180. package/engine/topologies/tilt/generators/infisical/machine_identity.star +420 -0
  181. package/engine/topologies/tilt/generators/infisical/organization.star +534 -0
  182. package/engine/topologies/tilt/generators/infisical/path_manager.star +476 -0
  183. package/engine/topologies/tilt/generators/infisical/secrets_generator.star +420 -0
  184. package/engine/topologies/tilt/generators/nginx_conf.star +143 -0
  185. package/engine/topologies/tilt/generators/npmrc.star +111 -0
  186. package/engine/topologies/tilt/generators/tsconfig.star +23 -0
  187. package/engine/topologies/tilt/generators/typescript/backend_library_tsconfig.star +71 -0
  188. package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +124 -0
  189. package/engine/topologies/tilt/generators/typescript/common.star +7 -0
  190. package/engine/topologies/tilt/generators/typescript/frontend_library_tsconfig.star +76 -0
  191. package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +110 -0
  192. package/engine/topologies/tilt/generators/typescript/include.star +23 -0
  193. package/engine/topologies/tilt/generators/typescript/playwright_config.star +40 -0
  194. package/engine/topologies/tilt/generators/typescript/prisma_config.star +49 -0
  195. package/engine/topologies/tilt/generators/validators.star +853 -0
  196. package/engine/topologies/tilt/generators/vite/backend.star +78 -0
  197. package/engine/topologies/tilt/generators/vite/frontend.star +98 -0
  198. package/engine/topologies/tilt/generators/vite/generators.star +43 -0
  199. package/engine/topologies/tilt/generators/vite/helpers.star +244 -0
  200. package/engine/topologies/tilt/generators/vite/library.star +65 -0
  201. package/engine/topologies/tilt/generators/vite/sdk.star +73 -0
  202. package/engine/topologies/tilt/generators/vite/templates.star +737 -0
  203. package/engine/topologies/tilt/generators/vite_config.star +22 -0
  204. package/engine/topologies/tilt/manifest/AGENTS.md +61 -0
  205. package/engine/topologies/tilt/manifest/README.md +693 -0
  206. package/engine/topologies/tilt/manifest/__init__.star +218 -0
  207. package/engine/topologies/tilt/manifest/constants.star +252 -0
  208. package/engine/topologies/tilt/manifest/errors.star +266 -0
  209. package/engine/topologies/tilt/manifest/integration.star +339 -0
  210. package/engine/topologies/tilt/manifest/json_to_yaml.py +54 -0
  211. package/engine/topologies/tilt/manifest/loader.star +214 -0
  212. package/engine/topologies/tilt/manifest/mapper.star +227 -0
  213. package/engine/topologies/tilt/manifest/parser.star +400 -0
  214. package/engine/topologies/tilt/manifest/schema.star +462 -0
  215. package/engine/topologies/tilt/manifest/tests/test_integration.star +276 -0
  216. package/engine/topologies/tilt/manifest/tests/test_loader_parser.star +397 -0
  217. package/engine/topologies/tilt/manifest/tests/test_manifest.star +316 -0
  218. package/engine/topologies/tilt/manifest/tests/test_validator.star +470 -0
  219. package/engine/topologies/tilt/manifest/validator.star +717 -0
  220. package/engine/topologies/tilt/manifest/yaml_to_json.py +18 -0
  221. package/engine/topologies/tilt/resources/AGENTS.md +71 -0
  222. package/engine/topologies/tilt/resources/conditions.star +3 -0
  223. package/engine/topologies/tilt/resources/databases.star +244 -0
  224. package/engine/topologies/tilt/resources/declaration.star +3 -0
  225. package/engine/topologies/tilt/resources/deps.star +3 -0
  226. package/engine/topologies/tilt/resources/infra-loader.star +437 -0
  227. package/engine/topologies/tilt/resources/libs.star +30 -0
  228. package/engine/topologies/tilt/resources/libs_discovery.star +102 -0
  229. package/engine/topologies/tilt/resources/libs_paths.star +79 -0
  230. package/engine/topologies/tilt/resources/libs_resource_definition.star +151 -0
  231. package/engine/topologies/tilt/resources/libs_setup.star +164 -0
  232. package/engine/topologies/tilt/resources/libs_tsconfig.star +37 -0
  233. package/engine/topologies/tilt/resources/orchestrator/apply.star +77 -0
  234. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +431 -0
  235. package/engine/topologies/tilt/resources/orchestrator/apply_manifest_orchestration.star +81 -0
  236. package/engine/topologies/tilt/resources/orchestrator/apply_migrator_orchestration.star +148 -0
  237. package/engine/topologies/tilt/resources/orchestrator/apply_resource_validation.star +36 -0
  238. package/engine/topologies/tilt/resources/orchestrator/apply_runtime_flags.star +44 -0
  239. package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +164 -0
  240. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/AGENTS.md +75 -0
  241. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/index.star +21 -0
  242. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/resolver.star +134 -0
  243. package/engine/topologies/tilt/resources/orchestrator/generators/env.star +258 -0
  244. package/engine/topologies/tilt/resources/orchestrator/generators/frontend.star +169 -0
  245. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +524 -0
  246. package/engine/topologies/tilt/resources/orchestrator/helpers.star +33 -0
  247. package/engine/topologies/tilt/resources/orchestrator.star +54 -0
  248. package/engine/topologies/tilt/resources/ordering.star +3 -0
  249. package/engine/topologies/tilt/resources/triggers.star +3 -0
  250. package/engine/topologies/tilt/resources/watchers.star +299 -0
  251. package/engine/where-things-live.star +717 -0
  252. package/ext/Tiltfile +66 -0
  253. package/ext/ide-components/README.md +219 -0
  254. package/ext/ide-components/code_executor/server.py +268 -0
  255. package/ext/ide-components/code_viewer/server.py +206 -0
  256. package/ext/ide-components/config_inspector/server.py +1050 -0
  257. package/ext/ide-components/file_browser/server.py +285 -0
  258. package/ext/ide-components/shared/config_service.py +469 -0
  259. package/ext/ide-components/shared/file_utils.py +298 -0
  260. package/ext/ide-components/shared/http_utils.py +232 -0
  261. package/ext/ide-components/shared/static/styles.css +1334 -0
  262. package/ext/ide-components/shared/syntax_highlighter.py +140 -0
  263. package/ext/ide-components/shared/template_engine.py +123 -0
  264. package/ext/ide-components/shared/templates/base.html +121 -0
  265. package/ext/ide-components/shared/test_config_service.py +326 -0
  266. package/ext/ide-components/shared/tilt_integration.py +158 -0
  267. package/ext/ui-enhancements/cron-status-server.sh +49 -0
  268. package/ext/ui-enhancements/cron-status-tracker.sh +146 -0
  269. package/ext/ui-enhancements/extension.yaml +36 -0
  270. package/ext/ui-enhancements/tiltfile.py +99 -0
  271. package/ext/ui-enhancements/ui/cron-jobs-tab.css +396 -0
  272. package/ext/ui-enhancements/ui/help-panel.css +279 -0
  273. package/ext/ui-enhancements/ui/icons.css +125 -0
  274. package/ext/ui-enhancements/ui/tooltips.css +114 -0
  275. package/package.json +13 -9
  276. package/specs/specs/TILT_SERVICE_DEFAULTS.star +205 -0
  277. package/specs/specs/TILT_TECH_STACK.star +159 -0
@@ -0,0 +1,636 @@
1
+ # =============================================================================
2
+ # 🐳 TILT SDK - DOCKER COMPOSE GENERATORS (modular)
3
+ # =============================================================================
4
+ # Path: .tilt/topologies/platform/docker/compose.star
5
+ # Purpose: docker-compose generation helpers extracted from .tilt/providers/docker.star
6
+ # =============================================================================
7
+
8
+ # === INLINED CONSTANTS for pure extension loading ===
9
+ BASE_PORT_FRONTEND = 3000
10
+ BASE_PORT_BACKEND = 4000
11
+ HEALTH_CHECK_PATH = "/health"
12
+ OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
13
+ # === END INLINED CONSTANTS ===
14
+
15
+
16
+ load("./compose_helpers.star", "with_runtime_image_tag", "compose_build_config")
17
+ load("../networking/traefik.star", "get_frontend_traefik_labels", "get_backend_traefik_labels")
18
+ load("../networking/api_path_constants.star", "get_api_path_for_domain")
19
+ load("../secrets/infisical.star", "get_infisical_environment_vars")
20
+ load("../auth/auth.star", "AuthConfig")
21
+
22
+ load("../constants.star", "PlatformDockerConstants")
23
+
24
+ # Load project name for dynamic host naming
25
+ # Use TDK_PROJECT_ROOT env var set by Tilt, fallback to current directory
26
+ def _load_project_name():
27
+ project_root = os.environ.get('TDK_PROJECT_ROOT', '')
28
+ project_json_path = '.tdk/project.json'
29
+ if project_root:
30
+ project_json_path = project_root + '/' + project_json_path
31
+
32
+ if os.path.exists(project_json_path):
33
+ _project_json = read_json(project_json_path)
34
+ return _project_json.get('project', {}).get('name', 'tdk-project')
35
+ return 'tdk-project'
36
+
37
+ _PROJECT_NAME = _load_project_name()
38
+
39
+
40
+ def _compute_env_file_rel_path(resource_path, full_resource_path, env_filename):
41
+ """
42
+ Compute the relative path from compose file location to env file.
43
+
44
+ The compose file is at: resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER/docker-compose.app.autogenerated.yml'
45
+ The env file is at: full_resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + env_filename
46
+
47
+ Args:
48
+ resource_path: The domain/service group path (e.g., 'services/product/users' or 'services/platform')
49
+ full_resource_path: The actual service path (e.g., 'services/product/users/user-management-backend' or 'shared-platform-engineering/my-lib/infra')
50
+ env_filename: The env file name (e.g., '.env.frontend.autogenerated' or '.env.backend.autogenerated')
51
+
52
+ Returns:
53
+ Relative path from compose file to env file
54
+ """
55
+ # Count how many levels deep the compose file is
56
+ # resource_path like 'services/product/users' has 3 parts
57
+ # compose file is at resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/', so we need len(parts) + 1 levels of ../
58
+ resource_path_parts = resource_path.split('/')
59
+ num_dotdots = len(resource_path_parts) + 1 # +1 for the autogenerated directory
60
+
61
+ # Build the dotdot prefix
62
+ dotdot_prefix = '/'.join(['..'] * num_dotdots)
63
+
64
+ # The env file is at full_resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + env_filename
65
+ return dotdot_prefix + '/' + full_resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + env_filename
66
+
67
+ # =============================================================================
68
+ # 🚦 FRONTEND / BACKEND ENTRIES
69
+ # =============================================================================
70
+
71
+ def generate_frontend_compose(resource_path, resource_name, res, manifest=None):
72
+ """Generate frontend docker-compose entry with Traefik routing."""
73
+ res_name = res['name']
74
+ image_name = with_runtime_image_tag(resource_name + '_' + res_name)
75
+ stack = manifest.get('stack', resource_name) if manifest else resource_name
76
+
77
+ target_path = res.get('target_path', manifest.get('target_path', '/usr/share/nginx/html'))
78
+ use_nginx = 'nginx' in target_path or target_path == '/usr/share/nginx/html'
79
+ # Read port from manifest (service.json), fallback to BASE_PORT_FRONTEND or 80 for nginx
80
+ port = manifest.get('port', 80 if use_nginx else BASE_PORT_FRONTEND)
81
+
82
+ # Base path for routing on {project}.localhost
83
+ base_path = '/' + manifest.get('basePath', stack + 's').lstrip('/')
84
+
85
+ traefik_cfg = manifest.get('traefik', {}) if manifest else {}
86
+ traefik_host = traefik_cfg.get('host')
87
+ # Use custom dockerfile from manifest if specified, otherwise use autogenerated default
88
+ dockerfile_path = res.get('dockerfile', OUTPUT.AUTOGENERATED_FOLDER + '/Dockerfile.app.autogenerated')
89
+ # Handle flat service structure (e.g., services/product/identity-management-frontend) vs nested (services/product/domain/service)
90
+ # Detect flat structure: services/product/{service} (3 path parts after root)
91
+ # vs nested: services/product/{domain}/{service} (4+ path parts)
92
+ full_resource_path = res.get('_resource_path', resource_path + '/' + res_name)
93
+ path_parts = full_resource_path.split('/')
94
+ # If path is like services/product/xxx (3 parts), it's flat - use path directly
95
+ # If path is like services/product/domain/xxx (4 parts), it's nested - use standard construction
96
+ if len(path_parts) <= 3:
97
+ # Flat structure: resource_path already includes res_name
98
+ build_config = compose_build_config(full_resource_path, '', dockerfile_path, 'production')
99
+ else:
100
+ # Nested structure: use resource_path and res_name separately
101
+ build_config = compose_build_config(resource_path, res_name, dockerfile_path, 'production')
102
+
103
+ # Calculate correct relative path to env_file from compose file location
104
+ # The compose file is at: resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER/docker-compose.app.autogenerated.yml'
105
+ # The env file is at: full_resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER/.env.frontend.autogenerated'
106
+ env_file_rel_path = _compute_env_file_rel_path(resource_path, full_resource_path, '.env.frontend.autogenerated')
107
+
108
+ traefik_labels = get_frontend_traefik_labels(
109
+ res_name,
110
+ stack,
111
+ base_path,
112
+ port,
113
+ traefik_host=traefik_host,
114
+ manifest=manifest,
115
+ )
116
+
117
+ return """ {res_name}:
118
+ image: {image_name}
119
+ {build_config}
120
+ <<: *frontend-memory-limit
121
+ env_file:
122
+ - {env_file_rel_path}
123
+ environment:
124
+ - VITE_APP_ENV=development
125
+ labels:
126
+ {traefik_labels}
127
+ networks:
128
+ - {network_traefik_public}
129
+ - {network_backend}
130
+ healthcheck:
131
+ test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:{port}"]
132
+ interval: 10s
133
+ timeout: 5s
134
+ retries: 3
135
+ # STORY 4 FIX: 30s grace period to prevent 504 errors on startup
136
+ start_period: 30s
137
+ """.format(
138
+ res_name=res_name,
139
+ image_name=image_name,
140
+ build_config=build_config,
141
+ env_file_rel_path=env_file_rel_path,
142
+ traefik_labels=traefik_labels,
143
+ port=port,
144
+ network_traefik_public=PlatformDockerConstants.NETWORK_TRAEFIK_PUBLIC,
145
+ network_backend=PlatformDockerConstants.NETWORK_BACKEND,
146
+ )
147
+
148
+
149
+ def _generate_single_backend_entry(resource_path, resource_name, res, manifest, instance_id=None):
150
+ """
151
+ Generate a single backend docker-compose entry.
152
+ """
153
+ res_name = res['name']
154
+ stack = manifest.get('stack', resource_name) if manifest else resource_name
155
+
156
+ # Get full service path from resource (for cross-root service support)
157
+ full_resource_path = res.get('_resource_path', resource_path + '/' + res_name)
158
+
159
+ # Calculate correct relative path to env_file from compose file location
160
+ env_file_rel_path = _compute_env_file_rel_path(resource_path, full_resource_path, '.env.backend.autogenerated')
161
+
162
+ internal_port = manifest.get('port', BASE_PORT_BACKEND) if manifest else BASE_PORT_BACKEND
163
+ dev_port = internal_port
164
+ # Use PROJECT_NAME from PlatformDockerConstants for portable database naming
165
+ _db_prefix = PlatformDockerConstants.PROJECT_NAME + "_"
166
+ db_name = manifest.get('databaseName', _db_prefix + stack) if manifest else _db_prefix + stack
167
+
168
+ # Compute database URLs using centralized constants
169
+ db_url_for_tilt = PlatformDockerConstants.get_database_url_for_env(db_name)
170
+ db_url_with_fallback = PlatformDockerConstants.get_compose_env_database_url(db_name)
171
+
172
+ health_path = manifest.get('healthCheckPath', HEALTH_CHECK_PATH) if manifest else HEALTH_CHECK_PATH
173
+
174
+ traefik_cfg = manifest.get('traefik', {}) if manifest else {}
175
+ traefik_host = traefik_cfg.get('host', stack + '.backend.' + _PROJECT_NAME + '.local')
176
+ # ALPHA: Use proper API v1 path with -management suffix via api_path_constants
177
+ # Old default: '/api/' + stack
178
+ # New default: get_api_path_for_domain → /api/v1/{stack}-management
179
+ traefik_path = traefik_cfg.get('pathPrefix', get_api_path_for_domain(stack, manifest))
180
+
181
+ nats_cfg = manifest.get('nats', {}) if manifest else {}
182
+ nats_queue = nats_cfg.get('queueGroup', stack + '_backend_svc')
183
+ nats_url = PlatformDockerConstants.NATS_URL
184
+
185
+ # Auth configuration - use centralized auth utilities
186
+ # Extract auth config from manifest to decouple auth.star from manifest structure
187
+ # Auto-detect identity-service mode if 'identity' is in dependsOn
188
+ internal_deps = manifest.get('dependsOn', []) if manifest else []
189
+ has_identity_dep = 'identity' in internal_deps
190
+
191
+ # If manifest has authMode, use it; otherwise auto-detect based on identity dependency
192
+ manifest_auth_mode = manifest.get('authMode') if manifest else None
193
+ if manifest_auth_mode:
194
+ auth_mode = manifest_auth_mode
195
+ elif has_identity_dep:
196
+ auth_mode = 'identity-service'
197
+ else:
198
+ auth_mode = None
199
+
200
+ auth_config = {
201
+ 'authMode': auth_mode,
202
+ 'jwtSecret': manifest.get('jwtSecret') if manifest else None,
203
+ 'identityServiceUrl': manifest.get('envVars', {}).get('IDENTITY_RESOURCE_URL') if manifest else None,
204
+ }
205
+
206
+ # If we auto-detected identity-service mode but no URL is set, use default
207
+ if auth_mode == 'identity-service' and not auth_config['identityServiceUrl']:
208
+ auth_config['identityServiceUrl'] = AuthConfig.get_identity_resource_url(None)
209
+
210
+ auth_env = AuthConfig.generate_docker_compose_auth_env(auth_config)
211
+
212
+ # Use .autogenerated/Dockerfile.app.autogenerated by default for build config (primary only)
213
+ build_config = compose_build_config(resource_path, res_name)
214
+
215
+ # REMOVED: External port exposure - all services accessed via Traefik only
216
+ # Services are available at {project}.localhost/api/v1/{service}
217
+ # Internal port 3000 is accessible within Docker network for service-to-service communication
218
+ ports_section = "" # No external ports - Traefik-only access
219
+
220
+ # Determine service name based on instance
221
+ if instance_id:
222
+ # Replica instance: add instance suffix
223
+ resource_entry_name = res_name + '-' + str(instance_id)
224
+ instance_label = str(instance_id)
225
+ # Replicas should reuse the already-built image; avoid per-replica builds
226
+ build_config = ""
227
+ else:
228
+ # Primary instance
229
+ resource_entry_name = res_name
230
+ instance_label = '0'
231
+
232
+ # All instances share the same Traefik service for load balancing
233
+ traefik_resource_name = res_name # LB service name (shared)
234
+
235
+ traefik_labels = get_backend_traefik_labels(
236
+ resource_entry_name=resource_entry_name,
237
+ traefik_host=traefik_host,
238
+ traefik_path=traefik_path,
239
+ traefik_resource_name=traefik_resource_name,
240
+ internal_port=internal_port,
241
+ health_path=health_path,
242
+ manifest=manifest,
243
+ )
244
+ infisical_env = get_infisical_environment_vars(as_array=True, resource_name=res_name)
245
+
246
+ # Build auth environment variables section using centralized auth utilities
247
+
248
+ return """
249
+ {resource_entry_name}:
250
+ image: {image_name}
251
+ restart: unless-stopped
252
+ {build_config}
253
+ {ports_section}
254
+ env_file:
255
+ - {env_file_rel_path}
256
+ environment:
257
+ {infisical_env}
258
+ - RESOURCE_NAME={stack}
259
+ - PORT={port_anchor}
260
+ - INSTANCE_ID={instance_label}
261
+ - NATS_QUEUE_GROUP={nats_queue}
262
+ - TILT_DATABASE_URL={db_url_for_tilt}
263
+ - TILT_NATS_URL={nats_url}
264
+ - NODE_ENV=development
265
+ - DATABASE_URL={db_url_for_tilt}
266
+ {auth_env}
267
+ labels:
268
+ {traefik_labels}
269
+ networks:
270
+ - {network_traefik_public}
271
+ - {network_backend}
272
+ - {network_database}
273
+ - {network_infisical}
274
+ healthcheck:
275
+ test: ["CMD", "curl", "-f", "--max-time", "5", "http://localhost:{internal_port}{health_path}"]
276
+ interval: 10s
277
+ timeout: 5s
278
+ retries: 6
279
+ # STORY 4 FIX: 30s grace period to prevent 504 errors during startup
280
+ # Services get 30s to initialize before health checks begin
281
+ start_period: 30s
282
+ # Note: depends_on removed - Tilt manages service startup order
283
+ deploy:
284
+ resources:
285
+ limits:
286
+ cpus: '0.5'
287
+ memory: 512M
288
+ reservations:
289
+ cpus: '0.2'
290
+ memory: 256M
291
+ """.format(
292
+ resource_entry_name=resource_entry_name,
293
+ res_name=res_name,
294
+ resource_name=resource_name,
295
+ image_name=with_runtime_image_tag(resource_name + '_' + res_name),
296
+ build_config=build_config,
297
+ ports_section=ports_section,
298
+ env_file_rel_path=env_file_rel_path,
299
+ stack=stack,
300
+ internal_port=internal_port,
301
+ port_anchor=internal_port,
302
+ dev_port=dev_port,
303
+ db_name=db_name,
304
+ db_url_for_tilt=db_url_for_tilt,
305
+ db_url_with_fallback=db_url_with_fallback,
306
+ instance_label=instance_label,
307
+ health_path=health_path,
308
+ traefik_host=traefik_host,
309
+ traefik_path=traefik_path,
310
+ traefik_resource_name=traefik_resource_name,
311
+ nats_queue=nats_queue,
312
+ nats_url=nats_url,
313
+ traefik_labels=traefik_labels,
314
+ infisical_env=infisical_env,
315
+ auth_env=auth_env,
316
+ network_traefik_public=PlatformDockerConstants.NETWORK_TRAEFIK_PUBLIC,
317
+ network_backend=PlatformDockerConstants.NETWORK_BACKEND,
318
+ network_database=PlatformDockerConstants.NETWORK_DATABASE,
319
+ network_infisical=PlatformDockerConstants.NETWORK_INFISICAL,
320
+ )
321
+
322
+
323
+ def generate_backend_compose_entry(resource_path, resource_name, res, manifest=None):
324
+ """
325
+ Generate backend docker-compose entries with TRUE multi-replica support.
326
+ """
327
+ replicas = manifest.get('replicas', 1) if manifest else 1
328
+
329
+ if replicas <= 1:
330
+ # Single instance - simple case
331
+ return _generate_single_backend_entry(resource_path, resource_name, res, manifest, instance_id=None)
332
+
333
+ # Multi-replica: generate separate service entries
334
+ entries = []
335
+
336
+ # Primary instance (gets the port mapping)
337
+ entries.append(_generate_single_backend_entry(resource_path, resource_name, res, manifest, instance_id=None))
338
+
339
+ # Additional replicas (numbered, expose only)
340
+ for i in range(1, replicas):
341
+ entries.append(_generate_single_backend_entry(resource_path, resource_name, res, manifest, instance_id=i))
342
+
343
+ return ''.join(entries)
344
+
345
+
346
+ def _generate_port_anchors(resource_entries):
347
+ """Extract unique ports and generate YAML anchors for them."""
348
+ backend_port = None
349
+ frontend_port = None
350
+ sdk_port = None
351
+ has_backend = False
352
+ has_sdk = False
353
+ has_frontend = False
354
+
355
+ for entry in resource_entries:
356
+ # Check if this is an SDK (ends with -sdk)
357
+ if "-sdk:" in entry or entry.strip().startswith("identity-sdk:"):
358
+ has_sdk = True
359
+ # Extract SDK port from entry
360
+ if "loadbalancer.server.port=" in entry:
361
+ for line in entry.split("\n"):
362
+ if "loadbalancer.server.port=" in line:
363
+ # Safe parsing without try/except
364
+ port_str = line.split("loadbalancer.server.port=")[-1].split('"')[0]
365
+ if port_str.isdigit():
366
+ sdk_port = int(port_str)
367
+ break
368
+ # Check if this is a frontend (has frontend-memory-limit but not SDK)
369
+ elif "<<: *frontend-memory-limit" in entry and not ("-sdk:" in entry or entry.strip().startswith("identity-sdk:")):
370
+ has_frontend = True
371
+ # Extract frontend port from entry
372
+ if "loadbalancer.server.port=" in entry:
373
+ for line in entry.split("\n"):
374
+ if "loadbalancer.server.port=" in line:
375
+ # Safe parsing without try/except
376
+ port_str = line.split("loadbalancer.server.port=")[-1].split('"')[0]
377
+ if port_str.isdigit():
378
+ frontend_port = int(port_str)
379
+ break
380
+ # Check if this is a backend (has a port, not frontend, not SDK)
381
+ # Backend entries have "healthcheck:" section with backend port
382
+ elif "healthcheck:" in entry and "<<: *frontend-memory-limit" not in entry and not ("-sdk:" in entry or entry.strip().startswith("identity-sdk:")):
383
+ has_backend = True
384
+ # Extract backend port from entry
385
+ if "loadbalancer.server.port=" in entry:
386
+ for line in entry.split("\n"):
387
+ if "loadbalancer.server.port=" in line:
388
+ # Safe parsing without try/except
389
+ port_str = line.split("loadbalancer.server.port=")[-1].split('"')[0]
390
+ if port_str.isdigit():
391
+ backend_port = int(port_str)
392
+ break
393
+
394
+ # Build anchors - use extracted port or fall back to default
395
+ anchors = []
396
+ anchors.append("x-backend-port: &backend-port {}".format(backend_port if backend_port else BASE_PORT_BACKEND))
397
+ if has_frontend and frontend_port:
398
+ anchors.append("x-frontend-port: &frontend-port {}".format(frontend_port))
399
+ if has_sdk and sdk_port:
400
+ anchors.append("x-sdk-port: &sdk-port {}".format(sdk_port))
401
+
402
+ return "\n".join(anchors)
403
+
404
+
405
+ def _detect_sdk_in_entries(resource_entries):
406
+ """Check if any entry is an SDK."""
407
+ for entry in resource_entries:
408
+ if "-sdk:" in entry or entry.strip().startswith("identity-sdk:"):
409
+ return True
410
+ return False
411
+
412
+ def _detect_frontend_in_entries(resource_entries):
413
+ """Check if any entry is a frontend."""
414
+ for entry in resource_entries:
415
+ if "<<: *frontend-memory-limit" in entry:
416
+ return True
417
+ return False
418
+
419
+ def _detect_backend_in_entries(resource_entries):
420
+ """Check if any entry is a backend."""
421
+ for entry in resource_entries:
422
+ if _is_backend_entry(entry):
423
+ return True
424
+ return False
425
+
426
+ def _is_sdk_entry(entry):
427
+ """Check if a specific entry is an SDK."""
428
+ return "-sdk:" in entry or entry.strip().startswith("identity-sdk:")
429
+
430
+ def _is_backend_entry(entry):
431
+ """Check if a specific entry is a backend (not frontend, not SDK)."""
432
+ if "-sdk:" in entry or entry.strip().startswith("identity-sdk:"):
433
+ return False
434
+ if "<<: *frontend-memory-limit" in entry:
435
+ return False
436
+ # Backend entries have healthcheck with backend port pattern
437
+ if "healthcheck:" in entry and "curl" in entry:
438
+ return True
439
+ return False
440
+
441
+ def _replace_ports_with_anchors(entry, has_sdk=False, has_frontend=False, has_backend=False):
442
+ """Replace hardcoded ports with YAML anchors in an entry."""
443
+ is_sdk = _is_sdk_entry(entry)
444
+ is_backend = _is_backend_entry(entry)
445
+ lines = entry.split("\n")
446
+ result = []
447
+
448
+ for line in lines:
449
+ new_line = line
450
+ # NOTE: We do NOT replace PORT=3000 with an anchor because YAML anchors
451
+ # in environment variable values are treated as literal strings, not resolved.
452
+ # The actual port value is already correctly set by _generate_single_backend_entry
453
+ # using the internal_port variable. Keep PORT=3000 as-is.
454
+
455
+ # YAML anchors do not resolve inside quoted Docker labels. Traefik must
456
+ # see a numeric loadbalancer.port (e.g. 4000), not `*backend-port`.
457
+ # NOTE: We do NOT replace healthcheck URLs with YAML anchors because anchors
458
+ # don't work inside quoted strings. The wget healthcheck URLs must use actual
459
+ # port numbers (e.g., http://127.0.0.1:3000), not anchor references.
460
+ # The correct port is already set by generate_frontend_compose.
461
+ # NOTE: We do NOT replace healthcheck URLs with anchors because YAML anchors
462
+ # don't work inside quoted strings. The healthcheck URL must use the actual
463
+ # port number (e.g., http://localhost:3000/health), not an anchor reference.
464
+ # The _generate_single_backend_entry function already uses the correct port
465
+ # via the internal_port variable in the healthcheck URL.
466
+
467
+ result.append(new_line)
468
+
469
+ return "\n".join(result)
470
+
471
+ def _process_entries_with_anchors(resource_entries, has_sdk, has_frontend, has_backend):
472
+ """Replace ports with anchors in all entries."""
473
+ result = []
474
+ for entry in resource_entries:
475
+ result.append(_replace_ports_with_anchors(entry, has_sdk, has_frontend, has_backend))
476
+ return result
477
+
478
+ def generate_app_compose_from_entries(resource_path, resource_entries, write_fn=None):
479
+ """Generate docker-compose.app.autogenerated.yml from service entries with port anchors.
480
+
481
+ Args:
482
+ resource_path: Path to the resource directory
483
+ resource_entries: List of docker-compose entries for services
484
+ write_fn: Optional write function for file output
485
+
486
+ Returns:
487
+ str: Generated docker-compose YAML content
488
+ """
489
+
490
+ # Detect service types
491
+ has_sdk = _detect_sdk_in_entries(resource_entries)
492
+ has_frontend = _detect_frontend_in_entries(resource_entries)
493
+ has_backend = _detect_backend_in_entries(resource_entries)
494
+
495
+ # Replace ports with anchors in entries
496
+ processed_entries = _process_entries_with_anchors(resource_entries, has_sdk, has_frontend, has_backend)
497
+
498
+ # Generate port anchors
499
+ port_anchors = _generate_port_anchors(resource_entries)
500
+
501
+ header = """###############################################################################
502
+ # 🛑 CRITICAL: SYSTEM-GENERATED FILE - DO NOT MODIFY DIRECTLY
503
+ #
504
+ # ANY MANUAL CHANGES MADE TO THIS FILE WILL BE WIPED ON THE NEXT 'tilt up'.
505
+ # Generation Source: .tilt/topologies/platform/docker/index.star
506
+ ###############################################################################
507
+
508
+ # =============================================================================
509
+ # PORT DEFINITIONS - Change these to change ports everywhere
510
+ # =============================================================================
511
+ {port_anchors}
512
+
513
+ # =============================================================================
514
+ # MEMORY LIMITS
515
+ # =============================================================================
516
+ x-nix-any-m1-arm64-memory-limit: &nix-any-m1-arm64-memory-limit
517
+ deploy:
518
+ resources:
519
+ limits:
520
+ memory: 192M
521
+ reservations:
522
+ memory: 64M
523
+
524
+ x-frontend-memory-limit: &frontend-memory-limit
525
+ deploy:
526
+ resources:
527
+ limits:
528
+ memory: 64M
529
+ cpus: '0.15'
530
+ reservations:
531
+ memory: 32M
532
+
533
+ services:
534
+ """.format(port_anchors=port_anchors)
535
+
536
+ footer = """
537
+ networks:
538
+ {network_traefik_public}:
539
+ external: true
540
+ {network_backend}:
541
+ external: true
542
+ {network_database}:
543
+ external: true
544
+ {network_infisical}:
545
+ external: true
546
+ """.format(
547
+ network_traefik_public=PlatformDockerConstants.NETWORK_TRAEFIK_PUBLIC,
548
+ network_backend=PlatformDockerConstants.NETWORK_BACKEND,
549
+ network_database=PlatformDockerConstants.NETWORK_DATABASE,
550
+ network_infisical=PlatformDockerConstants.NETWORK_INFISICAL,
551
+ )
552
+
553
+ content = header + "\n".join(processed_entries) + footer
554
+
555
+ if write_fn:
556
+ compose_path = resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/docker-compose.app.autogenerated.yml'
557
+ print("DEBUG COMPOSE GEN: Writing {} bytes to {}".format(len(content), compose_path))
558
+ write_fn(compose_path, content)
559
+ print("DEBUG COMPOSE GEN: Write completed")
560
+ else:
561
+ print("DEBUG COMPOSE GEN: No write_fn provided!")
562
+
563
+ return content
564
+
565
+
566
+ def generate_migrator_compose(resource_path, resource_name, dockerfile_path=None, write_fn=None):
567
+ """Generate docker-compose for database migrators.
568
+
569
+ Args:
570
+ resource_path: Path to the resource directory
571
+ resource_name: Name of the resource
572
+ dockerfile_path: Optional path to Dockerfile
573
+ write_fn: Optional write function for file output
574
+
575
+ Returns:
576
+ str: Generated docker-compose YAML content
577
+ """
578
+ migrator_name = resource_name + '-db-migrator'
579
+ image_name = with_runtime_image_tag(resource_name + '-db-migrator')
580
+
581
+ # Use PROJECT_NAME from PlatformDockerConstants for portable database naming
582
+ db_name = PlatformDockerConstants.PROJECT_NAME + '_' + resource_name
583
+ default_db_url = PlatformDockerConstants.get_database_url_for_env(db_name)
584
+
585
+ dockerfile_ref = dockerfile_path if dockerfile_path else resource_path + "/" + OUTPUT.AUTOGENERATED_FOLDER + "/Dockerfile.migrator.autogenerated"
586
+ infisical_env = get_infisical_environment_vars(as_array=False)
587
+
588
+ content = """###############################################################################
589
+ # 🛑 CRITICAL: SYSTEM-GENERATED FILE - DO NOT MODIFY DIRECTLY
590
+ # Generation Source: .tilt/topologies/platform/docker/index.star
591
+ ###############################################################################
592
+ # 🔐 Infisical Zero-Code Integration Enabled
593
+ # 🗃️ Database Virtualization: Connects to shared PostgreSQL instance
594
+ services:
595
+ {migrator_name}:
596
+ image: {image_name}
597
+ build:
598
+ context: ../../../../.
599
+ dockerfile: {dockerfile_ref}
600
+ target: migrator
601
+ environment:
602
+ {infisical_env}
603
+ RESOURCE_NAME: {resource_name}
604
+ TILT_DATABASE_URL: "{default_db_url}"
605
+ DATABASE_URL: ${{DATABASE_URL:-{default_db_url}}}
606
+ networks:
607
+ - {network_database}
608
+ - {network_backend}
609
+ - {network_infisical}
610
+ restart: "no"
611
+
612
+ networks:
613
+ {network_database}:
614
+ external: true
615
+ {network_backend}:
616
+ external: true
617
+ {network_infisical}:
618
+ external: true
619
+ """.format(
620
+ migrator_name=migrator_name,
621
+ image_name=image_name,
622
+ resource_path=resource_path,
623
+ dockerfile_ref=dockerfile_ref,
624
+ resource_name=resource_name,
625
+ db_name=db_name,
626
+ default_db_url=default_db_url,
627
+ infisical_env=infisical_env,
628
+ network_database=PlatformDockerConstants.NETWORK_DATABASE,
629
+ network_backend=PlatformDockerConstants.NETWORK_BACKEND,
630
+ network_infisical=PlatformDockerConstants.NETWORK_INFISICAL,
631
+ )
632
+
633
+ if write_fn:
634
+ write_fn(resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/docker-compose.migrator.autogenerated.yml', content)
635
+
636
+ return content
@@ -0,0 +1,52 @@
1
+ # =============================================================================
2
+ # 🐳 TILT SDK - COMPOSE HELPERS
3
+ # =============================================================================
4
+
5
+
6
+
7
+ # === INLINED CONSTANTS for pure extension loading ===
8
+ OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
9
+ # === END INLINED CONSTANTS ===
10
+
11
+
12
+ def with_runtime_image_tag(image_name):
13
+ """Apply stable local image tag (:dev)."""
14
+ tag = 'dev'
15
+
16
+ last_segment = image_name.split('/')[-1]
17
+ if ':' in last_segment:
18
+ return image_name[:image_name.rfind(':')] + ':' + tag
19
+
20
+ return image_name + ':' + tag
21
+
22
+ def compose_build_config(resource_path, res_name, dockerfile=OUTPUT.AUTOGENERATED_FOLDER + '/Dockerfile.app.autogenerated', target=None):
23
+ """Generate build config for docker-compose."""
24
+ # Check if dockerfile is already a full path (contains resource_path)
25
+ if dockerfile.startswith(resource_path + '/'):
26
+ # dockerfile is already a full path, use it directly
27
+ full_dockerfile_path = dockerfile
28
+ elif res_name:
29
+ # Nested structure: construct path as resource_path/res_name/dockerfile
30
+ full_dockerfile_path = "{resource_path}/{res_name}/{dockerfile}".format(
31
+ resource_path=resource_path,
32
+ res_name=res_name,
33
+ dockerfile=dockerfile
34
+ )
35
+ else:
36
+ # Flat structure: construct path as resource_path/dockerfile
37
+ full_dockerfile_path = "{resource_path}/{dockerfile}".format(
38
+ resource_path=resource_path,
39
+ dockerfile=dockerfile
40
+ )
41
+
42
+ base_config = """ build:
43
+ context: ../../../../.
44
+ dockerfile: {full_dockerfile_path}""".format(
45
+ full_dockerfile_path=full_dockerfile_path
46
+ )
47
+
48
+ if target:
49
+ base_config += """
50
+ target: {target}""".format(target=target)
51
+
52
+ return base_config