@tdk-landscape/tdk-cli-core 1.3.124 → 1.3.126

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 (36) hide show
  1. package/CHANGELOG.md +289 -18
  2. package/README.md +20 -1
  3. package/cli/dist/commands/doctor.js +3 -127
  4. package/cli/dist/commands/down.js +8 -1
  5. package/cli/dist/commands/eject.js +2 -2
  6. package/cli/dist/commands/logs.js +16 -10
  7. package/cli/dist/commands/networks.js +10 -10
  8. package/cli/dist/commands/resource.js +7 -126
  9. package/cli/dist/commands/ui.js +35 -14
  10. package/cli/dist/commands/upgrade.js +39 -52
  11. package/cli/dist/components/TabBar.js +2 -7
  12. package/cli/dist/generator/resource-templates.js +127 -0
  13. package/cli/dist/generator/template-engine.js +7 -1
  14. package/cli/dist/utils/doctor-runtime.js +6 -3
  15. package/cli/dist/utils/doctor-starlark.js +131 -0
  16. package/cli/dist/utils/env-validator.js +61 -19
  17. package/cli/dist/utils/service-manifest.js +145 -0
  18. package/cli/dist/utils/service-runtime-state.js +2 -2
  19. package/cli/dist/utils/services.js +8 -1
  20. package/cli/dist/utils/startup-report.js +10 -6
  21. package/cli/dist/utils/tilt-resource-state.js +19 -0
  22. package/cli/dist/utils/tilt.js +2 -1
  23. package/cli/dist/utils/ui-keymap.js +118 -0
  24. package/cli/package.json +1 -1
  25. package/discovery/manifest/loading.star +5 -0
  26. package/discovery/manifest/normalize.star +6 -0
  27. package/engine/topologies/tilt/common/utils.star +3 -3
  28. package/engine/topologies/tilt/common/utils_debug.star +1 -1
  29. package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +4 -6
  30. package/engine/topologies/tilt/manifest/loader.star +7 -0
  31. package/engine/topologies/tilt/manifest/parser.star +86 -3
  32. package/engine/topologies/tilt/manifest/tests/test_loader_parser.star +63 -1
  33. package/engine/topologies/tilt/resources/infra-loader.star +12 -11
  34. package/engine/topologies/tilt/resources/orchestrator/apply.star +2 -1
  35. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +19 -18
  36. package/package.json +1 -1
@@ -0,0 +1,118 @@
1
+ export const TABS = [
2
+ { id: "overview", label: "OVERVIEW", shortcut: "1" },
3
+ { id: "resources", label: "RESOURCES", shortcut: "2" },
4
+ { id: "events", label: "EVENTS", shortcut: "3" },
5
+ { id: "files", label: "FILES", shortcut: "4" },
6
+ { id: "config", label: "CONFIG", shortcut: "5" },
7
+ ];
8
+ const tabRange = `${TABS[0].shortcut}-${TABS[TABS.length - 1].shortcut}`;
9
+ const uiKeymap = [
10
+ {
11
+ id: "list-navigation",
12
+ keys: ["ArrowUp", "ArrowDown", "j", "k"],
13
+ label: "Navigate list items",
14
+ group: "Navigation",
15
+ display: { ascii: "UP/DOWN or k/j", unicode: "↑/↓ or k/j" },
16
+ },
17
+ {
18
+ id: "first-last",
19
+ keys: ["g", "G", "Home", "End"],
20
+ label: "First/last item",
21
+ group: "Navigation",
22
+ display: { ascii: "g/G or Home/End", unicode: "g/G or Home/End" },
23
+ },
24
+ {
25
+ id: "page-list",
26
+ keys: ["PageUp", "PageDown"],
27
+ label: "Move one page",
28
+ group: "Navigation",
29
+ display: { ascii: "PgUp/PgDn", unicode: "PgUp/PgDn" },
30
+ },
31
+ {
32
+ id: "select",
33
+ keys: ["Enter", "Space"],
34
+ label: "Select item / Open detail",
35
+ group: "Navigation",
36
+ display: { ascii: "Enter or Space", unicode: "Enter or Space" },
37
+ },
38
+ {
39
+ id: "cycle-tabs",
40
+ keys: ["Tab", "Shift+Tab"],
41
+ label: "Next / previous tab",
42
+ group: "Navigation",
43
+ display: { ascii: "Tab/Shift+Tab", unicode: "Tab/Shift+Tab" },
44
+ },
45
+ {
46
+ id: "direct-tabs",
47
+ keys: TABS.map((tab) => tab.shortcut),
48
+ label: "Direct tab access",
49
+ group: "Navigation",
50
+ display: { ascii: tabRange, unicode: tabRange },
51
+ },
52
+ {
53
+ id: "toggle-mouse",
54
+ keys: ["m"],
55
+ label: "Toggle mouse support",
56
+ group: "Actions",
57
+ },
58
+ {
59
+ id: "toggle-tooltips",
60
+ keys: ["t"],
61
+ label: "Toggle tooltips",
62
+ group: "Actions",
63
+ },
64
+ {
65
+ id: "toggle-enabled",
66
+ keys: ["e"],
67
+ label: "Toggle enabled/disabled services",
68
+ group: "Actions",
69
+ },
70
+ {
71
+ id: "refresh",
72
+ keys: ["r"],
73
+ label: "Refresh data",
74
+ group: "Actions",
75
+ },
76
+ {
77
+ id: "search",
78
+ keys: ["/"],
79
+ label: "Search/filter",
80
+ group: "Actions",
81
+ },
82
+ {
83
+ id: "help",
84
+ keys: ["?"],
85
+ label: "Show this help",
86
+ group: "Actions",
87
+ },
88
+ {
89
+ id: "quit",
90
+ keys: ["q"],
91
+ label: "Quit",
92
+ group: "Actions",
93
+ },
94
+ {
95
+ id: "back",
96
+ keys: ["Esc"],
97
+ label: "Back",
98
+ group: "Actions",
99
+ },
100
+ ];
101
+ export const UI_KEYMAP = uiKeymap;
102
+ function getUiKeymapEntry(id) {
103
+ const entry = UI_KEYMAP.find((shortcut) => shortcut.id === id);
104
+ if (!entry) {
105
+ throw new Error(`Unknown TDK UI shortcut: ${id}`);
106
+ }
107
+ return entry;
108
+ }
109
+ export function formatUiKeyNames(id, ascii = false) {
110
+ const entry = getUiKeymapEntry(id);
111
+ const display = ascii ? entry.display?.ascii : entry.display?.unicode;
112
+ return display ?? entry.keys.join("/");
113
+ }
114
+ export function formatUiKeyHint(id, label, ascii = false) {
115
+ const entry = getUiKeymapEntry(id);
116
+ return `[${formatUiKeyNames(id, ascii)}] ${label ?? entry.label}`;
117
+ }
118
+ //# sourceMappingURL=ui-keymap.js.map
package/cli/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tdk-cli-internal",
3
3
  "private": true,
4
- "version": "1.3.124",
4
+ "version": "1.3.126",
5
5
  "description": "TDK CLI — start services on your laptop.",
6
6
  "keywords": [
7
7
  "tdk",
@@ -8,6 +8,7 @@
8
8
  # Priority: service.json → synthesize from path
9
9
  # =============================================================================
10
10
 
11
+ load('../../engine/topologies/tilt/manifest/parser.star', 'generated_string_error')
11
12
  load('../../engine/topologies/tilt/manifest/constants.star',
12
13
  'MANIFEST_DEFAULTS',
13
14
  'DEFAULT_SYNCS',
@@ -100,6 +101,10 @@ def load_manifest(resource_path, persist_to_disk=False):
100
101
 
101
102
  if content and str(content).strip():
102
103
  manifest = decode_json(content)
104
+ # Refuse service.json values that would add keys to generated config (GHSA-phgf-pww4-7jxc)
105
+ unsafe_field_error = generated_string_error(manifest, manifest_full_path) if type(manifest) == "dict" else None
106
+ if unsafe_field_error:
107
+ fail(unsafe_field_error)
103
108
  manifest_source = 'service.json'
104
109
  manifest_path = manifest_full_path
105
110
  else:
@@ -5,6 +5,7 @@
5
5
  # Purpose: Normalize manifests and provide helper accessors
6
6
  # =============================================================================
7
7
 
8
+ load('../../engine/topologies/tilt/manifest/parser.star', 'generated_string_error')
8
9
  load('./constants.star', 'MANIFEST_DEFAULTS', 'MANIFEST_FILENAME')
9
10
  load('./loading.star', 'apply_manifest_defaults', 'check_prisma_folder', 'get_default_syncs_for_type')
10
11
  load('./validation.star', 'validate_manifest')
@@ -56,6 +57,11 @@ def _load_and_normalize(manifest_path, warn_only=True):
56
57
  print(" ⚠️ Invalid JSON in manifest: " + manifest_path)
57
58
  return None
58
59
  fail("Invalid JSON in manifest: " + manifest_path)
60
+
61
+ # Refuse service.json values that would add keys to generated config (GHSA-phgf-pww4-7jxc)
62
+ unsafe_field_error = generated_string_error(manifest, manifest_path) if type(manifest) == "dict" else None
63
+ if unsafe_field_error:
64
+ fail(unsafe_field_error)
59
65
 
60
66
  # Validate
61
67
  issues = validate_manifest(manifest)
@@ -318,11 +318,11 @@ def write_file_if_changed(file_path, content):
318
318
  existing = str(existing_blob).rstrip()
319
319
 
320
320
  if existing == expected:
321
- print("DEBUG WRITE: Skipping {} (no changes)".format(file_path))
321
+ debug_log("WRITE: Skipping {} (no changes)".format(file_path))
322
322
  else:
323
- print("DEBUG WRITE: Writing {} bytes to {}".format(len(expected), file_path))
323
+ debug_log("WRITE: Writing {} bytes to {}".format(len(expected), file_path))
324
324
  _write_generated_file(file_path, expected)
325
- print("DEBUG WRITE: Completed {}".format(file_path))
325
+ debug_log("WRITE: Completed {}".format(file_path))
326
326
 
327
327
  # Keep a generated-content snapshot beside the service outputs. `tdk config verify`
328
328
  # compares these snapshots with the files and the source manifest, detecting both
@@ -2,7 +2,7 @@
2
2
  # 🛠️ TILT SDK - DEBUG UTILITIES
3
3
  # =============================================================================
4
4
 
5
- DEBUG_MODE = os.environ.get('TILT_DEBUG', 'false').lower() in ['true', '1', 'yes']
5
+ DEBUG_MODE = (os.environ.get('TDK_DEBUG') or os.environ.get('TILT_DEBUG', 'false')).lower() in ['true', '1', 'yes']
6
6
 
7
7
 
8
8
  def debug_log(message, data=None):
@@ -3,6 +3,7 @@ OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
3
3
  # === END INLINED CONSTANTS ===
4
4
 
5
5
 
6
+ load("../../common/utils_debug.star", "debug_log")
6
7
  load("../../../../topologies/tilt/common/utils.star", "Utils")
7
8
  load("../../../../topologies/tilt/manifest/constants.star", "GENERATED_CONFIG_FILENAMES")
8
9
 
@@ -126,15 +127,12 @@ def generate_frontend_tsconfig(resource_path, write_fn, internal_deps=None, is_d
126
127
  }
127
128
  if is_docker:
128
129
  output_path = resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['tsconfig_docker']
129
- if is_verbose:
130
- print("📝 DEBUG: Writing Docker tsconfig to: {}".format(output_path))
130
+ debug_log("📝 Writing Docker tsconfig to: {}".format(output_path))
131
131
  write_fn(output_path, Utils.encode_json(content))
132
- if is_verbose:
133
- print("📝 DEBUG: Docker tsconfig write complete")
132
+ debug_log("📝 Docker tsconfig write complete")
134
133
  else:
135
134
  output_path = resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['tsconfig_local']
136
- if is_verbose:
137
- print("📝 DEBUG: Writing Local tsconfig to: {}".format(output_path))
135
+ debug_log("📝 Writing Local tsconfig to: {}".format(output_path))
138
136
  write_fn(output_path, Utils.encode_json(content))
139
137
 
140
138
  DEBUG = {}
@@ -19,6 +19,7 @@ DEFAULTS = {}
19
19
  # === END INLINED CONSTANTS ===
20
20
 
21
21
 
22
+ load("./parser.star", "generated_string_error")
22
23
  load("./constants.star",
23
24
  "MANIFEST_FILENAME",
24
25
  "MANIFEST_DEFAULTS",
@@ -63,6 +64,12 @@ def load_from_file(path):
63
64
 
64
65
  if manifest == None:
65
66
  return struct(manifest=None, error="Invalid manifest: " + path)
67
+
68
+ # Refuse service.json values that would add keys to generated config (GHSA-phgf-pww4-7jxc). Every Tilt discovery path loads service.json here.
69
+ if type(manifest) == "dict":
70
+ unsafe_field_error = generated_string_error(manifest, path)
71
+ if unsafe_field_error:
72
+ fail(unsafe_field_error)
66
73
 
67
74
  # An `mcp` resource is a Model Context Protocol server: an HTTP service on Bun that is built, routed and health-checked
68
75
  # exactly like a backend. Every backend code path keys off appType == 'backend', so normalise here, once, at the single
@@ -73,24 +73,106 @@ def parse(content, path=""):
73
73
  warnings=warnings,
74
74
  )
75
75
 
76
+ # Characters allowed in the string fields that are written into generated Compose, Traefik and NATS config as they are. A line break
77
+ # or YAML syntax in one of them adds keys to the generated service (GHSA-phgf-pww4-7jxc). Keep in step with GENERATED_STRING_FIELDS
78
+ # in cli/src/utils/service-manifest.ts.
79
+ _ALPHANUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
80
+ _PATH_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789/-"
81
+ _GENERATED_STRING_RULES = [
82
+ # (field path, allowed characters, required first character or "")
83
+ (['healthCheckPath'], _PATH_CHARS, '/'),
84
+ (['traefik', 'host'], _ALPHANUM + '.-', ''),
85
+ (['traefik', 'pathPrefix'], _PATH_CHARS, '/'),
86
+ (['traefik', 'healthCheck'], _PATH_CHARS, '/'),
87
+ (['nats', 'queueGroup'], _ALPHANUM + '_.-', ''),
88
+ (['databaseName'], _ALPHANUM + '_-', ''),
89
+ (['stack'], _ALPHANUM + '_-', ''),
90
+ (['image'], _ALPHANUM + '._/:@-', ''),
91
+ ]
92
+
93
+ def generated_string_error(manifest, resource_path=""):
94
+ """
95
+ Return an error message if a value that is written into generated config has unsafe characters, else None.
96
+
97
+ The value is not echoed, because it may contain the line break that makes it dangerous.
98
+ """
99
+ for keys, allowed, first in _GENERATED_STRING_RULES:
100
+ value = manifest
101
+ for key in keys:
102
+ value = value.get(key) if type(value) == "dict" else None
103
+ if value == None or value == "":
104
+ continue
105
+ field = '.'.join(keys)
106
+ where = " in " + resource_path if resource_path else ""
107
+ if type(value) != "string":
108
+ return "MANIFEST ERROR: '{}' must be a string{}".format(field, where)
109
+ if first and not value.startswith(first):
110
+ return "MANIFEST ERROR: '{}' must start with '{}'{}".format(field, first, where)
111
+ for c in value.elems():
112
+ if c not in allowed:
113
+ return "MANIFEST ERROR: '{}' has characters that are not allowed (no spaces, line breaks or YAML syntax){}".format(field, where)
114
+ return _line_break_error(manifest, resource_path)
115
+
116
+ # Line breaks (including the Unicode ones YAML 1.1 honours) and other control characters.
117
+ _LINE_BREAK_OR_CONTROL = [chr(i) for i in range(32)] + [chr(127), "\u0085", "
", "
"]
118
+
119
+ def _has_line_break(text):
120
+ for bad in _LINE_BREAK_OR_CONTROL:
121
+ if bad in text:
122
+ return True
123
+ return False
124
+
125
+ def _line_break_error(manifest, resource_path):
126
+ """
127
+ Many more values than _GENERATED_STRING_RULES reach generated config (port, appName, dockerfile, buildContext, basePath, envVars,
128
+ ...). A key can only be added through a line break, so no string key or value may contain one. `smoke` is skipped: it is never
129
+ written into generated config. Keep in step with lineBreakErrors in cli/src/utils/service-manifest.ts.
130
+ """
131
+ where = " in " + resource_path if resource_path else ""
132
+ pending = [(value, key) for key, value in manifest.items() if key != 'smoke']
133
+ for key in manifest.keys():
134
+ if type(key) == "string" and _has_line_break(key):
135
+ return "MANIFEST ERROR: a key contains a line break{}".format(where)
136
+ # Starlark has no recursion or while loops; a service.json never nests anywhere near this many values.
137
+ for _ in range(100000):
138
+ if not pending:
139
+ return None
140
+ value, field = pending.pop()
141
+ if type(value) == "string":
142
+ if _has_line_break(value):
143
+ return "MANIFEST ERROR: '{}' must not contain line breaks or control characters{}".format(field, where)
144
+ elif type(value) == "list":
145
+ for index, item in enumerate(value):
146
+ pending.append((item, "{}[{}]".format(field, index)))
147
+ elif type(value) == "dict":
148
+ for key, item in value.items():
149
+ if type(key) == "string" and _has_line_break(key):
150
+ return "MANIFEST ERROR: a key in '{}' contains a line break{}".format(field, where)
151
+ pending.append((item, "{}.{}".format(field, key)))
152
+ return "MANIFEST ERROR: service.json is nested too deeply{}".format(where)
153
+
76
154
  def normalize(manifest, resource_path=""):
77
155
  """
78
156
  Normalize manifest with default values and computed fields.
79
-
157
+
80
158
  Args:
81
159
  manifest: Raw parsed manifest dict
82
160
  resource_path: Resource directory path for context
83
-
161
+
84
162
  Returns:
85
163
  Normalized manifest dict with all fields populated
86
164
  """
87
165
  normalized = {}
88
166
  warnings = []
89
-
167
+
90
168
  # Copy all existing fields
91
169
  for key, value in manifest.items():
92
170
  normalized[key] = value
93
171
 
172
+ unsafe_field_error = generated_string_error(normalized, resource_path)
173
+ if unsafe_field_error:
174
+ fail(unsafe_field_error)
175
+
94
176
  # 1. Apply defaults for missing fields
95
177
  for field, default_value in MANIFEST_DEFAULTS.items():
96
178
  if field not in normalized or normalized[field] == None:
@@ -391,6 +473,7 @@ def get_resource_type_description(manifest):
391
473
  ManifestParser = struct(
392
474
  parse=parse,
393
475
  normalize=normalize,
476
+ generated_string_error=generated_string_error,
394
477
  extract_resource_path=extract_resource_path,
395
478
  extract_stack=extract_stack_from_path,
396
479
  parse_traefik=parse_traefik_config,
@@ -306,6 +306,67 @@ def _test_manifest_merging():
306
306
 
307
307
  print("✅ Manifest merging tests complete\n")
308
308
 
309
+ # =============================================================================
310
+ # TEST SUITE: Values written into generated config (GHSA-phgf-pww4-7jxc)
311
+ # =============================================================================
312
+
313
+ def _test_generated_string_fields():
314
+ """A line break or YAML syntax in a field that reaches generated compose output must be rejected."""
315
+ base = {'appName': 'api', 'stack': 'shop', 'traefik': {'pathPrefix': '/api/v1/shop-management'}}
316
+
317
+ assert_equal(None, ManifestParser.generated_string_error(base), "Clean manifest has no generated-field error")
318
+
319
+ safe = {
320
+ 'appName': 'api',
321
+ 'stack': 'shop-1',
322
+ 'healthCheckPath': '/api/v1/health',
323
+ 'databaseName': 'TDK_shop',
324
+ 'image': 'ghcr.io/acme/app:1.0',
325
+ 'traefik': {'host': 'shop.backend.my-project.local', 'pathPrefix': '/api/v1/shop', 'healthCheck': '/health'},
326
+ 'nats': {'queueGroup': 'shop_backend_svc'},
327
+ }
328
+ assert_equal(None, ManifestParser.generated_string_error(safe), "Safe values are accepted")
329
+
330
+ hostile = [
331
+ {'healthCheckPath': '/health\n privileged: true'},
332
+ {'healthCheckPath': '/health"'},
333
+ {'traefik': {'host': 'a.local\n privileged: true', 'pathPrefix': '/api'}},
334
+ {'traefik': {'pathPrefix': '/api/v1/x\n network_mode: host'}},
335
+ {'traefik': {'pathPrefix': '/api', 'healthCheck': '/h\n volumes: []'}},
336
+ {'traefik': {'pathPrefix': '/api'}, 'nats': {'queueGroup': 'g: true'}},
337
+ {'traefik': {'pathPrefix': '/api'}, 'databaseName': 'TDK_x\n privileged: true'},
338
+ {'traefik': {'pathPrefix': '/api'}, 'stack': 'shop # comment'},
339
+ {'traefik': {'pathPrefix': '/api'}, 'image': 'app\n privileged: true'},
340
+ {'traefik': {'pathPrefix': '/api'}, 'healthCheckPath': 'health'},
341
+ {'traefik': {'pathPrefix': '/api'}, 'healthCheckPath': 42},
342
+ ]
343
+ for manifest in hostile:
344
+ assert_not_none(ManifestParser.generated_string_error(manifest), "Rejects unsafe value: " + str(manifest))
345
+
346
+ # The message names the field and never echoes the value
347
+ message = ManifestParser.generated_string_error(hostile[0], 'services/shop/api')
348
+ assert_true("healthCheckPath" in message, "Error names the field")
349
+ assert_true("\n" not in message, "Error does not echo the value")
350
+
351
+ # Fields outside the allowlist also reach generated config, so a line break anywhere is refused
352
+ clean = {'appName': 'api', 'stack': 'shop', 'traefik': {'pathPrefix': '/api'}}
353
+ line_breaks = [
354
+ {'port': '4000\n privileged: true'},
355
+ {'appName': 'api\n privileged: true'},
356
+ {'dockerfile': 'Dockerfile\r privileged: true'},
357
+ {'buildContext': 'src\u2028privileged: true'},
358
+ {'envVars': [{'name': 'A', 'value': 'v\n privileged: true'}]},
359
+ {'params': {'a\n privileged': 'x'}},
360
+ ]
361
+ for extra in line_breaks:
362
+ manifest = dict(clean)
363
+ manifest.update(extra)
364
+ assert_not_none(ManifestParser.generated_string_error(manifest), "Rejects a line break: " + repr(extra))
365
+
366
+ smoke = dict(clean)
367
+ smoke['smoke'] = {'via': '/api', 'create': {'body': '{\n}'}}
368
+ assert_equal(None, ManifestParser.generated_string_error(smoke), "smoke is never written into generated config")
369
+
309
370
  # =============================================================================
310
371
  # TEST SUITE: Get Normalized Convenience Function
311
372
  # =============================================================================
@@ -366,12 +427,13 @@ def run_tests():
366
427
  _test_loader_functions()
367
428
  _test_parser_functions()
368
429
  _test_parse_and_normalize()
369
- _test_domain_extraction()
430
+ _test_stack_extraction()
370
431
  _test_frontend_normalization()
371
432
  _test_traefik_config()
372
433
  _test_manifest_merging()
373
434
  _test_get_normalized()
374
435
  _test_resource_description()
436
+ _test_generated_string_fields()
375
437
 
376
438
  # Print results
377
439
  print("\n" + "="*70)
@@ -18,6 +18,7 @@
18
18
  # Infra.load_all(should_enable_fn)
19
19
  # =============================================================================
20
20
 
21
+ load("../common/utils_debug.star", "debug_log")
21
22
  load("../../platform/docker/constants.star", "PlatformDockerConstants")
22
23
  load("../../platform/docker/compose/traefik_standalone.star", "generate_standalone_traefik_compose")
23
24
  load("../../platform/docker/networking/sablier_container_cycle.star", "sablier_middleware_suffix")
@@ -130,14 +131,14 @@ def _register_platform_postgres(should_enable, root_prefix="", env_file=None, wr
130
131
  """
131
132
  postgres_compose = _ensure_database_management_compose(root_prefix, write_fn)
132
133
  if _file_exists(postgres_compose):
133
- print("DEBUG INFRA: Loading postgres compose from {}".format(postgres_compose))
134
+ debug_log("INFRA: Loading postgres compose from {}".format(postgres_compose))
134
135
  _docker_compose(postgres_compose, env_file)
135
136
  dc_resource('postgres', labels=['infra.tools'], resource_deps=['init-networks'], auto_init=True)
136
137
  return
137
138
  message = "Shared platform Postgres compose was not materialized at {}".format(postgres_compose)
138
139
  if required:
139
140
  fail(message + " (write_fn present: {}). Refusing to register a postgres Tilt resource with no compose.".format(write_fn != None))
140
- print("DEBUG INFRA: Skipping postgres (compose file not found): {}".format(message))
141
+ debug_log("INFRA: Skipping postgres (compose file not found): {}".format(message))
141
142
 
142
143
 
143
144
  def force_start_platform_postgres(should_enable, root_prefix="", env_file=None, write_fn=None):
@@ -161,7 +162,7 @@ def force_start_platform_postgres(should_enable, root_prefix="", env_file=None,
161
162
  def _load_database_management(should_enable, root_prefix="", env_file=None, write_fn=None):
162
163
  """Load database and messaging infrastructure."""
163
164
  if not should_enable('database-management'):
164
- print("DEBUG INFRA: database-management not enabled")
165
+ debug_log("INFRA: database-management not enabled")
165
166
  return
166
167
 
167
168
  print("🗃️ Loading database management services...")
@@ -172,7 +173,7 @@ def _load_database_management(should_enable, root_prefix="", env_file=None, writ
172
173
  # Load messaging if compose file exists
173
174
  messaging_compose = root_prefix + 'services/platform/messaging/docker-compose.yml'
174
175
  if _file_exists(messaging_compose):
175
- print("DEBUG INFRA: Loading messaging compose from {}".format(messaging_compose))
176
+ debug_log("INFRA: Loading messaging compose from {}".format(messaging_compose))
176
177
  _docker_compose(messaging_compose, env_file)
177
178
  # Register only services present in the project compose file. Minimal
178
179
  # NATS-only examples should not need to add an unrelated Redis broker.
@@ -181,7 +182,7 @@ def _load_database_management(should_enable, root_prefix="", env_file=None, writ
181
182
  if _compose_has_service(messaging_compose, 'nats'):
182
183
  dc_resource('nats', labels=['infra.messaging'], auto_init=True)
183
184
  else:
184
- print("DEBUG INFRA: Skipping messaging (compose file not found)")
185
+ debug_log("INFRA: Skipping messaging (compose file not found)")
185
186
 
186
187
  # Only create kafka resources if debezium is enabled AND messaging exists
187
188
  cdc_enabled = should_enable('debezium')
@@ -211,7 +212,7 @@ def _load_infisical(should_enable, root_prefix="", env_file=None):
211
212
 
212
213
  compose_file = root_prefix + 'docker-compose.infisical.yml'
213
214
  if not _file_exists(compose_file):
214
- print("DEBUG INFRA: Skipping Infisical (compose file not found)")
215
+ debug_log("INFRA: Skipping Infisical (compose file not found)")
215
216
  return
216
217
 
217
218
  print("🔐 Loading Infisical...")
@@ -254,7 +255,7 @@ def _load_standalone_traefik(root_prefix, env_file, write_fn):
254
255
  def _load_proxy(should_enable, root_prefix="", env_file=None, write_fn=None):
255
256
  """Load Traefik reverse proxy with proper health check sequencing."""
256
257
  if not should_enable('proxy'):
257
- print("DEBUG INFRA: proxy not enabled")
258
+ debug_log("INFRA: proxy not enabled")
258
259
  return
259
260
 
260
261
  compose_files = [
@@ -266,7 +267,7 @@ def _load_proxy(should_enable, root_prefix="", env_file=None, write_fn=None):
266
267
  ]
267
268
  missing = [path for path in compose_files if not _file_exists(path)]
268
269
  if missing:
269
- print("DEBUG INFRA: Platform proxy compose missing ({}), using standalone Traefik".format(missing[0]))
270
+ debug_log("INFRA: Platform proxy compose missing ({}), using standalone Traefik".format(missing[0]))
270
271
  _load_standalone_traefik(root_prefix, env_file, write_fn)
271
272
  return
272
273
 
@@ -422,11 +423,11 @@ def load_all_infrastructure(should_enable, fix_docker_networks_fn=None, docker_p
422
423
  """
423
424
  # Initialize networks first
424
425
  if fix_docker_networks_fn:
425
- print("DEBUG INFRA: Initializing networks...")
426
+ debug_log("INFRA: Initializing networks...")
426
427
  _init_networks(fix_docker_networks_fn)
427
- print("DEBUG INFRA: Networks initialized")
428
+ debug_log("INFRA: Networks initialized")
428
429
  else:
429
- print("DEBUG INFRA: SKIPPING network initialization (no fix_docker_networks_fn)")
430
+ debug_log("INFRA: SKIPPING network initialization (no fix_docker_networks_fn)")
430
431
 
431
432
  # These stacks both define an `elasticsearch` resource (and host port 9200).
432
433
  # Running both at once causes compose/resource collisions and unstable startup.
@@ -4,6 +4,7 @@
4
4
  # Purpose: thin coordinator for service apply lifecycle.
5
5
  # =============================================================================
6
6
 
7
+ load("../../common/utils_debug.star", "debug_log")
7
8
  load('../databases.star', 'Database')
8
9
  load('./apply_runtime_flags.star', 'RuntimeFlags')
9
10
  load('./apply_resource_validation.star', 'ResourceValidation')
@@ -38,7 +39,7 @@ def apply_app_service(resource_config, ctx):
38
39
  for nested in resource_config.get('resources', []):
39
40
  nested_name = nested.get('name', '')
40
41
  if nested_name and should_enable(nested_name):
41
- print("DEBUG APPLY: stack '{}' not individually enabled, but nested service '{}' is - proceeding".format(resource_name, nested_name))
42
+ debug_log("APPLY: stack '{}' not individually enabled, but nested service '{}' is - proceeding".format(resource_name, nested_name))
42
43
  service_enabled = True
43
44
  break
44
45