@tdk-landscape/tdk-cli-core 1.3.136 → 1.3.138

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 (32) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/cli/dist/commands/doctor.js +11 -11
  3. package/cli/dist/commands/resource.js +35 -21
  4. package/cli/dist/generator/resource-templates.js +1 -1
  5. package/cli/dist/generator/template-engine.js +10 -7
  6. package/cli/dist/utils/constants.js +22 -3
  7. package/cli/dist/utils/doctor-report.js +2 -1
  8. package/cli/dist/utils/doctor-runtime.js +6 -1
  9. package/cli/dist/utils/doctor-wiring.js +5 -4
  10. package/cli/dist/utils/generated-paths.js +3 -2
  11. package/cli/dist/utils/project-config-defaults.js +2 -1
  12. package/cli/dist/utils/project-features.js +5 -13
  13. package/cli/dist/utils/resource-features.js +2 -1
  14. package/cli/dist/utils/services.js +2 -2
  15. package/cli/dist/utils/shared-platform-postgres.js +6 -5
  16. package/cli/dist/utils/smoke.js +3 -2
  17. package/cli/dist/utils/stack-features.js +5 -2
  18. package/cli/dist-bundle/chunk-Q6PZ6ZPA.js +315 -0
  19. package/cli/dist-bundle/cli.js +212 -212
  20. package/cli/dist-bundle/{cold-preflight-NXFER2W6.js → cold-preflight-6VMSECY4.js} +1 -1
  21. package/cli/package.json +1 -1
  22. package/engine/assets/docker/wake-gateway/server.ts +3 -3
  23. package/engine/topologies/platform/docker/compose/compose.star +4 -4
  24. package/engine/topologies/platform/docker/constants.star +25 -0
  25. package/engine/topologies/platform/docker/networking/traefik_static_routes.star +3 -1
  26. package/engine/topologies/tilt/generators/vite/templates.star +13 -9
  27. package/engine/topologies/tilt/resources/infra-loader.star +20 -14
  28. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +4 -4
  29. package/engine/topologies/tilt/resources/orchestrator/generators/env.star +3 -2
  30. package/engine/topologies/tilt/resources/orchestrator/generators/frontend.star +2 -1
  31. package/package.json +1 -1
  32. package/cli/dist-bundle/chunk-I63IBPUZ.js +0 -315
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ Notable changes to the `tdk` CLI. Versions match [npm](https://www.npmjs.com/pac
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ - Shared platform stacks (postgres, traefik, proxy, messaging, infisical, monitoring, cdc, elk) now run in Compose projects named `<project>-<stack>` (for example `tdk-erp-system-database-management`) instead of the directory-named `database-management`, `tdk-out` and so on. Two TDK projects on one Docker daemon no longer share them.
8
+
9
+ - Two TDK projects that each have a stack with the same name (for example `people`) no longer share Docker names. Compose projects are now `<project>-<stack>-autogenerated`, containers `<project>-<stack>-autogenerated-<service>-1` and images `<project>_<stack>_<service>:dev` (migrators `<project>_<stack>-db-migrator:dev`). Before, the second project's `tdk up` removed the first project's containers as orphans and overwrote its image. Containers from before this change keep the old names and are not removed by the new engine: stop them once, see [docs/layout.md](docs/layout.md).
10
+
7
11
  ## 1.3.130 (2026-10-08)
8
12
 
9
13
  - Releases now come with a signed checksum list: `checksums.txt.sigstore.json` (keyless Sigstore signature from the release workflow) is attached next to the binaries in [tdk-cli-releases](https://github.com/tdk-landscape/tdk-cli-releases/releases) and to a release with the same tag here. How to verify is in [docs/security.md](docs/security.md#releases). The binaries themselves are not signed individually (#869, #840).
@@ -8,7 +8,7 @@ import { Command } from "commander";
8
8
  import { hasVerdaccioLicense } from "../generator/extension-fetch.js";
9
9
  import { DEVCONTAINER_DOCKER_FIX, detectHost, isContainerHost, WEBCONTAINER_DOCS, WEBCONTAINER_UP_MESSAGE, } from "../utils/agent-host.js";
10
10
  import { projectNeedsBun } from "../utils/bun-requirement.js";
11
- import { MASTER_CONFIG_FILES, REQUIRED_PACKAGE_SCRIPTS } from "../utils/constants.js";
11
+ import { CONTAINER_RUNTIME_CHECK, MASTER_CONFIG_FILES, REQUIRED_PACKAGE_SCRIPTS, } from "../utils/constants.js";
12
12
  import { isPathDiscovered, readDiscoveryPaths } from "../utils/discovery-paths.js";
13
13
  import { collectDoctorChecks, createDoctorReport, getDoctorExitCode, } from "../utils/doctor-report.js";
14
14
  import { checkHealthRoutes, checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, projectConfigEnablesVerdaccio, summarizeServiceProbes, } from "../utils/doctor-runtime.js";
@@ -169,14 +169,14 @@ export async function checkDockerRuntime() {
169
169
  try {
170
170
  await execAsync("docker ps", EXEC_TIMEOUT_MS);
171
171
  return {
172
- name: "Container Runtime",
172
+ name: CONTAINER_RUNTIME_CHECK,
173
173
  didPass: true,
174
174
  message: "Docker Desktop Linux container engine is running",
175
175
  };
176
176
  }
177
177
  catch {
178
178
  return {
179
- name: "Container Runtime",
179
+ name: CONTAINER_RUNTIME_CHECK,
180
180
  didPass: false,
181
181
  message: "Docker is not running",
182
182
  fix: DOCTOR_FIXES.dockerDaemonDown,
@@ -187,7 +187,7 @@ export async function checkDockerRuntime() {
187
187
  try {
188
188
  await execAsync("docker ps", EXEC_TIMEOUT_MS);
189
189
  return {
190
- name: "Container Runtime",
190
+ name: CONTAINER_RUNTIME_CHECK,
191
191
  didPass: true,
192
192
  message: "Docker daemon is running",
193
193
  };
@@ -195,7 +195,7 @@ export async function checkDockerRuntime() {
195
195
  catch (err) {
196
196
  if (isTimeout(err)) {
197
197
  return {
198
- name: "Container Runtime",
198
+ name: CONTAINER_RUNTIME_CHECK,
199
199
  didPass: false,
200
200
  message: "Docker daemon is not responding",
201
201
  fix: DOCTOR_FIXES.dockerDaemonDown,
@@ -205,14 +205,14 @@ export async function checkDockerRuntime() {
205
205
  // Docker not running: check for Colima, then Podman
206
206
  if (await succeeds("colima status")) {
207
207
  return {
208
- name: "Container Runtime",
208
+ name: CONTAINER_RUNTIME_CHECK,
209
209
  didPass: true,
210
210
  message: "Colima (Docker runtime) is running",
211
211
  };
212
212
  }
213
213
  if (findOnPath("colima")) {
214
214
  return {
215
- name: "Container Runtime",
215
+ name: CONTAINER_RUNTIME_CHECK,
216
216
  didPass: false,
217
217
  message: "Colima is installed but not running",
218
218
  fix: DOCTOR_FIXES.dockerDaemonDown,
@@ -220,21 +220,21 @@ export async function checkDockerRuntime() {
220
220
  }
221
221
  if (await succeeds("podman ps")) {
222
222
  return {
223
- name: "Container Runtime",
223
+ name: CONTAINER_RUNTIME_CHECK,
224
224
  didPass: true,
225
225
  message: "Podman is running",
226
226
  };
227
227
  }
228
228
  if (hasDockerCli) {
229
229
  return {
230
- name: "Container Runtime",
230
+ name: CONTAINER_RUNTIME_CHECK,
231
231
  didPass: false,
232
232
  message: "Docker daemon is not running",
233
233
  fix: DOCTOR_FIXES.dockerDaemonDown,
234
234
  };
235
235
  }
236
236
  return {
237
- name: "Container Runtime",
237
+ name: CONTAINER_RUNTIME_CHECK,
238
238
  didPass: false,
239
239
  message: "Docker is not running",
240
240
  fix: DOCTOR_FIXES.dockerMissing,
@@ -1089,7 +1089,7 @@ export const doctorCommand = new Command("doctor")
1089
1089
  const { checks: results, errors } = await collectDoctorChecks(machineChecks, inProject ? projectChecks : []);
1090
1090
  if (isContainerHost(host.kind)) {
1091
1091
  for (const result of results) {
1092
- if (result.name === "Container Runtime" && !result.didPass) {
1092
+ if (result.name === CONTAINER_RUNTIME_CHECK && !result.didPass) {
1093
1093
  result.fix = DEVCONTAINER_DOCKER_FIX;
1094
1094
  }
1095
1095
  }
@@ -12,6 +12,7 @@ import { hasDddLicense } from "../generator/extension-fetch.js";
12
12
  import { getDockerfileTemplate, getTestTemplate, getWorkerIndexTemplate, } from "../generator/resource-templates.js";
13
13
  import { CREATABLE_RESOURCE_TYPES } from "../types/index.js";
14
14
  import { assertValid, confirmOrCancel } from "../utils/command-helpers.js";
15
+ import { BRING_YOUR_OWN_TYPE, PORT_RANGES, SERVICE_JSON } from "../utils/constants.js";
15
16
  import { chooseResourcePath, isPathDiscovered, readDiscoveryPaths, } from "../utils/discovery-paths.js";
16
17
  import { errorFactories, requireProjectRoot, runCommand, TdkError } from "../utils/errors.js";
17
18
  import { writeFilesWithProgress } from "../utils/file-helpers.js";
@@ -55,23 +56,31 @@ export const TYPE_SPECIFIC = {
55
56
  mcp: {
56
57
  healthCheckPath: "/health",
57
58
  },
58
- "bring-your-own": {
59
+ [BRING_YOUR_OWN_TYPE]: {
59
60
  healthCheckPath: "/health",
60
61
  },
61
62
  };
62
63
  const BYO_RESTART_POLICIES = ["no", "on-failure", "unless-stopped", "always"];
63
64
  export function resolveByoPort(value, assignedPort, resources) {
65
+ return resolveResourcePort(BRING_YOUR_OWN_TYPE, value, assignedPort, resources);
66
+ }
67
+ // `--port` is an override for every port-assigned type, validated against that type's range.
68
+ // Without it the next free port in the type's range is used.
69
+ export function resolveResourcePort(resourceType, value, assignedPort, resources) {
64
70
  if (!value)
65
71
  return assignedPort;
72
+ const { min, max } = PORT_RANGES[resourceType];
73
+ const flag = resourceType === BRING_YOUR_OWN_TYPE ? "BYO --port" : "--port";
74
+ const subject = resourceType === BRING_YOUR_OWN_TYPE ? "BYO port" : "Port";
66
75
  if (!/^\d+$/.test(value)) {
67
- throw new TdkError("BYO --port must be an integer from 4000 through 5999.");
76
+ throw new TdkError(`${flag} must be an integer from ${min} through ${max}.`);
68
77
  }
69
78
  const port = Number(value);
70
- if (!Number.isInteger(port) || port < 4000 || port > 5999) {
71
- throw new TdkError("BYO --port must be an integer from 4000 through 5999.");
79
+ if (!Number.isInteger(port) || port < min || port > max) {
80
+ throw new TdkError(`${flag} must be an integer from ${min} through ${max}.`);
72
81
  }
73
82
  if (resources.some((resource) => resource.config?.port === port)) {
74
- throw new TdkError(`BYO port ${port} is already assigned to another resource.`);
83
+ throw new TdkError(`${subject} ${port} is already assigned to another resource.`);
75
84
  }
76
85
  return port;
77
86
  }
@@ -123,7 +132,7 @@ export function createByoServiceJson(name, stack, port, options) {
123
132
  $schema: SERVICE_MANIFEST_SCHEMA_URL,
124
133
  schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
125
134
  appName: name,
126
- appType: "bring-your-own",
135
+ appType: BRING_YOUR_OWN_TYPE,
127
136
  stack,
128
137
  port,
129
138
  healthCheckPath: options.healthCheckPath,
@@ -251,7 +260,7 @@ export const TSCONFIG_TEMPLATE = {
251
260
  export { getBackendIndexTemplate };
252
261
  /** Resource types `tdk resource --type` accepts; `byo` is an alias of `bring-your-own`. */
253
262
  export function parseResourceType(type) {
254
- const normalized = type === "byo" ? "bring-your-own" : type;
263
+ const normalized = type === "byo" ? BRING_YOUR_OWN_TYPE : type;
255
264
  const validTypes = [...CREATABLE_RESOURCE_TYPES, "sdk"];
256
265
  if (!validTypes.includes(normalized)) {
257
266
  throw new TdkError(`Unknown resource type "${type}". Supported types: ${validTypes.join(", ")}.`, [`Use one of: ${validTypes.join(", ")}`, "Omit --type to create a backend"]);
@@ -276,7 +285,7 @@ export const resourceCommand = new Command("resource")
276
285
  .option("--no-proxy", "Disable the Traefik route (bring-your-own only)")
277
286
  .option("--restart <policy>", "Compose restart policy: no, on-failure, unless-stopped, always (bring-your-own only; use no for one-shot jobs)")
278
287
  .option("--image <name>", "Docker image name instead of building from Dockerfile (for bring-your-own)")
279
- .option("--port <port>", "Port number (default: next free in 4000-5999)")
288
+ .option("--port <port>", "Port for the resource, within its type's range (default: next free port in that range)")
280
289
  .option("--feature <feature...>", "Enable a resource feature (for example: prisma)")
281
290
  .action(async (name, options) => {
282
291
  if (options.frameworks) {
@@ -412,7 +421,7 @@ export const resourceCommand = new Command("resource")
412
421
  frontend: `apps/${resourceName}`,
413
422
  worker: `workers/${resourceName}`,
414
423
  mcp: `services/${stackName}/${resourceName}`,
415
- "bring-your-own": `services/${stackName}/${resourceName}`,
424
+ [BRING_YOUR_OWN_TYPE]: `services/${stackName}/${resourceName}`,
416
425
  sdk: `packages/${resourceName}`,
417
426
  };
418
427
  finalResourcePath = defaultPaths[resourceType];
@@ -434,7 +443,12 @@ export const resourceCommand = new Command("resource")
434
443
  }
435
444
  // Check if resource already exists
436
445
  const isExistingResource = existsSync(fullPath);
437
- const hasServiceJson = existsSync(resolve(fullPath, "service.json"));
446
+ const hasServiceJson = existsSync(resolve(fullPath, SERVICE_JSON));
447
+ if (options.port !== undefined && resourceType === "sdk") {
448
+ throw new TdkError("--port does not apply to --type sdk.", [
449
+ "Drop --port, or use a service type",
450
+ ]);
451
+ }
438
452
  if (resourceType === "sdk" && !hasServiceJson) {
439
453
  throw new TdkError(`No service.json in ${finalResourcePath}`, [
440
454
  'Add one with "appType": "sdk", then run this again to register it',
@@ -443,14 +457,14 @@ export const resourceCommand = new Command("resource")
443
457
  const shouldRegisterExisting = options.registerExisting ||
444
458
  resourceType === "sdk" ||
445
459
  (isExistingResource && hasServiceJson);
446
- if (isExistingResource && !shouldRegisterExisting && resourceType !== "bring-your-own") {
460
+ if (isExistingResource && !shouldRegisterExisting && resourceType !== BRING_YOUR_OWN_TYPE) {
447
461
  errorFactories.directoryExists(fullPath).exit();
448
462
  }
449
463
  const nextPort = resourceType === "sdk"
450
464
  ? 0
451
465
  : assignPort(resourceType, allResources);
452
466
  if (options.restart !== undefined) {
453
- if (resourceType !== "bring-your-own") {
467
+ if (resourceType !== BRING_YOUR_OWN_TYPE) {
454
468
  throw new TdkError("--restart can only be used with --type bring-your-own.", [
455
469
  "Add --type bring-your-own, or drop --restart",
456
470
  ]);
@@ -461,9 +475,9 @@ export const resourceCommand = new Command("resource")
461
475
  ]);
462
476
  }
463
477
  }
464
- const assignedPort = resourceType === "bring-your-own"
465
- ? resolveByoPort(options.port, nextPort, allResources)
466
- : nextPort;
478
+ const assignedPort = resourceType === "sdk"
479
+ ? 0
480
+ : resolveResourcePort(resourceType, options.port, nextPort, allResources);
467
481
  console.log(chalk.gray("\nResource details:"));
468
482
  console.log(chalk.gray(` Name: ${resourceName}`));
469
483
  console.log(chalk.gray(` Type: ${resourceType}`));
@@ -483,7 +497,7 @@ export const resourceCommand = new Command("resource")
483
497
  if (shouldRegisterExisting && hasServiceJson) {
484
498
  // Read existing service.json
485
499
  const { readFileSync } = await import("node:fs");
486
- const existingServiceJsonPath = resolve(fullPath, "service.json");
500
+ const existingServiceJsonPath = resolve(fullPath, SERVICE_JSON);
487
501
  const existingContent = readFileSync(existingServiceJsonPath, "utf-8");
488
502
  const existingServiceJson = JSON.parse(existingContent);
489
503
  // Update with new values while preserving existing fields
@@ -506,7 +520,7 @@ export const resourceCommand = new Command("resource")
506
520
  return;
507
521
  }
508
522
  // Handle bring-your-own type
509
- if (resourceType === "bring-your-own") {
523
+ if (resourceType === BRING_YOUR_OWN_TYPE) {
510
524
  console.log(chalk.blue("\n📁 Creating bring-your-own resource..."));
511
525
  // Parse port option
512
526
  const port = assignedPort;
@@ -528,7 +542,7 @@ export const resourceCommand = new Command("resource")
528
542
  ...(options.restart ? { restart: options.restart } : {}),
529
543
  });
530
544
  const { writeFileSync } = await import("node:fs");
531
- writeFileSync(resolve(fullPath, "service.json"), JSON.stringify(byoServiceJson, null, 2));
545
+ writeFileSync(resolve(fullPath, SERVICE_JSON), JSON.stringify(byoServiceJson, null, 2));
532
546
  // Create Dockerfile stub if no image provided and dockerfile doesn't exist
533
547
  if (!options.image && !existsSync(dockerfilePath)) {
534
548
  mkdirSync(resolve(dockerfilePath, ".."), { recursive: true });
@@ -599,7 +613,7 @@ This file contains the resource configuration for TDK.
599
613
  const tasks = [
600
614
  {
601
615
  type: "json",
602
- filename: "service.json",
616
+ filename: SERVICE_JSON,
603
617
  content: serviceJson,
604
618
  description: "Generating service.json",
605
619
  emoji: "📝",
@@ -676,7 +690,7 @@ This file contains the resource configuration for TDK.
676
690
  const languageFiles = backendLanguage?.createFiles?.(resourceName);
677
691
  if (languageFiles) {
678
692
  const owned = new Set(languageFiles.map((file) => file.filename));
679
- const sharedTasks = tasks.filter((task) => task.filename === "service.json" ||
693
+ const sharedTasks = tasks.filter((task) => task.filename === SERVICE_JSON ||
680
694
  !(owned.has(task.filename) ||
681
695
  task.filename === "Dockerfile" ||
682
696
  task.filename === "src/index.ts" ||
@@ -715,7 +729,7 @@ This file contains the resource configuration for TDK.
715
729
  writeFilesWithProgress(migratorPath, [
716
730
  {
717
731
  type: "json",
718
- filename: "service.json",
732
+ filename: SERVICE_JSON,
719
733
  content: migratorConfig,
720
734
  description: "Generating Prisma migrator manifest",
721
735
  emoji: "🗃️",
@@ -89,7 +89,7 @@ async function main() {
89
89
  continue;
90
90
  }
91
91
 
92
- console.log('[Worker] Fetched \${jobs.length} jobs');
92
+ console.log(\`[Worker] Fetched \${jobs.length} jobs\`);
93
93
 
94
94
  for (const job of jobs) {
95
95
  try {
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import Handlebars from "handlebars";
7
7
  import { PLATFORM_STANDARDS } from "../config/platform-standards.js";
8
8
  import { writeTextFileAtomic } from "../utils/atomic-write.js";
9
+ import { DATABASE_MANAGEMENT_FEATURE, TILTIGNORE_FILE } from "../utils/constants.js";
9
10
  import { writeTextFile } from "../utils/file-helpers.js";
10
11
  import { assertTdkGeneratedPath } from "../utils/generated-paths.js";
11
12
  import { DEFAULT_ALWAYS_ENABLED_INFRA } from "../utils/project-config-defaults.js";
@@ -52,7 +53,7 @@ let embeddedTemplatesCache = null;
52
53
  function getEmbeddedTemplates() {
53
54
  if (!embeddedTemplatesCache) {
54
55
  embeddedTemplatesCache = {
55
- ".tiltignore": loadTemplate(".tiltignore.hbs"),
56
+ [TILTIGNORE_FILE]: loadTemplate(".tiltignore.hbs"),
56
57
  "TILT_RESOURCE_DEFAULTS.star": loadTemplate("TILT_RESOURCE_DEFAULTS.star.hbs"),
57
58
  "TILT_TECH_STACK.star": loadTemplate("TILT_TECH_STACK.star.hbs"),
58
59
  Tiltfile: loadTemplate("Tiltfile.hbs"),
@@ -66,6 +67,7 @@ function getEmbeddedTemplates() {
66
67
  const RESOURCE_DESCRIPTIONS = {
67
68
  proxy: "Traefik reverse proxy",
68
69
  verdaccio: "Private npm registry",
70
+ // This key is emitted into spec.master, so it must stay the literal stack id.
69
71
  "database-management": "PostgreSQL database",
70
72
  };
71
73
  const INFRA_DESCRIPTIONS = {
@@ -199,6 +201,7 @@ export class TemplateEngine {
199
201
  "TILT_RESOURCE_DEFAULTS.star": this.generateServiceDefaults(context),
200
202
  "spec.master": this.generateSpecMaster(context),
201
203
  Tiltfile: this.generateTiltfile(context),
204
+ // Key must stay the literal: Record<GeneratedFileName, ...> requires it.
202
205
  ".tiltignore": this.generateTiltIgnore(context),
203
206
  };
204
207
  }
@@ -460,16 +463,16 @@ export async function generateMasterConfigs(projectRoot, options = {}) {
460
463
  writeTextFileAtomic(filePath, content);
461
464
  console.log(`✓ Generated: .tdk/.tdk-out/${filename}`);
462
465
  }
463
- if (isStackFeatureEnabledInStacks(projectConfig.phases, "database-management")) {
464
- const composePath = assertTdkGeneratedPath(projectRoot, path.join("services", "platform", "database-management", "docker-compose.yml"));
466
+ if (isStackFeatureEnabledInStacks(projectConfig.phases, DATABASE_MANAGEMENT_FEATURE)) {
467
+ const composePath = assertTdkGeneratedPath(projectRoot, path.join("services", "platform", DATABASE_MANAGEMENT_FEATURE, "docker-compose.yml"));
465
468
  const composeDir = path.dirname(composePath);
466
469
  fs.mkdirSync(composeDir, { recursive: true });
467
470
  writeTextFileAtomic(composePath, generateDatabaseManagementCompose(projectConfig));
468
471
  console.log("✓ Generated: services/platform/database-management/docker-compose.yml");
469
472
  }
470
473
  // Copy .tiltignore to project root so Tilt uses it
471
- const tiltignoreSource = path.join(outputDir, ".tiltignore");
472
- const tiltignoreTarget = assertTdkGeneratedPath(projectRoot, ".tiltignore");
474
+ const tiltignoreSource = path.join(outputDir, TILTIGNORE_FILE);
475
+ const tiltignoreTarget = assertTdkGeneratedPath(projectRoot, TILTIGNORE_FILE);
473
476
  if (fs.existsSync(tiltignoreSource)) {
474
477
  if (!fs.existsSync(tiltignoreTarget)) {
475
478
  fs.copyFileSync(tiltignoreSource, tiltignoreTarget);
@@ -656,7 +659,7 @@ export function verifyMasterConfigs(projectRoot) {
656
659
  }
657
660
  // `tdk project` never overwrites a root .tiltignore the repo already has, so one
658
661
  // without TDK's header belongs to the team: it is not drift.
659
- const rootTiltIgnore = path.join(projectRoot, ".tiltignore");
662
+ const rootTiltIgnore = path.join(projectRoot, TILTIGNORE_FILE);
660
663
  const rootTiltIgnoreText = fs.existsSync(rootTiltIgnore)
661
664
  ? fs.readFileSync(rootTiltIgnore, "utf-8")
662
665
  : undefined;
@@ -664,7 +667,7 @@ export function verifyMasterConfigs(projectRoot) {
664
667
  warnings.push(".tiltignore: not generated by TDK, left as is");
665
668
  }
666
669
  else {
667
- addDrift(".tiltignore", rootTiltIgnoreText, expectedFiles[".tiltignore"]);
670
+ addDrift(TILTIGNORE_FILE, rootTiltIgnoreText, expectedFiles[TILTIGNORE_FILE]);
668
671
  }
669
672
  for (const manifestPath of discoverServiceManifestPaths(projectRoot)) {
670
673
  const displayPath = path.relative(projectRoot, manifestPath).split(path.sep).join("/");
@@ -1,3 +1,22 @@
1
+ // Name of the doctor check that verifies a container runtime is reachable.
2
+ // It is looked up by name in doctor-report.ts, so both sides must use this
3
+ // constant instead of a bare string literal.
4
+ export const CONTAINER_RUNTIME_CHECK = "Container Runtime";
5
+ // Resource type id for services the user brings themselves. Referenced by
6
+ // value in several modules, so a typo in one copy would compile and silently
7
+ // stop matching. `types/index.ts` keeps its own literal: it is the leaf module
8
+ // that defines ResourceType, so importing this file back would be circular.
9
+ // Typed as the literal (not the wider ResourceType union): computed keys built
10
+ // from it stay narrow enough to satisfy Record<ResourceType, ...>.
11
+ export const BRING_YOUR_OWN_TYPE = "bring-your-own";
12
+ // Compile-time check that the literal above really is a ResourceType member.
13
+ const _bringYourOwnIsResourceType = BRING_YOUR_OWN_TYPE;
14
+ // Stack feature id for the shared platform database.
15
+ export const DATABASE_MANAGEMENT_FEATURE = "database-management";
16
+ // Root .tiltignore written by the generator and checked by doctor drift.
17
+ export const TILTIGNORE_FILE = ".tiltignore";
18
+ // Manifest file name for a resource, used by discovery and the resource command.
19
+ export const SERVICE_JSON = "service.json";
1
20
  export const MASTER_CONFIG_FILES = [
2
21
  "TILT_TECH_STACK.star",
3
22
  "TILT_RESOURCE_DEFAULTS.star",
@@ -11,7 +30,7 @@ export const VALID_RESOURCE_TYPES = [
11
30
  "worker",
12
31
  "migrator",
13
32
  "mcp",
14
- "bring-your-own",
33
+ BRING_YOUR_OWN_TYPE,
15
34
  ];
16
35
  // Frontend/library/sdk services are served statically or consumed as
17
36
  // packages, so they never need a "start" script - only backend/worker/
@@ -25,7 +44,7 @@ export const REQUIRED_PACKAGE_SCRIPTS = {
25
44
  frontend: ["dev", "build"],
26
45
  library: ["dev", "build"],
27
46
  sdk: ["dev", "build"],
28
- "bring-your-own": [],
47
+ [BRING_YOUR_OWN_TYPE]: [],
29
48
  };
30
49
  // Version specifiers that pin nothing at all - a fresh `bun install`/`npm install`
31
50
  // can pull a different (possibly breaking) version on every run.
@@ -42,7 +61,7 @@ export const PORT_RANGES = {
42
61
  backend: { base: 4000, min: 4000, max: 4999, range: "4000-4999" },
43
62
  // An MCP server is an HTTP service, so it shares the backend-compatible range with bring-your-own.
44
63
  mcp: { base: 4000, min: 4000, max: 5999, range: "4000-5999" },
45
- "bring-your-own": { base: 4000, min: 4000, max: 5999, range: "4000-5999" },
64
+ [BRING_YOUR_OWN_TYPE]: { base: 4000, min: 4000, max: 5999, range: "4000-5999" },
46
65
  worker: { base: 6000, min: 6000, max: 6999, range: "6000-6999" },
47
66
  health: { base: 5000, min: 5000, max: 5999, range: "5000-5999" },
48
67
  migrator: { base: 7000, min: 7000, max: 7999, range: "7000-7999" },
@@ -1,3 +1,4 @@
1
+ import { CONTAINER_RUNTIME_CHECK } from "./constants.js";
1
2
  /** Wait for concurrent probes to finish even if one throws, so they can release resources. */
2
3
  export async function collectDoctorChecks(machineChecks, projectChecks) {
3
4
  const checks = [];
@@ -21,7 +22,7 @@ export async function collectDoctorChecks(machineChecks, projectChecks) {
21
22
  return { checks, errors };
22
23
  }
23
24
  function containerRuntimeReachable(checks) {
24
- const runtime = checks.find((check) => check.name === "Container Runtime");
25
+ const runtime = checks.find((check) => check.name === CONTAINER_RUNTIME_CHECK);
25
26
  return runtime ? runtime.didPass : null;
26
27
  }
27
28
  /** Produce the same readiness decision for human and machine consumers. */
@@ -646,7 +646,7 @@ export function orderTiltFailures(failures) {
646
646
  * Best-effort root cause for runtime crashes when buildHistory has no error.
647
647
  * Reads recent docker logs for the matching container name.
648
648
  */
649
- export function probeContainerRuntimeError(resourceName, exec = execSync) {
649
+ export function probeContainerRuntimeError(resourceName, exec = execSync, projectName = getProjectName()) {
650
650
  try {
651
651
  const names = runHostCommand(exec, "docker", ["ps", "-a", "--format", "{{.Names}}"], "docker ps -a --format '{{.Names}}'", {
652
652
  stdio: "pipe",
@@ -656,7 +656,12 @@ export function probeContainerRuntimeError(resourceName, exec = execSync) {
656
656
  .split("\n")
657
657
  .map((line) => line.trim())
658
658
  .filter(Boolean);
659
+ // Compose names the container <project>-<stack>-autogenerated-<resource>-1. Prefer this project's
660
+ // container: another TDK project can run a stack with the same name on the same Docker daemon.
661
+ const composeSuffix = `-autogenerated-${resourceName}-1`;
659
662
  const match = names.find((name) => name === resourceName) ??
663
+ names.find((name) => name.startsWith(`${projectName}-`) && name.endsWith(composeSuffix)) ??
664
+ names.find((name) => name.endsWith(composeSuffix)) ??
660
665
  names.find((name) => name.includes(resourceName));
661
666
  if (!match)
662
667
  return null;
@@ -4,6 +4,7 @@ import { execFileSync, execSync } from "node:child_process";
4
4
  import { existsSync, readdirSync, readFileSync } from "node:fs";
5
5
  import { basename, dirname, join, relative } from "node:path";
6
6
  import { BACKEND_LANGUAGES } from "../backend-languages/registry.js";
7
+ import { BRING_YOUR_OWN_TYPE, DATABASE_MANAGEMENT_FEATURE } from "./constants.js";
7
8
  import { execAsync } from "./exec-async.js";
8
9
  import { formatCount } from "./formatting.js";
9
10
  import { findProjectRoot } from "./paths.js";
@@ -123,7 +124,7 @@ export function checkPrismaConsistency(projectRoot = findProjectRoot() ?? proces
123
124
  findings.push(...prismaConfigFindings(resource.path).map((finding) => `${resource.name}: ${finding}`));
124
125
  const appType = resource.config?.appType ?? resource.type;
125
126
  if (appType === "migrator") {
126
- const postgresDependency = resource.config?.dependsOn?.some((name) => name === "postgres" || name === "database-management");
127
+ const postgresDependency = resource.config?.dependsOn?.some((name) => name === "postgres" || name === DATABASE_MANAGEMENT_FEATURE);
127
128
  if (!postgresDependency)
128
129
  findings.push(`${resource.name}: migrator must depend on postgres or database-management`);
129
130
  }
@@ -182,7 +183,7 @@ function ownsRuntimeFiles(resource) {
182
183
  * (or its `buildContext`), or pulls `image`, and never generates the golden-layer Dockerfile that COPYs a package.json.
183
184
  */
184
185
  function isBringYourOwn(resource) {
185
- return (resource.config?.appType ?? resource.type) === "bring-your-own";
186
+ return (resource.config?.appType ?? resource.type) === BRING_YOUR_OWN_TYPE;
186
187
  }
187
188
  export function checkResourcePackageJson(projectRoot = findProjectRoot() ?? process.cwd()) {
188
189
  const missing = discoverResourcesFromRoot(projectRoot)
@@ -236,7 +237,7 @@ function isRoutedToPort(resource) {
236
237
  const appType = resource.config?.appType;
237
238
  if (isApiServiceType(appType))
238
239
  return true;
239
- return appType === "bring-your-own" && resource.config?.exposeViaProxy !== false;
240
+ return appType === BRING_YOUR_OWN_TYPE && resource.config?.exposeViaProxy !== false;
240
241
  }
241
242
  /**
242
243
  * Two routed resources on one `port`. This is a warning, not a failure: the engine publishes no service
@@ -343,7 +344,7 @@ export function checkSharedStackRoutes(projectRoot = findProjectRoot() ?? proces
343
344
  for (const resource of discoverResourcesFromRoot(projectRoot)) {
344
345
  const config = resource.config;
345
346
  const routable = isApiServiceType(config?.appType) ||
346
- (config?.appType === "bring-your-own" && config.exposeViaProxy !== false);
347
+ (config?.appType === BRING_YOUR_OWN_TYPE && config.exposeViaProxy !== false);
347
348
  if (!routable)
348
349
  continue;
349
350
  const directory = dirname(resource.path);
@@ -2,13 +2,14 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  import { existsSync, lstatSync } from "node:fs";
4
4
  import { isAbsolute, normalize, relative, sep } from "node:path";
5
+ import { TILTIGNORE_FILE } from "./constants.js";
5
6
  /** Reviewed TDK-owned destinations written by `tdk config regenerate` or Tilt generation. */
6
7
  export const TDK_GENERATED_PATH_ALLOWLIST = [
7
8
  ".autogenerated/**",
8
9
  ".tdk/.tdk-out/**",
9
10
  "Tiltfile",
10
11
  "TILT_*.star",
11
- ".tiltignore",
12
+ TILTIGNORE_FILE,
12
13
  "package.json (create only)",
13
14
  "shared-platform-engineering/docker-templates/**",
14
15
  "services/platform/database-management/docker-compose.yml",
@@ -29,7 +30,7 @@ export function isTdkGeneratedPath(path) {
29
30
  }
30
31
  return (normalized === "Tiltfile" ||
31
32
  /^TILT_[^/]+\.star$/.test(normalized) ||
32
- normalized === ".tiltignore" ||
33
+ normalized === TILTIGNORE_FILE ||
33
34
  normalized === "package.json" ||
34
35
  normalized === "services/platform/database-management/docker-compose.yml");
35
36
  }
@@ -1,5 +1,6 @@
1
1
  // Copyright (c) 2026 TDK Landscape contributors
2
2
  // SPDX-License-Identifier: MIT
3
+ import { DATABASE_MANAGEMENT_FEATURE } from "./constants.js";
3
4
  /**
4
5
  * Defaults shared by project generation and CLI predicates.
5
6
  *
@@ -13,5 +14,5 @@
13
14
  * they report Postgres-will-start while the engine does not start it (or
14
15
  * the reverse). See shared-platform-postgres.ts.
15
16
  */
16
- export const DEFAULT_ALWAYS_ENABLED_INFRA = ["database-management", "proxy"];
17
+ export const DEFAULT_ALWAYS_ENABLED_INFRA = [DATABASE_MANAGEMENT_FEATURE, "proxy"];
17
18
  //# sourceMappingURL=project-config-defaults.js.map
@@ -1,14 +1,6 @@
1
1
  // Copyright (c) 2026 TDK Landscape contributors
2
2
  // SPDX-License-Identifier: MIT
3
- /**
4
- * Project-Level Features
5
- *
6
- * These are infrastructure and optional services enabled at the project level.
7
- * They are available to all resources and are configured in project.json stacks.
8
- *
9
- * Default: All CORE features enabled in pre_alpha stack
10
- * Optional/premium: disabled by default (can be enabled in optional_infra)
11
- */
3
+ import { DATABASE_MANAGEMENT_FEATURE } from "./constants.js";
12
4
  export const PROJECT_FEATURES = {
13
5
  // ============================================================================
14
6
  // CORE INFRASTRUCTURE (always enabled in pre_alpha)
@@ -33,8 +25,8 @@ export const PROJECT_FEATURES = {
33
25
  enabled_by_default: false,
34
26
  dependsOn: [],
35
27
  },
36
- "database-management": {
37
- name: "database-management",
28
+ [DATABASE_MANAGEMENT_FEATURE]: {
29
+ name: DATABASE_MANAGEMENT_FEATURE,
38
30
  description: "PostgreSQL database for data persistence and SQL operations",
39
31
  category: "core",
40
32
  phase: "pre_alpha",
@@ -50,7 +42,7 @@ export const PROJECT_FEATURES = {
50
42
  category: "optional",
51
43
  phase: "pre_alpha",
52
44
  enabled_by_default: false,
53
- dependsOn: ["database-management"],
45
+ dependsOn: [DATABASE_MANAGEMENT_FEATURE],
54
46
  },
55
47
  elk: {
56
48
  name: "elk",
@@ -66,7 +58,7 @@ export const PROJECT_FEATURES = {
66
58
  category: "optional",
67
59
  phase: "pre_alpha",
68
60
  enabled_by_default: false,
69
- dependsOn: ["database-management"],
61
+ dependsOn: [DATABASE_MANAGEMENT_FEATURE],
70
62
  },
71
63
  "golden-image": {
72
64
  name: "golden-image",
@@ -9,6 +9,7 @@
9
9
  * Frontend services: api-client, env-config, api-index (enabled by default)
10
10
  * Backend services: prisma (enabled by default)
11
11
  */
12
+ import { BRING_YOUR_OWN_TYPE } from "./constants.js";
12
13
  export const RESOURCE_FEATURES = {
13
14
  // ============================================================================
14
15
  // FRONTEND GENERATORS
@@ -161,7 +162,7 @@ export const RESOURCE_FEATURES = {
161
162
  */
162
163
  export function getDefaultFeaturesForResourceType(appType) {
163
164
  // bring-your-own doesn't generate application code, so no features
164
- if (appType === "bring-your-own") {
165
+ if (appType === BRING_YOUR_OWN_TYPE) {
165
166
  return [];
166
167
  }
167
168
  const defaults = [];
@@ -3,14 +3,14 @@
3
3
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
4
4
  import { dirname, join } from "node:path";
5
5
  import { Cache, createCacheValidator } from "./cache.js";
6
- import { AUTOGENERATED_FILE_PATTERNS, SKIP_DIRECTORIES } from "./constants.js";
6
+ import { AUTOGENERATED_FILE_PATTERNS, SERVICE_JSON, SKIP_DIRECTORIES } from "./constants.js";
7
7
  import { errorFactories } from "./errors.js";
8
8
  import { formatCount } from "./formatting.js";
9
9
  import { findProjectRoot } from "./paths.js";
10
10
  import { getInvalidServiceRoutePathFields, validateGeneratedStringFields, validateServiceManifest, } from "./service-manifest.js";
11
11
  import { deriveStackStatus } from "./stack-status.js";
12
12
  import { includes } from "./validation.js";
13
- const RESOURCE_JSON_FILENAME = "service.json";
13
+ const RESOURCE_JSON_FILENAME = SERVICE_JSON;
14
14
  function findServiceJsonFiles(dir, maxDepth = 5, currentDepth = 0) {
15
15
  const results = [];
16
16
  if (currentDepth > maxDepth) {