@tdk-landscape/tdk-cli-core 1.3.51 → 1.3.52
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/.github/workflows/ci.yml +13 -0
- package/.github/workflows/release-binaries.yml +76 -1
- package/CHANGELOG.md +50 -0
- package/README.md +52 -17
- package/cli/README.md +16 -43
- package/cli/biome.json +0 -4
- package/cli/dist/commands/help.d.ts.map +1 -1
- package/cli/dist/commands/help.js +22 -39
- package/cli/dist/commands/help.js.map +1 -1
- package/cli/dist/commands/project.d.ts +6 -0
- package/cli/dist/commands/project.d.ts.map +1 -1
- package/cli/dist/commands/project.js +16 -0
- package/cli/dist/commands/project.js.map +1 -1
- package/cli/dist/commands/up.d.ts.map +1 -1
- package/cli/dist/commands/up.js +5 -0
- package/cli/dist/commands/up.js.map +1 -1
- package/cli/dist/commands/upgrade.d.ts +3 -0
- package/cli/dist/commands/upgrade.d.ts.map +1 -1
- package/cli/dist/commands/upgrade.js +43 -1
- package/cli/dist/commands/upgrade.js.map +1 -1
- package/cli/dist/utils/constants.d.ts +1 -1
- package/cli/dist/utils/constants.d.ts.map +1 -1
- package/cli/dist/utils/constants.js +1 -1
- package/cli/dist/utils/constants.js.map +1 -1
- package/cli/dist/utils/doctor-runtime.js +5 -5
- package/cli/dist/utils/doctor-runtime.js.map +1 -1
- package/cli/dist/utils/prompt.d.ts.map +1 -1
- package/cli/dist/utils/prompt.js +6 -1
- package/cli/dist/utils/prompt.js.map +1 -1
- package/cli/dist/utils/service-urls.js +1 -1
- package/cli/dist/utils/service-urls.js.map +1 -1
- package/cli/package.json +2 -1
- package/cli/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +2 -2
- package/cli/src/commands/__tests__/doctor.test.ts +8 -8
- package/cli/src/commands/__tests__/upgrade.test.ts +90 -3
- package/cli/src/commands/help.ts +22 -39
- package/cli/src/commands/project.ts +20 -0
- package/cli/src/commands/up.ts +10 -0
- package/cli/src/commands/upgrade.ts +52 -1
- package/cli/src/utils/constants.ts +1 -1
- package/cli/src/utils/doctor-runtime.ts +5 -5
- package/cli/src/utils/prompt.ts +8 -1
- package/cli/src/utils/service-urls.ts +1 -1
- package/discovery/identity-management-backend/service.yaml +2 -2
- package/discovery/identity-management-frontend/service.yaml +1 -1
- package/discovery/registry.star +3 -1
- package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +2 -2
- package/engine/assets/docker/golden-layers.Dockerfile +18 -18
- package/engine/assets/docker/wake-gateway/decide.test.ts +25 -0
- package/engine/assets/docker/wake-gateway/decide.ts +19 -0
- package/engine/assets/docker/wake-gateway/server.ts +60 -14
- package/engine/assets/scripts/mac-m1-resource-monitor.sh +1 -1
- package/engine/scripts/debug-container.sh +8 -3
- package/engine/spec.master +2 -2
- package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +1 -1
- package/engine/topologies/tilt/common/utils.star +1 -1
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +53 -11
- package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +20 -0
- package/install.sh +19 -242
- package/openspec/changes/prioritized-cold-start/design.md +5 -1
- package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +9 -3
- package/openspec/changes/prioritized-cold-start/tasks.md +13 -0
- package/package.json +1 -1
- package/platform/services/platform/proxy/service.yaml +1 -1
- package/scripts/release-binaries.sh +8 -5
- package/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +2 -2
- package/tests/tilt-engine/test_sablier_defer_start.py +41 -0
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/codebase-audit-report.md +0 -62
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/.openspec.yaml +0 -5
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/design.md +0 -94
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/proposal.md +0 -46
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CLEANUP_REPORT_8.md +0 -174
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CRITICAL_ASSESSMENT_8.md +0 -160
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/circular-deps/CRITICAL_ASSESSMENT_4.md +0 -381
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/dead-code/CRITICAL_ASSESSMENT_3.md +0 -316
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/CRITICAL_ASSESSMENT_1.md +0 -290
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/IMPLEMENTATION_SUMMARY_1.md +0 -233
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/defensive-cleanup/CRITICAL_ASSESSMENT_6.md +0 -835
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/CRITICAL_ASSESSMENT_7.md +0 -254
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/IMPLEMENTATION_SUMMARY_7.md +0 -187
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/strong-typing/CRITICAL_ASSESSMENT_5.md +0 -269
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/CRITICAL_ASSESSMENT_2.md +0 -312
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/IMPLEMENTATION_SUMMARY.md +0 -170
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/tasks.md +0 -137
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# 2. Or update service.json
|
|
8
8
|
#
|
|
9
9
|
# Generation Source: GoldenImage.generate_layered_dockerfile()
|
|
10
|
-
# Service:
|
|
10
|
+
# Service: tdk-project-l1-l4
|
|
11
11
|
# Type: GOLDEN LAYERED IMAGES | L1-L4 Pre-built Layers
|
|
12
12
|
###############################################################################
|
|
13
13
|
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
# 🏗️ GOLDEN LAYERED IMAGES - Multi-Stage Base Images
|
|
16
16
|
# =============================================================================
|
|
17
17
|
# This Dockerfile builds 8 golden images:
|
|
18
|
-
# -
|
|
19
|
-
# -
|
|
20
|
-
# -
|
|
21
|
-
# -
|
|
22
|
-
# -
|
|
23
|
-
# -
|
|
24
|
-
# -
|
|
25
|
-
# -
|
|
18
|
+
# - tdk-project-l1:latest - OS base + Bun runtime
|
|
19
|
+
# - tdk-project-l2:latest - Dependencies installed
|
|
20
|
+
# - tdk-project-l3-backend:latest - Backend build tools (Prisma, no Infisical CLI)
|
|
21
|
+
# - tdk-project-l3-frontend:latest - Frontend build tools (no Prisma)
|
|
22
|
+
# - tdk-project-l3-migrator:latest - Migrator build tools (Prisma, no Infisical CLI)
|
|
23
|
+
# - tdk-project-l4-backend:latest - Backend production runtime
|
|
24
|
+
# - tdk-project-l4-frontend:latest - Frontend production runtime (Nginx)
|
|
25
|
+
# - tdk-project-l4-migrator:latest - Migrator production runtime
|
|
26
26
|
#
|
|
27
27
|
# Services use these as base images to skip heavy dependency installation.
|
|
28
28
|
#
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
# =============================================================================
|
|
39
39
|
FROM oven/bun:1.3.11-alpine AS l1_golden
|
|
40
40
|
|
|
41
|
-
LABEL layer="l1" description="OS base with runtime environment" maintainer="
|
|
41
|
+
LABEL layer="l1" description="OS base with runtime environment" maintainer="tdk-project"
|
|
42
42
|
|
|
43
43
|
# System dependencies for all services
|
|
44
44
|
RUN apk add --no-cache ca-certificates tzdata curl file bash openssl
|
|
@@ -57,7 +57,7 @@ WORKDIR /app
|
|
|
57
57
|
# =============================================================================
|
|
58
58
|
FROM l1_golden AS l2_golden
|
|
59
59
|
|
|
60
|
-
LABEL layer="l2" description="Base with common dependencies installed" maintainer="
|
|
60
|
+
LABEL layer="l2" description="Base with common dependencies installed" maintainer="tdk-project"
|
|
61
61
|
|
|
62
62
|
# Install minimal debugging tools (removed: vim, python3, make, g++ = -67MB)
|
|
63
63
|
RUN apk add --no-cache curl netcat-openbsd jq bind-tools
|
|
@@ -74,7 +74,7 @@ RUN echo "Bun version: $(bun --version)"
|
|
|
74
74
|
# =============================================================================
|
|
75
75
|
FROM l2_golden AS l3_backend_golden
|
|
76
76
|
|
|
77
|
-
LABEL layer="l3-backend" description="Backend build tools with Prisma" maintainer="
|
|
77
|
+
LABEL layer="l3-backend" description="Backend build tools with Prisma" maintainer="tdk-project"
|
|
78
78
|
|
|
79
79
|
# Install Prisma CLI globally (pinned for deterministic builds)
|
|
80
80
|
RUN bun add -g prisma@7.5.0 @prisma/client@7.5.0
|
|
@@ -94,7 +94,7 @@ RUN echo "Prisma version: $(bunx prisma --version | head -1)"
|
|
|
94
94
|
# =============================================================================
|
|
95
95
|
FROM l2_golden AS l3_frontend_golden
|
|
96
96
|
|
|
97
|
-
LABEL layer="l3-frontend" description="Frontend build tools without Prisma" maintainer="
|
|
97
|
+
LABEL layer="l3-frontend" description="Frontend build tools without Prisma" maintainer="tdk-project"
|
|
98
98
|
|
|
99
99
|
# Frontend doesn't need Prisma, just verify Bun
|
|
100
100
|
RUN echo "Bun version: $(bun --version)"
|
|
@@ -105,7 +105,7 @@ RUN echo "Bun version: $(bun --version)"
|
|
|
105
105
|
# =============================================================================
|
|
106
106
|
FROM l2_golden AS l3_migrator_golden
|
|
107
107
|
|
|
108
|
-
LABEL layer="l3-migrator" description="Migrator build tools with Prisma" maintainer="
|
|
108
|
+
LABEL layer="l3-migrator" description="Migrator build tools with Prisma" maintainer="tdk-project"
|
|
109
109
|
|
|
110
110
|
# Install Prisma CLI globally (required for migrations, pinned)
|
|
111
111
|
RUN bun add -g prisma@7.5.0 @prisma/client@7.5.0
|
|
@@ -125,7 +125,7 @@ RUN echo "Prisma version: $(bunx prisma --version | head -1)"
|
|
|
125
125
|
# =============================================================================
|
|
126
126
|
FROM l1_golden AS l4_backend_bun
|
|
127
127
|
|
|
128
|
-
LABEL layer="l4-backend-bun" description="Backend production runtime (Bun)" maintainer="
|
|
128
|
+
LABEL layer="l4-backend-bun" description="Backend production runtime (Bun)" maintainer="tdk-project"
|
|
129
129
|
|
|
130
130
|
ENV NODE_ENV=production
|
|
131
131
|
|
|
@@ -150,7 +150,7 @@ WORKDIR /app
|
|
|
150
150
|
# =============================================================================
|
|
151
151
|
FROM node:22-alpine AS l4_backend_node
|
|
152
152
|
|
|
153
|
-
LABEL layer="l4-backend-node" description="Backend production runtime (Node.js - lightweight)" maintainer="
|
|
153
|
+
LABEL layer="l4-backend-node" description="Backend production runtime (Node.js - lightweight)" maintainer="tdk-project"
|
|
154
154
|
|
|
155
155
|
ENV NODE_ENV=production
|
|
156
156
|
|
|
@@ -180,7 +180,7 @@ FROM l4_backend_bun AS l4_backend_golden
|
|
|
180
180
|
# =============================================================================
|
|
181
181
|
FROM nginx:alpine AS l4_frontend_golden
|
|
182
182
|
|
|
183
|
-
LABEL layer="l4-frontend" description="Frontend production runtime with Nginx" maintainer="
|
|
183
|
+
LABEL layer="l4-frontend" description="Frontend production runtime with Nginx" maintainer="tdk-project"
|
|
184
184
|
|
|
185
185
|
ENV NODE_ENV=production
|
|
186
186
|
|
|
@@ -198,7 +198,7 @@ WORKDIR /usr/share/nginx/html
|
|
|
198
198
|
# =============================================================================
|
|
199
199
|
FROM l1_golden AS l4_migrator_golden
|
|
200
200
|
|
|
201
|
-
LABEL layer="l4-migrator" description="Migrator runtime with Prisma" maintainer="
|
|
201
|
+
LABEL layer="l4-migrator" description="Migrator runtime with Prisma" maintainer="tdk-project"
|
|
202
202
|
|
|
203
203
|
ENV NODE_ENV=production
|
|
204
204
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
COMPOSE_START_TIMEOUT_MS,
|
|
4
|
+
EARLY_RESPONSE_MS,
|
|
5
|
+
MAX_EARLY_RESPONSE_MS,
|
|
6
|
+
RETRY_AFTER_SECONDS,
|
|
4
7
|
TILT_TRIGGER_TIMEOUT_MS,
|
|
5
8
|
decideWakeAction,
|
|
9
|
+
earlyResponseMs,
|
|
6
10
|
evaluateReadiness,
|
|
7
11
|
wakeTimeoutMs,
|
|
8
12
|
} from "./decide";
|
|
@@ -112,6 +116,27 @@ describe("wakeTimeoutMs", () => {
|
|
|
112
116
|
});
|
|
113
117
|
});
|
|
114
118
|
|
|
119
|
+
describe("earlyResponseMs", () => {
|
|
120
|
+
test("holds a waking request for EARLY_RESPONSE_MS by default", () => {
|
|
121
|
+
expect(earlyResponseMs()).toBe(EARLY_RESPONSE_MS);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("the default answers well before Bun's 255s idle cap drops the connection", () => {
|
|
125
|
+
expect(EARLY_RESPONSE_MS).toBeLessThan(255_000);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("an explicit X-Wake-Respond-Within-Ms wins, but is capped under Bun's idle limit", () => {
|
|
129
|
+
expect(earlyResponseMs(5_000)).toBe(5_000);
|
|
130
|
+
expect(earlyResponseMs(600_000)).toBe(MAX_EARLY_RESPONSE_MS);
|
|
131
|
+
expect(MAX_EARLY_RESPONSE_MS).toBeLessThan(255_000);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("tells the caller to retry soon", () => {
|
|
135
|
+
expect(RETRY_AFTER_SECONDS).toBeGreaterThan(0);
|
|
136
|
+
expect(RETRY_AFTER_SECONDS * 1000).toBeLessThan(EARLY_RESPONSE_MS);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
115
140
|
describe("evaluateReadiness", () => {
|
|
116
141
|
test("ready once every required name reports healthy", () => {
|
|
117
142
|
const outcome = evaluateReadiness(["a", "b"], { a: true, b: true });
|
|
@@ -84,6 +84,25 @@ export function wakeTimeoutMs(startedViaCompose: boolean, overrideMs?: number):
|
|
|
84
84
|
return startedViaCompose ? COMPOSE_START_TIMEOUT_MS : TILT_TRIGGER_TIMEOUT_MS;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* How long a waking request is held before the gateway answers
|
|
89
|
+
* `503` + `Retry-After` while the start keeps going in the background.
|
|
90
|
+
* Found live (task 7.2): during a full `tdk up` bring-up the Docker daemon
|
|
91
|
+
* took ~5 min to start one container, and Bun.serve's 255s idle cap dropped
|
|
92
|
+
* the held connection with no response at all.
|
|
93
|
+
*/
|
|
94
|
+
export const EARLY_RESPONSE_MS = 60_000;
|
|
95
|
+
/** Largest hold allowed: stays under Bun.serve's 255s idle cap. */
|
|
96
|
+
export const MAX_EARLY_RESPONSE_MS = 240_000;
|
|
97
|
+
/** The `Retry-After` value sent with the early 503. */
|
|
98
|
+
export const RETRY_AFTER_SECONDS = 10;
|
|
99
|
+
|
|
100
|
+
/** How long to hold a request: an explicit X-Wake-Respond-Within-Ms wins, capped under Bun's idle limit. */
|
|
101
|
+
export function earlyResponseMs(overrideMs?: number): number {
|
|
102
|
+
if (overrideMs && overrideMs > 0) return Math.min(overrideMs, MAX_EARLY_RESPONSE_MS);
|
|
103
|
+
return EARLY_RESPONSE_MS;
|
|
104
|
+
}
|
|
105
|
+
|
|
87
106
|
export interface WaitOutcome {
|
|
88
107
|
ready: boolean;
|
|
89
108
|
/** Names still not running/healthy when the wait ended (empty when ready). */
|
|
@@ -32,9 +32,14 @@
|
|
|
32
32
|
* X-Wake-Image The image the compose service declares
|
|
33
33
|
* (<service>_<resource>:dev).
|
|
34
34
|
* X-Wake-Timeout-Ms, X-Wake-Proxy-Port, X-Wake-Session-Duration
|
|
35
|
+
* X-Wake-Respond-Within-Ms How long to hold the request before answering
|
|
36
|
+
* 503 + Retry-After while the start continues in the
|
|
37
|
+
* background (default decide.ts EARLY_RESPONSE_MS).
|
|
35
38
|
*/
|
|
36
39
|
import {
|
|
40
|
+
RETRY_AFTER_SECONDS,
|
|
37
41
|
decideWakeAction,
|
|
42
|
+
earlyResponseMs,
|
|
38
43
|
evaluateReadiness,
|
|
39
44
|
wakeTimeoutMs,
|
|
40
45
|
type ContainerState,
|
|
@@ -168,6 +173,7 @@ function parseRequest(req: Request) {
|
|
|
168
173
|
const container = req.headers.get("X-Wake-Container") || resource;
|
|
169
174
|
const deps = parseDeps(req.headers.get("X-Wake-Deps") || "");
|
|
170
175
|
const timeoutOverrideMs = Number(req.headers.get("X-Wake-Timeout-Ms")) || undefined;
|
|
176
|
+
const respondWithinOverrideMs = Number(req.headers.get("X-Wake-Respond-Within-Ms")) || undefined;
|
|
171
177
|
const proxyPort = req.headers.get("X-Wake-Proxy-Port") || "80";
|
|
172
178
|
const sessionDuration = req.headers.get("X-Wake-Session-Duration") || "10m";
|
|
173
179
|
const composeFiles = (req.headers.get("X-Wake-Compose-Files") || "").split(",").filter(Boolean);
|
|
@@ -178,16 +184,62 @@ function parseRequest(req: Request) {
|
|
|
178
184
|
image: req.headers.get("X-Wake-Image") || "",
|
|
179
185
|
};
|
|
180
186
|
const hasCompose = Boolean(compose.project && compose.files.length && compose.image);
|
|
181
|
-
return {
|
|
187
|
+
return {
|
|
188
|
+
resource,
|
|
189
|
+
container,
|
|
190
|
+
deps,
|
|
191
|
+
timeoutOverrideMs,
|
|
192
|
+
respondWithinOverrideMs,
|
|
193
|
+
proxyPort,
|
|
194
|
+
sessionDuration,
|
|
195
|
+
compose,
|
|
196
|
+
hasCompose,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
type WakeParams = ReturnType<typeof parseRequest>;
|
|
201
|
+
type WakeOutcome = { ready: true } | { ready: false; timeoutMs: number };
|
|
202
|
+
|
|
203
|
+
// One wake per resource at a time: a caller that got an early 503 and retries,
|
|
204
|
+
// or a second caller arriving meanwhile, joins the wake already in progress
|
|
205
|
+
// instead of starting the container again.
|
|
206
|
+
const inflight = new Map<string, Promise<WakeOutcome>>();
|
|
207
|
+
|
|
208
|
+
function wakeOnce(params: WakeParams): Promise<WakeOutcome> {
|
|
209
|
+
let wake = inflight.get(params.resource);
|
|
210
|
+
if (!wake) {
|
|
211
|
+
wake = runWake(params).finally(() => inflight.delete(params.resource));
|
|
212
|
+
inflight.set(params.resource, wake);
|
|
213
|
+
}
|
|
214
|
+
return wake;
|
|
182
215
|
}
|
|
183
216
|
|
|
184
217
|
async function handleWake(req: Request): Promise<Response> {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
if (!resource) {
|
|
218
|
+
const params = parseRequest(req);
|
|
219
|
+
if (!params.resource) {
|
|
188
220
|
return new Response("wake gateway: missing X-Wake-Resource header (set by the generated Traefik static route)", { status: 400 });
|
|
189
221
|
}
|
|
190
222
|
|
|
223
|
+
const holdMs = earlyResponseMs(params.respondWithinOverrideMs);
|
|
224
|
+
const outcome = await Promise.race([wakeOnce(params), Bun.sleep(holdMs).then(() => null)]);
|
|
225
|
+
if (outcome === null) {
|
|
226
|
+
console.log(`wake ${params.resource}: still starting after ${holdMs}ms, answering 503`);
|
|
227
|
+
return new Response(
|
|
228
|
+
`wake gateway: ${params.resource} is starting; retry in ${RETRY_AFTER_SECONDS}s`,
|
|
229
|
+
{ status: 503, headers: { "Retry-After": String(RETRY_AFTER_SECONDS) } },
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
if (outcome.ready) {
|
|
233
|
+
return proxyThrough(req, params.container, params.proxyPort);
|
|
234
|
+
}
|
|
235
|
+
return new Response(
|
|
236
|
+
`wake gateway: timed out after ${outcome.timeoutMs}ms waiting for ${params.resource} and its dependencies to become healthy`,
|
|
237
|
+
{ status: 504 },
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function runWake(params: WakeParams): Promise<WakeOutcome> {
|
|
242
|
+
const { resource, container, deps, timeoutOverrideMs, sessionDuration, compose, hasCompose } = params;
|
|
191
243
|
const resourceState = await containerState(container);
|
|
192
244
|
const dependencies = await Promise.all(
|
|
193
245
|
deps.map(async (dep) => ({ name: dep.resourceName, state: await containerState(dep.containerName) })),
|
|
@@ -225,8 +277,6 @@ async function handleWake(req: Request): Promise<Response> {
|
|
|
225
277
|
]);
|
|
226
278
|
|
|
227
279
|
const requiredNames = [resource, ...deps.map((d) => d.resourceName)];
|
|
228
|
-
const containerByName: Record<string, string> = { [resource]: container };
|
|
229
|
-
for (const dep of deps) containerByName[dep.resourceName] = dep.containerName;
|
|
230
280
|
|
|
231
281
|
while (Date.now() < deadline) {
|
|
232
282
|
const healthy: Record<string, boolean> = {};
|
|
@@ -236,17 +286,12 @@ async function handleWake(req: Request): Promise<Response> {
|
|
|
236
286
|
for (const dep of deps) {
|
|
237
287
|
healthy[dep.resourceName] = await isHealthy(dep.containerName);
|
|
238
288
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return proxyThrough(req, container, proxyPort);
|
|
289
|
+
if (evaluateReadiness(requiredNames, healthy).ready) {
|
|
290
|
+
return { ready: true };
|
|
242
291
|
}
|
|
243
292
|
await Bun.sleep(POLL_INTERVAL_MS);
|
|
244
293
|
}
|
|
245
|
-
|
|
246
|
-
return new Response(
|
|
247
|
-
`wake gateway: timed out after ${timeoutMs}ms waiting for ${resource} and its dependencies to become healthy`,
|
|
248
|
-
{ status: 504 },
|
|
249
|
-
);
|
|
294
|
+
return { ready: false, timeoutMs };
|
|
250
295
|
}
|
|
251
296
|
|
|
252
297
|
// Headers that must not be forwarded verbatim to the upstream fetch: Host
|
|
@@ -258,6 +303,7 @@ const STRIPPED_REQUEST_HEADERS = new Set([
|
|
|
258
303
|
"x-wake-container",
|
|
259
304
|
"x-wake-deps",
|
|
260
305
|
"x-wake-timeout-ms",
|
|
306
|
+
"x-wake-respond-within-ms",
|
|
261
307
|
"x-wake-proxy-port",
|
|
262
308
|
"x-wake-session-duration",
|
|
263
309
|
"x-wake-compose-project",
|
|
@@ -84,7 +84,7 @@ while IFS= read -r container; do
|
|
|
84
84
|
|
|
85
85
|
printf "%-20s\t%s%%\t%s\t%s\n" "$CONTAINER_DISPLAY" "$CPU_PERCENT" "$MEMORY_USAGE" "$STATUS"
|
|
86
86
|
fi
|
|
87
|
-
done <<< "$
|
|
87
|
+
done <<< "$PROJECT_CONTAINERS"
|
|
88
88
|
|
|
89
89
|
echo ""
|
|
90
90
|
echo -e "${BLUE}📈 Resource Summary:${NC}"
|
|
@@ -13,6 +13,11 @@ YELLOW='\033[1;33m'
|
|
|
13
13
|
BLUE='\033[0;34m'
|
|
14
14
|
NC='\033[0m' # No Color
|
|
15
15
|
|
|
16
|
+
# Container name filter: project name from .tdk/project.json (override with TDK_PROJECT_NAME)
|
|
17
|
+
PROJECT_FILTER="${TDK_PROJECT_NAME:-$(sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' .tdk/project.json 2>/dev/null | head -1)}"
|
|
18
|
+
PROJECT_FILTER="${PROJECT_FILTER:-tdk-project}"
|
|
19
|
+
PROJECT_FILTER="${PROJECT_FILTER//-/[-_]}"
|
|
20
|
+
|
|
16
21
|
# Help text
|
|
17
22
|
show_help() {
|
|
18
23
|
cat << EOF
|
|
@@ -68,11 +73,11 @@ EOF
|
|
|
68
73
|
# List containers
|
|
69
74
|
list_containers() {
|
|
70
75
|
echo -e "${GREEN}=== Running Containers ===${NC}"
|
|
71
|
-
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | grep -
|
|
76
|
+
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | grep -iE "$PROJECT_FILTER"
|
|
72
77
|
|
|
73
78
|
echo ""
|
|
74
79
|
echo -e "${YELLOW}=== Recently Exited Containers ===${NC}"
|
|
75
|
-
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | grep -
|
|
80
|
+
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | grep -iE "$PROJECT_FILTER" | grep -i "exited"
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
# Get container ID from service name
|
|
@@ -122,7 +127,7 @@ run_command() {
|
|
|
122
127
|
# Show failed containers
|
|
123
128
|
show_failed() {
|
|
124
129
|
echo -e "${RED}=== Failed/Exited Containers ===${NC}"
|
|
125
|
-
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | grep -
|
|
130
|
+
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | grep -iE "$PROJECT_FILTER" | grep -iE "(exited|error)"
|
|
126
131
|
|
|
127
132
|
echo ""
|
|
128
133
|
echo -e "${YELLOW}To inspect logs of a failed container:${NC}"
|
package/engine/spec.master
CHANGED
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
|
|
63
63
|
# PROJECT_ROOT is automatically set by Tiltfile based on where you run `tilt up`.
|
|
64
64
|
# You don't need to set it. Example:
|
|
65
|
-
# - If you run in /home/dev/
|
|
65
|
+
# - If you run in /home/dev/tdk-project, PROJECT_ROOT = "/home/dev/tdk-project"
|
|
66
66
|
# - All other paths are built from this root.
|
|
67
67
|
#
|
|
68
68
|
# This file is manually maintained to match the directory structure.
|
|
@@ -563,7 +563,7 @@ FEATURES = struct(
|
|
|
563
563
|
|
|
564
564
|
CONSTANTS = struct(
|
|
565
565
|
# System identity - EXPLICIT
|
|
566
|
-
SYSTEM_NAME = "
|
|
566
|
+
SYSTEM_NAME = "tdk-project-tilt-platform",
|
|
567
567
|
SYSTEM_VERSION = "2.0.0",
|
|
568
568
|
|
|
569
569
|
# Runtimes - EXPLICIT supported options
|
|
@@ -110,7 +110,7 @@ def L3_generate_migration_engine(res_path, use_golden = True):
|
|
|
110
110
|
+ "COPY " + res_path_rel + "/" + OUTPUT.AUTOGENERATED_FOLDER + "/bunfig.docker.autogenerated.toml ./bunfig.toml\n"
|
|
111
111
|
+ "COPY bun.lock* ./\n"
|
|
112
112
|
+ "# Install runtime deps only for migrator image (skip test/dev toolchain)\n"
|
|
113
|
-
+ "# NOTE: keep bunfig.toml (it carries the
|
|
113
|
+
+ "# NOTE: keep bunfig.toml (it carries the project/@tdk-landscape registry\n"
|
|
114
114
|
+ "# scopes pointing at Verdaccio) - deleting it forces scoped packages to fall\n"
|
|
115
115
|
+ "# back to the public npm registry and fail with ConnectionRefused/404.\n"
|
|
116
116
|
+ "RUN --mount=type=cache,target=/cache/bun,sharing=shared rm -f bun.lock bun.lockb && env -u CI BUN_CONFIG_FROZEN_LOCKFILE=false BUN_TMPDIR=/tmp BUN_INSTALL=/tmp/bun-install bun install --production --no-frozen-lockfile --concurrent-scripts --cache-dir=/cache/bun\n"
|
|
@@ -60,7 +60,7 @@ MIGRATED_PACKAGES = {
|
|
|
60
60
|
load("../manifest/constants.star", "MANIFEST_FILENAME")
|
|
61
61
|
|
|
62
62
|
# List of all shared platform networks
|
|
63
|
-
# All networks use PROJECT_NAME format (e.g.,
|
|
63
|
+
# All networks use PROJECT_NAME format (e.g., tdk_project_*)
|
|
64
64
|
PLATFORM_NETWORKS = [
|
|
65
65
|
PlatformDockerConstants.NETWORK_TRAEFIK_PUBLIC,
|
|
66
66
|
PlatformDockerConstants.PROJECT_NAME + "_traefik-private",
|
package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star
CHANGED
|
@@ -244,7 +244,51 @@ def _wake_compose_invocation(project_root, resource_path, res_name, resource_nam
|
|
|
244
244
|
|
|
245
245
|
def _register_docker_build(config, live_update_rules):
|
|
246
246
|
"""Register docker_build with live_update for hot-reload support."""
|
|
247
|
-
|
|
247
|
+
TypescriptBuilders.build_typescript_service(
|
|
248
|
+
_app_image_name(config['resource_name'], config['res_name']),
|
|
249
|
+
config.get('project_root', '') or '.',
|
|
250
|
+
_resolve_dockerfile(config),
|
|
251
|
+
live_update_rules,
|
|
252
|
+
config['res_deps'],
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _register_deferred_image_prebuild(config, auto_init_apps):
|
|
257
|
+
"""Build a deferStart resource's image during `tdk up` without creating its container.
|
|
258
|
+
|
|
259
|
+
`auto_init=False` on the dc_resource skips its docker_build too (confirmed
|
|
260
|
+
live on tdk-erp-system, task 7.2), so a fresh project's first request would
|
|
261
|
+
find no image and the wake gateway would fall back to `tilt trigger`, which
|
|
262
|
+
queues behind every other build. This local_resource builds the same image
|
|
263
|
+
(same Dockerfile, `production` target, host network, and an allowlist
|
|
264
|
+
.dockerignore equivalent to docker_build's only=/ignore=) under the tag the
|
|
265
|
+
compose service declares, so the gateway can start it directly.
|
|
266
|
+
"""
|
|
267
|
+
project_root = config.get('project_root', '')
|
|
268
|
+
dockerfile = _resolve_dockerfile(config)
|
|
269
|
+
prebuild_dockerfile = dockerfile + '.prebuild'
|
|
270
|
+
ignore_content = TypescriptBuilders.prebuild_dockerignore(dockerfile, project_root, config['res_path'])
|
|
271
|
+
# BuildKit reads `<dockerfile>.dockerignore` for `-f <dockerfile>`; the
|
|
272
|
+
# original Dockerfile already has a (denylist) one, hence the copy.
|
|
273
|
+
cmd = (
|
|
274
|
+
"cp '" + dockerfile + "' '" + prebuild_dockerfile + "'"
|
|
275
|
+
+ " && printf '%s' '" + ignore_content + "' > '" + prebuild_dockerfile + ".dockerignore'"
|
|
276
|
+
+ " && docker build --network host --target production"
|
|
277
|
+
+ " -f '" + prebuild_dockerfile + "'"
|
|
278
|
+
+ " -t '" + _app_image_name(config['resource_name'], config['res_name']) + "'"
|
|
279
|
+
+ " '" + (project_root or '.') + "'"
|
|
280
|
+
)
|
|
281
|
+
local_resource(
|
|
282
|
+
name=config['res_name'] + '-image',
|
|
283
|
+
cmd=cmd,
|
|
284
|
+
labels=config['labels'],
|
|
285
|
+
resource_deps=config['res_deps'],
|
|
286
|
+
auto_init=auto_init_apps,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _resolve_dockerfile(config):
|
|
291
|
+
"""Absolute (when project_root is set) path of a resource's Dockerfile."""
|
|
248
292
|
project_root = config.get('project_root', '')
|
|
249
293
|
# Use custom dockerfile from config if specified, otherwise use autogenerated default
|
|
250
294
|
# The res_path is relative to project_root (e.g., 'services/product/...')
|
|
@@ -263,13 +307,7 @@ def _register_docker_build(config, live_update_rules):
|
|
|
263
307
|
dockerfile = project_root + dockerfile
|
|
264
308
|
else:
|
|
265
309
|
dockerfile = project_root + '/' + dockerfile
|
|
266
|
-
|
|
267
|
-
image_name,
|
|
268
|
-
project_root or '.',
|
|
269
|
-
dockerfile,
|
|
270
|
-
live_update_rules,
|
|
271
|
-
config['res_deps'],
|
|
272
|
-
)
|
|
310
|
+
return dockerfile
|
|
273
311
|
|
|
274
312
|
|
|
275
313
|
def _register_run_only_resource(config, resource_path, compose_project_name, auto_init_apps=True):
|
|
@@ -318,9 +356,11 @@ def _register_single_resource(config, resource_path, compose_project_name, auto_
|
|
|
318
356
|
|
|
319
357
|
# A sablier.enable + sablier.deferStart resource is held back from tdk up's
|
|
320
358
|
# normal bring-up: its container is never created or started here, only via
|
|
321
|
-
# the Traefik wake path on first request.
|
|
322
|
-
#
|
|
323
|
-
|
|
359
|
+
# the Traefik wake path on first request. auto_init=False also skips the
|
|
360
|
+
# dc_resource's docker_build, so its image is built by a separate
|
|
361
|
+
# `<name>-image` resource instead (_register_deferred_image_prebuild).
|
|
362
|
+
defers_start = resource_defers_start(config.get('manifest', {}))
|
|
363
|
+
effective_auto_init = auto_init_apps and not defers_start
|
|
324
364
|
|
|
325
365
|
print("DEBUG REGISTER: Registering '{}' app_type='{}' auto_init={}".format(res_name, app_type, effective_auto_init))
|
|
326
366
|
|
|
@@ -333,6 +373,8 @@ def _register_single_resource(config, resource_path, compose_project_name, auto_
|
|
|
333
373
|
config['syncs']
|
|
334
374
|
)
|
|
335
375
|
_register_docker_build(config, live_update_rules)
|
|
376
|
+
if defers_start:
|
|
377
|
+
_register_deferred_image_prebuild(config, auto_init_apps)
|
|
336
378
|
else:
|
|
337
379
|
print("DEBUG REGISTER: '{}' skipping docker_build (app_type='{}', syncs={})".format(res_name, app_type, config.get('syncs')))
|
|
338
380
|
|
|
@@ -162,9 +162,29 @@ def _build_typescript_service(name, context, dockerfile, live_update_rules, deps
|
|
|
162
162
|
)
|
|
163
163
|
|
|
164
164
|
|
|
165
|
+
def prebuild_dockerignore(dockerfile, project_root, res_path):
|
|
166
|
+
"""Allowlist .dockerignore matching _build_typescript_service's only=/ignore=.
|
|
167
|
+
|
|
168
|
+
A plain `docker build` has no only= filter, so without this it would send
|
|
169
|
+
the whole project root as context. `res_path` is relative to the project
|
|
170
|
+
root (the build context); `dockerfile` may be absolute.
|
|
171
|
+
"""
|
|
172
|
+
abs_res = project_root + '/' + res_path if project_root else res_path
|
|
173
|
+
lines = ['*']
|
|
174
|
+
for path in _get_resource_only_paths(abs_res):
|
|
175
|
+
lines.append('!' + (res_path if path == abs_res else path))
|
|
176
|
+
ignores = _load_autogenerated_dockerignore_patterns(dockerfile, res_path)
|
|
177
|
+
for pattern in get_filewatch_ignore_patterns(res_path):
|
|
178
|
+
if pattern not in ignores:
|
|
179
|
+
ignores.append(pattern)
|
|
180
|
+
lines.extend(ignores)
|
|
181
|
+
return '\n'.join(lines) + '\n'
|
|
182
|
+
|
|
183
|
+
|
|
165
184
|
TypescriptBuilders = struct(
|
|
166
185
|
build_typescript_service = _build_typescript_service,
|
|
167
186
|
load_dockerignore_patterns = _load_autogenerated_dockerignore_patterns,
|
|
187
|
+
prebuild_dockerignore = prebuild_dockerignore,
|
|
168
188
|
)
|
|
169
189
|
|
|
170
190
|
DEBUG = {}
|