@tdk-landscape/tdk-cli-core 1.3.34 → 1.3.36
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/discovery/discovery_orchestrator.star +4 -4
- package/discovery/manifest/loading.star +9 -5
- package/discovery/manifest/normalize.star +3 -3
- package/discovery/manifest/validation.star +1 -1
- package/discovery/validation.star +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +120 -6
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/resource.js +1 -1
- package/dist/commands/resource.js.map +1 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +2 -48
- package/dist/commands/up.js.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +11 -0
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/resource-features.d.ts +1 -1
- package/dist/utils/resource-features.js +1 -1
- package/dist/utils/service-urls.d.ts +30 -0
- package/dist/utils/service-urls.d.ts.map +1 -0
- package/dist/utils/service-urls.js +93 -0
- package/dist/utils/service-urls.js.map +1 -0
- package/engine/REFACTORING_MANIFEST_SYSTEM.md +1 -1
- package/engine/lifecycle/orchestrator.star +4 -3
- package/engine/schemas/service-schema.json +5 -5
- package/engine/topologies/platform/docker/compose/compose.star +44 -24
- package/engine/topologies/platform/docker/constants.star +4 -0
- package/engine/topologies/platform/docker/generators/golden_docker_generator_v2.star +1 -0
- package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +10 -6
- package/engine/topologies/platform/docker/networking/api_path_constants.star +30 -0
- package/engine/topologies/platform/docker/networking/traefik.star +2 -2
- package/engine/topologies/platform/net/AGENTS.md +64 -0
- package/engine/topologies/platform/security/AGENTS.md +49 -0
- package/engine/topologies/platform/state/AGENTS.md +64 -0
- package/engine/topologies/tilt/generators/vite/backend.star +1 -1
- package/engine/topologies/tilt/generators/vite/frontend.star +2 -1
- package/engine/topologies/tilt/generators/vite/helpers.star +1 -1
- package/engine/topologies/tilt/manifest/constants.star +1 -1
- package/engine/topologies/tilt/manifest/mapper.star +2 -2
- package/engine/topologies/tilt/manifest/parser.star +2 -2
- package/engine/topologies/tilt/manifest/schema.star +2 -2
- package/engine/topologies/tilt/manifest/tests/test_loader_parser.star +3 -3
- package/engine/topologies/tilt/manifest/tests/test_validator.star +2 -2
- package/engine/topologies/tilt/manifest/validator.star +7 -7
- package/engine/topologies/tilt/resources/orchestrator/generators/env.star +3 -2
- package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +3 -2
- package/package.json +1 -1
- package/engine/schemas/manifest-schema.json +0 -301
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Resource-Level Features
|
|
3
3
|
*
|
|
4
4
|
* These are code generators and configurations that run per-service/resource.
|
|
5
|
-
* They are controlled via the
|
|
5
|
+
* They are controlled via the featuresEnabled: [] array in each service.json file.
|
|
6
6
|
*
|
|
7
7
|
* Frontend services: api-client, env-config, api-index (enabled by default)
|
|
8
8
|
* Backend services: prisma (enabled by default)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DiscoveredResource } from "../types/index.js";
|
|
2
|
+
export interface HealthTarget {
|
|
3
|
+
name: string;
|
|
4
|
+
appType: "frontend" | "backend";
|
|
5
|
+
url: string;
|
|
6
|
+
}
|
|
7
|
+
export interface HealthProbe extends HealthTarget {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
/** HTTP status code, or undefined when the request never completed. */
|
|
10
|
+
status?: number;
|
|
11
|
+
/** Failure reason when the request never completed (DNS, refused, timeout). */
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function getProjectName(): string;
|
|
15
|
+
export declare function resolveSubdomainBases(): {
|
|
16
|
+
appBase: string;
|
|
17
|
+
apiBase: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function appendHealthPath(path: string): string;
|
|
20
|
+
/** The route `tdk up` advertises for a service, without the /health suffix. */
|
|
21
|
+
export declare function resolveServicePath(resource: DiscoveredResource): string;
|
|
22
|
+
/**
|
|
23
|
+
* Health URLs for every routable service, matching what `tdk up` prints.
|
|
24
|
+
* Workers, libraries, SDKs and migrators have no Traefik route, so they are
|
|
25
|
+
* skipped rather than reported as unreachable.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildHealthTargets(resources: DiscoveredResource[]): HealthTarget[];
|
|
28
|
+
export declare function pingHealthTarget(target: HealthTarget, timeoutMs: number): Promise<HealthProbe>;
|
|
29
|
+
export declare function pingHealthTargets(targets: HealthTarget[], timeoutMs: number): Promise<HealthProbe[]>;
|
|
30
|
+
//# sourceMappingURL=service-urls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-urls.d.ts","sourceRoot":"","sources":["../../src/utils/service-urls.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAG5D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAY,SAAQ,YAAY;IAC/C,EAAE,EAAE,OAAO,CAAC;IACZ,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,cAAc,IAAI,MAAM,CAYvC;AAED,wBAAgB,qBAAqB,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAuB5E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAMvE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,kBAAkB,EAAE,GAAG,YAAY,EAAE,CAkBlF;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,CAAC,CAiBtB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,YAAY,EAAE,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,EAAE,CAAC,CAExB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { findProjectRoot } from "./paths.js";
|
|
4
|
+
export function getProjectName() {
|
|
5
|
+
const root = findProjectRoot();
|
|
6
|
+
if (root) {
|
|
7
|
+
try {
|
|
8
|
+
const content = readFileSync(join(root, ".tdk", "project.json"), "utf-8");
|
|
9
|
+
const parsed = JSON.parse(content);
|
|
10
|
+
if (parsed?.project?.name) {
|
|
11
|
+
return parsed.project.name;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch { }
|
|
15
|
+
}
|
|
16
|
+
return "beauty-crm";
|
|
17
|
+
}
|
|
18
|
+
export function resolveSubdomainBases() {
|
|
19
|
+
const projectName = getProjectName();
|
|
20
|
+
const raw = process.env.TDK_SERVICE_BASE_URL ?? `http://${projectName}.localhost`;
|
|
21
|
+
try {
|
|
22
|
+
const u = new URL(raw.includes("://") ? raw : `http://${raw}`);
|
|
23
|
+
const host = u.hostname;
|
|
24
|
+
if (host === "localhost" || /^\d+\.\d+\.\d+\.\d+$/.test(host)) {
|
|
25
|
+
return {
|
|
26
|
+
appBase: `${u.protocol}//${host}${u.port ? `:${u.port}` : ""}`,
|
|
27
|
+
apiBase: `${u.protocol}//${host}${u.port ? `:${u.port}` : ""}`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const bare = host.replace(/^(app|api)\./, "");
|
|
31
|
+
return {
|
|
32
|
+
appBase: `${u.protocol}//app.${bare}${u.port ? `:${u.port}` : ""}`,
|
|
33
|
+
apiBase: `${u.protocol}//api.${bare}${u.port ? `:${u.port}` : ""}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return {
|
|
38
|
+
appBase: `http://app.${projectName}.localhost`,
|
|
39
|
+
apiBase: `http://api.${projectName}.localhost`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function appendHealthPath(path) {
|
|
44
|
+
return `${path.replace(/\/+$/, "")}/health`;
|
|
45
|
+
}
|
|
46
|
+
/** The route `tdk up` advertises for a service, without the /health suffix. */
|
|
47
|
+
export function resolveServicePath(resource) {
|
|
48
|
+
if (resource.config?.appType === "backend") {
|
|
49
|
+
const servicePathName = resource.name.replace(/-api$/, "");
|
|
50
|
+
return resource.config?.apiPath ?? `/api/${servicePathName}`;
|
|
51
|
+
}
|
|
52
|
+
return resource.config?.basePath ?? `/${resource.name}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Health URLs for every routable service, matching what `tdk up` prints.
|
|
56
|
+
* Workers, libraries, SDKs and migrators have no Traefik route, so they are
|
|
57
|
+
* skipped rather than reported as unreachable.
|
|
58
|
+
*/
|
|
59
|
+
export function buildHealthTargets(resources) {
|
|
60
|
+
const { appBase, apiBase } = resolveSubdomainBases();
|
|
61
|
+
return resources
|
|
62
|
+
.filter((resource) => resource.config?.appType === "frontend" || resource.config?.appType === "backend")
|
|
63
|
+
.map((resource) => {
|
|
64
|
+
const base = resource.config.appType === "backend" ? apiBase : appBase;
|
|
65
|
+
return {
|
|
66
|
+
name: resource.name,
|
|
67
|
+
appType: resource.config.appType,
|
|
68
|
+
url: `${base}${appendHealthPath(resolveServicePath(resource))}`,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export async function pingHealthTarget(target, timeoutMs) {
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch(target.url, {
|
|
75
|
+
method: "GET",
|
|
76
|
+
redirect: "manual",
|
|
77
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
78
|
+
});
|
|
79
|
+
return { ...target, ok: response.ok, status: response.status };
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const error = err instanceof Error && err.name === "TimeoutError"
|
|
83
|
+
? `no response within ${timeoutMs}ms`
|
|
84
|
+
: err instanceof Error
|
|
85
|
+
? err.message
|
|
86
|
+
: String(err);
|
|
87
|
+
return { ...target, ok: false, error };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export async function pingHealthTargets(targets, timeoutMs) {
|
|
91
|
+
return Promise.all(targets.map((target) => pingHealthTarget(target, timeoutMs)));
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=service-urls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-urls.js","sourceRoot":"","sources":["../../src/utils/service-urls.ts"],"names":[],"mappings":"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"}
|
|
@@ -632,7 +632,7 @@ VALIDATION_RULES = {
|
|
|
632
632
|
|
|
633
633
|
'prisma_has_database': {
|
|
634
634
|
'severity': 'warning',
|
|
635
|
-
'condition': lambda m: 'prisma' in m.get('
|
|
635
|
+
'condition': lambda m: 'prisma' in m.get('featuresEnabled', []),
|
|
636
636
|
'check': lambda m: m.get('databaseName'),
|
|
637
637
|
'message': 'Service with prisma feature should specify databaseName',
|
|
638
638
|
},
|
|
@@ -192,14 +192,15 @@ def should_wait_for_dependencies(resource_config):
|
|
|
192
192
|
resources = resource_config.get('resources', [])
|
|
193
193
|
for res in resources:
|
|
194
194
|
manifest = res.get('_manifest', {})
|
|
195
|
-
|
|
195
|
+
features = manifest.get('featuresEnabled', [])
|
|
196
|
+
if manifest.get('databaseName') or features.count('prisma') > 0:
|
|
196
197
|
dependencies.append('database-management')
|
|
197
198
|
break
|
|
198
|
-
|
|
199
|
+
|
|
199
200
|
# Check for NATS dependency
|
|
200
201
|
for res in resources:
|
|
201
202
|
manifest = res.get('_manifest', {})
|
|
202
|
-
if 'nats' in manifest.get('
|
|
203
|
+
if 'nats' in manifest.get('featuresEnabled', []):
|
|
203
204
|
dependencies.append('messaging')
|
|
204
205
|
break
|
|
205
206
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://
|
|
2
|
+
"$id": "https://tdk-landscape.github.io/schema.service.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"allOf": [
|
|
@@ -50,14 +50,14 @@
|
|
|
50
50
|
},
|
|
51
51
|
"then": {
|
|
52
52
|
"properties": {
|
|
53
|
-
"
|
|
53
|
+
"featuresEnabled": {
|
|
54
54
|
"contains": {
|
|
55
55
|
"const": "nats"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"required": [
|
|
60
|
-
"
|
|
60
|
+
"featuresEnabled"
|
|
61
61
|
]
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
],
|
|
181
181
|
"type": "object"
|
|
182
182
|
},
|
|
183
|
-
"
|
|
183
|
+
"featuresEnabled": {
|
|
184
184
|
"description": "Platform features:\n• nats - NATS JetStream eventing\n• prisma - DB ORM & migrations\n• redis - Caching layer\n• infisical - Secrets management\n• vitest - Test framework\n• traefik - Reverse proxy routing\n• websocket - WS support\n• graphql - GraphQL server\n• grpc - gRPC protocol",
|
|
185
185
|
"items": {
|
|
186
186
|
"enum": [
|
|
@@ -215,7 +215,7 @@
|
|
|
215
215
|
"uniqueItems": true
|
|
216
216
|
},
|
|
217
217
|
"nats": {
|
|
218
|
-
"description": "Custom NATS configuration. Auto-generated if 'nats' in
|
|
218
|
+
"description": "Custom NATS configuration. Auto-generated if 'nats' in featuresEnabled.",
|
|
219
219
|
"properties": {
|
|
220
220
|
"queueGroup": {
|
|
221
221
|
"description": "NATS queue group for load balancing",
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
# === INLINED CONSTANTS for pure extension loading ===
|
|
9
9
|
BASE_PORT_FRONTEND = 3000
|
|
10
10
|
BASE_PORT_BACKEND = 4000
|
|
11
|
+
# The nginx frontend runtime layer always serves on 80: L4_generate_frontend_runtime
|
|
12
|
+
# emits a fixed `EXPOSE 80` and the generated nginx.conf has a fixed `listen 80`.
|
|
13
|
+
NGINX_CONTAINER_PORT = 80
|
|
11
14
|
HEALTH_CHECK_PATH = "/health"
|
|
12
15
|
OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
|
|
13
16
|
# === END INLINED CONSTANTS ===
|
|
@@ -15,7 +18,7 @@ OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
|
|
|
15
18
|
|
|
16
19
|
load("./compose_helpers.star", "with_runtime_image_tag", "compose_build_config")
|
|
17
20
|
load("../networking/traefik.star", "get_frontend_traefik_labels", "get_backend_traefik_labels")
|
|
18
|
-
load("../networking/api_path_constants.star", "get_api_path_for_domain")
|
|
21
|
+
load("../networking/api_path_constants.star", "get_api_path_for_domain", "get_frontend_base_path")
|
|
19
22
|
load("../secrets/infisical.star", "get_infisical_environment_vars")
|
|
20
23
|
load("../auth/auth.star", "AuthConfig")
|
|
21
24
|
|
|
@@ -76,11 +79,17 @@ def generate_frontend_compose(resource_path, resource_name, res, manifest=None):
|
|
|
76
79
|
|
|
77
80
|
target_path = res.get('target_path', manifest.get('target_path', '/usr/share/nginx/html'))
|
|
78
81
|
use_nginx = 'nginx' in target_path or target_path == '/usr/share/nginx/html'
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#
|
|
83
|
-
|
|
82
|
+
# The manifest `port` is the Vite dev-server port. These compose entries always
|
|
83
|
+
# build the `production` target, so for an nginx runtime the port inside the
|
|
84
|
+
# container is 80, not the dev port. Using the dev port here made the container
|
|
85
|
+
# healthcheck probe a port nothing listens on, which left the container marked
|
|
86
|
+
# unhealthy -- and Traefik's Docker provider skips unhealthy containers, so the
|
|
87
|
+
# router was never registered and every request 404'd.
|
|
88
|
+
port = NGINX_CONTAINER_PORT if use_nginx else manifest.get('port', BASE_PORT_FRONTEND)
|
|
89
|
+
|
|
90
|
+
# Base path for routing on {project}.localhost. Shared with the Vite config and
|
|
91
|
+
# PUBLIC_URL generators -- see get_frontend_base_path for why they must agree.
|
|
92
|
+
base_path = get_frontend_base_path(manifest, res_name)
|
|
84
93
|
|
|
85
94
|
traefik_cfg = manifest.get('traefik', {}) if manifest else {}
|
|
86
95
|
traefik_host = traefik_cfg.get('host')
|
|
@@ -232,15 +241,33 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
232
241
|
# All instances share the same Traefik service for load balancing
|
|
233
242
|
traefik_resource_name = res_name # LB service name (shared)
|
|
234
243
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
+
# Workers have no HTTP server: they consume from NATS and exit-on-crash. Giving
|
|
245
|
+
# them a Traefik route advertises an endpoint that can never answer, and giving
|
|
246
|
+
# them an HTTP healthcheck marks a perfectly healthy worker as unhealthy forever.
|
|
247
|
+
is_worker = manifest.get('appType') == 'worker' if manifest else False
|
|
248
|
+
|
|
249
|
+
if is_worker:
|
|
250
|
+
traefik_labels = ' - "traefik.enable=false"'
|
|
251
|
+
healthcheck_section = ""
|
|
252
|
+
else:
|
|
253
|
+
traefik_labels = get_backend_traefik_labels(
|
|
254
|
+
resource_entry_name=resource_entry_name,
|
|
255
|
+
traefik_host=traefik_host,
|
|
256
|
+
traefik_path=traefik_path,
|
|
257
|
+
traefik_resource_name=traefik_resource_name,
|
|
258
|
+
internal_port=internal_port,
|
|
259
|
+
health_path=health_path,
|
|
260
|
+
manifest=manifest,
|
|
261
|
+
)
|
|
262
|
+
healthcheck_section = """ healthcheck:
|
|
263
|
+
test: ["CMD", "curl", "-f", "--max-time", "5", "http://localhost:{internal_port}{health_path}"]
|
|
264
|
+
interval: 10s
|
|
265
|
+
timeout: 5s
|
|
266
|
+
retries: 6
|
|
267
|
+
# STORY 4 FIX: 30s grace period to prevent 504 errors during startup
|
|
268
|
+
# Services get 30s to initialize before health checks begin
|
|
269
|
+
start_period: 30s
|
|
270
|
+
""".format(internal_port=internal_port, health_path=health_path)
|
|
244
271
|
infisical_env = get_infisical_environment_vars(as_array=True, resource_name=res_name)
|
|
245
272
|
|
|
246
273
|
# Build auth environment variables section using centralized auth utilities
|
|
@@ -271,15 +298,7 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
271
298
|
- {network_backend}
|
|
272
299
|
- {network_database}
|
|
273
300
|
- {network_infisical}
|
|
274
|
-
|
|
275
|
-
test: ["CMD", "curl", "-f", "--max-time", "5", "http://localhost:{internal_port}{health_path}"]
|
|
276
|
-
interval: 10s
|
|
277
|
-
timeout: 5s
|
|
278
|
-
retries: 6
|
|
279
|
-
# STORY 4 FIX: 30s grace period to prevent 504 errors during startup
|
|
280
|
-
# Services get 30s to initialize before health checks begin
|
|
281
|
-
start_period: 30s
|
|
282
|
-
# Note: depends_on removed - Tilt manages service startup order
|
|
301
|
+
{healthcheck_section} # Note: depends_on removed - Tilt manages service startup order
|
|
283
302
|
deploy:
|
|
284
303
|
resources:
|
|
285
304
|
limits:
|
|
@@ -311,6 +330,7 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
311
330
|
nats_queue=nats_queue,
|
|
312
331
|
nats_url=nats_url,
|
|
313
332
|
traefik_labels=traefik_labels,
|
|
333
|
+
healthcheck_section=healthcheck_section,
|
|
314
334
|
infisical_env=infisical_env,
|
|
315
335
|
auth_env=auth_env,
|
|
316
336
|
network_traefik_public=PlatformDockerConstants.NETWORK_TRAEFIK_PUBLIC,
|
|
@@ -37,6 +37,9 @@ APP_LOCAL_DOMAIN = APP_SUBDOMAIN_PREFIX + "." + LOCAL_DOMAIN
|
|
|
37
37
|
API_LOCAL_DOMAIN = API_SUBDOMAIN_PREFIX + "." + LOCAL_DOMAIN
|
|
38
38
|
EMAIL_DOMAIN = PROJECT_NAME_HYPHEN + ".local"
|
|
39
39
|
|
|
40
|
+
# Published resource-config JSON Schema, same for every project (not project-specific like EMAIL_DOMAIN)
|
|
41
|
+
RESOURCE_SCHEMA_URL = "https://tdk-landscape.github.io/schema.service.json"
|
|
42
|
+
|
|
40
43
|
# NPM scope constant
|
|
41
44
|
NPM_SCOPE = "@" + PROJECT_NAME_HYPHEN + "/"
|
|
42
45
|
|
|
@@ -129,6 +132,7 @@ PlatformDockerConstants = struct(
|
|
|
129
132
|
APP_LOCAL_DOMAIN = APP_LOCAL_DOMAIN,
|
|
130
133
|
API_LOCAL_DOMAIN = API_LOCAL_DOMAIN,
|
|
131
134
|
EMAIL_DOMAIN = EMAIL_DOMAIN,
|
|
135
|
+
RESOURCE_SCHEMA_URL = RESOURCE_SCHEMA_URL,
|
|
132
136
|
NPM_SCOPE = NPM_SCOPE,
|
|
133
137
|
BUN_VERSION = BUN_VERSION,
|
|
134
138
|
BUN_IMAGE = BUN_IMAGE,
|
|
@@ -54,10 +54,10 @@ GOLDEN_L4_FRONTEND_IMAGE = _docker_cfg.get('golden_l4_frontend_image', _GOLDEN_P
|
|
|
54
54
|
_docker_health = get_docker_healthcheck_config()
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def L4_generate_backend_runtime(res_path, port = BASE_PORT_BACKEND, cmd = 'bun run start', use_infisical = True, resource_name = 'service', use_golden = True, has_prisma_config = False, manifest = None):
|
|
57
|
+
def L4_generate_backend_runtime(res_path, port = BASE_PORT_BACKEND, cmd = 'bun run start', use_infisical = True, resource_name = 'service', use_golden = True, use_prisma = True, has_prisma_config = False, manifest = None):
|
|
58
58
|
"""
|
|
59
59
|
Generate the backend runtime layer for deployed services.
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
Args:
|
|
62
62
|
res_path: Resource path (service directory)
|
|
63
63
|
port: Port to expose (default: BASE_PORT_BACKEND from master config)
|
|
@@ -65,7 +65,11 @@ def L4_generate_backend_runtime(res_path, port = BASE_PORT_BACKEND, cmd = 'bun r
|
|
|
65
65
|
use_infisical: Enable Infisical secret management (default: True)
|
|
66
66
|
resource_name: Service name for logging and configuration (default: 'service')
|
|
67
67
|
use_golden: Whether to use golden L4-backend image (default: True)
|
|
68
|
-
|
|
68
|
+
use_prisma: Whether the service has the "prisma" feature enabled - gates copying
|
|
69
|
+
the .prisma/prisma dirs from the L3 build stage, which only exist there when
|
|
70
|
+
L3 itself ran prisma generate (default: True)
|
|
71
|
+
has_prisma_config: Whether service has a generated prisma.config.ts to copy in
|
|
72
|
+
(default: False)
|
|
69
73
|
manifest: Service manifest dict for feature detection (default: None)
|
|
70
74
|
|
|
71
75
|
Returns:
|
|
@@ -88,16 +92,16 @@ def L4_generate_backend_runtime(res_path, port = BASE_PORT_BACKEND, cmd = 'bun r
|
|
|
88
92
|
# Bun isolated linker creates service symlinks that resolve through /app/node_modules/.bun.
|
|
89
93
|
# Copy from production_purger to avoid dragging full dev dependency store into runtime.
|
|
90
94
|
"COPY --from=l3_production_purger /app/" + res_path + "/node_modules/.bun /app/node_modules/.bun\n",
|
|
91
|
-
prisma_runtime_copy(res_path),
|
|
95
|
+
prisma_runtime_copy(res_path) if use_prisma else "",
|
|
92
96
|
"COPY --from=l3_backend_build /app/" + res_path + "/dist ./dist\n",
|
|
93
97
|
"COPY " + res_path_rel + "/package.json ./package.json\n",
|
|
94
|
-
prisma_bun_symlink_fix(),
|
|
98
|
+
prisma_bun_symlink_fix() if use_prisma else "",
|
|
95
99
|
bun_hoisted_packages_symlink_fix(),
|
|
96
100
|
]
|
|
97
101
|
|
|
98
102
|
# Check for special tooling requirements from manifest features
|
|
99
103
|
# Services requiring Hugo (or other tools) should specify in manifest features array
|
|
100
|
-
manifest_features = manifest.get('
|
|
104
|
+
manifest_features = manifest.get('featuresEnabled', []) if manifest else []
|
|
101
105
|
if "hugo" in manifest_features:
|
|
102
106
|
parts.append("USER root\n")
|
|
103
107
|
parts.append("RUN apk add --no-cache hugo\n")
|
|
@@ -115,6 +115,36 @@ def get_api_path_for_stack(stack, manifest=None):
|
|
|
115
115
|
# Backwards compatibility alias
|
|
116
116
|
get_api_path_for_domain = get_api_path_for_stack
|
|
117
117
|
|
|
118
|
+
|
|
119
|
+
def get_frontend_base_path(manifest, res_name=None):
|
|
120
|
+
"""Returns the public route a frontend is served under, e.g. "/floor-app".
|
|
121
|
+
|
|
122
|
+
Single source of truth. Three separate generators need this value and they
|
|
123
|
+
MUST agree, because they are three halves of one route:
|
|
124
|
+
|
|
125
|
+
- compose.star -> the Traefik router rule and its stripprefix middleware
|
|
126
|
+
- vite/frontend -> `base`, which prefixes every asset URL in index.html
|
|
127
|
+
- env.star -> PUBLIC_URL
|
|
128
|
+
|
|
129
|
+
If they disagree the app still "starts" but serves a white page: Traefik
|
|
130
|
+
routes /a, index.html asks for /b/assets/*, and nothing answers. They used
|
|
131
|
+
to default to `stack + 's'`, `'/' + stack + 's'` and `'/' + stack`
|
|
132
|
+
respectively -- three different answers, one of which ("operations" + "s")
|
|
133
|
+
produced the nonsense route /operationss.
|
|
134
|
+
|
|
135
|
+
Defaults to the service name: it is unique per service (a stack with two
|
|
136
|
+
frontends would otherwise collide on one route) and it matches the URL
|
|
137
|
+
`tdk up` prints, so what the CLI advertises is what Traefik serves.
|
|
138
|
+
"""
|
|
139
|
+
base_path = manifest.get('basePath') if manifest else None
|
|
140
|
+
if not base_path:
|
|
141
|
+
base_path = res_name or (manifest.get('appName') if manifest else None) or ''
|
|
142
|
+
if not base_path:
|
|
143
|
+
return ''
|
|
144
|
+
if not base_path.startswith('/'):
|
|
145
|
+
base_path = '/' + base_path
|
|
146
|
+
return base_path
|
|
147
|
+
|
|
118
148
|
def get_api_path_for_resource(resource_name):
|
|
119
149
|
"""Returns the full API path for a resource name.
|
|
120
150
|
|
|
@@ -60,7 +60,7 @@ def get_frontend_traefik_labels(res_name, domain, base_path, port, traefik_host=
|
|
|
60
60
|
middleware_name = res_name + TRAEFIK_MIDDLEWARE_SUFFIX
|
|
61
61
|
|
|
62
62
|
# Check if maintenance feature is enabled
|
|
63
|
-
features = manifest.get('
|
|
63
|
+
features = manifest.get('featuresEnabled', []) if manifest else []
|
|
64
64
|
maintenance_middleware = ""
|
|
65
65
|
if 'maintenance' in features:
|
|
66
66
|
maintenance_middleware = ",maintenance@file"
|
|
@@ -112,7 +112,7 @@ def get_backend_traefik_labels(
|
|
|
112
112
|
middleware_name = resource_entry_name + TRAEFIK_MIDDLEWARE_SUFFIX
|
|
113
113
|
|
|
114
114
|
# Check if maintenance feature is enabled
|
|
115
|
-
features = manifest.get('
|
|
115
|
+
features = manifest.get('featuresEnabled', []) if manifest else []
|
|
116
116
|
maintenance_middleware = ""
|
|
117
117
|
if 'maintenance' in features:
|
|
118
118
|
maintenance_middleware = ",maintenance@file"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# AGENTS.md - Networking & Proxy
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Traefik reverse proxy, networking configuration, and service routing.
|
|
6
|
+
|
|
7
|
+
## Key Files
|
|
8
|
+
|
|
9
|
+
### Traefik
|
|
10
|
+
- **`proxy.star`** - Traefik configuration generation
|
|
11
|
+
- **`labels.star`** - Docker labels for Traefik routing
|
|
12
|
+
|
|
13
|
+
### Network Management
|
|
14
|
+
- **`network.star`** - Docker network setup
|
|
15
|
+
- **`hosts.star`** - /etc/hosts file management
|
|
16
|
+
|
|
17
|
+
## Common Tasks
|
|
18
|
+
|
|
19
|
+
### Generate Traefik labels
|
|
20
|
+
```starlark
|
|
21
|
+
load("./proxy.star", "generate_traefik_labels")
|
|
22
|
+
labels = generate_traefik_labels(
|
|
23
|
+
resource_name="order-management-backend",
|
|
24
|
+
port=4000,
|
|
25
|
+
host="order.backend.{project}.localhost",
|
|
26
|
+
path_prefix="/api/orders"
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Output
|
|
31
|
+
```python
|
|
32
|
+
{
|
|
33
|
+
"traefik.enable": "true",
|
|
34
|
+
"traefik.http.routers.order-management-backend.rule": "Host(`order.backend.{project}.localhost`) && PathPrefix(`/api/orders`)",
|
|
35
|
+
"traefik.http.routers.order-management-backend.entrypoints": "web",
|
|
36
|
+
"traefik.http.services.order-management-backend.loadbalancer.server.port": "4000"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Internet/localhost
|
|
44
|
+
↓
|
|
45
|
+
Traefik (port 80/443)
|
|
46
|
+
↓
|
|
47
|
+
Routes to services by Host/Path
|
|
48
|
+
↓
|
|
49
|
+
Service containers
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Integration
|
|
53
|
+
|
|
54
|
+
- Called by `resources/` when creating service resources
|
|
55
|
+
- Used by `platform/docker/` for Docker Compose generation
|
|
56
|
+
- Labels applied to docker_build and docker_compose
|
|
57
|
+
|
|
58
|
+
## Local Development
|
|
59
|
+
|
|
60
|
+
Services accessible at:
|
|
61
|
+
- `{service}.backend.{project}.localhost`
|
|
62
|
+
- `{service}.frontend.{project}.localhost`
|
|
63
|
+
|
|
64
|
+
Managed in `/etc/hosts` automatically.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# AGENTS.md - Security & Secrets
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Secrets management, Infisical integration, and security configuration.
|
|
6
|
+
|
|
7
|
+
## Key Files
|
|
8
|
+
|
|
9
|
+
### Secrets Management
|
|
10
|
+
- **`infisical.star`** - Infisical secrets provider integration
|
|
11
|
+
- **`secrets.star`** - Generic secrets handling
|
|
12
|
+
- **`encryption.star`** - Encryption utilities
|
|
13
|
+
|
|
14
|
+
### Configuration
|
|
15
|
+
- **`tokens.star`** - Token generation and validation
|
|
16
|
+
- **`keys.star`** - Key management
|
|
17
|
+
|
|
18
|
+
## Common Tasks
|
|
19
|
+
|
|
20
|
+
### Load secrets from Infisical
|
|
21
|
+
```starlark
|
|
22
|
+
load("./infisical.star", "Infisical")
|
|
23
|
+
secrets = Infisical.load_secrets(resource_name, environment)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Get database credentials
|
|
27
|
+
```starlark
|
|
28
|
+
load("./infisical.star", "Infisical")
|
|
29
|
+
db_url = Infisical.get_database_url(resource_name)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Integration
|
|
33
|
+
|
|
34
|
+
- Called by `resources/` when setting up service environment
|
|
35
|
+
- Used in Docker Compose for injecting secrets
|
|
36
|
+
- Part of service startup chain
|
|
37
|
+
|
|
38
|
+
## Local Development
|
|
39
|
+
|
|
40
|
+
Without Infisical, falls back to:
|
|
41
|
+
- Environment variables
|
|
42
|
+
- Default values in `spec.master`
|
|
43
|
+
|
|
44
|
+
## Security Notes
|
|
45
|
+
|
|
46
|
+
- Never commit real secrets
|
|
47
|
+
- Use Infisical for production
|
|
48
|
+
- Local development uses safe defaults
|
|
49
|
+
- Secrets injected at runtime, not in images
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# AGENTS.md - Database & State Management
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
PostgreSQL database virtualization, per-service database provisioning, and state management.
|
|
6
|
+
|
|
7
|
+
## Key Files
|
|
8
|
+
|
|
9
|
+
### Database Provisioning
|
|
10
|
+
- **`relational.star`** - Main database provisioning logic
|
|
11
|
+
- **`compose.star`** - Docker Compose entries for databases
|
|
12
|
+
|
|
13
|
+
### Prisma Integration
|
|
14
|
+
- **`prisma/migrate_script.star`** - Migration script generation
|
|
15
|
+
- **`prisma/db_readiness.star`** - Database readiness checks
|
|
16
|
+
- **`prisma/prisma_build.star`** - Prisma in Docker builds
|
|
17
|
+
- **`prisma/prisma_runtime.star`** - Prisma client in runtime
|
|
18
|
+
|
|
19
|
+
### Utilities
|
|
20
|
+
- **`utils.star`** - Database utilities
|
|
21
|
+
- **`constants.star`** - Database constants (ports, users)
|
|
22
|
+
|
|
23
|
+
## Common Tasks
|
|
24
|
+
|
|
25
|
+
### Provision database for service
|
|
26
|
+
```starlark
|
|
27
|
+
load("./relational.star", "provision_database")
|
|
28
|
+
compose_entry = provision_database("order-management-backend", "{project}_order")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Get database URL
|
|
32
|
+
```starlark
|
|
33
|
+
load("./relational.star", "get_database_url")
|
|
34
|
+
url = get_database_url("order-management-backend")
|
|
35
|
+
# Returns: postgresql://postgres:postgres@order-management-backend-db:5432/{project}_order
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Wait for database
|
|
39
|
+
```starlark
|
|
40
|
+
load("./prisma/db_readiness.star", "wait_for_db")
|
|
41
|
+
script = wait_for_db(db_url, timeout=30)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Architecture
|
|
45
|
+
|
|
46
|
+
Each service with `databaseName` in manifest gets:
|
|
47
|
+
1. PostgreSQL container in Docker Compose
|
|
48
|
+
2. Volume for persistence
|
|
49
|
+
3. Readiness check before migrations
|
|
50
|
+
4. Dedicated port mapping (avoids conflicts)
|
|
51
|
+
|
|
52
|
+
## Integration
|
|
53
|
+
|
|
54
|
+
- Called by `resources/deps.star` - when setting up service dependencies
|
|
55
|
+
- Used by `platform/docker/` - for Docker Compose generation
|
|
56
|
+
- Part of service resource dependencies chain
|
|
57
|
+
|
|
58
|
+
## Database Naming
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
{project}_{domain} // e.g., {project}_order, {project}_user
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Each domain gets one database shared by its services (backend, migrator, etc).
|
|
@@ -45,7 +45,7 @@ def generate_backend(manifest, write_fn=None):
|
|
|
45
45
|
|
|
46
46
|
# 🔥 SMART DETECTION: Check if service uses vite-plugin-node for HMR
|
|
47
47
|
use_vite_node = manifest.get('useViteNode', False)
|
|
48
|
-
features = manifest.get('
|
|
48
|
+
features = manifest.get('featuresEnabled', [])
|
|
49
49
|
if 'vite-node' in features or 'vite-plugin-node' in features or 'hmr' in features:
|
|
50
50
|
use_vite_node = True
|
|
51
51
|
|