@tdk-landscape/tdk-cli-core 1.3.117 → 1.3.119

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.
@@ -0,0 +1,211 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { DEFAULT_ALWAYS_ENABLED_INFRA } from "./project-config-defaults.js";
4
+ import { discoverResourcesFromRoot } from "./services.js";
5
+ /**
6
+ * Shared platform Postgres start predicate.
7
+ *
8
+ * Both names mean the one shared platform Postgres Tilt resource `postgres`.
9
+ * The engine start path is selection-bounded (tdk up --only); verify/doctor
10
+ * evaluate the project resource set they already inspect (no tdk up filter).
11
+ * Both surfaces use this same predicate shape over their own resource set.
12
+ *
13
+ * Feature state is read from project.json **and** the generated Tiltfile /
14
+ * spec.master when present, because a stock `tdk up` focus pass expands
15
+ * CORE_INFRA → database-management even when project.json omits it.
16
+ *
17
+ * Spec: openspec/changes/dependson-starts-platform-postgres
18
+ */
19
+ export const POSTGRES_DEPENDENCY_NAMES = ["postgres", "database-management"];
20
+ const PHASE_KEYS = ["pre_alpha", "alpha", "beta", "out_of_scope"];
21
+ export function isSharedPlatformPostgresDependency(name) {
22
+ return POSTGRES_DEPENDENCY_NAMES.includes(name);
23
+ }
24
+ function readProjectJson(projectRoot) {
25
+ try {
26
+ return JSON.parse(readFileSync(join(projectRoot, ".tdk", "project.json"), "utf-8"));
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ function readTextIfExists(path) {
33
+ try {
34
+ return existsSync(path) ? readFileSync(path, "utf-8") : undefined;
35
+ }
36
+ catch {
37
+ return undefined;
38
+ }
39
+ }
40
+ function parseStarlarkStringList(source, name) {
41
+ const re = new RegExp(`${name}\\s*=\\s*\\[([^\\]]*)\\]`);
42
+ const m = source.match(re);
43
+ if (!m)
44
+ return undefined;
45
+ return [...m[1].matchAll(/"([^"]+)"|'([^']+)'/g)].map((x) => x[1] ?? x[2] ?? "");
46
+ }
47
+ function tiltfileAlwaysEnabledInfra(projectRoot) {
48
+ const path = join(projectRoot, ".tdk", ".tdk-out", "Tiltfile");
49
+ const text = readTextIfExists(path);
50
+ if (!text)
51
+ return undefined;
52
+ return parseStarlarkStringList(text, "ALWAYS_ENABLED_INFRA");
53
+ }
54
+ function specMasterHasDatabaseManagement(projectRoot) {
55
+ const path = join(projectRoot, ".tdk", ".tdk-out", "spec.master");
56
+ const text = readTextIfExists(path);
57
+ if (text === undefined)
58
+ return undefined;
59
+ // PRE_ALPHA/DEFAULTS style: "database-management": True
60
+ return /"database-management"\s*:\s*True/.test(text);
61
+ }
62
+ function generatedDefaultFocusEnablesDatabaseManagement(projectRoot) {
63
+ const output = join(projectRoot, ".tdk", ".tdk-out");
64
+ const tiltfile = readTextIfExists(join(output, "Tiltfile"));
65
+ const spec = readTextIfExists(join(output, "spec.master"));
66
+ const config = readTextIfExists(join(output, "tdk-cli-ext", "discovery", "config.star"));
67
+ const profiles = readTextIfExists(join(output, "tdk-cli-ext", "engine", "topologies", "tilt", "config", "profiles.star"));
68
+ if (!tiltfile || !spec || !config || !profiles)
69
+ return false;
70
+ const preAlpha = spec.match(/PRE_ALPHA_RESOURCES\s*=\s*\{([^}]*)\}/)?.[1];
71
+ const coreInfra = parseStarlarkStringList(config, "CORE_INFRA");
72
+ return (/FOCUS_MODE\s*,\s*FOCUS_ENABLED_ALL\s*,\s*FOCUS_ENABLED_RESOURCES\s*=\s*Config\.apply_focus\(cfg\)/.test(tiltfile) &&
73
+ /if FOCUS_MODE and FOCUS_ENABLED_ALL\s*:/.test(tiltfile) &&
74
+ /"[^"]+"\s*:\s*True|'[^']+'\s*:\s*True/.test(preAlpha ?? "") &&
75
+ (coreInfra?.includes("postgres") ?? false) &&
76
+ /["']postgres["']\s*:\s*["']database-management["']/.test(config) &&
77
+ /for infra in CORE_INFRA_EXPORT\s*:/.test(profiles) &&
78
+ /needed\[INFRA_STACK_MAP_EXPORT\[infra\]\]\s*=\s*True/.test(profiles));
79
+ }
80
+ function projectJsonFeatureOn(projectRoot) {
81
+ const parsed = readProjectJson(projectRoot);
82
+ if (!parsed)
83
+ return false;
84
+ const phases = (parsed.phases ?? {});
85
+ for (const phase of PHASE_KEYS) {
86
+ const stacks = phases[phase]?.enabledStacks;
87
+ if (Array.isArray(stacks) && stacks.includes("database-management")) {
88
+ return true;
89
+ }
90
+ }
91
+ const alwaysEnabled = parsed.always_enabled_infra ?? DEFAULT_ALWAYS_ENABLED_INFRA;
92
+ return Array.isArray(alwaysEnabled) && alwaysEnabled.includes("database-management");
93
+ }
94
+ /**
95
+ * True when database-management is on for this project from project.json
96
+ * **or** the generated Tiltfile/spec.master when those files exist.
97
+ */
98
+ export function databaseManagementEnabled(projectRoot) {
99
+ if (projectJsonFeatureOn(projectRoot))
100
+ return true;
101
+ const fromTilt = tiltfileAlwaysEnabledInfra(projectRoot);
102
+ if (Array.isArray(fromTilt) && fromTilt.includes("database-management"))
103
+ return true;
104
+ if (specMasterHasDatabaseManagement(projectRoot) === true)
105
+ return true;
106
+ return false;
107
+ }
108
+ /** Known names for unknown-dependsOn detection: discovered services/stacks + project stacks/infra. */
109
+ function knownDependsonNames(projectRoot) {
110
+ const known = new Set();
111
+ for (const name of POSTGRES_DEPENDENCY_NAMES)
112
+ known.add(name);
113
+ for (const resource of discoverResourcesFromRoot(projectRoot)) {
114
+ known.add(resource.name);
115
+ if (resource.stack)
116
+ known.add(resource.stack);
117
+ }
118
+ const parsed = readProjectJson(projectRoot);
119
+ if (parsed) {
120
+ const phases = (parsed.phases ?? {});
121
+ for (const phase of PHASE_KEYS) {
122
+ const stacks = phases[phase]?.enabledStacks;
123
+ if (Array.isArray(stacks)) {
124
+ for (const stack of stacks)
125
+ known.add(stack);
126
+ }
127
+ }
128
+ const alwaysEnabled = parsed.always_enabled_infra ?? DEFAULT_ALWAYS_ENABLED_INFRA;
129
+ if (Array.isArray(alwaysEnabled)) {
130
+ for (const entry of alwaysEnabled)
131
+ known.add(entry);
132
+ }
133
+ }
134
+ const fromTilt = tiltfileAlwaysEnabledInfra(projectRoot);
135
+ if (Array.isArray(fromTilt)) {
136
+ for (const entry of fromTilt)
137
+ known.add(entry);
138
+ }
139
+ return known;
140
+ }
141
+ /**
142
+ * Evaluate whether shared platform Postgres will start for this project.
143
+ *
144
+ * willStart = featureOn OR any inspected project resource depends on either name.
145
+ * Resource set is project scope (discoverResourcesFromRoot), NOT tdk up --only.
146
+ *
147
+ * Also reports focusWouldEnableDatabaseManagement so doctor/verify can warn that
148
+ * default focus expands CORE_INFRA → database-management even when project.json
149
+ * says the feature is off (the 7.1 gap).
150
+ */
151
+ export function evaluateSharedPlatformPostgres(projectRoot) {
152
+ const featureOnFromProjectJson = projectJsonFeatureOn(projectRoot);
153
+ const fromTilt = tiltfileAlwaysEnabledInfra(projectRoot);
154
+ const featureOnFromTiltfile = Array.isArray(fromTilt) && fromTilt.includes("database-management");
155
+ const featureOnFromSpecMaster = specMasterHasDatabaseManagement(projectRoot) === true;
156
+ const featureOn = featureOnFromProjectJson || featureOnFromTiltfile || featureOnFromSpecMaster;
157
+ const focusWouldEnableDatabaseManagement = !featureOnFromProjectJson && generatedDefaultFocusEnablesDatabaseManagement(projectRoot);
158
+ const known = knownDependsonNames(projectRoot);
159
+ const dependsOnUsers = [];
160
+ const unknownDependsOnNames = [];
161
+ for (const resource of discoverResourcesFromRoot(projectRoot)) {
162
+ const deps = resource.config?.dependsOn ?? [];
163
+ let usesSharedPostgres = false;
164
+ for (const dep of deps) {
165
+ if (isSharedPlatformPostgresDependency(dep)) {
166
+ usesSharedPostgres = true;
167
+ continue;
168
+ }
169
+ if (!known.has(dep)) {
170
+ unknownDependsOnNames.push({ resource: resource.name, name: dep });
171
+ }
172
+ }
173
+ if (usesSharedPostgres) {
174
+ dependsOnUsers.push(resource.name);
175
+ }
176
+ }
177
+ const willStart = featureOn || dependsOnUsers.length > 0;
178
+ const reason = featureOn
179
+ ? "feature"
180
+ : dependsOnUsers.length > 0
181
+ ? "dependsOn"
182
+ : "none";
183
+ return {
184
+ featureOn,
185
+ featureOnFromProjectJson,
186
+ featureOnFromTiltfile,
187
+ featureOnFromSpecMaster,
188
+ focusWouldEnableDatabaseManagement,
189
+ dependsOnUsers,
190
+ willStart,
191
+ reason,
192
+ unknownDependsOnNames,
193
+ };
194
+ }
195
+ /** Human-readable will-start / will-not-start sentence shared by doctor + verify. */
196
+ export function sharedPlatformPostgresMessage(evaluation) {
197
+ const projectScopeNote = " (project resource set; tdk up may select a subset and not start Postgres)";
198
+ if (!evaluation.willStart) {
199
+ const focusNote = evaluation.focusWouldEnableDatabaseManagement
200
+ ? " Note: default focus/CORE_INFRA expansion would enable database-management on a typical tdk up even when project.json lists it off."
201
+ : "";
202
+ return `Shared platform Postgres will not start for this project.json/generated Tiltfile state (no database-management feature, no selected dependsOn).${focusNote}`;
203
+ }
204
+ const why = evaluation.reason === "feature"
205
+ ? `the database-management feature is enabled${evaluation.featureOnFromTiltfile || evaluation.featureOnFromSpecMaster
206
+ ? " (generated Tiltfile/spec.master)"
207
+ : ""}`
208
+ : `resource(s) ${evaluation.dependsOnUsers.join(", ")} depend on postgres/database-management`;
209
+ return `Shared platform Postgres will start because ${why}${projectScopeNote}`;
210
+ }
211
+ //# sourceMappingURL=shared-platform-postgres.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.117",
4
+ "version": "1.3.119",
5
5
  "description": "TDK CLI — start services on your laptop.",
6
6
  "keywords": [
7
7
  "tdk",
@@ -225,6 +225,8 @@ TILT_CONTEXT = {
225
225
  'project_root': PROJECT_ROOT,
226
226
  'all_services': _app_resources_list,
227
227
  'golden_image_resource': GOLDEN_IMAGE_RESOURCE,
228
+ 'focus_mode': FOCUS_MODE,
229
+ 'focus_enabled_resources': FOCUS_ENABLED_RESOURCES,
228
230
  }
229
231
 
230
232
  print("🚀 Creating Tilt resources for {} services...".format(len(_app_resources_list)))
@@ -314,7 +316,9 @@ if FOCUS_MODE and FOCUS_ENABLED_RESOURCES:
314
316
  # local mail catcher standing in for a managed email provider. Every
315
317
  # service it declares is loaded via docker_compose() in infra-loader.star,
316
318
  # so it must be enabled here too, or it is silently disabled the same way.
317
- _ALWAYS_ENABLED_INFRA_RESOURCES = ['init-networks', 'postgres', 'traefik', 'golden-layers-build']
319
+ _ALWAYS_ENABLED_INFRA_RESOURCES = ['init-networks', 'traefik', 'golden-layers-build']
320
+ if should_enable('database-management') or TILT_CONTEXT.get('_shared_platform_postgres_force_started', False):
321
+ _ALWAYS_ENABLED_INFRA_RESOURCES.append('postgres')
318
322
  _messaging_compose_path = PROJECT_ROOT + '/services/platform/messaging/docker-compose.yml'
319
323
  if str(local("test -f '" + _messaging_compose_path + "' && echo yes || echo no", quiet=True, echo_off=True)).strip() == 'yes':
320
324
  # infra-loader.star only creates these Tilt resources when this file
@@ -273,6 +273,8 @@ def _is_allowed_generated_path(file_path, project_root):
273
273
  return True
274
274
  if path.startswith('.tdk/.tdk-out/'):
275
275
  return True
276
+ if path == 'services/platform/database-management/docker-compose.yml':
277
+ return True
276
278
  if path.startswith('product_autogenerated_resource_manifests/') or path.startswith('product_autogenerated_services_list/'):
277
279
  return True
278
280
  if path == 'Tiltfile' or path == '.tiltignore':
@@ -24,6 +24,7 @@ Validators Architecture:
24
24
  load("../common/utils.star", "Utils")
25
25
  load("../../platform/docker/constants.star", "PlatformDockerConstants")
26
26
  load("../manifest/constants.star", "GENERATED_CONFIG_FILENAMES")
27
+ load("../resources/shared-platform-postgres.star", "is_shared_platform_postgres_dependency")
27
28
 
28
29
  load("../common/utils.star", "LIBRARY_ROOTS")
29
30
 
@@ -182,6 +183,10 @@ def validate_dependency_graph(resource_path, manifest, all_services):
182
183
  # Check manifest dependsOn
183
184
  internal_deps = manifest.get("dependsOn", [])
184
185
  for dep in internal_deps:
186
+ # Shared platform Postgres aliases are not app services; they resolve to
187
+ # the Tilt resource `postgres` and must not be reported as missing.
188
+ if is_shared_platform_postgres_dependency(dep):
189
+ continue
185
190
  dep_exists = False
186
191
  for svc in all_services:
187
192
  # Check service name/stack match (supports legacy 'stack' field)
@@ -196,7 +201,7 @@ def validate_dependency_graph(resource_path, manifest, all_services):
196
201
  break
197
202
  if dep_exists:
198
203
  break
199
-
204
+
200
205
  if not dep_exists:
201
206
  missing_deps.append({
202
207
  "name": dep,
@@ -13,6 +13,7 @@ load("./__init__.star", "Manifest")
13
13
  load("./loader.star", "ManifestLoader")
14
14
  load("./parser.star", "ManifestParser")
15
15
  load("./validator.star", "ManifestValidator")
16
+ load("../resources/shared-platform-postgres.star", "SHARED_POSTGRES_DEPENDENCY_NAMES")
16
17
 
17
18
  # Phase 5: Migration complete - always use new system
18
19
  def load_and_validate_manifest(manifest_path, all_manifests=None):
@@ -290,21 +291,27 @@ def check_port_conflicts(manifests):
290
291
  def check_dependencies_valid(manifests):
291
292
  """
292
293
  Check if all dependencies reference valid services.
293
-
294
+
295
+ Shared platform Postgres names (`postgres`, `database-management`) are not
296
+ app services; they resolve to the Tilt resource `postgres` and are not
297
+ reported as missing.
298
+
294
299
  Args:
295
300
  manifests: List of manifest dicts
296
-
301
+
297
302
  Returns:
298
303
  List of dependency error structs
299
304
  """
300
305
  errors = []
301
306
  app_names = {m.get('appName'): m for m in manifests if m.get('appName')}
302
-
307
+
303
308
  for manifest in manifests:
304
309
  app_name = manifest.get('appName', 'unknown')
305
310
  internal_deps = manifest.get('dependsOn', [])
306
-
311
+
307
312
  for dep in internal_deps:
313
+ if dep in SHARED_POSTGRES_DEPENDENCY_NAMES:
314
+ continue
308
315
  if dep not in app_names:
309
316
  errors.append(ManifestErrors.new(
310
317
  message="Service '{}' depends on non-existent service '{}'".format(app_name, dep),
@@ -25,6 +25,7 @@ load("./constants.star",
25
25
  load("./schema.star", "ManifestSchema")
26
26
  load("./errors.star", "ManifestErrors")
27
27
  load("./parser.star", "extract_stack_from_path")
28
+ load("../resources/shared-platform-postgres.star", "is_shared_platform_postgres_dependency")
28
29
 
29
30
  def validate(manifest, context=None, level='all'):
30
31
  """
@@ -463,6 +464,9 @@ def _validate_cross_resource(manifest, context):
463
464
 
464
465
  missing_deps = []
465
466
  for dep in internal_deps:
467
+ # Shared platform Postgres aliases are not app services.
468
+ if is_shared_platform_postgres_dependency(dep):
469
+ continue
466
470
  if dep not in app_names:
467
471
  missing_deps.append(dep)
468
472
 
@@ -92,32 +92,83 @@ volumes:
92
92
 
93
93
 
94
94
  def _ensure_database_management_compose(root_prefix, write_fn):
95
- """Ensure the database-management stack feature has its compose file."""
95
+ """Ensure the database-management stack feature has its compose file.
96
+
97
+ write_fn is expected to be Utils.write_file_if_changed, which prepends
98
+ TDK_PROJECT_ROOT to relative paths — the same base as root_prefix when
99
+ the Tiltfile sets TDK_PROJECT_ROOT = PROJECT_ROOT. A custom write_fn that
100
+ does not prepend may write a different file; if the path we return is
101
+ absolute and still missing after the relative write, write that exact
102
+ path once so the exists-check and the write cannot diverge.
103
+ """
96
104
  compose_rel = "services/platform/database-management/docker-compose.yml"
97
105
  compose_file = root_prefix + compose_rel if root_prefix else compose_rel
98
106
  if _file_exists(compose_file):
99
107
  return compose_file
100
108
  if write_fn:
101
109
  write_fn(compose_rel, _generate_database_management_compose())
110
+ if not _file_exists(compose_file) and compose_file.startswith('/'):
111
+ write_fn(compose_file, _generate_database_management_compose())
102
112
  return compose_file
103
113
 
114
+ # No module-level mutable registration flag: Tilt freezes Starlark globals after
115
+ # module load, so writing a key on a global dict raises "cannot insert into
116
+ # frozen hash table". Feature-on (`_load_database_management`) and force-start
117
+ # (`force_start_platform_postgres`) are mutually exclusive via should_enable —
118
+ # the orchestrator only force-starts when database-management is off — so each
119
+ # Tiltfile evaluation registers `postgres` at most once on its own path.
120
+
121
+
122
+ def _register_platform_postgres(should_enable, root_prefix="", env_file=None, write_fn=None, required=False):
123
+ """Materialize the platform compose and register the shared `postgres` Tilt resource.
124
+
125
+ Does NOT load messaging, kafka, redis, nats, provision-db, or Prisma.
126
+
127
+ When `required` is true (dependsOn force-start path), a missing compose after
128
+ ensure is a hard failure — not a silent skip — so Tilt never waits on a
129
+ `postgres` resource that has no compose definition.
130
+ """
131
+ postgres_compose = _ensure_database_management_compose(root_prefix, write_fn)
132
+ if _file_exists(postgres_compose):
133
+ print("DEBUG INFRA: Loading postgres compose from {}".format(postgres_compose))
134
+ _docker_compose(postgres_compose, env_file)
135
+ dc_resource('postgres', labels=['infra.tools'], resource_deps=['init-networks'], auto_init=True)
136
+ return
137
+ message = "Shared platform Postgres compose was not materialized at {}".format(postgres_compose)
138
+ if required:
139
+ 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
+
142
+
143
+ def force_start_platform_postgres(should_enable, root_prefix="", env_file=None, write_fn=None):
144
+ """Force-start the shared platform Postgres for a run whose selected resources
145
+ depend on `postgres` / `database-management` while `database-management` is off.
146
+
147
+ Reuses the existing platform compose path and the existing `postgres` Tilt
148
+ resource — no second image, port, migrator, or Prisma resource.
149
+
150
+ The orchestrator MUST pass the same `write_fn` the feature path uses
151
+ (`ctx['write_file']`) so `services/platform/database-management/docker-compose.yml`
152
+ is materialized when missing. A failed materialize fails the run.
153
+ """
154
+ if env_file == None:
155
+ candidate_env_file = root_prefix + '.env' if root_prefix else '.env'
156
+ env_file = candidate_env_file if _file_exists(candidate_env_file) else None
157
+ print("🗃️ Force-starting shared platform Postgres (selected dependsOn postgres/database-management)")
158
+ _register_platform_postgres(should_enable, root_prefix, env_file, write_fn, required=True)
159
+
160
+
104
161
  def _load_database_management(should_enable, root_prefix="", env_file=None, write_fn=None):
105
162
  """Load database and messaging infrastructure."""
106
163
  if not should_enable('database-management'):
107
164
  print("DEBUG INFRA: database-management not enabled")
108
165
  return
109
-
166
+
110
167
  print("🗃️ Loading database management services...")
111
-
168
+
112
169
  # Load postgres from the database-management stack feature compose.
113
- postgres_compose = _ensure_database_management_compose(root_prefix, write_fn)
114
- if _file_exists(postgres_compose):
115
- print("DEBUG INFRA: Loading postgres compose from {}".format(postgres_compose))
116
- _docker_compose(postgres_compose, env_file)
117
- dc_resource('postgres', labels=['infra.tools'], resource_deps=['init-networks'], auto_init=True)
118
- else:
119
- print("DEBUG INFRA: Skipping postgres (compose file not found)")
120
-
170
+ _register_platform_postgres(should_enable, root_prefix, env_file, write_fn)
171
+
121
172
  # Load messaging if compose file exists
122
173
  messaging_compose = root_prefix + 'services/platform/messaging/docker-compose.yml'
123
174
  if _file_exists(messaging_compose):
@@ -131,7 +182,7 @@ def _load_database_management(should_enable, root_prefix="", env_file=None, writ
131
182
  dc_resource('nats', labels=['infra.messaging'], auto_init=True)
132
183
  else:
133
184
  print("DEBUG INFRA: Skipping messaging (compose file not found)")
134
-
185
+
135
186
  # Only create kafka resources if debezium is enabled AND messaging exists
136
187
  cdc_enabled = should_enable('debezium')
137
188
  if cdc_enabled and _file_exists(messaging_compose):
@@ -422,7 +473,7 @@ Infra = struct(
422
473
  # Main loader
423
474
  load_all = load_all_infrastructure,
424
475
  init_networks = _init_networks,
425
-
476
+
426
477
  # Individual loaders (for granular control)
427
478
  load_database = _load_database_management,
428
479
  load_verdaccio = _load_verdaccio,
@@ -432,4 +483,8 @@ Infra = struct(
432
483
  load_debezium = _load_debezium,
433
484
  load_elk = _load_elk,
434
485
  load_golden_image = _load_golden_image,
486
+
487
+ # Shared platform Postgres start signal (dependsOn postgres/database-management
488
+ # when the database-management feature is off). Idempotent; no messaging/kafka/Prisma.
489
+ force_start_postgres = force_start_platform_postgres,
435
490
  )
@@ -12,10 +12,68 @@ load('../../../platform/docker/constants.star', 'PlatformDockerConstants')
12
12
  load('../../../tilt/manifest/constants.star', 'BASE_PORT_FRONTEND', 'BASE_PORT_BACKEND')
13
13
  load('../../../platform/docker/networking/traefik_static_routes.star', 'generate_static_wake_route', 'normalize_abs_path')
14
14
  load('../../../platform/docker/networking/sablier_container_cycle.star', 'sablier_middleware_suffix')
15
+ # Shared platform Postgres force-start path. infra-loader.star must not load
16
+ # apply_compose (no circular load): it only loads platform/docker + registries.
17
+ load('../infra-loader.star', 'Infra')
18
+ load('../shared-platform-postgres.star',
19
+ _shared_postgres_names = 'SHARED_POSTGRES_DEPENDENCY_NAMES',
20
+ _is_shared_platform_postgres_dependency = 'is_shared_platform_postgres_dependency',
21
+ _manifest_needs_shared_platform_postgres = 'manifest_needs_shared_platform_postgres')
15
22
 
16
23
  load('./builders/typescript.star', 'TypescriptBuilders')
17
24
 
18
25
 
26
+ # Public wrappers for shared platform Postgres helpers. Starlark `load` does not
27
+ # re-export loaded names. Canonical list + predicates live in
28
+ # shared-platform-postgres.star — do not hardcode a second copy of the names here.
29
+
30
+ def is_shared_platform_postgres_dependency(name):
31
+ """True when a dependsOn name is one of the shared platform Postgres aliases."""
32
+ return _is_shared_platform_postgres_dependency(name)
33
+
34
+
35
+ def manifest_needs_shared_platform_postgres(manifest):
36
+ """True when a manifest's dependsOn lists postgres or database-management."""
37
+ return _manifest_needs_shared_platform_postgres(manifest)
38
+
39
+
40
+ def selected_for_shared_platform_postgres(name, ctx):
41
+ """Use the same focus selection later passed to set_enabled_resources."""
42
+ if not ctx.get('focus_mode', False):
43
+ return True
44
+ return name in (ctx.get('focus_enabled_resources') or [])
45
+
46
+
47
+ def force_start_shared_platform_postgres_once(ctx, should_enable, resource_name=""):
48
+ """Force-start shared platform Postgres at most once per Tiltfile evaluation.
49
+
50
+ `register_compose_resources` runs per stack and can see unselected nested
51
+ services; two selected services that both `dependsOn` Postgres would otherwise call
52
+ `Infra.force_start_postgres` twice (docker_compose + dc_resource each time).
53
+ Module-level mutable flags are unusable — Tilt freezes Starlark globals — so
54
+ the guard lives on `ctx`, a dict the Tiltfile already owns for this run.
55
+
56
+ Returns True when this call performed the force-start, False when it skipped
57
+ (already started this run, or database-management is already on).
58
+ """
59
+ if ctx.get('_shared_platform_postgres_force_started', False):
60
+ print("DEBUG COMPOSE: shared platform Postgres already force-started this run; skip for '{}'".format(resource_name))
61
+ return False
62
+ if should_enable('database-management'):
63
+ return False
64
+ project_root_for_env = ctx.get('project_root', '')
65
+ root_prefix = (project_root_for_env + '/') if project_root_for_env else ''
66
+ env_candidate = (project_root_for_env + '/.env') if project_root_for_env else '.env'
67
+ env_file = _env_file_if_exists(env_candidate)
68
+ write_fn = ctx.get('write_file', None)
69
+ if write_fn == None:
70
+ fail("dependsOn postgres/database-management on selected resource '{}' requires ctx.write_file to materialize services/platform/database-management/docker-compose.yml".format(resource_name))
71
+ print("DEBUG COMPOSE: force-starting shared platform Postgres once (selected '{}' dependsOn)".format(resource_name))
72
+ Infra.force_start_postgres(should_enable, root_prefix=root_prefix, env_file=env_file, write_fn=write_fn)
73
+ ctx['_shared_platform_postgres_force_started'] = True
74
+ return True
75
+
76
+
19
77
  def _env_file_if_exists(env_file):
20
78
  """.env is gitignored (no .env.example shipped) across every TDK example repo, so a
21
79
  fresh clone must not hard-require it: docker_compose()'s env_file kwarg type-checks as
@@ -51,22 +109,30 @@ def _build_infra_dependencies(resource_name, should_enable, ctx, has_backend=Fal
51
109
  def _resolve_dependency_to_resource(dep_name, all_services_map):
52
110
  """
53
111
  Resolve a short dependency name (e.g., 'identity') to the full YAML resource name.
54
-
112
+
55
113
  The discovery registry creates YAML resources with names like:
56
114
  - 'identity-management-backend-yaml' for identity backend
57
115
  - 'api-gateway-yaml' for api-gateway
58
-
116
+
59
117
  But manifests reference them as short names like 'identity' or 'api-gateway'.
118
+
119
+ Shared platform Postgres aliases (`postgres`, `database-management`) resolve
120
+ to the literal Tilt resource `postgres` in BOTH feature states — they must
121
+ NOT fall through to `postgres-yaml` or any other name.
60
122
  """
123
+ # Shared platform Postgres: exact names only (typos stay on the normal path).
124
+ if is_shared_platform_postgres_dependency(dep_name):
125
+ return 'postgres'
126
+
61
127
  # Check if already a full YAML resource name
62
128
  if dep_name in all_services_map:
63
129
  return dep_name
64
-
130
+
65
131
  # Try with -yaml suffix (for resources already resolved)
66
132
  yaml_name = dep_name + '-yaml'
67
133
  if yaml_name in all_services_map:
68
134
  return yaml_name
69
-
135
+
70
136
  # Try to find matching service by stack/short name
71
137
  for resource_name, resource_info in all_services_map.items():
72
138
  # Check if this service's stack matches the dependency
@@ -81,16 +147,51 @@ def _resolve_dependency_to_resource(dep_name, all_services_map):
81
147
  return res['name'] + '-yaml'
82
148
  # Fallback to first resource
83
149
  return resources[0]['name'] + '-yaml'
84
-
150
+
85
151
  # Try pattern matching: identity -> identity-*-yaml
86
152
  for resource_name in all_services_map.keys():
87
153
  if resource_name.startswith(dep_name + '-') and resource_name.endswith('-yaml'):
88
154
  return resource_name
89
-
155
+
90
156
  # Return original with -yaml suffix as last resort
91
157
  return dep_name + '-yaml'
92
158
 
93
159
 
160
+ def resolve_dependency_to_resource(dep_name, all_services_map):
161
+ """Public wrapper over `_resolve_dependency_to_resource` (exported for tests)."""
162
+ return _resolve_dependency_to_resource(dep_name, all_services_map)
163
+
164
+
165
+ def build_infra_dependencies(resource_name, should_enable, ctx=None, has_backend=False):
166
+ """Public wrapper over `_build_infra_dependencies` (exported for tests).
167
+
168
+ feature off → no postgres; feature on → has postgres (plus backend
169
+ provision-db edge when has_backend is true).
170
+ """
171
+ return _build_infra_dependencies(resource_name, should_enable, ctx if ctx != None else {}, has_backend)
172
+
173
+
174
+ def build_resource_deps(res, res_name, manifest, resource_config, infra_deps, config_gen_resources=None, resource_manifests=None, runtime_flags=None, all_services_map=None):
175
+ """Public wrapper over `_build_resource_deps` (exported for tests).
176
+
177
+ Ensures the dependsOn→resource_deps edge is exercised by tests: feature off
178
+ + dependsOn postgres → postgres edge present; feature on + dependsOn →
179
+ exactly one postgres edge (infra_deps already carries it).
180
+ """
181
+ default_runtime_flags = {
182
+ 'enforce_migrator_deps': False,
183
+ 'auto_init_apps': True,
184
+ 'disable_app_replicas': False,
185
+ }
186
+ return _build_resource_deps(
187
+ res, res_name, manifest, resource_config, infra_deps,
188
+ config_gen_resources if config_gen_resources != None else {},
189
+ resource_manifests if resource_manifests != None else {},
190
+ runtime_flags if runtime_flags != None else default_runtime_flags,
191
+ all_services_map,
192
+ )
193
+
194
+
94
195
  def _build_resource_deps(res, res_name, manifest, resource_config, infra_deps, config_gen_resources, resource_manifests, runtime_flags, all_services_map=None):
95
196
  # Drop self-deps: shared infra_deps always includes verdaccio when enabled, so
96
197
  # registering verdaccio itself would otherwise produce resource_deps=['verdaccio'].
@@ -572,6 +673,26 @@ def register_compose_resources(resource_config, ctx, runtime_flags, manifest_sta
572
673
  if res_name:
573
674
  all_services_map[res_name] = svc
574
675
 
676
+ # Selection-bound shared platform Postgres start signal.
677
+ # Only enabled resources in THIS stack are evaluated. Registration sees
678
+ # unselected siblings before Tilt's enabled-resource filter runs.
679
+ # When database-management is off and any selected resource's
680
+ # manifest depends on postgres/database-management, force-start the shared
681
+ # platform Postgres via the existing infra loader (materializes
682
+ # services/platform/database-management/docker-compose.yml + registers the
683
+ # existing `postgres` Tilt resource). Does NOT open migrators/provision-db/Prisma.
684
+ # Idempotent across services: force_start_shared_platform_postgres_once
685
+ # guards on ctx so two selected dependents do not register postgres twice.
686
+ if not should_enable('database-management'):
687
+ for res in resource_config.get('resources', []):
688
+ if not selected_for_shared_platform_postgres(res.get('name', ''), ctx):
689
+ continue
690
+ res_manifest = resource_manifests.get(res['name'], {})
691
+ if manifest_needs_shared_platform_postgres(res_manifest):
692
+ print("DEBUG COMPOSE: selected '{}' dependsOn shared platform Postgres; force-starting postgres".format(res.get('name', '')))
693
+ force_start_shared_platform_postgres_once(ctx, should_enable, res.get('name', ''))
694
+ break
695
+
575
696
  resource_entries = []
576
697
  resource_configs = []
577
698
 
@@ -627,11 +748,17 @@ def register_compose_resources(resource_config, ctx, runtime_flags, manifest_sta
627
748
  ))
628
749
 
629
750
  resource_entries.append(entry)
630
- resource_configs.append(_build_resource_config(
751
+ built_config = _build_resource_config(
631
752
  res, manifest, resource_config, full_res_path, infra_deps,
632
753
  config_gen_resources, resource_manifests, runtime_flags, all_services_map,
633
754
  ctx.get('project_root', ''),
634
- ))
755
+ )
756
+ # A focused run still registers disabled siblings before Tilt applies
757
+ # set_enabled_resources. Do not leave their edge pointing at a Postgres
758
+ # resource that this evaluation did not register.
759
+ if not should_enable('database-management') and not ctx.get('_shared_platform_postgres_force_started', False):
760
+ built_config['res_deps'] = [dep for dep in built_config['res_deps'] if dep != 'postgres']
761
+ resource_configs.append(built_config)
635
762
 
636
763
  print("DEBUG COMPOSE: Calling Docker.app_compose for '{}' with write_fn={}".format(resource_name, write_file))
637
764
  Docker.app_compose(resource_path, resource_entries, write_file)