@tdk-landscape/tdk-cli-core 1.3.15 → 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 (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,431 @@
1
+ # =============================================================================
2
+ # 🧩 ORCHESTRATOR APPLY - COMPOSE RESOURCE REGISTRATION (Refactored)
3
+ # =============================================================================
4
+
5
+ # === INLINED CONSTANTS for pure extension loading ===
6
+ OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
7
+ # === END INLINED CONSTANTS ===
8
+
9
+
10
+ load('../../../platform/docker/index.star', 'Docker')
11
+ load('../../../platform/docker/constants.star', 'PlatformDockerConstants')
12
+ load('../../../tilt/manifest/constants.star', 'BASE_PORT_FRONTEND')
13
+
14
+ load('./builders/typescript.star', 'TypescriptBuilders')
15
+
16
+
17
+ def _env_file_if_exists(env_file):
18
+ """.env is gitignored (no .env.example shipped) across every TDK example repo, so a
19
+ fresh clone must not hard-require it: docker_compose()'s env_file kwarg type-checks as
20
+ path|string (rejects None), so only return a path when the file actually exists."""
21
+ exists = str(local("test -f '" + env_file + "' && echo yes || echo no", quiet=True, echo_off=True)).strip() == 'yes'
22
+ return env_file if exists else None
23
+
24
+
25
+ def _docker_compose_app(compose_paths, project_name, env_file):
26
+ resolved_env_file = _env_file_if_exists(env_file)
27
+ if resolved_env_file:
28
+ docker_compose(compose_paths, project_name=project_name, env_file=resolved_env_file)
29
+ else:
30
+ docker_compose(compose_paths, project_name=project_name)
31
+
32
+
33
+ def _build_infra_dependencies(resource_name, should_enable, ctx, has_backend=False):
34
+ infra_deps = []
35
+ if should_enable('database-management'):
36
+ infra_deps.append('postgres')
37
+ if has_backend:
38
+ infra_deps.append('provision-db-' + resource_name)
39
+ if should_enable(PlatformDockerConstants.VERDACCIO_RESOURCE_NAME):
40
+ project_root = ctx.get('project_root', '')
41
+ verdaccio_compose = ((project_root.rstrip('/') + '/') if project_root else '') + 'docker-compose.verdaccio.yml'
42
+ if str(local("test -f '" + verdaccio_compose + "' && echo yes || echo no", quiet=True, echo_off=True)).strip() == 'yes':
43
+ infra_deps.append(PlatformDockerConstants.VERDACCIO_RESOURCE_NAME)
44
+ if should_enable('golden-image') and ctx.get('golden_image_resource'):
45
+ infra_deps.append(ctx['golden_image_resource'])
46
+ return infra_deps
47
+
48
+
49
+ def _resolve_dependency_to_resource(dep_name, all_services_map):
50
+ """
51
+ Resolve a short dependency name (e.g., 'identity') to the full YAML resource name.
52
+
53
+ The discovery registry creates YAML resources with names like:
54
+ - 'identity-management-backend-yaml' for identity backend
55
+ - 'api-gateway-yaml' for api-gateway
56
+
57
+ But manifests reference them as short names like 'identity' or 'api-gateway'.
58
+ """
59
+ # Check if already a full YAML resource name
60
+ if dep_name in all_services_map:
61
+ return dep_name
62
+
63
+ # Try with -yaml suffix (for resources already resolved)
64
+ yaml_name = dep_name + '-yaml'
65
+ if yaml_name in all_services_map:
66
+ return yaml_name
67
+
68
+ # Try to find matching service by stack/short name
69
+ for resource_name, resource_info in all_services_map.items():
70
+ # Check if this service's stack matches the dependency
71
+ resource_domain = resource_info.get('stack', '')
72
+ if resource_domain == dep_name:
73
+ # Return the first resource of this service with -yaml suffix
74
+ resources = resource_info.get('resources', [])
75
+ if resources:
76
+ # Prefer backend resources over frontends
77
+ for res in resources:
78
+ if not res.get('frontend', False):
79
+ return res['name'] + '-yaml'
80
+ # Fallback to first resource
81
+ return resources[0]['name'] + '-yaml'
82
+
83
+ # Try pattern matching: identity -> identity-*-yaml
84
+ for resource_name in all_services_map.keys():
85
+ if resource_name.startswith(dep_name + '-') and resource_name.endswith('-yaml'):
86
+ return resource_name
87
+
88
+ # Return original with -yaml suffix as last resort
89
+ return dep_name + '-yaml'
90
+
91
+
92
+ def _build_resource_deps(res, res_name, manifest, resource_config, infra_deps, config_gen_resources, resource_manifests, runtime_flags, all_services_map=None):
93
+ res_deps = list(infra_deps)
94
+ if res_name in config_gen_resources:
95
+ res_deps.append(config_gen_resources[res_name])
96
+ if res.get('frontend', False):
97
+ backend_name = manifest.get('backendName', res_name.replace('frontend', 'backend'))
98
+ if backend_name != res_name and backend_name in resource_manifests:
99
+ res_deps.append(backend_name)
100
+ elif runtime_flags['enforce_migrator_deps'] and resource_config.get('has_migrator', False):
101
+ migrator_name = resource_config['name'] + '-db-migrator'
102
+ if migrator_name not in res_deps:
103
+ res_deps.append(migrator_name)
104
+
105
+ # NEW: Resolve dependsOn from manifest
106
+ internal_deps = manifest.get('dependsOn', [])
107
+ for dep in internal_deps:
108
+ resolved_dep = _resolve_dependency_to_resource(dep, all_services_map)
109
+ if resolved_dep and resolved_dep not in res_deps:
110
+ res_deps.append(resolved_dep)
111
+
112
+ return res_deps
113
+
114
+
115
+ def _register_replicas(config, auto_init_apps):
116
+ """Register additional replica resources for high-availability services."""
117
+ replicas = config.get('replicas', 1)
118
+ if replicas <= 1:
119
+ return
120
+ # Docker compose generates replicas starting from 1 (e.g., service-1, service-2)
121
+ # Primary is the base name (service), replicas are service-1, service-2, etc.
122
+ for i in range(1, replicas):
123
+ replica_name = config['res_name'] + '-' + str(i)
124
+ dc_resource(
125
+ replica_name,
126
+ labels=config['labels'],
127
+ resource_deps=config['res_deps'],
128
+ auto_init=auto_init_apps,
129
+ )
130
+
131
+
132
+ def _build_live_update_rules(res_path, full_res_path, syncs):
133
+ """Build live update sync and run rules for a resource."""
134
+ live_update_rules = []
135
+ for sync_path in syncs:
136
+ full_sync_path = full_res_path + '/' + sync_path
137
+ dest = '/app/' + full_res_path + '/' + sync_path
138
+ live_update_rules.append(sync(full_sync_path, dest))
139
+
140
+ package_json_path = '/app/' + full_res_path + '/package.json'
141
+ bun_lock_path = '/app/' + full_res_path + '/bun.lock'
142
+ live_update_rules.append(run('cd /app/' + full_res_path + ' && bun install',
143
+ trigger=[package_json_path, bun_lock_path]))
144
+ return live_update_rules
145
+
146
+
147
+ def _register_docker_build(config, live_update_rules):
148
+ """Register docker_build with live_update for hot-reload support."""
149
+ image_name = config['resource_name'] + '_' + config['res_name'] + ':dev'
150
+ project_root = config.get('project_root', '')
151
+ # Use custom dockerfile from config if specified, otherwise use autogenerated default
152
+ # The res_path is relative to project_root (e.g., 'services/product/...')
153
+ # The dockerfile config is relative to res_path (e.g., '.autogenerated/Dockerfile...')
154
+ rel_dockerfile = config.get('dockerfile', '')
155
+ if rel_dockerfile:
156
+ if rel_dockerfile.startswith('/'):
157
+ dockerfile = config['res_path'] + rel_dockerfile
158
+ else:
159
+ dockerfile = config['res_path'] + '/' + rel_dockerfile
160
+ else:
161
+ dockerfile = config['res_path'] + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/Dockerfile.app.autogenerated'
162
+ # Prepend project_root to make absolute path from the Tilt working directory
163
+ if project_root:
164
+ if dockerfile.startswith('/'):
165
+ dockerfile = project_root + dockerfile
166
+ else:
167
+ dockerfile = project_root + '/' + dockerfile
168
+ TypescriptBuilders.build_typescript_service(
169
+ image_name,
170
+ project_root or '.',
171
+ dockerfile,
172
+ live_update_rules,
173
+ config['res_deps'],
174
+ )
175
+
176
+
177
+ def _register_run_only_resource(config, resource_path, compose_project_name, auto_init_apps=True):
178
+ """Register a run-only local resource for docker compose up."""
179
+ project_root = config.get('project_root', '')
180
+ compose_dir = resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER
181
+ compose_file = compose_dir + '/docker-compose.app.autogenerated.yml'
182
+ port_override = compose_dir + '/docker-compose.port-override.yml'
183
+ env_file = '.env'
184
+ if project_root:
185
+ root = project_root.rstrip('/')
186
+ compose_file = root + '/' + compose_file
187
+ port_override = root + '/' + port_override
188
+ env_file = root + '/.env'
189
+ compose_files = '-f ' + compose_file
190
+ if str(local("test -f '" + port_override + "' && echo yes || echo no", quiet=True, echo_off=True)).strip() == 'yes':
191
+ compose_files = compose_files + ' -f ' + port_override
192
+ # .env is gitignored (no .env.example shipped) across every TDK example repo, so a
193
+ # fresh clone must not hard-require it: only pass --env-file when it actually exists.
194
+ env_file_flag = ''
195
+ if str(local("test -f '" + env_file + "' && echo yes || echo no", quiet=True, echo_off=True)).strip() == 'yes':
196
+ env_file_flag = ' --env-file ' + env_file
197
+ local_resource(
198
+ name=config['res_name'] + '-run-only',
199
+ cmd='docker compose -p '
200
+ + compose_project_name
201
+ + ' '
202
+ + compose_files
203
+ + env_file_flag
204
+ + ' up -d --no-build '
205
+ + config['res_name'],
206
+ labels=config['labels'] + ['run-only'],
207
+ resource_deps=config['res_deps'],
208
+ auto_init=auto_init_apps,
209
+ allow_parallel=True,
210
+ )
211
+
212
+
213
+ def _register_single_resource(config, resource_path, compose_project_name, auto_init_apps):
214
+ """Register a single dc_resource with optional docker_build and run-only resource."""
215
+ app_type = config.get('app_type', 'backend')
216
+ res_name = config['res_name']
217
+
218
+ print("DEBUG REGISTER: Registering '{}' app_type='{}' auto_init={}".format(res_name, app_type, auto_init_apps))
219
+
220
+ # Skip docker_build for libraries and SDKs - they don't have Dockerfiles
221
+ if app_type not in ['library', 'sdk'] and config.get('syncs'):
222
+ print("DEBUG REGISTER: '{}' has syncs, calling docker_build".format(res_name))
223
+ live_update_rules = _build_live_update_rules(
224
+ config['res_path'],
225
+ config['res_path'],
226
+ config['syncs']
227
+ )
228
+ _register_docker_build(config, live_update_rules)
229
+ else:
230
+ print("DEBUG REGISTER: '{}' skipping docker_build (app_type='{}', syncs={})".format(res_name, app_type, config.get('syncs')))
231
+
232
+ print("DEBUG REGISTER: Calling dc_resource for '{}' with deps={}".format(res_name, config['res_deps']))
233
+ dc_resource(
234
+ config['res_name'],
235
+ labels=config['labels'],
236
+ resource_deps=config['res_deps'],
237
+ auto_init=auto_init_apps,
238
+ )
239
+ print("DEBUG REGISTER: dc_resource completed for '{}'".format(res_name))
240
+
241
+ _register_run_only_resource(config, resource_path, compose_project_name, auto_init_apps)
242
+ _register_replicas(config, auto_init_apps)
243
+
244
+
245
+ def _register_dc_resources(resource_configs, resource_path, compose_project_name, auto_init_apps):
246
+ """Register all dc_resources for the service."""
247
+ for config in resource_configs:
248
+ _register_single_resource(config, resource_path, compose_project_name, auto_init_apps)
249
+
250
+
251
+ def _has_backend_resource(resource_config, resource_manifests):
252
+ """Check if service has a backend resource."""
253
+ for resource in resource_config.get('resources', []):
254
+ manifest = resource_manifests.get(resource['name'], {})
255
+ if manifest.get('appType') == 'backend':
256
+ return True
257
+ return False
258
+
259
+
260
+ def _should_skip_frontend(res, should_enable):
261
+ """Check if frontend resource should be skipped (backends-only mode)."""
262
+ return res.get('frontend', False) and should_enable('backends-only')
263
+
264
+
265
+ def _process_frontend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable):
266
+ """Process a frontend resource and return compose entry."""
267
+ target_path = res.get('target_path', manifest.get('target_path', '/usr/share/nginx/html'))
268
+ use_nginx = 'nginx' in target_path or target_path == '/usr/share/nginx/html'
269
+ port = res.get('port', manifest.get('port', BASE_PORT_FRONTEND))
270
+ Docker.frontend(
271
+ res_path=full_res_path,
272
+ resource_name=res['name'],
273
+ port=port,
274
+ target_path=target_path,
275
+ use_nginx=use_nginx,
276
+ use_golden=should_enable('golden-image'),
277
+ )
278
+ return Docker.frontend_compose(resource_path=resource_path, resource_name=resource_name, res=res, manifest=manifest)
279
+
280
+
281
+ def _process_backend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable):
282
+ """Process a backend resource and return compose entry."""
283
+ Docker.backend(
284
+ res_path=full_res_path,
285
+ resource_name=res['name'],
286
+ port=None, # Use default BASE_PORT_BACKEND
287
+ build_cmd=res.get('start_command', 'bun run build'),
288
+ use_golden=should_enable('golden-image'),
289
+ )
290
+ return Docker.backend_compose(resource_path=resource_path, resource_name=resource_name, res=res, manifest=manifest)
291
+
292
+
293
+ def _build_resource_config(res, manifest, resource_config, full_res_path, infra_deps,
294
+ config_gen_resources, resource_manifests, runtime_flags, all_services_map=None, project_root=''):
295
+ """Build configuration dict for a single resource."""
296
+ res_name = res['name']
297
+ res_deps = _build_resource_deps(
298
+ res, res_name, manifest, resource_config, infra_deps,
299
+ config_gen_resources, resource_manifests, runtime_flags, all_services_map,
300
+ )
301
+ replicas = manifest.get('replicas', 1)
302
+ if runtime_flags['disable_app_replicas']:
303
+ replicas = 1
304
+
305
+ # Extract environment variables from manifest params
306
+ env_vars = manifest.get('params', {})
307
+
308
+ return {
309
+ 'res_name': res_name,
310
+ 'resource_name': resource_config['name'],
311
+ 'res_deps': res_deps,
312
+ 'labels': ['app.' + res_name],
313
+ 'replicas': replicas,
314
+ 'syncs': res.get('syncs', []),
315
+ 'app_type': manifest.get('appType', 'backend'),
316
+ 'dockerfile': res.get('dockerfile', OUTPUT.AUTOGENERATED_FOLDER + '/Dockerfile.app.autogenerated'),
317
+ 'res_path': full_res_path,
318
+ 'env': env_vars,
319
+ 'project_root': project_root,
320
+ }
321
+
322
+
323
+ def register_compose_resources(resource_config, ctx, runtime_flags, manifest_state):
324
+ """Register all compose resources for a service."""
325
+ resource_name = resource_config.get('name', '')
326
+ resource_path = resource_config.get('path', '')
327
+
328
+ print("DEBUG COMPOSE: register_compose_resources called for '{}' path='{}'".format(resource_name, resource_path))
329
+
330
+ if not resource_name:
331
+ print("⚠️ Skipping service without name")
332
+ return {'compose_project_name': ''}
333
+
334
+ if not resource_path:
335
+ # Silently skip services without paths - these may be library references
336
+ # or incomplete manifests
337
+ print("DEBUG COMPOSE: SKIPPING '{}' - no path".format(resource_name))
338
+ return {'compose_project_name': ''}
339
+
340
+ print("DEBUG COMPOSE: Proceeding with '{}' - creating compose resources".format(resource_name))
341
+ should_enable = ctx['should_enable']
342
+ write_file = ctx['write_file']
343
+ compose_project_name = resource_name + '-autogenerated'
344
+
345
+ resource_manifests = manifest_state['resource_manifests']
346
+ config_gen_resources = manifest_state['config_gen_resources']
347
+ has_backend = _has_backend_resource(resource_config, resource_manifests)
348
+ infra_deps = _build_infra_dependencies(resource_name, should_enable, ctx, has_backend)
349
+
350
+ # Build map of all services for dependency resolution
351
+ # This maps short names like 'identity' to full resource names
352
+ all_services_map = {}
353
+ all_services = ctx.get('all_services', [])
354
+ for svc in all_services:
355
+ svc_name = svc.get('name', '')
356
+ if svc_name:
357
+ all_services_map[svc_name] = svc
358
+ # Also map resource names
359
+ for res in svc.get('resources', []):
360
+ res_name = res.get('name', '')
361
+ if res_name:
362
+ all_services_map[res_name] = svc
363
+
364
+ resource_entries = []
365
+ resource_configs = []
366
+
367
+ for res in resource_config.get('resources', []):
368
+ if _should_skip_frontend(res, should_enable):
369
+ continue
370
+
371
+ full_res_path = res.get('_resource_path', resource_path + '/' + res['name'])
372
+ manifest = resource_manifests.get(res['name'], {})
373
+
374
+ # Skip library/sdk type resources - they don't have Docker builds
375
+ app_type = manifest.get('appType', 'backend')
376
+ if app_type in ['library', 'sdk']:
377
+ continue
378
+
379
+ if res.get('frontend', False):
380
+ entry = _process_frontend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable)
381
+ else:
382
+ entry = _process_backend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable)
383
+
384
+ resource_entries.append(entry)
385
+ resource_configs.append(_build_resource_config(
386
+ res, manifest, resource_config, full_res_path, infra_deps,
387
+ config_gen_resources, resource_manifests, runtime_flags, all_services_map,
388
+ ctx.get('project_root', ''),
389
+ ))
390
+
391
+ print("DEBUG COMPOSE: Calling Docker.app_compose for '{}' with write_fn={}".format(resource_name, write_file))
392
+ Docker.app_compose(resource_path, resource_entries, write_file)
393
+
394
+ print("DEBUG COMPOSE: {} resource_entries count = {}".format(resource_name, len(resource_entries)))
395
+
396
+ # Prepend project root to make path absolute from project root
397
+ project_root = ctx.get('project_root', '')
398
+
399
+ # Only register docker_compose if we have actual services (not just libraries)
400
+ if resource_entries:
401
+ compose_file_rel = resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/docker-compose.app.autogenerated.yml'
402
+ compose_file = project_root + '/' + compose_file_rel if project_root else compose_file_rel
403
+ # Load env file from project root for docker-compose variables (DB_PASSWORD, etc.)
404
+ env_file = project_root + '/.env' if project_root else '.env'
405
+ print("DEBUG COMPOSE: Calling docker_compose for '{}' with {} env_file={}".format(resource_name, compose_file, env_file))
406
+ _docker_compose_app(compose_file, compose_project_name, env_file)
407
+ port_override_rel = resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/docker-compose.port-override.yml'
408
+ port_override = project_root + '/' + port_override_rel if project_root else port_override_rel
409
+ if str(local("test -f '" + port_override + "' && echo yes || echo no", quiet=True, echo_off=True)).strip() == 'yes':
410
+ print("DEBUG COMPOSE: Applying port override {}".format(port_override))
411
+ _docker_compose_app(port_override, compose_project_name, env_file)
412
+ else:
413
+ print("DEBUG COMPOSE: SKIPPING docker_compose for '{}' - no resource_entries".format(resource_name))
414
+
415
+ for compose_file in resource_config.get('compose_files', []):
416
+ if compose_file != '.autogenerated/docker-compose.app.autogenerated.yml':
417
+ compose_file_full = project_root + '/' + resource_path + '/' + compose_file if project_root else resource_path + '/' + compose_file
418
+ env_file = project_root + '/.env' if project_root else '.env'
419
+ _docker_compose_app(compose_file_full, compose_project_name, env_file)
420
+
421
+ print("DEBUG COMPOSE: Calling _register_dc_resources for '{}' with {} configs, auto_init={}".format(resource_name, len(resource_configs), runtime_flags['auto_init_apps']))
422
+ _register_dc_resources(resource_configs, resource_path, compose_project_name, runtime_flags['auto_init_apps'])
423
+
424
+ return {'compose_project_name': compose_project_name}
425
+
426
+
427
+ ComposeResourceRegistration = struct(
428
+ register = register_compose_resources,
429
+ )
430
+
431
+ DEBUG = {}
@@ -0,0 +1,81 @@
1
+ # =============================================================================
2
+ # 📄 ORCHESTRATOR APPLY - MANIFEST ORCHESTRATION
3
+ # =============================================================================
4
+
5
+ # === INLINED CONSTANTS for pure extension loading ===
6
+ BASE_PORT_FRONTEND = 3000
7
+ BASE_PORT_BACKEND = 4000
8
+ # === END INLINED CONSTANTS ===
9
+
10
+
11
+ load('../../../../../discovery/loading.star', 'Manifest')
12
+ load('./generators/manifest_resource.star', 'ManifestResource')
13
+
14
+
15
+
16
+ def prepare_resource_manifests(resource_config, ctx):
17
+ resource_name = resource_config['name']
18
+ resource_manifests = {}
19
+ backend_manifest_cache = {}
20
+ config_gen_resources = {}
21
+
22
+ all_resources = resource_config.get('resources', [])
23
+
24
+ # First pass: load all manifests.
25
+ for resource in resource_config.get('resources', []):
26
+ # Use the resource's _resource_path if available (for services from different scan roots)
27
+ # Otherwise fall back to constructing path from resource_config path + resource name
28
+ resource_path = resource.get('_resource_path', resource_config['path'] + '/' + resource['name'])
29
+
30
+ # Load the manifest from the resource path
31
+ manifest = Manifest.load_manifest(resource_path)
32
+ resource_manifests[resource['name']] = manifest
33
+
34
+ app_name = manifest.get('appName', resource['name'])
35
+ stack = manifest.get('stack', resource_name)
36
+ port = manifest.get('port', BASE_PORT_FRONTEND)
37
+ app_type = manifest.get('appType', 'backend')
38
+
39
+ print(
40
+ " [Tilt] ✅ Loaded: " + app_name
41
+ + " | Stack: " + stack
42
+ + " | Port: " + str(port)
43
+ + " | Type: " + app_type
44
+ )
45
+
46
+ if manifest.get('appType') == 'backend':
47
+ backend_manifest_cache[manifest.get('appName')] = manifest
48
+
49
+ # Second pass: create config generation resources.
50
+ for resource in resource_config.get('resources', []):
51
+ manifest = resource_manifests.get(resource['name'], {})
52
+ backend_manifest = None
53
+
54
+ if resource.get('frontend', False) or manifest.get('appType') == 'frontend':
55
+ backend_name = manifest.get('backendName', resource['name'].replace('-frontend', '-backend'))
56
+ backend_manifest = backend_manifest_cache.get(backend_name)
57
+ if not backend_manifest:
58
+ backend_manifest = Manifest.load_related(manifest)
59
+
60
+ # Use the resource's actual service path for config generation
61
+ resource_path = resource.get('_resource_path', resource_config['path'] + '/' + resource['name'])
62
+
63
+ config_gen_resource = ManifestResource.create_config_resource(
64
+ resource_name,
65
+ resource,
66
+ resource_path, # Pass correct path instead of resource_config['path']
67
+ manifest,
68
+ backend_manifest,
69
+ ctx,
70
+ )
71
+ config_gen_resources[resource['name']] = config_gen_resource
72
+
73
+ return {
74
+ 'resource_manifests': resource_manifests,
75
+ 'config_gen_resources': config_gen_resources,
76
+ }
77
+
78
+
79
+ ManifestOrchestration = struct(
80
+ prepare = prepare_resource_manifests,
81
+ )
@@ -0,0 +1,148 @@
1
+ # =============================================================================
2
+ # 🗃️ ORCHESTRATOR APPLY - MIGRATOR ORCHESTRATION
3
+ # =============================================================================
4
+ # BLOCKER FIX #2: Sequential migration execution to prevent race conditions
5
+ # =============================================================================
6
+
7
+ # === INLINED CONSTANTS for pure extension loading ===
8
+ OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
9
+ # === END INLINED CONSTANTS ===
10
+
11
+
12
+ load('../../../platform/docker/index.star', 'Docker')
13
+
14
+ load('../../../platform/docker/generators/golden_docker_generator_v2.star', 'L4_generate_orchestrator')
15
+
16
+
17
+ def _find_backend_resource_path(resource_config):
18
+ resource_path = resource_config.get('path', '')
19
+ if not resource_path:
20
+ return None
21
+ backend_resource_path = None
22
+
23
+ for res in resource_config.get('resources', []):
24
+ if res.get('frontend', False):
25
+ continue
26
+
27
+ candidate = resource_path + '/' + res['name']
28
+ prisma_check = local("test -d " + candidate + "/prisma && echo yes || echo no", quiet=True)
29
+ prisma_check_stdout = getattr(prisma_check, "stdout", prisma_check)
30
+ has_prisma = str(prisma_check_stdout).strip() == 'yes'
31
+
32
+ if has_prisma:
33
+ return candidate
34
+
35
+ if backend_resource_path == None:
36
+ backend_resource_path = candidate
37
+
38
+ return backend_resource_path
39
+
40
+
41
+ def register_migrators(resource_config, ctx, runtime_flags, compose_project_name):
42
+ should_enable = ctx['should_enable']
43
+ write_file = ctx['write_file']
44
+ resource_name = resource_config.get('name', '')
45
+ resource_path = resource_config.get('path', '')
46
+
47
+ if not resource_name:
48
+ print("⚠️ Skipping migrator registration for service without name")
49
+ return
50
+
51
+ if not resource_path:
52
+ # Silently skip migrator registration for services without paths
53
+ return
54
+
55
+ if not resource_config.get('has_migrator', False):
56
+ return
57
+ if not should_enable('database-management'):
58
+ return
59
+
60
+ backend_resource_path = _find_backend_resource_path(resource_config)
61
+ dockerfile_res_path = backend_resource_path if backend_resource_path else resource_path
62
+
63
+ df_contents = L4_generate_orchestrator(
64
+ dockerfile_res_path,
65
+ res_type='migrator',
66
+ resource_name=resource_name,
67
+ use_nginx=False,
68
+ port=3000,
69
+ target_path='/app/dist',
70
+ build_cmd='echo "No build needed for migrator"',
71
+ cmd='bun run db:migrate',
72
+ use_infisical=True,
73
+ use_shared_libs=True,
74
+ use_prisma=True,
75
+ use_golden=should_enable('golden-image'),
76
+ )
77
+ write_file(dockerfile_res_path + "/" + OUTPUT.AUTOGENERATED_FOLDER + "/Dockerfile.migrator.autogenerated", df_contents)
78
+
79
+ migrator_dockerignore_content = Docker.generate_dockerignore(dockerfile_res_path, 'migrator')
80
+ write_file(
81
+ dockerfile_res_path + "/" + OUTPUT.AUTOGENERATED_FOLDER + "/Dockerfile.migrator.autogenerated.dockerignore",
82
+ migrator_dockerignore_content,
83
+ )
84
+
85
+ Docker.migrator_compose(
86
+ resource_path=resource_path,
87
+ resource_name=resource_name,
88
+ dockerfile_path=dockerfile_res_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/Dockerfile.migrator.autogenerated',
89
+ write_fn=write_file,
90
+ )
91
+ docker_compose(
92
+ resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/docker-compose.migrator.autogenerated.yml',
93
+ project_name=compose_project_name,
94
+ )
95
+
96
+ migrator_names = resource_config.get('migrator_names', [resource_name + '-db-migrator'])
97
+ for migrator_name in migrator_names:
98
+ # =============================================================================
99
+ # BLOCKER FIX #2: Sequential migration dependencies prevent race conditions
100
+ # =============================================================================
101
+ # Each migrator depends on postgres, its own provisioned DB, golden image,
102
+ # AND the previous migrator to ensure sequential execution
103
+ migrator_db_deps = ['postgres', 'provision-db-' + resource_name]
104
+ if should_enable('golden-image') and ctx.get('golden_image_resource'):
105
+ migrator_db_deps.append(ctx['golden_image_resource'])
106
+
107
+ # Add sequential dependency on previous migrator to prevent parallel execution
108
+ # Use ctx to track sequence (mutable dict passed from caller)
109
+ migrator_index = ctx.setdefault('migrator_sequence', {})
110
+ previous_migrator = _get_previous_migrator(resource_name, migrator_index)
111
+ if previous_migrator:
112
+ migrator_db_deps.append(previous_migrator)
113
+ print("🔗 Sequential migration: " + migrator_name + " will run after " + previous_migrator)
114
+
115
+ dc_resource(
116
+ migrator_name,
117
+ labels=['app.' + resource_name, 'migrator'],
118
+ resource_deps=migrator_db_deps,
119
+ auto_init=runtime_flags['auto_init_migrators'],
120
+ )
121
+
122
+
123
+ # =============================================================================
124
+ # BLOCKER FIX #2: Migration sequencing tracker
125
+ # =============================================================================
126
+ # Pass migrator sequence via ctx to avoid frozen module-level state
127
+ # The caller initializes ctx['migrator_sequence'] = {} before calling register_migrators
128
+
129
+ def _get_previous_migrator(current_service, migrator_index):
130
+ """
131
+ Get the previous migrator in sequence for dependency chaining.
132
+ Returns the migrator name that should complete before this one starts.
133
+ """
134
+ if current_service not in migrator_index:
135
+ migrator_index[current_service] = len(migrator_index)
136
+
137
+ idx = migrator_index[current_service]
138
+ if idx > 0:
139
+ # Find the service with index idx - 1
140
+ for svc, svc_idx in migrator_index.items():
141
+ if svc_idx == idx - 1:
142
+ return svc + '-db-migrator'
143
+ return None
144
+
145
+
146
+ MigratorOrchestration = struct(
147
+ register = register_migrators,
148
+ )