@tdk-landscape/tdk-cli-core 1.3.49 → 1.3.51
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/.claude/skills/layer-autoresearch/SKILL.md +48 -0
- package/.claude/skills/tdk-troubleshoot/SKILL.md +222 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +40 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +18 -0
- package/.github/dependabot.yml +33 -0
- package/.github/pull_request_template.md +5 -0
- package/.github/workflows/ci.yml +187 -0
- package/.github/workflows/release-binaries.yml +19 -9
- package/.github/zizmor.yml +9 -0
- package/CODE_OF_CONDUCT.md +9 -0
- package/CONTRIBUTING.md +42 -0
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/SECURITY.md +17 -0
- package/benchmarks/results/container-scale-2026-09-25T20-54-49-736Z.json +45 -0
- package/benchmarks/results/container-scale-2026-09-25T21-01-20-540Z.json +150 -0
- package/benchmarks/results/container-scale-2026-09-25T21-15-01-441Z.json +73 -0
- package/benchmarks/results/container-scale-2026-09-25T22-09-01-630Z.json +77 -0
- package/benchmarks/results/container-scale-2026-09-25T22-18-28-371Z.json +202 -0
- package/cli/README.md +25 -0
- package/cli/biome.json +1 -1
- package/cli/dist/cli.d.ts +3 -0
- package/cli/dist/cli.d.ts.map +1 -0
- package/cli/dist/cli.js +64 -0
- package/cli/dist/cli.js.map +1 -0
- package/cli/dist/commands/completion.d.ts +3 -0
- package/cli/dist/commands/completion.d.ts.map +1 -0
- package/cli/dist/commands/completion.js +294 -0
- package/cli/dist/commands/completion.js.map +1 -0
- package/cli/dist/commands/config.d.ts +3 -0
- package/cli/dist/commands/config.d.ts.map +1 -0
- package/cli/dist/commands/config.js +231 -0
- package/cli/dist/commands/config.js.map +1 -0
- package/cli/dist/commands/doctor.d.ts +19 -0
- package/cli/dist/commands/doctor.d.ts.map +1 -0
- package/cli/dist/commands/doctor.js +745 -0
- package/cli/dist/commands/doctor.js.map +1 -0
- package/cli/dist/commands/down.d.ts +3 -0
- package/cli/dist/commands/down.d.ts.map +1 -0
- package/cli/dist/commands/down.js +32 -0
- package/cli/dist/commands/down.js.map +1 -0
- package/cli/dist/commands/help.d.ts +2 -0
- package/cli/dist/commands/help.d.ts.map +1 -0
- package/cli/dist/commands/help.js +138 -0
- package/cli/dist/commands/help.js.map +1 -0
- package/cli/dist/commands/networks.d.ts +3 -0
- package/cli/dist/commands/networks.d.ts.map +1 -0
- package/cli/dist/commands/networks.js +260 -0
- package/cli/dist/commands/networks.js.map +1 -0
- package/cli/dist/commands/project.d.ts +3 -0
- package/cli/dist/commands/project.d.ts.map +1 -0
- package/cli/dist/commands/project.js +370 -0
- package/cli/dist/commands/project.js.map +1 -0
- package/cli/dist/commands/projects.d.ts +3 -0
- package/cli/dist/commands/projects.d.ts.map +1 -0
- package/cli/dist/commands/projects.js +71 -0
- package/cli/dist/commands/projects.js.map +1 -0
- package/cli/dist/commands/resource.d.ts +69 -0
- package/cli/dist/commands/resource.d.ts.map +1 -0
- package/cli/dist/commands/resource.js +579 -0
- package/cli/dist/commands/resource.js.map +1 -0
- package/cli/dist/commands/resources.d.ts +3 -0
- package/cli/dist/commands/resources.d.ts.map +1 -0
- package/cli/dist/commands/resources.js +73 -0
- package/cli/dist/commands/resources.js.map +1 -0
- package/cli/dist/commands/stack.d.ts +3 -0
- package/cli/dist/commands/stack.d.ts.map +1 -0
- package/cli/dist/commands/stack.js +97 -0
- package/cli/dist/commands/stack.js.map +1 -0
- package/cli/dist/commands/stacks.d.ts +3 -0
- package/cli/dist/commands/stacks.d.ts.map +1 -0
- package/cli/dist/commands/stacks.js +43 -0
- package/cli/dist/commands/stacks.js.map +1 -0
- package/cli/dist/commands/status.d.ts +3 -0
- package/cli/dist/commands/status.d.ts.map +1 -0
- package/cli/dist/commands/status.js +71 -0
- package/cli/dist/commands/status.js.map +1 -0
- package/cli/dist/commands/ui.d.ts +3 -0
- package/cli/dist/commands/ui.d.ts.map +1 -0
- package/cli/dist/commands/ui.js +445 -0
- package/cli/dist/commands/ui.js.map +1 -0
- package/cli/dist/commands/up.d.ts +3 -0
- package/cli/dist/commands/up.d.ts.map +1 -0
- package/cli/dist/commands/up.js +124 -0
- package/cli/dist/commands/up.js.map +1 -0
- package/cli/dist/commands/upgrade.d.ts +11 -0
- package/cli/dist/commands/upgrade.d.ts.map +1 -0
- package/cli/dist/commands/upgrade.js +496 -0
- package/cli/dist/commands/upgrade.js.map +1 -0
- package/cli/dist/commands/version.d.ts +3 -0
- package/cli/dist/commands/version.d.ts.map +1 -0
- package/cli/dist/commands/version.js +9 -0
- package/cli/dist/commands/version.js.map +1 -0
- package/cli/dist/components/Accessible.d.ts +8 -0
- package/cli/dist/components/Accessible.d.ts.map +1 -0
- package/cli/dist/components/Accessible.js +10 -0
- package/cli/dist/components/Accessible.js.map +1 -0
- package/cli/dist/components/BaseTooltip.d.ts +4 -0
- package/cli/dist/components/BaseTooltip.d.ts.map +1 -0
- package/cli/dist/components/BaseTooltip.js +28 -0
- package/cli/dist/components/BaseTooltip.js.map +1 -0
- package/cli/dist/components/DetailPanel.d.ts +4 -0
- package/cli/dist/components/DetailPanel.d.ts.map +1 -0
- package/cli/dist/components/DetailPanel.js +18 -0
- package/cli/dist/components/DetailPanel.js.map +1 -0
- package/cli/dist/components/FileTree.d.ts +4 -0
- package/cli/dist/components/FileTree.d.ts.map +1 -0
- package/cli/dist/components/FileTree.js +47 -0
- package/cli/dist/components/FileTree.js.map +1 -0
- package/cli/dist/components/ResourceSelectInput.d.ts +4 -0
- package/cli/dist/components/ResourceSelectInput.d.ts.map +1 -0
- package/cli/dist/components/ResourceSelectInput.js +7 -0
- package/cli/dist/components/ResourceSelectInput.js.map +1 -0
- package/cli/dist/components/ResourceTable.d.ts +4 -0
- package/cli/dist/components/ResourceTable.d.ts.map +1 -0
- package/cli/dist/components/ResourceTable.js +18 -0
- package/cli/dist/components/ResourceTable.js.map +1 -0
- package/cli/dist/components/TabBar.d.ts +4 -0
- package/cli/dist/components/TabBar.d.ts.map +1 -0
- package/cli/dist/components/TabBar.js +17 -0
- package/cli/dist/components/TabBar.js.map +1 -0
- package/cli/dist/components/Tooltip.d.ts +12 -0
- package/cli/dist/components/Tooltip.d.ts.map +1 -0
- package/cli/dist/components/Tooltip.js +12 -0
- package/cli/dist/components/Tooltip.js.map +1 -0
- package/cli/dist/components/index.d.ts +8 -0
- package/cli/dist/components/index.d.ts.map +1 -0
- package/cli/dist/components/index.js +8 -0
- package/cli/dist/components/index.js.map +1 -0
- package/cli/dist/config/platform-standards.d.ts +159 -0
- package/cli/dist/config/platform-standards.d.ts.map +1 -0
- package/cli/dist/config/platform-standards.js +181 -0
- package/cli/dist/config/platform-standards.js.map +1 -0
- package/cli/dist/generator/extension-fetch.d.ts +25 -0
- package/cli/dist/generator/extension-fetch.d.ts.map +1 -0
- package/cli/dist/generator/extension-fetch.js +259 -0
- package/cli/dist/generator/extension-fetch.js.map +1 -0
- package/cli/dist/generator/template-engine.d.ts +62 -0
- package/cli/dist/generator/template-engine.d.ts.map +1 -0
- package/cli/dist/generator/template-engine.js +623 -0
- package/cli/dist/generator/template-engine.js.map +1 -0
- package/cli/dist/index.d.ts +11 -0
- package/cli/dist/index.d.ts.map +1 -0
- package/cli/dist/index.js +11 -0
- package/cli/dist/index.js.map +1 -0
- package/cli/dist/types/index.d.ts +257 -0
- package/cli/dist/types/index.d.ts.map +1 -0
- package/cli/dist/types/index.js +17 -0
- package/cli/dist/types/index.js.map +1 -0
- package/cli/dist/utils/cache.d.ts +19 -0
- package/cli/dist/utils/cache.d.ts.map +1 -0
- package/cli/dist/utils/cache.js +64 -0
- package/cli/dist/utils/cache.js.map +1 -0
- package/cli/dist/utils/command-helpers.d.ts +17 -0
- package/cli/dist/utils/command-helpers.d.ts.map +1 -0
- package/cli/dist/utils/command-helpers.js +41 -0
- package/cli/dist/utils/command-helpers.js.map +1 -0
- package/cli/dist/utils/constants.d.ts +53 -0
- package/cli/dist/utils/constants.d.ts.map +1 -0
- package/cli/dist/utils/constants.js +115 -0
- package/cli/dist/utils/constants.js.map +1 -0
- package/cli/dist/utils/discovery-context.d.ts +4 -0
- package/cli/dist/utils/discovery-context.d.ts.map +1 -0
- package/cli/dist/utils/discovery-context.js +39 -0
- package/cli/dist/utils/discovery-context.js.map +1 -0
- package/cli/dist/utils/docker.d.ts +4 -0
- package/cli/dist/utils/docker.d.ts.map +1 -0
- package/cli/dist/utils/docker.js +19 -0
- package/cli/dist/utils/docker.js.map +1 -0
- package/cli/dist/utils/doctor-runtime.d.ts +81 -0
- package/cli/dist/utils/doctor-runtime.d.ts.map +1 -0
- package/cli/dist/utils/doctor-runtime.js +498 -0
- package/cli/dist/utils/doctor-runtime.js.map +1 -0
- package/cli/dist/utils/env-validator.d.ts +15 -0
- package/cli/dist/utils/env-validator.d.ts.map +1 -0
- package/cli/dist/utils/env-validator.js +109 -0
- package/cli/dist/utils/env-validator.js.map +1 -0
- package/cli/dist/utils/errors.d.ts +31 -0
- package/cli/dist/utils/errors.d.ts.map +1 -0
- package/cli/dist/utils/errors.js +140 -0
- package/cli/dist/utils/errors.js.map +1 -0
- package/cli/dist/utils/file-helpers.d.ts +14 -0
- package/cli/dist/utils/file-helpers.d.ts.map +1 -0
- package/cli/dist/utils/file-helpers.js +45 -0
- package/cli/dist/utils/file-helpers.js.map +1 -0
- package/cli/dist/utils/formatting.d.ts +30 -0
- package/cli/dist/utils/formatting.d.ts.map +1 -0
- package/cli/dist/utils/formatting.js +164 -0
- package/cli/dist/utils/formatting.js.map +1 -0
- package/cli/dist/utils/paths.d.ts +3 -0
- package/cli/dist/utils/paths.d.ts.map +1 -0
- package/cli/dist/utils/paths.js +41 -0
- package/cli/dist/utils/paths.js.map +1 -0
- package/cli/dist/utils/port-assignment.d.ts +6 -0
- package/cli/dist/utils/port-assignment.d.ts.map +1 -0
- package/cli/dist/utils/port-assignment.js +81 -0
- package/cli/dist/utils/port-assignment.js.map +1 -0
- package/cli/dist/utils/project-features.d.ts +23 -0
- package/cli/dist/utils/project-features.d.ts.map +1 -0
- package/cli/dist/utils/project-features.js +98 -0
- package/cli/dist/utils/project-features.js.map +1 -0
- package/cli/dist/utils/project-templates.d.ts +7 -0
- package/cli/dist/utils/project-templates.d.ts.map +1 -0
- package/cli/dist/utils/project-templates.js +24 -0
- package/cli/dist/utils/project-templates.js.map +1 -0
- package/cli/dist/utils/prompt.d.ts +28 -0
- package/cli/dist/utils/prompt.d.ts.map +1 -0
- package/cli/dist/utils/prompt.js +79 -0
- package/cli/dist/utils/prompt.js.map +1 -0
- package/cli/dist/utils/resource-features.d.ts +29 -0
- package/cli/dist/utils/resource-features.d.ts.map +1 -0
- package/cli/dist/utils/resource-features.js +175 -0
- package/cli/dist/utils/resource-features.js.map +1 -0
- package/cli/dist/utils/service-urls.d.ts +30 -0
- package/cli/dist/utils/service-urls.d.ts.map +1 -0
- package/cli/dist/utils/service-urls.js +93 -0
- package/cli/dist/utils/service-urls.js.map +1 -0
- package/cli/dist/utils/services.d.ts +18 -0
- package/cli/dist/utils/services.d.ts.map +1 -0
- package/cli/dist/utils/services.js +292 -0
- package/cli/dist/utils/services.js.map +1 -0
- package/cli/dist/utils/stack-features.d.ts +22 -0
- package/cli/dist/utils/stack-features.d.ts.map +1 -0
- package/cli/dist/utils/stack-features.js +13 -0
- package/cli/dist/utils/stack-features.js.map +1 -0
- package/cli/dist/utils/tar.d.ts +7 -0
- package/cli/dist/utils/tar.d.ts.map +1 -0
- package/cli/dist/utils/tar.js +21 -0
- package/cli/dist/utils/tar.js.map +1 -0
- package/cli/dist/utils/tilt.d.ts +20 -0
- package/cli/dist/utils/tilt.d.ts.map +1 -0
- package/cli/dist/utils/tilt.js +95 -0
- package/cli/dist/utils/tilt.js.map +1 -0
- package/cli/dist/utils/validation.d.ts +16 -0
- package/cli/dist/utils/validation.d.ts.map +1 -0
- package/cli/dist/utils/validation.js +58 -0
- package/cli/dist/utils/validation.js.map +1 -0
- package/cli/package.json +11 -12
- package/cli/src/commands/__tests__/always-enabled-infra.test.ts +2 -8
- package/cli/src/commands/__tests__/doctor.test.ts +181 -8
- package/cli/src/commands/__tests__/phases-model.test.ts +2 -4
- package/cli/src/commands/__tests__/project-resource-features-e2e.test.ts +4 -24
- package/cli/src/commands/__tests__/saas-starter-integration.test.ts +8 -10
- package/cli/src/commands/__tests__/stack-features-e2e.test.ts +10 -4
- package/cli/src/commands/__tests__/upgrade.test.ts +23 -13
- package/cli/src/commands/doctor.ts +136 -0
- package/cli/src/commands/help.ts +13 -3
- package/cli/src/commands/project.ts +11 -14
- package/cli/src/commands/projects.ts +2 -2
- package/cli/src/commands/up.ts +13 -4
- package/cli/src/commands/upgrade.ts +11 -4
- package/cli/src/config/platform-standards.test.ts +6 -6
- package/cli/src/generator/__tests__/extension-fetch.test.ts +36 -14
- package/cli/src/generator/extension-fetch.ts +12 -5
- package/cli/src/generator/template-engine.ts +19 -21
- package/cli/src/types/index.ts +2 -0
- package/cli/src/utils/__tests__/tilt.test.ts +29 -0
- package/cli/src/utils/constants.ts +4 -0
- package/cli/src/utils/doctor-runtime.ts +67 -27
- package/cli/src/utils/env-validator.ts +2 -2
- package/cli/src/utils/project-templates.ts +2 -1
- package/cli/src/utils/prompt.ts +2 -4
- package/cli/src/utils/resource-features.ts +10 -4
- package/cli/src/utils/services.ts +2 -1
- package/cli/src/utils/tilt.ts +20 -0
- package/docs/demo.svg +217 -0
- package/engine/assets/docker/wake-gateway/Dockerfile +27 -0
- package/engine/assets/docker/wake-gateway/decide.test.ts +133 -0
- package/engine/assets/docker/wake-gateway/decide.ts +105 -0
- package/engine/assets/docker/wake-gateway/server.ts +311 -0
- package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +13 -23
- package/engine/topologies/platform/docker/compose/compose.star +7 -14
- package/engine/topologies/platform/docker/compose/compose_helpers.star +1 -1
- package/engine/topologies/platform/docker/compose/traefik_standalone.star +66 -6
- package/engine/topologies/platform/docker/config/healthcheck.star +56 -0
- package/engine/topologies/platform/docker/constants.star +10 -0
- package/engine/topologies/platform/docker/layers/l1_base_layers.star +1 -1
- package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +55 -20
- package/engine/topologies/platform/docker/networking/traefik_static_routes.star +161 -0
- package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +2 -2
- package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +1 -1
- package/engine/topologies/tilt/manifest/schema.star +6 -0
- package/engine/topologies/tilt/manifest/validator.star +16 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +143 -9
- package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +12 -6
- package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +2 -1
- package/knip.json +1 -1
- package/openspec/changes/optimize-runtime-docker-layers/.openspec.yaml +3 -0
- package/openspec/changes/optimize-runtime-docker-layers/design.md +81 -0
- package/openspec/changes/optimize-runtime-docker-layers/proposal.md +44 -0
- package/openspec/changes/optimize-runtime-docker-layers/specs/runtime-container-footprint/spec.md +79 -0
- package/openspec/changes/optimize-runtime-docker-layers/tasks.md +36 -0
- package/openspec/changes/prioritized-cold-start/.openspec.yaml +4 -0
- package/openspec/changes/prioritized-cold-start/design.md +133 -0
- package/openspec/changes/prioritized-cold-start/proposal.md +41 -0
- package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +92 -0
- package/openspec/changes/prioritized-cold-start/tasks.md +56 -0
- package/openspec/config.yaml +1 -0
- package/package.json +35 -5
- package/scripts/autoresearch/layer-eval.sh +65 -0
- package/scripts/benchmark/README.md +25 -0
- package/scripts/benchmark/container-scale.ts +503 -0
- package/scripts/benchmark/pre-release-gate.sh +9 -0
- package/scripts/measure-idle-footprint.sh +58 -0
- package/scripts/record-demo.sh +43 -0
- package/tests/tilt-engine/test_runtime_container_footprint.py +162 -0
- package/tests/tilt-engine/test_sablier_defer_start.py +196 -0
- package/tests/tilt-engine/test_standalone_traefik.py +52 -0
- package/tests/tilt-engine/test_traefik_static_wake_route.py +187 -0
- package/bun.lock +0 -538
|
@@ -23,6 +23,7 @@ load("../secrets/infisical.star", "get_infisical_environment_vars")
|
|
|
23
23
|
load("../auth/auth.star", "AuthConfig")
|
|
24
24
|
|
|
25
25
|
load("../constants.star", "PlatformDockerConstants")
|
|
26
|
+
load("../config/healthcheck.star", "DOCKER_HEALTHCHECK", "compose_healthcheck_timing")
|
|
26
27
|
|
|
27
28
|
# Load project name for dynamic host naming
|
|
28
29
|
# Use TDK_PROJECT_ROOT env var set by Tilt, fallback to current directory
|
|
@@ -125,6 +126,7 @@ def generate_frontend_compose(resource_path, resource_name, res, manifest=None):
|
|
|
125
126
|
|
|
126
127
|
return """ {res_name}:
|
|
127
128
|
image: {image_name}
|
|
129
|
+
init: true
|
|
128
130
|
{build_config}
|
|
129
131
|
<<: *frontend-memory-limit
|
|
130
132
|
env_file:
|
|
@@ -138,12 +140,7 @@ def generate_frontend_compose(resource_path, resource_name, res, manifest=None):
|
|
|
138
140
|
- {network_backend}
|
|
139
141
|
healthcheck:
|
|
140
142
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:{port}"]
|
|
141
|
-
|
|
142
|
-
timeout: 5s
|
|
143
|
-
retries: 3
|
|
144
|
-
# STORY 4 FIX: 30s grace period to prevent 504 errors on startup
|
|
145
|
-
start_period: 30s
|
|
146
|
-
""".format(
|
|
143
|
+
{healthcheck_timing}""".format(
|
|
147
144
|
res_name=res_name,
|
|
148
145
|
image_name=image_name,
|
|
149
146
|
build_config=build_config,
|
|
@@ -152,6 +149,7 @@ def generate_frontend_compose(resource_path, resource_name, res, manifest=None):
|
|
|
152
149
|
port=port,
|
|
153
150
|
network_traefik_public=PlatformDockerConstants.NETWORK_TRAEFIK_PUBLIC,
|
|
154
151
|
network_backend=PlatformDockerConstants.NETWORK_BACKEND,
|
|
152
|
+
healthcheck_timing=compose_healthcheck_timing(DOCKER_HEALTHCHECK["frontend_start_period_seconds"]),
|
|
155
153
|
)
|
|
156
154
|
|
|
157
155
|
|
|
@@ -261,13 +259,7 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
261
259
|
)
|
|
262
260
|
healthcheck_section = """ healthcheck:
|
|
263
261
|
test: ["CMD", "curl", "-f", "--max-time", "5", "http://localhost:{internal_port}{health_path}"]
|
|
264
|
-
|
|
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)
|
|
262
|
+
""".format(internal_port=internal_port, health_path=health_path) + compose_healthcheck_timing()
|
|
271
263
|
infisical_env = get_infisical_environment_vars(as_array=True, resource_name=res_name)
|
|
272
264
|
|
|
273
265
|
# Build auth environment variables section using centralized auth utilities
|
|
@@ -275,6 +267,7 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
275
267
|
return """
|
|
276
268
|
{resource_entry_name}:
|
|
277
269
|
image: {image_name}
|
|
270
|
+
init: true
|
|
278
271
|
restart: unless-stopped
|
|
279
272
|
{build_config}
|
|
280
273
|
{ports_section}
|
|
@@ -619,7 +612,7 @@ services:
|
|
|
619
612
|
{migrator_name}:
|
|
620
613
|
image: {image_name}
|
|
621
614
|
build:
|
|
622
|
-
context:
|
|
615
|
+
context: ../../../.
|
|
623
616
|
dockerfile: {dockerfile_ref}
|
|
624
617
|
target: migrator
|
|
625
618
|
environment:
|
|
@@ -40,7 +40,7 @@ def compose_build_config(resource_path, res_name, dockerfile=OUTPUT.AUTOGENERATE
|
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
base_config = """ build:
|
|
43
|
-
context:
|
|
43
|
+
context: ../../../.
|
|
44
44
|
dockerfile: {full_dockerfile_path}""".format(
|
|
45
45
|
full_dockerfile_path=full_dockerfile_path
|
|
46
46
|
)
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
# skip Traefik entirely and api.{project}.localhost never served.
|
|
7
7
|
|
|
8
8
|
load("../constants.star", "PlatformDockerConstants")
|
|
9
|
+
load("../config/healthcheck.star", "compose_healthcheck_timing")
|
|
10
|
+
load("../networking/traefik_static_routes.star", "normalize_abs_path")
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
def generate_standalone_traefik_compose():
|
|
@@ -24,6 +26,7 @@ services:
|
|
|
24
26
|
restart: unless-stopped
|
|
25
27
|
depends_on:
|
|
26
28
|
- sablier
|
|
29
|
+
- wake-gateway
|
|
27
30
|
command:
|
|
28
31
|
- "--api.insecure=true"
|
|
29
32
|
- "--ping=true"
|
|
@@ -31,6 +34,13 @@ services:
|
|
|
31
34
|
- "--providers.docker.exposedbydefault=false"
|
|
32
35
|
- "--providers.docker.watch=true"
|
|
33
36
|
- "--providers.docker.network={network}"
|
|
37
|
+
# Static routes for sablier.deferStart resources (openspec/changes/
|
|
38
|
+
# prioritized-cold-start): a never-created resource has no container
|
|
39
|
+
# and thus no Docker labels for the provider above to discover, so its
|
|
40
|
+
# route is defined here instead, pointed at wake-gateway. Harmless when
|
|
41
|
+
# no deferStart resource exists (empty directory, no routes emitted).
|
|
42
|
+
- "--providers.file.directory=/etc/traefik/dynamic"
|
|
43
|
+
- "--providers.file.watch=true"
|
|
34
44
|
- "--entrypoints.web.address=:80"
|
|
35
45
|
- "--entrypoints.websecure.address=:443"
|
|
36
46
|
- "--log.level=INFO"
|
|
@@ -44,15 +54,12 @@ services:
|
|
|
44
54
|
- "8080:8080"
|
|
45
55
|
volumes:
|
|
46
56
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
57
|
+
- ./{dynamic_dir_name}:/etc/traefik/dynamic:ro
|
|
47
58
|
networks:
|
|
48
59
|
- traefik-public
|
|
49
60
|
healthcheck:
|
|
50
61
|
test: ["CMD", "traefik", "healthcheck", "--ping"]
|
|
51
|
-
|
|
52
|
-
timeout: 5s
|
|
53
|
-
retries: 6
|
|
54
|
-
start_period: 10s
|
|
55
|
-
|
|
62
|
+
{healthcheck_timing}
|
|
56
63
|
sablier:
|
|
57
64
|
image: sablierapp/sablier:1.18.0
|
|
58
65
|
container_name: {name}_sablier
|
|
@@ -66,8 +73,61 @@ services:
|
|
|
66
73
|
networks:
|
|
67
74
|
- traefik-public
|
|
68
75
|
|
|
76
|
+
# Wake gateway (openspec/changes/prioritized-cold-start): fronts every
|
|
77
|
+
# deferStart resource's static route above. Triggers a cold resource (and
|
|
78
|
+
# its dependencies, which Tilt never cascades to on its own -- see design.md
|
|
79
|
+
# D3/task 1.5) via `tilt trigger`, or Sablier's own start when a container
|
|
80
|
+
# already exists, then holds the request until healthy. Needs the same
|
|
81
|
+
# Tilt session token Sablier/Traefik don't: confirmed empirically that
|
|
82
|
+
# `tilt trigger` from inside a container is otherwise rejected with
|
|
83
|
+
# "(403): invalid session token" even when the Tilt HTTP port is reachable.
|
|
84
|
+
wake-gateway:
|
|
85
|
+
build: ./{vendored_gateway_dir}
|
|
86
|
+
container_name: {gateway_container}
|
|
87
|
+
restart: unless-stopped
|
|
88
|
+
environment:
|
|
89
|
+
- TILT_HOST=host.docker.internal
|
|
90
|
+
- TILT_PORT=10350
|
|
91
|
+
- SABLIER_URL=http://sablier:10000
|
|
92
|
+
- PORT={gateway_port}
|
|
93
|
+
volumes:
|
|
94
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
95
|
+
- {tilt_dev_dir}:/root/.tilt-dev:ro{project_root_mount}
|
|
96
|
+
networks:
|
|
97
|
+
- traefik-public
|
|
98
|
+
|
|
69
99
|
networks:
|
|
70
100
|
traefik-public:
|
|
71
101
|
name: {network}
|
|
72
102
|
external: true
|
|
73
|
-
""".format(
|
|
103
|
+
""".format(
|
|
104
|
+
name=name,
|
|
105
|
+
network=network,
|
|
106
|
+
healthcheck_timing=compose_healthcheck_timing(10),
|
|
107
|
+
dynamic_dir_name=PlatformDockerConstants.TRAEFIK_DYNAMIC_DIR_REL.split("/")[-1],
|
|
108
|
+
vendored_gateway_dir="tdk-cli-ext/engine/assets/docker/wake-gateway",
|
|
109
|
+
gateway_container=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_CONTAINER,
|
|
110
|
+
gateway_port=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_PORT,
|
|
111
|
+
tilt_dev_dir=os.environ.get("HOME", "") + "/.tilt-dev",
|
|
112
|
+
project_root_mount=_project_root_mount(),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _project_root_mount():
|
|
117
|
+
"""Read-only mount of the project at its own absolute host path.
|
|
118
|
+
|
|
119
|
+
The gateway runs `docker compose up --no-build` for a deferred resource
|
|
120
|
+
itself (bypassing Tilt's build queue), using the compose files Tilt uses.
|
|
121
|
+
Compose resolves relative paths in them (env_file etc.) client-side, so
|
|
122
|
+
they must exist at the same absolute paths inside the gateway container.
|
|
123
|
+
Empty when TDK_PROJECT_ROOT is unset (the generated Tiltfile sets it).
|
|
124
|
+
"""
|
|
125
|
+
root = project_root_abspath()
|
|
126
|
+
if not root:
|
|
127
|
+
return ""
|
|
128
|
+
return "\n - " + root + ":" + root + ":ro"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def project_root_abspath():
|
|
132
|
+
"""Absolute, normalized project root (TDK_PROJECT_ROOT), or "" when unset."""
|
|
133
|
+
return normalize_abs_path(os.environ.get("TDK_PROJECT_ROOT", ""))
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Docker healthcheck timing - single source for golden images, L4 runtimes and
|
|
3
|
+
# generated compose files.
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Steady-state probes are infrequent (each probe is a `docker exec`, which adds
|
|
6
|
+
# up across 100+ containers), while `start_interval` keeps first-healthy time
|
|
7
|
+
# fast during the start period. `start_interval` needs Docker Engine 25+ and
|
|
8
|
+
# Compose 2.20.2+ (checked by `tdk doctor`).
|
|
9
|
+
#
|
|
10
|
+
# Override per run with environment variables, e.g.:
|
|
11
|
+
# TDK_HEALTHCHECK_INTERVAL_SECONDS=15 tdk up
|
|
12
|
+
# =============================================================================
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _env_int(name, default):
|
|
16
|
+
raw = os.environ.get(name, '')
|
|
17
|
+
if raw and raw.isdigit() and int(raw) > 0:
|
|
18
|
+
return int(raw)
|
|
19
|
+
return default
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
DOCKER_HEALTHCHECK = {
|
|
23
|
+
"path": "/health",
|
|
24
|
+
"interval_seconds": _env_int("TDK_HEALTHCHECK_INTERVAL_SECONDS", 30),
|
|
25
|
+
"start_interval_seconds": _env_int("TDK_HEALTHCHECK_START_INTERVAL_SECONDS", 2),
|
|
26
|
+
"timeout_seconds": _env_int("TDK_HEALTHCHECK_TIMEOUT_SECONDS", 5),
|
|
27
|
+
"start_period_seconds": _env_int("TDK_HEALTHCHECK_START_PERIOD_SECONDS", 30),
|
|
28
|
+
"frontend_start_period_seconds": _env_int("TDK_HEALTHCHECK_START_PERIOD_SECONDS", 30),
|
|
29
|
+
"retries": _env_int("TDK_HEALTHCHECK_RETRIES", 3),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def dockerfile_healthcheck_flags(start_period_seconds = None):
|
|
34
|
+
"""Flags for a Dockerfile `HEALTHCHECK` instruction."""
|
|
35
|
+
h = DOCKER_HEALTHCHECK
|
|
36
|
+
start_period = start_period_seconds if start_period_seconds != None else h["start_period_seconds"]
|
|
37
|
+
return "--interval={}s --start-interval={}s --timeout={}s --start-period={}s --retries={}".format(
|
|
38
|
+
h["interval_seconds"],
|
|
39
|
+
h["start_interval_seconds"],
|
|
40
|
+
h["timeout_seconds"],
|
|
41
|
+
start_period,
|
|
42
|
+
h["retries"],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def compose_healthcheck_timing(start_period_seconds = None, indent = " "):
|
|
47
|
+
"""Timing keys for a compose `healthcheck:` block (everything except `test:`)."""
|
|
48
|
+
h = DOCKER_HEALTHCHECK
|
|
49
|
+
start_period = start_period_seconds if start_period_seconds != None else h["start_period_seconds"]
|
|
50
|
+
return (
|
|
51
|
+
indent + "interval: {}s\n".format(h["interval_seconds"])
|
|
52
|
+
+ indent + "timeout: {}s\n".format(h["timeout_seconds"])
|
|
53
|
+
+ indent + "retries: {}\n".format(h["retries"])
|
|
54
|
+
+ indent + "start_period: {}s\n".format(start_period)
|
|
55
|
+
+ indent + "start_interval: {}s\n".format(h["start_interval_seconds"])
|
|
56
|
+
)
|
|
@@ -62,6 +62,13 @@ NETWORK_DATABASE = PROJECT_NAME + "_database"
|
|
|
62
62
|
NETWORK_INFISICAL = PROJECT_NAME + "_infisical-network"
|
|
63
63
|
NETWORK_PROXY = PROJECT_NAME + "_proxy"
|
|
64
64
|
|
|
65
|
+
# Traefik file-provider dynamic config: one generated YAML per `sablier.deferStart`
|
|
66
|
+
# resource's static route (see openspec/changes/prioritized-cold-start), mounted
|
|
67
|
+
# read-only into the standalone Traefik container at /etc/traefik/dynamic.
|
|
68
|
+
TRAEFIK_DYNAMIC_DIR_REL = ".tdk/.tdk-out/traefik-dynamic"
|
|
69
|
+
TRAEFIK_WAKE_GATEWAY_CONTAINER = PROJECT_NAME_HYPHEN + "-wake-gateway"
|
|
70
|
+
TRAEFIK_WAKE_GATEWAY_PORT = 9999
|
|
71
|
+
|
|
65
72
|
# NATS constants
|
|
66
73
|
NATS_HOST = PROJECT_NAME + "_nats"
|
|
67
74
|
NATS_PORT = "4222"
|
|
@@ -146,6 +153,9 @@ PlatformDockerConstants = struct(
|
|
|
146
153
|
NETWORK_DATABASE = NETWORK_DATABASE,
|
|
147
154
|
NETWORK_INFISICAL = NETWORK_INFISICAL,
|
|
148
155
|
NETWORK_PROXY = NETWORK_PROXY,
|
|
156
|
+
TRAEFIK_DYNAMIC_DIR_REL = TRAEFIK_DYNAMIC_DIR_REL,
|
|
157
|
+
TRAEFIK_WAKE_GATEWAY_CONTAINER = TRAEFIK_WAKE_GATEWAY_CONTAINER,
|
|
158
|
+
TRAEFIK_WAKE_GATEWAY_PORT = TRAEFIK_WAKE_GATEWAY_PORT,
|
|
149
159
|
NATS_HOST = NATS_HOST,
|
|
150
160
|
NATS_PORT = NATS_PORT,
|
|
151
161
|
NATS_URL = NATS_URL,
|
|
@@ -61,7 +61,7 @@ def L1_generate_os_base(base_image = None, maintainer = None, use_golden = True)
|
|
|
61
61
|
"# ---- L1: os_base (" + base_image + ") ----\n"
|
|
62
62
|
+ "FROM " + base_image + " AS l1_os_base\n"
|
|
63
63
|
+ "LABEL maintainer=\"" + maintainer + "\"\n"
|
|
64
|
-
+ "RUN apk add --no-cache ca-certificates tzdata curl
|
|
64
|
+
+ "RUN apk add --no-cache ca-certificates tzdata curl bash openssl \\\n"
|
|
65
65
|
+ " && addgroup -S app && adduser -S app -G app\n"
|
|
66
66
|
+ "WORKDIR /app\n"
|
|
67
67
|
)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# === INLINED CONSTANTS for pure extension loading ===
|
|
2
2
|
BASE_PORT_FRONTEND = 3000
|
|
3
3
|
BASE_PORT_BACKEND = 4000
|
|
4
|
-
get_docker_healthcheck_config = lambda: {"path": "/health", "interval": "10s", "timeout": "5s"}
|
|
5
4
|
OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
|
|
6
5
|
# === END INLINED CONSTANTS ===
|
|
7
6
|
|
|
@@ -29,6 +28,7 @@ load('./prisma/prisma_runtime.star',
|
|
|
29
28
|
)
|
|
30
29
|
|
|
31
30
|
load('./bun_helpers.star', 'bun_hoisted_packages_symlink_fix')
|
|
31
|
+
load('../config/healthcheck.star', 'DOCKER_HEALTHCHECK', 'dockerfile_healthcheck_flags')
|
|
32
32
|
load('./infisical/infisical_docker.star', 'InfisicalDocker')
|
|
33
33
|
|
|
34
34
|
|
|
@@ -51,8 +51,55 @@ _docker_cfg = GLOBAL_CONFIG['docker']
|
|
|
51
51
|
GOLDEN_L4_BACKEND_IMAGE = _docker_cfg.get('golden_l4_backend_image', _GOLDEN_PREFIX + '-l4-backend:latest')
|
|
52
52
|
GOLDEN_L4_FRONTEND_IMAGE = _docker_cfg.get('golden_l4_frontend_image', _GOLDEN_PREFIX + '-l4-frontend:latest')
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
DEFAULT_BUN_START_COMMAND = 'bun run start'
|
|
55
|
+
|
|
56
|
+
_ENTRY_FILE_EXTENSIONS = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.jsx', '.tsx']
|
|
57
|
+
_SHELL_CHARS = ['=', '&', '|', ';', '$', '`', '<', '>', '(', ')', '"', "'", '*', '\\']
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _is_entry_file(token):
|
|
61
|
+
if token.startswith('-'):
|
|
62
|
+
return False
|
|
63
|
+
for ch in _SHELL_CHARS:
|
|
64
|
+
if ch in token:
|
|
65
|
+
return False
|
|
66
|
+
for ext in _ENTRY_FILE_EXTENSIONS:
|
|
67
|
+
if token.endswith(ext):
|
|
68
|
+
return True
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def start_command_from_script(start_script):
|
|
73
|
+
"""
|
|
74
|
+
`bun run start` starts a Bun wrapper that spawns a second Bun runtime for the
|
|
75
|
+
real server. When the start script only runs one entry file, run that file
|
|
76
|
+
directly so each container holds a single Bun process. Anything else keeps
|
|
77
|
+
`bun run start` so custom start scripts behave exactly as before.
|
|
78
|
+
"""
|
|
79
|
+
tokens = (start_script or '').split()
|
|
80
|
+
if len(tokens) == 3 and tokens[0] == 'bun' and tokens[1] == 'run' and _is_entry_file(tokens[2]):
|
|
81
|
+
return 'bun ' + tokens[2]
|
|
82
|
+
if len(tokens) == 2 and tokens[0] == 'bun' and _is_entry_file(tokens[1]):
|
|
83
|
+
return 'bun ' + tokens[1]
|
|
84
|
+
return DEFAULT_BUN_START_COMMAND
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def resolve_bun_start_command(res_path, cmd = DEFAULT_BUN_START_COMMAND):
|
|
88
|
+
"""Resolve the runtime command from the service's package.json `start` script."""
|
|
89
|
+
if cmd != DEFAULT_BUN_START_COMMAND:
|
|
90
|
+
return cmd
|
|
91
|
+
package_json_path = res_path + '/package.json'
|
|
92
|
+
if not res_path.startswith('/'):
|
|
93
|
+
package_json_path = os.environ.get('TDK_PROJECT_ROOT', '.') + '/' + package_json_path
|
|
94
|
+
if not os.path.exists(package_json_path):
|
|
95
|
+
return cmd
|
|
96
|
+
package_json = read_json(package_json_path, default = {}) or {}
|
|
97
|
+
scripts = package_json.get('scripts', {}) or {}
|
|
98
|
+
return start_command_from_script(scripts.get('start', ''))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _cmd_instruction(command):
|
|
102
|
+
return "CMD [" + ", ".join(["\"" + part + "\"" for part in command.split()]) + "]\n"
|
|
56
103
|
|
|
57
104
|
|
|
58
105
|
def L4_generate_backend_runtime(res_path, port = BASE_PORT_BACKEND, cmd = 'bun run start', use_infisical = False, resource_name = 'service', use_golden = True, use_prisma = True, has_prisma_config = False, manifest = None):
|
|
@@ -79,6 +126,7 @@ def L4_generate_backend_runtime(res_path, port = BASE_PORT_BACKEND, cmd = 'bun r
|
|
|
79
126
|
"""
|
|
80
127
|
# Normalize res_path for Docker COPY (must be relative to build context)
|
|
81
128
|
res_path_rel = normalize_res_path(res_path)
|
|
129
|
+
cmd = resolve_bun_start_command(res_path, cmd)
|
|
82
130
|
|
|
83
131
|
# Use golden L4-backend image if enabled (production-optimized backend runtime)
|
|
84
132
|
base_image = GOLDEN_L4_BACKEND_IMAGE if use_golden else BUN_BASE
|
|
@@ -91,9 +139,6 @@ def L4_generate_backend_runtime(res_path, port = BASE_PORT_BACKEND, cmd = 'bun r
|
|
|
91
139
|
"ARG SKIP_INFISICAL_SETUP=0\n",
|
|
92
140
|
"ENV NODE_ENV=production\n",
|
|
93
141
|
"COPY --from=l3_production_purger /app/" + res_path + "/node_modules ./node_modules\n",
|
|
94
|
-
# Bun isolated linker creates service symlinks that resolve through /app/node_modules/.bun.
|
|
95
|
-
# Copy from production_purger to avoid dragging full dev dependency store into runtime.
|
|
96
|
-
"COPY --from=l3_production_purger /app/" + res_path + "/node_modules/.bun /app/node_modules/.bun\n",
|
|
97
142
|
prisma_runtime_copy(res_path) if use_prisma else "",
|
|
98
143
|
prisma_cli_runtime_copy(res_path) if use_prisma else "",
|
|
99
144
|
"COPY --from=l3_backend_build /app/" + res_path + "/dist ./dist\n",
|
|
@@ -124,12 +169,7 @@ def L4_generate_backend_runtime(res_path, port = BASE_PORT_BACKEND, cmd = 'bun r
|
|
|
124
169
|
|
|
125
170
|
# Only add healthcheck if not using golden image (already in golden L4)
|
|
126
171
|
if not use_golden:
|
|
127
|
-
parts.append("HEALTHCHECK
|
|
128
|
-
_docker_health["interval_seconds"],
|
|
129
|
-
_docker_health["timeout_seconds"],
|
|
130
|
-
_docker_health["start_period_seconds"],
|
|
131
|
-
_docker_health["retries"],
|
|
132
|
-
))
|
|
172
|
+
parts.append("HEALTHCHECK " + dockerfile_healthcheck_flags() + " CMD bun --version || exit 1\n")
|
|
133
173
|
|
|
134
174
|
if use_infisical:
|
|
135
175
|
# Use InfisicalDocker generator for proper secret injection setup
|
|
@@ -143,7 +183,7 @@ def L4_generate_backend_runtime(res_path, port = BASE_PORT_BACKEND, cmd = 'bun r
|
|
|
143
183
|
)
|
|
144
184
|
parts.append(infisical_setup)
|
|
145
185
|
else:
|
|
146
|
-
parts.append(
|
|
186
|
+
parts.append(_cmd_instruction(cmd))
|
|
147
187
|
|
|
148
188
|
# Provide build targets that match external tooling (e.g. --target production/ development)
|
|
149
189
|
parts.append("\nFROM l4_backend_runtime AS production\nFROM l4_backend_runtime AS development\n")
|
|
@@ -184,12 +224,7 @@ def L4_generate_frontend_runtime(res_path, target_path = '/usr/share/nginx/html'
|
|
|
184
224
|
+ "COPY --from=l3_frontend_build /app/" + res_path + "/dist " + target_path + "\n"
|
|
185
225
|
+ "COPY " + res_path_rel + "/" + OUTPUT.AUTOGENERATED_FOLDER + "/nginx.autogenerated.conf /etc/nginx/conf.d/default.conf\n"
|
|
186
226
|
+ "EXPOSE 80\n"
|
|
187
|
-
+ ("" if use_golden else "HEALTHCHECK
|
|
188
|
-
_docker_health["interval_seconds"],
|
|
189
|
-
_docker_health["timeout_seconds"],
|
|
190
|
-
_docker_health["frontend_start_period_seconds"],
|
|
191
|
-
_docker_health["retries"],
|
|
192
|
-
))
|
|
227
|
+
+ ("" if use_golden else "HEALTHCHECK " + dockerfile_healthcheck_flags(DOCKER_HEALTHCHECK["frontend_start_period_seconds"]) + " CMD curl -f http://localhost/ || exit 1\n")
|
|
193
228
|
+ "CMD [\"nginx\", \"-g\", \"daemon off;\"]\n"
|
|
194
229
|
+ "FROM l4_frontend_runtime AS production\n"
|
|
195
230
|
+ "FROM l4_frontend_runtime AS development\n"
|
|
@@ -208,7 +243,7 @@ def L4_generate_frontend_runtime(res_path, target_path = '/usr/share/nginx/html'
|
|
|
208
243
|
+ "COPY --from=l3_frontend_build /app/" + res_path + "/" + OUTPUT.AUTOGENERATED_FOLDER + " ./" + OUTPUT.AUTOGENERATED_FOLDER + "\n"
|
|
209
244
|
+ "COPY " + res_path_rel + "/package.json ./package.json\n"
|
|
210
245
|
+ "EXPOSE " + str(expose_port) + "\n"
|
|
211
|
-
+
|
|
246
|
+
+ _cmd_instruction(resolve_bun_start_command(res_path))
|
|
212
247
|
+ "FROM l4_frontend_runtime AS production\n"
|
|
213
248
|
+ "FROM l4_frontend_runtime AS development\n"
|
|
214
249
|
)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Traefik static (file-provider) route for a deferred (`sablier.deferStart`)
|
|
3
|
+
# resource -- see openspec/changes/prioritized-cold-start.
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# A never-created resource has no container and thus no Docker labels, so its
|
|
6
|
+
# route can't come from Traefik's Docker-label discovery the way every other
|
|
7
|
+
# resource's does. This generates the file-provider equivalent instead:
|
|
8
|
+
# same routing rule as the Docker-label `-project` router (traefik.star's
|
|
9
|
+
# get_backend_traefik_labels), but pointed at the wake gateway with a fixed
|
|
10
|
+
# LOW priority so the real, Docker-discovered router always wins once the
|
|
11
|
+
# container exists (verified empirically -- see design.md D4: at equal
|
|
12
|
+
# priority the file-provider router won even when the Docker-discovered
|
|
13
|
+
# backend was healthy).
|
|
14
|
+
#
|
|
15
|
+
# Config for a request travels to the gateway via headers (X-Wake-*), not
|
|
16
|
+
# the path/query. The route still strips the same `apiPath` prefix the real
|
|
17
|
+
# Docker-label `-project` router strips (get_backend_traefik_labels) before
|
|
18
|
+
# forwarding -- confirmed live against tdk-erp-system (task 7.2) that a
|
|
19
|
+
# backend's app only serves its bare health path (e.g. `/health`), not the
|
|
20
|
+
# full `/api/{name}/health` the public route is matched on; without this, the
|
|
21
|
+
# gateway's own proxy-through would forward the unstripped path once healthy
|
|
22
|
+
# and the app would 404 on it, even though the wake itself succeeded.
|
|
23
|
+
|
|
24
|
+
load("./traefik_helpers.star", "project_backend_rule", "cli_api_path", "build_entrypoints")
|
|
25
|
+
load("./traefik_constants.star",
|
|
26
|
+
"TRAEFIK_BACKEND_ENABLE_HTTP",
|
|
27
|
+
"TRAEFIK_BACKEND_ENABLE_HTTPS",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
STATIC_ROUTE_PRIORITY = 1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def normalize_abs_path(path):
|
|
34
|
+
"""Absolute path with `.`/`..` segments collapsed, or "" for "".
|
|
35
|
+
|
|
36
|
+
Tilt's os.path.abspath does not collapse `..` (confirmed:
|
|
37
|
+
abspath("/a/.tdk/.tdk-out/../..") comes back unchanged), and the
|
|
38
|
+
generated Tiltfile's project root is exactly that shape
|
|
39
|
+
(config.main_dir + "/../.."). The gateway's project mount and the compose
|
|
40
|
+
paths in its route headers must match byte-for-byte, so both go through here.
|
|
41
|
+
"""
|
|
42
|
+
if not path:
|
|
43
|
+
return ""
|
|
44
|
+
parts = []
|
|
45
|
+
for segment in os.path.abspath(path).split("/"):
|
|
46
|
+
if segment == "" or segment == ".":
|
|
47
|
+
continue
|
|
48
|
+
if segment == "..":
|
|
49
|
+
if parts:
|
|
50
|
+
parts.pop()
|
|
51
|
+
continue
|
|
52
|
+
parts.append(segment)
|
|
53
|
+
return "/" + "/".join(parts)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _container_name(compose_project_name, res_name):
|
|
57
|
+
"""Docker Compose's own naming convention: <project>-<service>-<replica>.
|
|
58
|
+
|
|
59
|
+
Confirmed empirically against a live TDK container (analytics-autogenerated
|
|
60
|
+
-data-governance-api-1) -- see design.md D2 Context. Always replica 1: a
|
|
61
|
+
deferred resource's replica count is unrelated to which single instance
|
|
62
|
+
the wake gateway needs to address."""
|
|
63
|
+
return compose_project_name + "-" + res_name + "-1"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def generate_static_wake_route(
|
|
67
|
+
resource_entry_name,
|
|
68
|
+
compose_project_name,
|
|
69
|
+
manifest,
|
|
70
|
+
internal_port,
|
|
71
|
+
gateway_host,
|
|
72
|
+
gateway_port,
|
|
73
|
+
dependencies=None,
|
|
74
|
+
compose_files=None,
|
|
75
|
+
env_file="",
|
|
76
|
+
image="",
|
|
77
|
+
):
|
|
78
|
+
"""YAML for one resource's Traefik file-provider dynamic-config entry.
|
|
79
|
+
|
|
80
|
+
`dependencies` is a list of (res_name, compose_project_name) tuples for
|
|
81
|
+
the resource's required startup dependencies (D5) -- each may live in a
|
|
82
|
+
different compose project than the resource itself, so the project name
|
|
83
|
+
travels alongside the resource name rather than being assumed shared.
|
|
84
|
+
|
|
85
|
+
`compose_files` (absolute paths), `env_file` and `image` let the gateway
|
|
86
|
+
create the container itself with `docker compose up --no-build` when the
|
|
87
|
+
image already exists, instead of waiting in Tilt's build queue behind
|
|
88
|
+
`tilt trigger`. Omitted headers make the gateway use `tilt trigger` only.
|
|
89
|
+
|
|
90
|
+
Written to the directory PlatformDockerConstants.TRAEFIK_DYNAMIC_DIR_REL
|
|
91
|
+
mounts into the standalone Traefik container (traefik_standalone.star).
|
|
92
|
+
"""
|
|
93
|
+
dependencies = dependencies or []
|
|
94
|
+
|
|
95
|
+
rule = project_backend_rule(manifest, resource_entry_name)
|
|
96
|
+
api_path = cli_api_path(resource_entry_name, manifest)
|
|
97
|
+
entrypoints = build_entrypoints(TRAEFIK_BACKEND_ENABLE_HTTP, TRAEFIK_BACKEND_ENABLE_HTTPS)
|
|
98
|
+
router_name = resource_entry_name + "-wake"
|
|
99
|
+
headers_middleware_name = resource_entry_name + "-wake-headers"
|
|
100
|
+
strip_middleware_name = resource_entry_name + "-wake-strip"
|
|
101
|
+
service_name = resource_entry_name + "-wake-gateway"
|
|
102
|
+
container_name = _container_name(compose_project_name, resource_entry_name)
|
|
103
|
+
deps_header = ",".join([
|
|
104
|
+
dep_res_name + "=" + _container_name(dep_project_name, dep_res_name)
|
|
105
|
+
for dep_res_name, dep_project_name in dependencies
|
|
106
|
+
])
|
|
107
|
+
compose_headers = ""
|
|
108
|
+
if compose_files and image:
|
|
109
|
+
compose_headers = (
|
|
110
|
+
'\n X-Wake-Compose-Project: "' + compose_project_name + '"'
|
|
111
|
+
+ '\n X-Wake-Compose-Files: "' + ",".join(compose_files) + '"'
|
|
112
|
+
+ '\n X-Wake-Env-File: "' + env_file + '"'
|
|
113
|
+
+ '\n X-Wake-Image: "' + image + '"'
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return """# SYSTEM-GENERATED - DO NOT EDIT (sablier.deferStart static wake route)
|
|
117
|
+
http:
|
|
118
|
+
routers:
|
|
119
|
+
{router_name}:
|
|
120
|
+
rule: "{rule}"
|
|
121
|
+
entryPoints:
|
|
122
|
+
- {entrypoint}
|
|
123
|
+
service: {service_name}
|
|
124
|
+
middlewares:
|
|
125
|
+
- {strip_middleware_name}
|
|
126
|
+
- {headers_middleware_name}
|
|
127
|
+
priority: {priority}
|
|
128
|
+
middlewares:
|
|
129
|
+
{strip_middleware_name}:
|
|
130
|
+
stripPrefix:
|
|
131
|
+
prefixes:
|
|
132
|
+
- "{api_path}"
|
|
133
|
+
{headers_middleware_name}:
|
|
134
|
+
headers:
|
|
135
|
+
customRequestHeaders:
|
|
136
|
+
X-Wake-Resource: "{resource_entry_name}"
|
|
137
|
+
X-Wake-Container: "{container_name}"
|
|
138
|
+
X-Wake-Deps: "{deps_header}"
|
|
139
|
+
X-Wake-Proxy-Port: "{internal_port}"{compose_headers}
|
|
140
|
+
services:
|
|
141
|
+
{service_name}:
|
|
142
|
+
loadBalancer:
|
|
143
|
+
servers:
|
|
144
|
+
- url: "http://{gateway_host}:{gateway_port}"
|
|
145
|
+
""".format(
|
|
146
|
+
router_name=router_name,
|
|
147
|
+
rule=rule,
|
|
148
|
+
entrypoint=entrypoints.split(",")[0],
|
|
149
|
+
service_name=service_name,
|
|
150
|
+
strip_middleware_name=strip_middleware_name,
|
|
151
|
+
headers_middleware_name=headers_middleware_name,
|
|
152
|
+
api_path=api_path,
|
|
153
|
+
priority=STATIC_ROUTE_PRIORITY,
|
|
154
|
+
resource_entry_name=resource_entry_name,
|
|
155
|
+
container_name=container_name,
|
|
156
|
+
deps_header=deps_header,
|
|
157
|
+
internal_port=internal_port,
|
|
158
|
+
compose_headers=compose_headers,
|
|
159
|
+
gateway_host=gateway_host,
|
|
160
|
+
gateway_port=gateway_port,
|
|
161
|
+
)
|
|
@@ -64,7 +64,7 @@ def generate_backend_tsconfig(resource_path, prisma_path, write_fn, internal_dep
|
|
|
64
64
|
# isolated linker), while @types/node is only a transitive dependency of
|
|
65
65
|
# bun-types and isn't hoisted to the top level under the isolated linker -
|
|
66
66
|
# "types": ["node"] fails to resolve there even though it works locally.
|
|
67
|
-
"types": ["
|
|
67
|
+
"types": ["node"],
|
|
68
68
|
}
|
|
69
69
|
content = {
|
|
70
70
|
"compilerOptions": compiler_options,
|
|
@@ -114,7 +114,7 @@ def generate_backend_tsconfig(resource_path, prisma_path, write_fn, internal_dep
|
|
|
114
114
|
"outDir": "./dist",
|
|
115
115
|
"rootDir": docker_root_dir,
|
|
116
116
|
# See note above: "bun" resolves under the isolated linker, "node" doesn't.
|
|
117
|
-
"types": ["
|
|
117
|
+
"types": ["node"],
|
|
118
118
|
}
|
|
119
119
|
docker_content = {
|
|
120
120
|
"compilerOptions": docker_compiler_options,
|
|
@@ -93,7 +93,7 @@ def generate_frontend_tsconfig(resource_path, write_fn, internal_deps=None, is_d
|
|
|
93
93
|
# (including Docker's isolated linker), while @types/node (when only pulled in
|
|
94
94
|
# transitively, e.g. via bun-types) isn't hoisted under the isolated linker and
|
|
95
95
|
# "types": ["node"] fails to resolve there even though it works locally.
|
|
96
|
-
"types": ["
|
|
96
|
+
"types": ["node"],
|
|
97
97
|
},
|
|
98
98
|
"exclude": [
|
|
99
99
|
"node_modules",
|
|
@@ -277,6 +277,12 @@ MANIFEST_SCHEMA = {
|
|
|
277
277
|
'description': 'How long the resource stays up after its last request, as a Go duration (e.g. "10m", "1h")',
|
|
278
278
|
'example': '10m',
|
|
279
279
|
},
|
|
280
|
+
'deferStart': {
|
|
281
|
+
'type': 'boolean',
|
|
282
|
+
'required': False,
|
|
283
|
+
'default': False,
|
|
284
|
+
'description': 'Skip this resource during tdk up entirely (no container is created or started); a Traefik static route wakes it and its dependencies on the first request instead. Requires enable: true.',
|
|
285
|
+
},
|
|
280
286
|
},
|
|
281
287
|
'description': 'On-demand scaling via Sablier: stop idle containers, wake them on the next request. Not recommended for stateful resources (databases) with in-flight writes. (Premium - requires TDK_LICENSE_KEY; see hasSablierLicense() in extension-fetch.ts)',
|
|
282
288
|
},
|
|
@@ -388,6 +388,22 @@ def _validate_cross_field(manifest, context):
|
|
|
388
388
|
else:
|
|
389
389
|
stats['rules_passed'] += 1
|
|
390
390
|
|
|
391
|
+
# sablier.deferStart requires sablier.enable
|
|
392
|
+
sablier = manifest.get('sablier', {}) or {}
|
|
393
|
+
if sablier.get('deferStart', False):
|
|
394
|
+
stats['fields_checked'] += 1
|
|
395
|
+
|
|
396
|
+
if not sablier.get('enable', False):
|
|
397
|
+
errors.append(ManifestErrors.new(
|
|
398
|
+
message="sablier.deferStart requires sablier.enable to also be true",
|
|
399
|
+
category=ManifestErrors.CATEGORY['VALIDATION'],
|
|
400
|
+
severity=ManifestErrors.SEVERITY['ERROR'],
|
|
401
|
+
context={'field': 'sablier.deferStart', 'sablier': sablier},
|
|
402
|
+
))
|
|
403
|
+
stats['rules_failed'] += 1
|
|
404
|
+
else:
|
|
405
|
+
stats['rules_passed'] += 1
|
|
406
|
+
|
|
391
407
|
# Backend should have Traefik config
|
|
392
408
|
if app_type == 'backend':
|
|
393
409
|
stats['fields_checked'] += 1
|