@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.
- package/CHANGELOG.md +4 -0
- package/cli/dist/commands/doctor.js +11 -11
- package/cli/dist/commands/resource.js +35 -21
- package/cli/dist/generator/resource-templates.js +1 -1
- package/cli/dist/generator/template-engine.js +10 -7
- package/cli/dist/utils/constants.js +22 -3
- package/cli/dist/utils/doctor-report.js +2 -1
- package/cli/dist/utils/doctor-runtime.js +6 -1
- package/cli/dist/utils/doctor-wiring.js +5 -4
- package/cli/dist/utils/generated-paths.js +3 -2
- package/cli/dist/utils/project-config-defaults.js +2 -1
- package/cli/dist/utils/project-features.js +5 -13
- package/cli/dist/utils/resource-features.js +2 -1
- package/cli/dist/utils/services.js +2 -2
- package/cli/dist/utils/shared-platform-postgres.js +6 -5
- package/cli/dist/utils/smoke.js +3 -2
- package/cli/dist/utils/stack-features.js +5 -2
- package/cli/dist-bundle/chunk-Q6PZ6ZPA.js +315 -0
- package/cli/dist-bundle/cli.js +212 -212
- package/cli/dist-bundle/{cold-preflight-NXFER2W6.js → cold-preflight-6VMSECY4.js} +1 -1
- package/cli/package.json +1 -1
- package/engine/assets/docker/wake-gateway/server.ts +3 -3
- package/engine/topologies/platform/docker/compose/compose.star +4 -4
- package/engine/topologies/platform/docker/constants.star +25 -0
- package/engine/topologies/platform/docker/networking/traefik_static_routes.star +3 -1
- package/engine/topologies/tilt/generators/vite/templates.star +13 -9
- package/engine/topologies/tilt/resources/infra-loader.star +20 -14
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +4 -4
- package/engine/topologies/tilt/resources/orchestrator/generators/env.star +3 -2
- package/engine/topologies/tilt/resources/orchestrator/generators/frontend.star +2 -1
- package/package.json +1 -1
- 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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 ===
|
|
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
|
-
|
|
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(
|
|
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 <
|
|
71
|
-
throw new TdkError(
|
|
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(
|
|
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:
|
|
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" ?
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 !==
|
|
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 !==
|
|
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 === "
|
|
465
|
-
?
|
|
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,
|
|
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 ===
|
|
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,
|
|
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:
|
|
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 ===
|
|
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:
|
|
732
|
+
filename: SERVICE_JSON,
|
|
719
733
|
content: migratorConfig,
|
|
720
734
|
description: "Generating Prisma migrator manifest",
|
|
721
735
|
emoji: "🗃️",
|
|
@@ -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
|
-
|
|
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,
|
|
464
|
-
const composePath = assertTdkGeneratedPath(projectRoot, path.join("services", "platform",
|
|
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,
|
|
472
|
-
const tiltignoreTarget = assertTdkGeneratedPath(projectRoot,
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ===
|
|
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 ===
|
|
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) ===
|
|
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 ===
|
|
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 ===
|
|
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
|
-
|
|
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 ===
|
|
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 = [
|
|
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
|
-
|
|
37
|
-
name:
|
|
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: [
|
|
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: [
|
|
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 ===
|
|
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 =
|
|
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) {
|