@tdk-landscape/tdk-cli-core 1.3.120 → 1.3.122

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 (56) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/README.md +21 -14
  3. package/cli/dist/cli.js +1 -1
  4. package/cli/dist/commands/config.js +6 -2
  5. package/cli/dist/commands/doctor.js +64 -7
  6. package/cli/dist/commands/help.js +17 -1
  7. package/cli/dist/commands/logs.js +34 -7
  8. package/cli/dist/commands/networks.js +1 -1
  9. package/cli/dist/commands/resources.js +1 -1
  10. package/cli/dist/commands/stack.js +94 -22
  11. package/cli/dist/commands/status.js +35 -2
  12. package/cli/dist/commands/ui.js +105 -9
  13. package/cli/dist/commands/up.js +105 -28
  14. package/cli/dist/components/ServiceIssues.js +12 -0
  15. package/cli/dist/components/TabBar.js +3 -2
  16. package/cli/dist/components/index.js +1 -0
  17. package/cli/dist/generator/__tests__/golden-helpers.js +42 -0
  18. package/cli/dist/generator/template-engine.js +5 -3
  19. package/cli/dist/utils/atomic-write.js +35 -0
  20. package/cli/dist/utils/bun-floor.js +8 -0
  21. package/cli/dist/utils/cold-preflight.js +6 -4
  22. package/cli/dist/utils/doctor-runtime.js +6 -6
  23. package/cli/dist/utils/doctor-wiring.js +62 -1
  24. package/cli/dist/utils/errors.js +20 -2
  25. package/cli/dist/utils/host-port-plan.js +3 -0
  26. package/cli/dist/utils/machine-output.js +8 -1
  27. package/cli/dist/utils/port-conflict-fix.js +24 -0
  28. package/cli/dist/utils/port-fallback-notice.js +18 -0
  29. package/cli/dist/utils/port-listener-probe.js +19 -0
  30. package/cli/dist/utils/secret-redaction.js +55 -0
  31. package/cli/dist/utils/service-config-checks.js +188 -0
  32. package/cli/dist/utils/service-runtime-state.js +67 -0
  33. package/cli/dist/utils/services.js +2 -15
  34. package/cli/dist/utils/single-flight.js +20 -0
  35. package/cli/dist/utils/stack-status.js +15 -0
  36. package/cli/dist/utils/startup-report.js +73 -0
  37. package/cli/dist/utils/suggestions.js +58 -0
  38. package/cli/dist/utils/tilt-events.js +195 -0
  39. package/cli/dist/utils/tilt-resource-state.js +12 -0
  40. package/cli/dist/utils/tilt-startup.js +7 -0
  41. package/cli/dist/utils/tilt-unreachable-message.js +8 -0
  42. package/cli/dist/utils/ui-service-state.js +35 -0
  43. package/cli/dist/utils/up-readiness.js +2 -0
  44. package/cli/package.json +1 -1
  45. package/cli/templates/.tiltignore.hbs +1 -2
  46. package/cli/templates/TILT_RESOURCE_DEFAULTS.star.hbs +1 -1
  47. package/cli/templates/TILT_TECH_STACK.star.hbs +5 -5
  48. package/cli/templates/Tiltfile.hbs +3 -3
  49. package/cli/templates/spec.master.hbs +1 -1
  50. package/engine/topologies/platform/docker/compose/compose.star +1 -86
  51. package/engine/topologies/platform/docker/compose/traefik_standalone.star +4 -4
  52. package/engine/topologies/platform/docker/networking/traefik.star +40 -7
  53. package/engine/topologies/platform/docker/networking/traefik_helpers.star +1 -1
  54. package/engine/topologies/tilt/resources/infra-loader.star +3 -2
  55. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +20 -0
  56. package/package.json +1 -1
@@ -83,6 +83,22 @@ def get_frontend_traefik_labels(res_name, domain, base_path, port, traefik_host=
83
83
 
84
84
  return labels
85
85
 
86
+ def _stack_is_shared(manifest):
87
+ """True when this backend shares its stack with another routable backend (`_stackBackendCount`, set by register_compose_resources).
88
+
89
+ The stack-scoped routers (the host/`pathPrefix` router and `<name>-management`) carry one rule per stack. With two backends in
90
+ one stack they would carry the same rule, so Traefik would pick between them arbitrarily. Each backend keeps its own
91
+ `<name>-project` router (`/api/<name>`).
92
+ """
93
+ return bool(manifest) and manifest.get('_stackBackendCount', 1) > 1
94
+
95
+
96
+ def _explicit_backend_route(manifest):
97
+ """The `traefik.host` / `traefik.pathPrefix` a manifest set on purpose, as (host, path); either may be empty."""
98
+ traefik_cfg = (manifest.get('traefik') or {}) if manifest else {}
99
+ return traefik_cfg.get('host') or '', traefik_cfg.get('pathPrefix') or ''
100
+
101
+
86
102
  def get_backend_traefik_labels(
87
103
  resource_entry_name,
88
104
  traefik_host,
@@ -123,12 +139,31 @@ def get_backend_traefik_labels(
123
139
  # No middleware if path is empty - router has no middlewares
124
140
  middleware_config = ""
125
141
 
126
- labels = """ - "{traefik_enable_label}"
127
- - "traefik.http.routers.{resource_entry_name}.rule={backend_route_rule}"
142
+ stack_shared = _stack_is_shared(manifest)
143
+ explicit_host, explicit_path = _explicit_backend_route(manifest)
144
+ if stack_shared:
145
+ # Another backend in this stack would match the same stack rule, so it is not emitted. The service labels stay (the
146
+ # -project router points at the service). A manifest that set its own host or pathPrefix keeps a router, but one whose
147
+ # rule is only what it set, never the shared stack host or path.
148
+ backend_route_rule = backend_rule(explicit_host, explicit_path)
149
+
150
+ if stack_shared and not backend_route_rule:
151
+ backend_router_labels = ""
152
+ else:
153
+ backend_router_labels = """ - "traefik.http.routers.{resource_entry_name}.rule={backend_route_rule}"
128
154
  - "traefik.http.routers.{resource_entry_name}.entrypoints={backend_entrypoints}"
129
155
  - "traefik.http.routers.{resource_entry_name}.service={traefik_resource_name}"
130
156
  {middleware_config}
131
- - "traefik.http.services.{traefik_resource_name}.loadbalancer.server.port={internal_port}"
157
+ """.format(
158
+ resource_entry_name=resource_entry_name,
159
+ backend_route_rule=backend_route_rule,
160
+ backend_entrypoints=backend_entrypoints,
161
+ traefik_resource_name=traefik_resource_name,
162
+ middleware_config=middleware_config,
163
+ )
164
+
165
+ labels = """ - "{traefik_enable_label}"
166
+ {backend_router_labels} - "traefik.http.services.{traefik_resource_name}.loadbalancer.server.port={internal_port}"
132
167
  - "traefik.http.services.{traefik_resource_name}.loadbalancer.healthcheck.path={health_path}"
133
168
  - "traefik.http.services.{traefik_resource_name}.loadbalancer.healthcheck.interval={health_interval}"
134
169
  - "traefik.http.services.{traefik_resource_name}.loadbalancer.healthcheck.timeout={health_timeout}"
@@ -137,10 +172,8 @@ def get_backend_traefik_labels(
137
172
  """.format(
138
173
  resource_entry_name=resource_entry_name,
139
174
  traefik_enable_label=TRAEFIK_ENABLE_LABEL,
140
- backend_route_rule=backend_route_rule,
141
- backend_entrypoints=backend_entrypoints,
175
+ backend_router_labels=backend_router_labels,
142
176
  traefik_resource_name=traefik_resource_name,
143
- middleware_config=middleware_config,
144
177
  internal_port=internal_port,
145
178
  health_path=health_path,
146
179
  health_interval=TRAEFIK_HEALTHCHECK_INTERVAL,
@@ -188,7 +221,7 @@ def get_backend_traefik_labels(
188
221
  router_priority=router_priority,
189
222
  )
190
223
 
191
- if management_path and management_path != api_path:
224
+ if management_path and management_path != api_path and not stack_shared:
192
225
  labels += """
193
226
  - "traefik.http.routers.{resource_entry_name}-management.rule=Host(`{api_host}`) && PathPrefix(`{management_path}`)"
194
227
  - "traefik.http.routers.{resource_entry_name}-management.entrypoints={project_entrypoints}"
@@ -64,7 +64,7 @@ def frontend_rule(res_name, base_path, traefik_host):
64
64
 
65
65
  def backend_rule(traefik_host, traefik_path):
66
66
  parts = []
67
- if TRAEFIK_ENABLE_BACKEND_HOST_RULE:
67
+ if TRAEFIK_ENABLE_BACKEND_HOST_RULE and traefik_host:
68
68
  parts.append("Host(`{host}`)".format(host=traefik_host))
69
69
  if TRAEFIK_ENABLE_BACKEND_PATH_RULE and traefik_path:
70
70
  parts.append("PathPrefix(`{path}`)".format(path=traefik_path))
@@ -70,7 +70,7 @@ services:
70
70
  POSTGRES_PASSWORD: ${{DB_PASSWORD}}
71
71
  POSTGRES_DB: postgres
72
72
  ports:
73
- - "{host_port}:5432"
73
+ - "{bind_address}:{host_port}:5432"
74
74
  volumes:
75
75
  - {name}_postgres_data:/var/lib/postgresql/data
76
76
  networks:
@@ -88,7 +88,7 @@ networks:
88
88
 
89
89
  volumes:
90
90
  {name}_postgres_data:
91
- """.format(name=name, database_network=database_network, host_port=os.environ.get('TDK_POSTGRES_PORT', '15432'))
91
+ """.format(name=name, database_network=database_network, host_port=os.environ.get('TDK_POSTGRES_PORT', '15432'), bind_address=os.environ.get('TDK_BIND_ADDRESS', '127.0.0.1'))
92
92
 
93
93
 
94
94
  def _ensure_database_management_compose(root_prefix, write_fn):
@@ -236,6 +236,7 @@ def _load_standalone_traefik(root_prefix, env_file, write_fn):
236
236
  sablier_enabled,
237
237
  os.environ.get('TDK_HTTP_PORT', '8080'),
238
238
  os.environ.get('TDK_HTTPS_PORT', '8443'),
239
+ os.environ.get('TDK_BIND_ADDRESS', '127.0.0.1'),
239
240
  )
240
241
  if write_fn:
241
242
  write_fn(compose_rel, content)
@@ -632,6 +632,21 @@ def _build_resource_config(res, manifest, resource_config, full_res_path, infra_
632
632
  }
633
633
 
634
634
 
635
+ def count_routable_backends_by_stack(resource_config, resource_manifests, default_stack):
636
+ """Count the backends of each stack that get a Traefik route (workers, frontends, SDKs, libraries and unproxied BYO do not)."""
637
+ counts = {}
638
+ for resource in resource_config.get('resources', []):
639
+ manifest = resource_manifests.get(resource['name'], {})
640
+ app_type = manifest.get('appType', 'backend')
641
+ if resource.get('frontend', False) or app_type in ['frontend', 'worker', 'library', 'sdk', 'infra']:
642
+ continue
643
+ if app_type == 'bring-your-own' and not manifest.get('exposeViaProxy', True):
644
+ continue
645
+ stack = manifest.get('stack', default_stack)
646
+ counts[stack] = counts.get(stack, 0) + 1
647
+ return counts
648
+
649
+
635
650
  def register_compose_resources(resource_config, ctx, runtime_flags, manifest_state):
636
651
  """Register all compose resources for a service."""
637
652
  resource_name = resource_config.get('name', '')
@@ -695,6 +710,7 @@ def register_compose_resources(resource_config, ctx, runtime_flags, manifest_sta
695
710
 
696
711
  resource_entries = []
697
712
  resource_configs = []
713
+ stack_backend_counts = count_routable_backends_by_stack(resource_config, resource_manifests, resource_name)
698
714
 
699
715
  for res in resource_config.get('resources', []):
700
716
  if _should_skip_frontend(res, should_enable):
@@ -702,6 +718,10 @@ def register_compose_resources(resource_config, ctx, runtime_flags, manifest_sta
702
718
 
703
719
  full_res_path = res.get('_resource_path', resource_path + '/' + res['name'])
704
720
  manifest = resource_manifests.get(res['name'], {})
721
+ if manifest:
722
+ # Tell the Traefik label generator how many routable backends share this stack (see _stack_is_shared in networking/traefik.star).
723
+ manifest = dict(manifest)
724
+ manifest['_stackBackendCount'] = stack_backend_counts.get(manifest.get('stack', resource_name), 1)
705
725
 
706
726
  # Skip non-app resources - they don't use the golden app Dockerfile pipeline.
707
727
  # infra (e.g. verdaccio) is loaded via registries/verdaccio_loader.star instead.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdk-landscape/tdk-cli-core",
3
- "version": "1.3.120",
3
+ "version": "1.3.122",
4
4
  "description": "TDK CLI — start services on your laptop.",
5
5
  "homepage": "https://tdk-landscape.github.io/tdk-website",
6
6
  "repository": {