@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.
Files changed (84) hide show
  1. package/.github/workflows/ci.yml +13 -0
  2. package/.github/workflows/release-binaries.yml +76 -1
  3. package/CHANGELOG.md +50 -0
  4. package/README.md +52 -17
  5. package/cli/README.md +16 -43
  6. package/cli/biome.json +0 -4
  7. package/cli/dist/commands/help.d.ts.map +1 -1
  8. package/cli/dist/commands/help.js +22 -39
  9. package/cli/dist/commands/help.js.map +1 -1
  10. package/cli/dist/commands/project.d.ts +6 -0
  11. package/cli/dist/commands/project.d.ts.map +1 -1
  12. package/cli/dist/commands/project.js +16 -0
  13. package/cli/dist/commands/project.js.map +1 -1
  14. package/cli/dist/commands/up.d.ts.map +1 -1
  15. package/cli/dist/commands/up.js +5 -0
  16. package/cli/dist/commands/up.js.map +1 -1
  17. package/cli/dist/commands/upgrade.d.ts +3 -0
  18. package/cli/dist/commands/upgrade.d.ts.map +1 -1
  19. package/cli/dist/commands/upgrade.js +43 -1
  20. package/cli/dist/commands/upgrade.js.map +1 -1
  21. package/cli/dist/utils/constants.d.ts +1 -1
  22. package/cli/dist/utils/constants.d.ts.map +1 -1
  23. package/cli/dist/utils/constants.js +1 -1
  24. package/cli/dist/utils/constants.js.map +1 -1
  25. package/cli/dist/utils/doctor-runtime.js +5 -5
  26. package/cli/dist/utils/doctor-runtime.js.map +1 -1
  27. package/cli/dist/utils/prompt.d.ts.map +1 -1
  28. package/cli/dist/utils/prompt.js +6 -1
  29. package/cli/dist/utils/prompt.js.map +1 -1
  30. package/cli/dist/utils/service-urls.js +1 -1
  31. package/cli/dist/utils/service-urls.js.map +1 -1
  32. package/cli/package.json +2 -1
  33. package/cli/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +2 -2
  34. package/cli/src/commands/__tests__/doctor.test.ts +8 -8
  35. package/cli/src/commands/__tests__/upgrade.test.ts +90 -3
  36. package/cli/src/commands/help.ts +22 -39
  37. package/cli/src/commands/project.ts +20 -0
  38. package/cli/src/commands/up.ts +10 -0
  39. package/cli/src/commands/upgrade.ts +52 -1
  40. package/cli/src/utils/constants.ts +1 -1
  41. package/cli/src/utils/doctor-runtime.ts +5 -5
  42. package/cli/src/utils/prompt.ts +8 -1
  43. package/cli/src/utils/service-urls.ts +1 -1
  44. package/discovery/identity-management-backend/service.yaml +2 -2
  45. package/discovery/identity-management-frontend/service.yaml +1 -1
  46. package/discovery/registry.star +3 -1
  47. package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +2 -2
  48. package/engine/assets/docker/golden-layers.Dockerfile +18 -18
  49. package/engine/assets/docker/wake-gateway/decide.test.ts +25 -0
  50. package/engine/assets/docker/wake-gateway/decide.ts +19 -0
  51. package/engine/assets/docker/wake-gateway/server.ts +60 -14
  52. package/engine/assets/scripts/mac-m1-resource-monitor.sh +1 -1
  53. package/engine/scripts/debug-container.sh +8 -3
  54. package/engine/spec.master +2 -2
  55. package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +1 -1
  56. package/engine/topologies/tilt/common/utils.star +1 -1
  57. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +53 -11
  58. package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +20 -0
  59. package/install.sh +19 -242
  60. package/openspec/changes/prioritized-cold-start/design.md +5 -1
  61. package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +9 -3
  62. package/openspec/changes/prioritized-cold-start/tasks.md +13 -0
  63. package/package.json +1 -1
  64. package/platform/services/platform/proxy/service.yaml +1 -1
  65. package/scripts/release-binaries.sh +8 -5
  66. package/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +2 -2
  67. package/tests/tilt-engine/test_sablier_defer_start.py +41 -0
  68. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/codebase-audit-report.md +0 -62
  69. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/.openspec.yaml +0 -5
  70. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/design.md +0 -94
  71. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/proposal.md +0 -46
  72. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CLEANUP_REPORT_8.md +0 -174
  73. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CRITICAL_ASSESSMENT_8.md +0 -160
  74. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/circular-deps/CRITICAL_ASSESSMENT_4.md +0 -381
  75. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/dead-code/CRITICAL_ASSESSMENT_3.md +0 -316
  76. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/CRITICAL_ASSESSMENT_1.md +0 -290
  77. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/IMPLEMENTATION_SUMMARY_1.md +0 -233
  78. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/defensive-cleanup/CRITICAL_ASSESSMENT_6.md +0 -835
  79. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/CRITICAL_ASSESSMENT_7.md +0 -254
  80. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/IMPLEMENTATION_SUMMARY_7.md +0 -187
  81. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/strong-typing/CRITICAL_ASSESSMENT_5.md +0 -269
  82. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/CRITICAL_ASSESSMENT_2.md +0 -312
  83. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/IMPLEMENTATION_SUMMARY.md +0 -170
  84. 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: beauty-crm-l1-l4
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
- # - beauty-crm-l1:latest - OS base + Bun runtime
19
- # - beauty-crm-l2:latest - Dependencies installed
20
- # - beauty-crm-l3-backend:latest - Backend build tools (Prisma, no Infisical CLI)
21
- # - beauty-crm-l3-frontend:latest - Frontend build tools (no Prisma)
22
- # - beauty-crm-l3-migrator:latest - Migrator build tools (Prisma, no Infisical CLI)
23
- # - beauty-crm-l4-backend:latest - Backend production runtime
24
- # - beauty-crm-l4-frontend:latest - Frontend production runtime (Nginx)
25
- # - beauty-crm-l4-migrator:latest - Migrator production runtime
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="beauty-crm"
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="beauty-crm"
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="beauty-crm"
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="beauty-crm"
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="beauty-crm"
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="beauty-crm"
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="beauty-crm"
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="beauty-crm"
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="beauty-crm"
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 { resource, container, deps, timeoutOverrideMs, proxyPort, sessionDuration, compose, hasCompose };
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 { resource, container, deps, timeoutOverrideMs, proxyPort, sessionDuration, compose, hasCompose } =
186
- parseRequest(req);
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
- const outcome = evaluateReadiness(requiredNames, healthy);
240
- if (outcome.ready) {
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 <<< "$BEAUTY_CONTAINERS"
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 -i "beauty"
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 -i "beauty" | grep -i "exited"
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 -i "beauty" | grep -iE "(exited|error)"
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}"
@@ -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/beauty-crm, PROJECT_ROOT = "/home/dev/beauty-crm"
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 = "beauty-crm-tilt-platform",
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 @beauty-crm/@tdk-landscape registry\n"
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., beauty_crm_*)
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",
@@ -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
- image_name = _app_image_name(config['resource_name'], config['res_name'])
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
- TypescriptBuilders.build_typescript_service(
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. docker_build below is unaffected
322
- # (it has no auto_init gate), so the image still builds either way.
323
- effective_auto_init = auto_init_apps and not resource_defers_start(config.get('manifest', {}))
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 = {}