@tdk-landscape/tdk-cli-core 1.3.49 → 1.3.50
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 +149 -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/engine/assets/docker/wake-gateway/Dockerfile +26 -0
- package/engine/assets/docker/wake-gateway/decide.test.ts +87 -0
- package/engine/assets/docker/wake-gateway/decide.ts +75 -0
- package/engine/assets/docker/wake-gateway/server.ts +220 -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 +44 -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 +107 -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 +107 -8
- 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 +113 -0
- package/openspec/changes/prioritized-cold-start/proposal.md +40 -0
- package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +92 -0
- package/openspec/changes/prioritized-cold-start/tasks.md +46 -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/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 +38 -0
- package/tests/tilt-engine/test_traefik_static_wake_route.py +119 -0
- package/bun.lock +0 -538
|
@@ -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,107 @@
|
|
|
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, so the original request path reaches the real backend
|
|
17
|
+
# unchanged once the resource is ready (see wake-gateway/server.ts).
|
|
18
|
+
|
|
19
|
+
load("./traefik_helpers.star", "project_backend_rule", "build_entrypoints")
|
|
20
|
+
load("./traefik_constants.star",
|
|
21
|
+
"TRAEFIK_BACKEND_ENABLE_HTTP",
|
|
22
|
+
"TRAEFIK_BACKEND_ENABLE_HTTPS",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
STATIC_ROUTE_PRIORITY = 1
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _container_name(compose_project_name, res_name):
|
|
29
|
+
"""Docker Compose's own naming convention: <project>-<service>-<replica>.
|
|
30
|
+
|
|
31
|
+
Confirmed empirically against a live TDK container (analytics-autogenerated
|
|
32
|
+
-data-governance-api-1) -- see design.md D2 Context. Always replica 1: a
|
|
33
|
+
deferred resource's replica count is unrelated to which single instance
|
|
34
|
+
the wake gateway needs to address."""
|
|
35
|
+
return compose_project_name + "-" + res_name + "-1"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def generate_static_wake_route(
|
|
39
|
+
resource_entry_name,
|
|
40
|
+
compose_project_name,
|
|
41
|
+
manifest,
|
|
42
|
+
internal_port,
|
|
43
|
+
gateway_host,
|
|
44
|
+
gateway_port,
|
|
45
|
+
dependencies=None,
|
|
46
|
+
):
|
|
47
|
+
"""YAML for one resource's Traefik file-provider dynamic-config entry.
|
|
48
|
+
|
|
49
|
+
`dependencies` is a list of (res_name, compose_project_name) tuples for
|
|
50
|
+
the resource's required startup dependencies (D5) -- each may live in a
|
|
51
|
+
different compose project than the resource itself, so the project name
|
|
52
|
+
travels alongside the resource name rather than being assumed shared.
|
|
53
|
+
|
|
54
|
+
Written to the directory PlatformDockerConstants.TRAEFIK_DYNAMIC_DIR_REL
|
|
55
|
+
mounts into the standalone Traefik container (traefik_standalone.star).
|
|
56
|
+
"""
|
|
57
|
+
dependencies = dependencies or []
|
|
58
|
+
|
|
59
|
+
rule = project_backend_rule(manifest, resource_entry_name)
|
|
60
|
+
entrypoints = build_entrypoints(TRAEFIK_BACKEND_ENABLE_HTTP, TRAEFIK_BACKEND_ENABLE_HTTPS)
|
|
61
|
+
router_name = resource_entry_name + "-wake"
|
|
62
|
+
middleware_name = resource_entry_name + "-wake-headers"
|
|
63
|
+
service_name = resource_entry_name + "-wake-gateway"
|
|
64
|
+
container_name = _container_name(compose_project_name, resource_entry_name)
|
|
65
|
+
deps_header = ",".join([
|
|
66
|
+
dep_res_name + "=" + _container_name(dep_project_name, dep_res_name)
|
|
67
|
+
for dep_res_name, dep_project_name in dependencies
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
return """# SYSTEM-GENERATED - DO NOT EDIT (sablier.deferStart static wake route)
|
|
71
|
+
http:
|
|
72
|
+
routers:
|
|
73
|
+
{router_name}:
|
|
74
|
+
rule: "{rule}"
|
|
75
|
+
entryPoints:
|
|
76
|
+
- {entrypoint}
|
|
77
|
+
service: {service_name}
|
|
78
|
+
middlewares:
|
|
79
|
+
- {middleware_name}
|
|
80
|
+
priority: {priority}
|
|
81
|
+
middlewares:
|
|
82
|
+
{middleware_name}:
|
|
83
|
+
headers:
|
|
84
|
+
customRequestHeaders:
|
|
85
|
+
X-Wake-Resource: "{resource_entry_name}"
|
|
86
|
+
X-Wake-Container: "{container_name}"
|
|
87
|
+
X-Wake-Deps: "{deps_header}"
|
|
88
|
+
X-Wake-Proxy-Port: "{internal_port}"
|
|
89
|
+
services:
|
|
90
|
+
{service_name}:
|
|
91
|
+
loadBalancer:
|
|
92
|
+
servers:
|
|
93
|
+
- url: "http://{gateway_host}:{gateway_port}"
|
|
94
|
+
""".format(
|
|
95
|
+
router_name=router_name,
|
|
96
|
+
rule=rule,
|
|
97
|
+
entrypoint=entrypoints.split(",")[0],
|
|
98
|
+
service_name=service_name,
|
|
99
|
+
middleware_name=middleware_name,
|
|
100
|
+
priority=STATIC_ROUTE_PRIORITY,
|
|
101
|
+
resource_entry_name=resource_entry_name,
|
|
102
|
+
container_name=container_name,
|
|
103
|
+
deps_header=deps_header,
|
|
104
|
+
internal_port=internal_port,
|
|
105
|
+
gateway_host=gateway_host,
|
|
106
|
+
gateway_port=gateway_port,
|
|
107
|
+
)
|
|
@@ -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
|
package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star
CHANGED
|
@@ -9,7 +9,9 @@ OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
|
|
|
9
9
|
|
|
10
10
|
load('../../../platform/docker/index.star', 'Docker')
|
|
11
11
|
load('../../../platform/docker/constants.star', 'PlatformDockerConstants')
|
|
12
|
-
load('../../../tilt/manifest/constants.star', 'BASE_PORT_FRONTEND')
|
|
12
|
+
load('../../../tilt/manifest/constants.star', 'BASE_PORT_FRONTEND', 'BASE_PORT_BACKEND')
|
|
13
|
+
load('../../../platform/docker/networking/traefik_static_routes.star', 'generate_static_wake_route')
|
|
14
|
+
load('../../../platform/docker/networking/sablier_container_cycle.star', 'sablier_middleware_suffix')
|
|
13
15
|
|
|
14
16
|
load('./builders/typescript.star', 'TypescriptBuilders')
|
|
15
17
|
|
|
@@ -114,6 +116,71 @@ def _build_resource_deps(res, res_name, manifest, resource_config, infra_deps, c
|
|
|
114
116
|
return res_deps
|
|
115
117
|
|
|
116
118
|
|
|
119
|
+
def resource_defers_start(manifest):
|
|
120
|
+
"""True when a manifest opts into `sablier.enable` + `sablier.deferStart`
|
|
121
|
+
AND the licensed sablier module is actually active.
|
|
122
|
+
|
|
123
|
+
deferStart stays behind the same TDK_LICENSE_KEY gate as the rest of
|
|
124
|
+
`sablier` (an explicit product decision, not just the opt-in manifest
|
|
125
|
+
flag): the free-tier stub's sablier_middleware_suffix() always reports
|
|
126
|
+
`enabled=False` regardless of what the manifest says, so checking through
|
|
127
|
+
it here -- rather than reading manifest['sablier']['enable'] directly --
|
|
128
|
+
is what keeps deferStart off for unlicensed projects. Without this check,
|
|
129
|
+
a project with no license would still get "no container created or
|
|
130
|
+
started," silently bypassing the license boundary for the disable-the-
|
|
131
|
+
container half of the feature even though the Docker-label wake half
|
|
132
|
+
stays correctly disabled.
|
|
133
|
+
|
|
134
|
+
A deferred resource's image still builds (docker_build has no auto_init
|
|
135
|
+
gate), but its dc_resource/run-only/replica resources are held back with
|
|
136
|
+
auto_init=False: no container is created or started until the Traefik
|
|
137
|
+
static route wakes it on first request (see openspec/changes/
|
|
138
|
+
prioritized-cold-start)."""
|
|
139
|
+
_, sablier_licensed_and_enabled = sablier_middleware_suffix(manifest, '')
|
|
140
|
+
if not sablier_licensed_and_enabled:
|
|
141
|
+
return False
|
|
142
|
+
sablier = manifest.get('sablier', {}) if manifest else {}
|
|
143
|
+
return bool(sablier.get('deferStart', False))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def resolve_manifest_dependency_names(manifest, all_services_map):
|
|
147
|
+
"""Resolve a manifest's `dependsOn` short names to full resource names,
|
|
148
|
+
reusing the same `_resolve_dependency_to_resource` that `_build_resource_deps`
|
|
149
|
+
already trusts for Tilt's own resource_deps ordering -- an ambiguous name
|
|
150
|
+
behaves identically here as it already does there."""
|
|
151
|
+
resolved = []
|
|
152
|
+
for dep in (manifest.get('dependsOn', []) if manifest else []):
|
|
153
|
+
resolved_dep = _resolve_dependency_to_resource(dep, all_services_map)
|
|
154
|
+
if resolved_dep and resolved_dep not in resolved:
|
|
155
|
+
resolved.append(resolved_dep)
|
|
156
|
+
return resolved
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def resolve_defer_start_dependencies(manifest, resource_manifests, all_services_map):
|
|
160
|
+
"""Of a deferStart resource's manifest dependencies, resolve the subset
|
|
161
|
+
that are themselves deferStart resources, for the wake gateway's
|
|
162
|
+
X-Wake-Deps (see generate_static_wake_route's `dependencies` param and
|
|
163
|
+
openspec/changes/prioritized-cold-start design.md D5).
|
|
164
|
+
|
|
165
|
+
A dependsOn name `_resolve_dependency_to_resource` can't map to an exact
|
|
166
|
+
resource (its own fallback branches -- see that function) won't be a key
|
|
167
|
+
in `resource_manifests` either, so it's quietly excluded here rather than
|
|
168
|
+
guessed at -- consistent with D5's existing stance. Always-on infra
|
|
169
|
+
(database, NATS) is never itself a deferStart resource (D5), so in
|
|
170
|
+
practice this only ever picks up the one case D5 flags as mattering: a
|
|
171
|
+
deferStart resource depending on another."""
|
|
172
|
+
dependencies = []
|
|
173
|
+
for resolved_dep in resolve_manifest_dependency_names(manifest, all_services_map):
|
|
174
|
+
dep_manifest = resource_manifests.get(resolved_dep)
|
|
175
|
+
if not dep_manifest or not resource_defers_start(dep_manifest):
|
|
176
|
+
continue
|
|
177
|
+
dep_service_name = all_services_map.get(resolved_dep, {}).get('name', '')
|
|
178
|
+
if not dep_service_name:
|
|
179
|
+
continue
|
|
180
|
+
dependencies.append((resolved_dep, dep_service_name + '-autogenerated'))
|
|
181
|
+
return dependencies
|
|
182
|
+
|
|
183
|
+
|
|
117
184
|
def _register_replicas(config, auto_init_apps):
|
|
118
185
|
"""Register additional replica resources for high-availability services."""
|
|
119
186
|
replicas = config.get('replicas', 1)
|
|
@@ -219,9 +286,15 @@ def _register_single_resource(config, resource_path, compose_project_name, auto_
|
|
|
219
286
|
"""Register a single dc_resource with optional docker_build and run-only resource."""
|
|
220
287
|
app_type = config.get('app_type', 'backend')
|
|
221
288
|
res_name = config['res_name']
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
289
|
+
|
|
290
|
+
# A sablier.enable + sablier.deferStart resource is held back from tdk up's
|
|
291
|
+
# normal bring-up: its container is never created or started here, only via
|
|
292
|
+
# the Traefik wake path on first request. docker_build below is unaffected
|
|
293
|
+
# (it has no auto_init gate), so the image still builds either way.
|
|
294
|
+
effective_auto_init = auto_init_apps and not resource_defers_start(config.get('manifest', {}))
|
|
295
|
+
|
|
296
|
+
print("DEBUG REGISTER: Registering '{}' app_type='{}' auto_init={}".format(res_name, app_type, effective_auto_init))
|
|
297
|
+
|
|
225
298
|
# Skip docker_build for libraries and SDKs - they don't have Dockerfiles
|
|
226
299
|
if app_type not in ['library', 'sdk'] and config.get('syncs'):
|
|
227
300
|
print("DEBUG REGISTER: '{}' has syncs, calling docker_build".format(res_name))
|
|
@@ -239,12 +312,12 @@ def _register_single_resource(config, resource_path, compose_project_name, auto_
|
|
|
239
312
|
config['res_name'],
|
|
240
313
|
labels=config['labels'],
|
|
241
314
|
resource_deps=config['res_deps'],
|
|
242
|
-
auto_init=
|
|
315
|
+
auto_init=effective_auto_init,
|
|
243
316
|
)
|
|
244
317
|
print("DEBUG REGISTER: dc_resource completed for '{}'".format(res_name))
|
|
245
318
|
|
|
246
|
-
_register_run_only_resource(config, resource_path, compose_project_name,
|
|
247
|
-
_register_replicas(config,
|
|
319
|
+
_register_run_only_resource(config, resource_path, compose_project_name, effective_auto_init)
|
|
320
|
+
_register_replicas(config, effective_auto_init)
|
|
248
321
|
|
|
249
322
|
|
|
250
323
|
def _register_dc_resources(resource_configs, resource_path, compose_project_name, auto_init_apps):
|
|
@@ -322,6 +395,7 @@ def _build_resource_config(res, manifest, resource_config, full_res_path, infra_
|
|
|
322
395
|
'res_path': full_res_path,
|
|
323
396
|
'env': env_vars,
|
|
324
397
|
'project_root': project_root,
|
|
398
|
+
'manifest': manifest,
|
|
325
399
|
}
|
|
326
400
|
|
|
327
401
|
|
|
@@ -388,7 +462,32 @@ def register_compose_resources(resource_config, ctx, runtime_flags, manifest_sta
|
|
|
388
462
|
entry = _process_frontend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable)
|
|
389
463
|
else:
|
|
390
464
|
entry = _process_backend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable)
|
|
391
|
-
|
|
465
|
+
# A deferStart resource has no container Traefik can discover via
|
|
466
|
+
# labels until its first request creates one (D2/D4) -- give it a
|
|
467
|
+
# static file-provider route instead, pointed at the wake gateway.
|
|
468
|
+
# Scoped to backends for now, matching the proposal's motivating
|
|
469
|
+
# case; frontend support is a natural follow-up, not implemented
|
|
470
|
+
# here. Dependency waking (D5): TDK's real infra dependencies
|
|
471
|
+
# (database, NATS, a resource's own migrator) live in
|
|
472
|
+
# separately-registered compose files that `depends_on` cannot
|
|
473
|
+
# reference (confirmed empirically, see design.md D5), and they're
|
|
474
|
+
# always-on infra that's essentially never actually cold, so they
|
|
475
|
+
# never show up here regardless. The one real case D5 flags as
|
|
476
|
+
# mattering -- a peer resource that is itself deferStart -- is
|
|
477
|
+
# resolved via the manifest's own `dependsOn` below.
|
|
478
|
+
if resource_defers_start(manifest) and write_file:
|
|
479
|
+
internal_port = res.get('port', manifest.get('port', BASE_PORT_BACKEND))
|
|
480
|
+
static_route_path = PlatformDockerConstants.TRAEFIK_DYNAMIC_DIR_REL + '/' + res['name'] + '.yml'
|
|
481
|
+
write_file(static_route_path, generate_static_wake_route(
|
|
482
|
+
resource_entry_name=res['name'],
|
|
483
|
+
compose_project_name=compose_project_name,
|
|
484
|
+
manifest=manifest,
|
|
485
|
+
internal_port=internal_port,
|
|
486
|
+
gateway_host=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_CONTAINER,
|
|
487
|
+
gateway_port=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_PORT,
|
|
488
|
+
dependencies=resolve_defer_start_dependencies(manifest, resource_manifests, all_services_map),
|
|
489
|
+
))
|
|
490
|
+
|
|
392
491
|
resource_entries.append(entry)
|
|
393
492
|
resource_configs.append(_build_resource_config(
|
|
394
493
|
res, manifest, resource_config, full_res_path, infra_deps,
|
|
@@ -84,14 +84,20 @@ def _get_resource_only_paths(resource_path):
|
|
|
84
84
|
|
|
85
85
|
# Shared runtime scripts copied in generated Dockerfiles.
|
|
86
86
|
'shared-platform-engineering/docker-templates',
|
|
87
|
-
|
|
88
|
-
# Common DDD layers used by backend Dockerfiles.
|
|
89
|
-
'shared-ddd-layers/domain',
|
|
90
|
-
'shared-ddd-layers/infrastructure',
|
|
91
87
|
]
|
|
92
88
|
|
|
93
|
-
# Include only internal libraries referenced by this service package.json
|
|
94
|
-
#
|
|
89
|
+
# Include only internal libraries referenced by this service package.json
|
|
90
|
+
# (covers shared-ddd-layers/<lib> too, via resolve_lib_path's 'ddd' root -
|
|
91
|
+
# no Dockerfile ever COPYs a bare shared-ddd-layers/domain or /infrastructure
|
|
92
|
+
# path, so hardcoding those two here was pure dead weight: every one of a
|
|
93
|
+
# large landscape's docker_build resources got its own Tilt watch
|
|
94
|
+
# registration on paths most projects don't even have on disk. At 107
|
|
95
|
+
# resources that's 107 duplicate watchers on the same couple of
|
|
96
|
+
# nonexistent paths - real amplification if anything ever touches them,
|
|
97
|
+
# and a plausible contributor to "fsnotify: queue or buffer overflow" at
|
|
98
|
+
# this scale, on top of being wasted watch overhead regardless).
|
|
99
|
+
# This also prevents unrelated library changes from triggering rebuilds
|
|
100
|
+
# of services that don't use them.
|
|
95
101
|
deps_map = Utils.build_deps_mapping(resource_path)
|
|
96
102
|
for dep_path in deps_map.values():
|
|
97
103
|
if dep_path not in only_paths:
|
|
@@ -361,7 +361,8 @@ def _generate_all_configs_for_resource(
|
|
|
361
361
|
res_path=resource_path,
|
|
362
362
|
resource_name=resource_name,
|
|
363
363
|
use_golden=should_enable('golden-image'),
|
|
364
|
-
use_prisma='prisma' in manifest.get('featuresEnabled', [])
|
|
364
|
+
use_prisma='prisma' in manifest.get('featuresEnabled', []),
|
|
365
|
+
manifest=manifest,
|
|
365
366
|
)
|
|
366
367
|
|
|
367
368
|
write_file(resource_path + "/" + OUTPUT.AUTOGENERATED_FOLDER + "/" + GENERATED_CONFIG_FILENAMES['dockerfile_app'], dockerfile_content)
|
package/knip.json
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Design
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
See `proposal.md` (Why) for the measurements. Current runtime layer behaviour relevant to the approach:
|
|
6
|
+
|
|
7
|
+
- The backend runtime Dockerfile is `ENTRYPOINT ["/entrypoint.sh"]` + `CMD ["bun", "run", "start"]`. `infisical-entrypoint.sh` ends in `exec "$@"`, so the `bun run start` wrapper becomes PID 1 and spawns the real `bun run dist/index.js` child. The wrapper is currently what receives `SIGTERM` from `docker stop` and passes it on to the child.
|
|
8
|
+
- The start command is set in several places: `RUNTIME_CONFIG.bun.backend_start_command` (`l3_builder_layers.star`), default `cmd` parameters in `dockerfile.star`, `golden_docker_generator_v2.star` and `l4_runtime_layers.star`, the `_runtime_setup` default (`infisical_docker.star`), and a hard-coded `CMD` for the Bun-static frontend (`l4_runtime_layers.star:213`). The Node runtime already uses `node dist/index.js` directly.
|
|
9
|
+
- Healthcheck timing lives in `get_docker_healthcheck_config` (`golden_image_dockerfile.star:6`), but `compose.star` (lines 141, 264) hard-codes `interval: 10s` with `retries: 6`.
|
|
10
|
+
- The per-service L4 layer already installs hugo only when a service's `featuresEnabled` contains `hugo`. The golden `l4_backend_bun` stage installs it for every backend anyway.
|
|
11
|
+
|
|
12
|
+
## Goals / Non-Goals
|
|
13
|
+
|
|
14
|
+
**Goals:**
|
|
15
|
+
- Meet every requirement in `specs/runtime-container-footprint/spec.md` without changing any service's source files or `package.json`.
|
|
16
|
+
- Keep a single source of truth for healthcheck timing.
|
|
17
|
+
|
|
18
|
+
**Non-Goals:**
|
|
19
|
+
- Reworking the entrypoint script beyond what signal handling needs.
|
|
20
|
+
- Default startup scope and Sablier scale-to-zero (see proposal Non-goals).
|
|
21
|
+
|
|
22
|
+
## Decisions
|
|
23
|
+
|
|
24
|
+
### D1: Resolve the start command at generation time, exec the entry file directly
|
|
25
|
+
|
|
26
|
+
When generating a Bun backend or Bun-static frontend runtime, the generator reads the service's `package.json` `scripts.start`. If it is exactly `bun run <file>` or `bun <file>` (one file path, no `&&`, `;`, pipes or env assignments), the runtime gets `CMD ["bun", "<file>"]`. In every other case it falls back to `CMD ["bun", "run", "start"]`. The resolver (`resolve_bun_start_command` in `l4_runtime_layers.star`) runs at the single point where the backend and Bun-static frontend `CMD` are emitted, both on the plain path and the Infisical-entrypoint path. Upstream defaults (`cmd='bun run start'` in `dockerfile.star`, `golden_docker_generator_v2.star` and `infisical_docker.star`) stay as they are and act as the "no explicit override" value. An explicit different `cmd` passed by a caller is always kept.
|
|
27
|
+
|
|
28
|
+
*Alternatives considered:*
|
|
29
|
+
- Rewriting `start` in each service's `package.json`: rejected because it edits user-owned files and breaks `bun run start` locally.
|
|
30
|
+
- Always emitting `bun <file>` from a fixed path: rejected because it silently ignores custom start scripts.
|
|
31
|
+
- `bun run --bun start`: rejected because it still keeps the wrapper process.
|
|
32
|
+
|
|
33
|
+
### D2: Add `init: true` to generated runtime services
|
|
34
|
+
|
|
35
|
+
With direct exec, Bun becomes PID 1. The kernel does not apply default signal actions to PID 1, so an app without a `SIGTERM` handler would ignore `docker stop` and be killed only after the stop timeout. Generated compose services get `init: true` (Docker's bundled tini, about 1 MB resident). Tini passes signals on and reaps zombies. It is not a Bun process, so the "exactly one Bun process" requirement still holds.
|
|
36
|
+
|
|
37
|
+
*Alternative:* inject a `SIGTERM` handler into generated service templates. Rejected because it doesn't cover existing services or custom servers.
|
|
38
|
+
|
|
39
|
+
### D3: One healthcheck config, 30 s interval with a 2 s start interval
|
|
40
|
+
|
|
41
|
+
A new shared module, `engine/topologies/platform/docker/config/healthcheck.star`, replaces the per-file inlined `get_docker_healthcheck_config` copies. The copies had drifted: `l4_runtime_layers.star`'s copy lacked keys its own code read. Defaults: `interval 30s`, `start_period 30s` (unchanged), `start_interval 2s`, `timeout 5s`, `retries 3`. Golden images, the L4 runtime layer, `compose.star` and `traefik_standalone.star` all read it. Each value can be overridden per run with `TDK_HEALTHCHECK_INTERVAL_SECONDS`, `TDK_HEALTHCHECK_START_INTERVAL_SECONDS`, `TDK_HEALTHCHECK_TIMEOUT_SECONDS`, `TDK_HEALTHCHECK_START_PERIOD_SECONDS` and `TDK_HEALTHCHECK_RETRIES`. Environment variables are used because there is no per-project healthcheck setting today, and `tdk up` does not pass the project `.env` through to Tilt.
|
|
42
|
+
|
|
43
|
+
During the start period Docker probes every 2 s, so a service is marked healthy right after it starts answering (see the spec's "Startup readiness is not delayed" scenario). After that, probes drop to every 30 s. With `retries 3`, a failing service is marked unhealthy within about 90 s, compared with about 60 s today.
|
|
44
|
+
|
|
45
|
+
*Alternative:* 30 s interval with no start interval. Rejected because first-healthy time would move from about 10 s to about 30 s per container, which slows every dependency chain in `tdk up`.
|
|
46
|
+
|
|
47
|
+
`start_interval` needs Docker Engine 25+ and Compose 2.20.2+. `tdk doctor` gets a check that fails with an upgrade hint on older versions.
|
|
48
|
+
|
|
49
|
+
### D4: Golden migrator stage declares `HEALTHCHECK NONE`
|
|
50
|
+
|
|
51
|
+
Resolved by task 1.2. Migrators are one-shot jobs: every migrator container is built from the per-service `l4_migrator_runtime` stage (`prisma_runtime.star`), which already sets `HEALTHCHECK NONE` and runs `ENTRYPOINT ["/app/migrate.sh"]` to completion. The golden `l4_migrator_golden` stage's `bunx prisma --version` healthcheck is therefore always overridden and never protects anything. It only runs if someone starts the bare golden image, and then it starts a bunx/Prisma process every interval. The golden stage now sets `HEALTHCHECK NONE` too, so both layers agree.
|
|
52
|
+
|
|
53
|
+
Evidence (2026-09-25): `grep -rn L4_generate_migrator_runtime engine/` shows a single caller (`golden_docker_generator_v2.star:57`), and that stage emits `HEALTHCHECK NONE` before `ENTRYPOINT`. No project in the workspace declares `"appType": "migrator"`.
|
|
54
|
+
|
|
55
|
+
*Alternative (original plan):* a `test -x` check on a symlinked Prisma binary. Dropped because a healthcheck on a run-to-completion container has no meaning.
|
|
56
|
+
|
|
57
|
+
### D5: Remove hugo from the golden backend stage only
|
|
58
|
+
|
|
59
|
+
Delete `RUN apk add --no-cache hugo` from `l4_backend_bun` in `golden_image_dockerfile.star`. The per-service feature-gated install stays, but it never actually ran: `manifest_resource.star` called `Docker.backend(...)` without `manifest`, so `featuresEnabled` was always empty in the L4 layer. The manifest is now passed through, which makes the per-service install work and makes removing hugo from the golden stage safe.
|
|
60
|
+
|
|
61
|
+
Evidence (task 1.1, 2026-09-25): no service in `tdk-erp-system`, `tdk-saas-starter`, `tdk-restaurant-example` or `tdk-user-management` lists `"hugo"` in `service.json`, and no source, script or config file mentions hugo (search excluded `node_modules`, `.autogenerated`, `dist` and `.tdk`). Removal affects no existing project.
|
|
62
|
+
|
|
63
|
+
### D6: Verifying the idle budget
|
|
64
|
+
|
|
65
|
+
`tests/tilt-engine/test_runtime_container_footprint.py` executes the real Starlark generators through `tilt alpha tiltfile-result` (skipped when Tilt isn't installed) rather than inspecting source text. Those tests assert the generated `CMD`, `init: true` and healthcheck values. The idle budget is checked by a smoke script that generates a template backend, runs it, waits for healthy, samples `docker stats` for 60 s, and fails if memory is over 64 MiB or CPU averages 2% or more. It's run manually and in the release checklist rather than on every CI run, because it needs Docker.
|
|
66
|
+
|
|
67
|
+
## Risks / Trade-offs
|
|
68
|
+
|
|
69
|
+
- [The start-script parser misreads a script, e.g. `bun run dist/index.js --port 4000`] → Only a single file argument qualifies. Anything with extra tokens falls back to `bun run start`, so the fallback is always the current behaviour.
|
|
70
|
+
- [Docker Engine / Compose is older than 25 / 2.20.2 and rejects `start_interval`] → New `tdk doctor` version check; the release notes state the minimum version.
|
|
71
|
+
- [Unhealthy detection is slower (about 90 s vs 60 s)] → Acceptable for local dev. `retries` stays configurable.
|
|
72
|
+
- [A project relied on hugo without declaring the feature] → The build fails loudly (`hugo: not found`). Migration note: add `"hugo"` to `featuresEnabled`. No project in this workspace is affected (see D5 evidence).
|
|
73
|
+
- [Background auto-commit tooling in this workspace pushes partial engine edits] → Make each decision's edits in one sitting and run the snapshot tests before moving on, so every intermediate push is still consistent.
|
|
74
|
+
|
|
75
|
+
## Migration Plan
|
|
76
|
+
|
|
77
|
+
1. Ship the engine changes in a TDK release.
|
|
78
|
+
2. In each project: run `tdk project --yes` to re-vendor `.tdk/.tdk-out/tdk-cli-ext/` and regenerate compose files and Dockerfiles, touch the Tiltfile, then rebuild golden layers with the `golden-layers-build` resource or a fresh `tdk up`.
|
|
79
|
+
3. Existing images keep working until they are rebuilt, and nothing needs to change inside services.
|
|
80
|
+
|
|
81
|
+
**Rollback:** revert the engine change, re-run `tdk project --yes`, and rebuild the golden layers.
|