@tdk-landscape/tdk-cli-core 1.3.144 → 1.3.146
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/CHANGELOG.md +1 -0
- package/README.md +2 -2
- package/Tiltfile +2 -0
- package/cli/dist/commands/doctor.js +55 -2
- package/cli/dist/commands/status.js +17 -20
- package/cli/dist/commands/up.js +10 -1
- package/cli/dist/generator/template-engine.js +6 -3
- package/cli/dist/utils/doctor-host.js +66 -0
- package/cli/dist/utils/doctor-runtime.js +60 -0
- package/cli/dist/utils/host-port-config.js +11 -2
- package/cli/dist/utils/host-port-plan.js +25 -3
- package/cli/dist/utils/service-urls.js +16 -2
- package/cli/dist/utils/smoke.js +28 -0
- package/cli/dist-bundle/chunk-SWAGCVPQ.js +321 -0
- package/cli/dist-bundle/cli.js +206 -206
- package/cli/dist-bundle/{cold-preflight-CGPSYGDB.js → cold-preflight-2ZV2PDU2.js} +1 -1
- package/cli/package.json +1 -1
- package/cli/templates/Tiltfile.hbs +12 -0
- package/engine/Tiltfile +2 -0
- package/engine/assets/scripts/cleanup-tilt-images.sh +2 -2
- package/engine/topologies/platform/docker/build/frontend_build_plan.star +65 -0
- package/engine/topologies/platform/docker/build/golden_image_build.star +104 -48
- package/engine/topologies/platform/docker/layers/l3_builder_layers.star +3 -1
- package/engine/topologies/tilt/resources/build_concurrency.star +56 -0
- package/engine/topologies/tilt/resources/infra-loader.star +4 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +24 -2
- package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +5 -1
- package/engine/topologies/tilt/resources/watchers.star +7 -4
- package/ext/ide-components/shared/templates/base.html +4 -8
- package/package.json +1 -1
- package/cli/dist-bundle/chunk-2PTDOT3Y.js +0 -315
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
-
import{sb as a,tb as b,ub as c,vb as d,wb as e}from"./chunk-
|
|
2
|
+
import{sb as a,tb as b,ub as c,vb as d,wb as e}from"./chunk-SWAGCVPQ.js";import"./chunk-42IVF3YT.js";import"./chunk-6EA4DGCY.js";export{e as assertMachineReadyOrExit,d as formatColdPreflight,a as nodeTooOld,b as requireVerifiedDockerVersions,c as runColdPreflight};
|
package/cli/package.json
CHANGED
|
@@ -85,6 +85,7 @@ load('ext://tdk-cli',
|
|
|
85
85
|
'Watchers',
|
|
86
86
|
'Infra',
|
|
87
87
|
'Docker',
|
|
88
|
+
'FrontendBuildPlan',
|
|
88
89
|
'load_ui_enhancements',
|
|
89
90
|
'APP_RESOURCES',
|
|
90
91
|
'GLOBAL_CONFIG_EXPORT',
|
|
@@ -266,6 +267,13 @@ for _KNOWN_SVC in _app_resources_list:
|
|
|
266
267
|
_KNOWN_TILT_RESOURCES.add(_KNOWN_RES_NAME)
|
|
267
268
|
_KNOWN_TILT_RESOURCES.add(_KNOWN_RES_NAME + '-run-only')
|
|
268
269
|
_KNOWN_TILT_RESOURCES.add(_KNOWN_RES_NAME + '-config-gen')
|
|
270
|
+
# A frontend whose build script is the standard `tsc && vite build` gets a
|
|
271
|
+
# non-blocking <service>-typecheck resource (see Docker frontend registration).
|
|
272
|
+
# Name it here, from the same plan and path, so focus mode can enable it.
|
|
273
|
+
if _KNOWN_RES.get('frontend', False) and not should_enable('backends-only'):
|
|
274
|
+
_KNOWN_RES_DIR = PROJECT_ROOT + '/' + _KNOWN_RES.get('_resource_path', _KNOWN_SVC.get('path', '') + '/' + _KNOWN_RES_NAME)
|
|
275
|
+
if FrontendBuildPlan(_KNOWN_RES_DIR + '/package.json')['typecheck']:
|
|
276
|
+
_KNOWN_TILT_RESOURCES.add(_KNOWN_RES_NAME + '-typecheck')
|
|
269
277
|
_KNOWN_RES_MANIFEST = _KNOWN_RES.get('_manifest', {})
|
|
270
278
|
_KNOWN_RES_SABLIER = _KNOWN_RES_MANIFEST.get('sablier', {})
|
|
271
279
|
# Mirrors _register_single_resource: the prebuild only exists for
|
|
@@ -280,6 +288,8 @@ if FOCUS_MODE and FOCUS_ENABLED_RESOURCES:
|
|
|
280
288
|
for _R in FOCUS_ENABLED_RESOURCES:
|
|
281
289
|
if _R in _KNOWN_TILT_RESOURCES:
|
|
282
290
|
_FILTERED_RESOURCES.append(_R)
|
|
291
|
+
if _R + '-typecheck' in _KNOWN_TILT_RESOURCES:
|
|
292
|
+
_FILTERED_RESOURCES.append(_R + '-typecheck')
|
|
283
293
|
# Focus expansion normally resolves stack names to concrete
|
|
284
294
|
# resources before this loop, so the stack branch below is not
|
|
285
295
|
# reached for the common `tdk up <stack>` path. Keep a deferred
|
|
@@ -301,6 +311,8 @@ if FOCUS_MODE and FOCUS_ENABLED_RESOURCES:
|
|
|
301
311
|
# enabled name it cannot find.
|
|
302
312
|
if _STACK_RES_NAME + '-image' in _DEFERRED_IMAGE_RESOURCES:
|
|
303
313
|
_FILTERED_RESOURCES.append(_STACK_RES_NAME + '-image')
|
|
314
|
+
if _STACK_RES_NAME + '-typecheck' in _KNOWN_TILT_RESOURCES:
|
|
315
|
+
_FILTERED_RESOURCES.append(_STACK_RES_NAME + '-typecheck')
|
|
304
316
|
else:
|
|
305
317
|
_MATCHED = [kr for kr in _KNOWN_TILT_RESOURCES if kr != 'service-discovery-daemon' and kr.startswith(_R + '-')]
|
|
306
318
|
_FILTERED_RESOURCES.extend(_MATCHED)
|
package/engine/Tiltfile
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
load('./topologies/tilt/common/utils.star', _Utils='Utils')
|
|
14
14
|
load('./topologies/tilt/discovery/loading.star', _Manifest='Manifest')
|
|
15
15
|
load('./topologies/tilt/config/global.star', _Config='Config')
|
|
16
|
+
load('./topologies/platform/docker/build/frontend_build_plan.star', _frontend_build_plan='frontend_build_plan')
|
|
16
17
|
|
|
17
18
|
# Registry
|
|
18
19
|
load('./topologies/tilt/discovery/registry.star',
|
|
@@ -49,6 +50,7 @@ load('./topologies/tilt/resources/orchestrator/apply_compose_resource_registrati
|
|
|
49
50
|
Utils = _Utils
|
|
50
51
|
Manifest = _Manifest
|
|
51
52
|
Config = _Config
|
|
53
|
+
FrontendBuildPlan = _frontend_build_plan
|
|
52
54
|
|
|
53
55
|
# Registry Data
|
|
54
56
|
GLOBAL_CONFIG_EXPORT = _GLOBAL_CONFIG_EXPORT
|
|
@@ -81,13 +81,13 @@ for repo in $REPOS; do
|
|
|
81
81
|
while IFS= read -r row; do
|
|
82
82
|
[[ -z "$row" ]] && continue
|
|
83
83
|
if (( i < KEEP_COUNT )); then
|
|
84
|
-
((i
|
|
84
|
+
((++i))
|
|
85
85
|
continue
|
|
86
86
|
fi
|
|
87
87
|
image_id="$(awk '{print $3}' <<< "$row")"
|
|
88
88
|
tag="$(awk '{print $2}' <<< "$row")"
|
|
89
89
|
TO_DELETE+=("$image_id|$repo:$tag")
|
|
90
|
-
((i
|
|
90
|
+
((++i))
|
|
91
91
|
done <<< "$repo_blob"
|
|
92
92
|
done
|
|
93
93
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# 🏗️ TILT SDK - FRONTEND BUILD PLAN
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Decides how a frontend image runs its production build.
|
|
5
|
+
#
|
|
6
|
+
# A `"build": "tsc --noEmit && vite build"` script (the restaurant example uses it). Running that script
|
|
7
|
+
# inside the image build makes the type-check block the image and compete for
|
|
8
|
+
# CPU with other builds. When the script has exactly that standard shape, the
|
|
9
|
+
# image runs `vite build` directly and the type-check becomes a separate,
|
|
10
|
+
# non-blocking Tilt resource. Any other script runs unchanged.
|
|
11
|
+
# =============================================================================
|
|
12
|
+
|
|
13
|
+
DEFAULT_FRONTEND_BUILD_CMD = 'bun run build'
|
|
14
|
+
|
|
15
|
+
# Only a non-emitting type-check is moved out of the image. A plain `tsc` writes output that the build may
|
|
16
|
+
# use (the CLI scaffold's tsconfig emits to ./dist), so that script keeps running unchanged in the image.
|
|
17
|
+
_TSC_FORMS = ['tsc --noEmit']
|
|
18
|
+
# The generated config is copied into the image; any other config file is not, so a script that selects one is not used.
|
|
19
|
+
_IMAGE_CONFIG_DIR = '.autogenerated/'
|
|
20
|
+
_VITE_BUILD = 'vite build'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _default_plan():
|
|
24
|
+
return {'build_cmd': DEFAULT_FRONTEND_BUILD_CMD, 'typecheck': False, 'typecheck_cmd': ''}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def frontend_build_plan(package_json_path):
|
|
28
|
+
"""Returns {'build_cmd', 'typecheck', 'typecheck_cmd'} for the package.json at package_json_path.
|
|
29
|
+
|
|
30
|
+
build_cmd is the command the image runs; the generated Vite config is appended to it.
|
|
31
|
+
typecheck is True only when the standard `tsc --noEmit && vite build` form was recognised;
|
|
32
|
+
typecheck_cmd is then the host command for the non-blocking type-check resource.
|
|
33
|
+
A missing or unreadable package.json, or any other script, gets the default plan.
|
|
34
|
+
"""
|
|
35
|
+
if not os.path.exists(package_json_path):
|
|
36
|
+
return _default_plan()
|
|
37
|
+
|
|
38
|
+
scripts = read_json(package_json_path).get('scripts', {})
|
|
39
|
+
script = scripts.get('build', '').strip()
|
|
40
|
+
steps = script.split(' && ')
|
|
41
|
+
if len(steps) != 2:
|
|
42
|
+
return _default_plan()
|
|
43
|
+
|
|
44
|
+
typecheck_step = steps[0].strip()
|
|
45
|
+
vite_step = steps[1].strip()
|
|
46
|
+
if typecheck_step not in _TSC_FORMS:
|
|
47
|
+
return _default_plan()
|
|
48
|
+
if vite_step != _VITE_BUILD and not vite_step.startswith(_VITE_BUILD + ' '):
|
|
49
|
+
return _default_plan()
|
|
50
|
+
|
|
51
|
+
# Flags after `vite build` are kept. A --config must name the generated config, which the image copies.
|
|
52
|
+
extra_args = vite_step[len(_VITE_BUILD):]
|
|
53
|
+
tokens = extra_args.split()
|
|
54
|
+
for i, token in enumerate(tokens):
|
|
55
|
+
value = ''
|
|
56
|
+
if token == '--config' and i + 1 < len(tokens):
|
|
57
|
+
value = tokens[i + 1]
|
|
58
|
+
elif token.startswith('--config='):
|
|
59
|
+
value = token[len('--config='):]
|
|
60
|
+
else:
|
|
61
|
+
continue
|
|
62
|
+
if not value.startswith(_IMAGE_CONFIG_DIR):
|
|
63
|
+
return _default_plan()
|
|
64
|
+
|
|
65
|
+
return {'build_cmd': 'bunx vite build' + extra_args, 'typecheck': True, 'typecheck_cmd': 'bunx tsc --noEmit'}
|
|
@@ -2,7 +2,64 @@
|
|
|
2
2
|
# 🏗️ TILT SDK - GOLDEN IMAGE BUILDERS
|
|
3
3
|
# =============================================================================
|
|
4
4
|
|
|
5
|
-
load('./golden_image_constants.star', 'GOLDEN_DOCKERFILE', 'GOLDEN_IMAGE_PREFIX')
|
|
5
|
+
load('./golden_image_constants.star', 'GOLDEN_DOCKERFILE', 'GOLDEN_IMAGE_PREFIX', 'GOLDEN_LAYERS')
|
|
6
|
+
|
|
7
|
+
# Label that records which golden Dockerfile an image was built from.
|
|
8
|
+
GOLDEN_HASH_LABEL = 'tdk.golden.hash'
|
|
9
|
+
|
|
10
|
+
# Shell template for the golden build. Placeholders (@@...@@) are replaced in
|
|
11
|
+
# build_golden_layers(); plain `$VAR` references are expanded by the shell.
|
|
12
|
+
_GOLDEN_BUILD_TEMPLATE = '''
|
|
13
|
+
DF_REL=@@DF_REL@@
|
|
14
|
+
ROOT_REL=@@ROOT_REL@@
|
|
15
|
+
DF="$(cd "$(dirname "$DF_REL")" && pwd)/$(basename "$DF_REL")"
|
|
16
|
+
ROOT="$(cd "$ROOT_REL" && pwd)"
|
|
17
|
+
HASH="$(cat "$DF" | (sha256sum 2>/dev/null || shasum -a 256) | cut -c1-64)"
|
|
18
|
+
IMAGES="@@IMAGES@@"
|
|
19
|
+
|
|
20
|
+
if [ -z "$TDK_GOLDEN_REBUILD" ]; then
|
|
21
|
+
fresh=1
|
|
22
|
+
for img in $IMAGES; do
|
|
23
|
+
[ "$(docker image inspect -f '{{index .Config.Labels "@@HASH_LABEL@@"}}' "$img" 2>/dev/null)" = "$HASH" ] || fresh=0
|
|
24
|
+
done
|
|
25
|
+
if [ "$fresh" = 1 ]; then
|
|
26
|
+
echo "✅ Golden images are up to date (hash $HASH) - skipping build"
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "🏗️ Building golden layered images (@@COUNT@@ total)..."
|
|
32
|
+
if docker buildx version >/dev/null 2>&1; then
|
|
33
|
+
echo " 🧩 Building all golden targets with one docker buildx bake (independent stages run in parallel)"
|
|
34
|
+
cat > golden-layers.bake.hcl <<HCL
|
|
35
|
+
variable "HASH" {
|
|
36
|
+
default = ""
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
group "default" {
|
|
40
|
+
targets = [@@TARGET_NAMES@@]
|
|
41
|
+
}
|
|
42
|
+
@@BAKE_TARGETS@@
|
|
43
|
+
HCL
|
|
44
|
+
# Buildx 0.19 and later ask for read access outside the bake file's directory; older releases do not know the flag.
|
|
45
|
+
BUILDX_VER="$(docker buildx version 2>/dev/null | sed -n 's/.* v\\([0-9]*\\)\\.\\([0-9]*\\).*/\\1 \\2/p')"
|
|
46
|
+
BUILDX_MAJOR="${BUILDX_VER%% *}"
|
|
47
|
+
BUILDX_MINOR="${BUILDX_VER##* }"
|
|
48
|
+
BAKE_ALLOW=""
|
|
49
|
+
if [ "${BUILDX_MAJOR:-0}" -gt 0 ] || [ "${BUILDX_MINOR:-0}" -ge 19 ]; then BAKE_ALLOW="--allow fs.read=$ROOT"; fi
|
|
50
|
+
HASH="$HASH" docker buildx bake $BAKE_ALLOW -f golden-layers.bake.hcl --load
|
|
51
|
+
else
|
|
52
|
+
echo " ⚠️ docker buildx is not available - building golden targets one at a time"
|
|
53
|
+
@@SERIAL_BUILDS@@
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo "✅ Golden layers built (@@COUNT@@ images):"
|
|
57
|
+
@@SUMMARY@@
|
|
58
|
+
'''
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _sh_quote(text):
|
|
62
|
+
return "'" + text.replace("'", "'\\''") + "'"
|
|
6
63
|
|
|
7
64
|
|
|
8
65
|
def build_golden_layers(project_root='.'):
|
|
@@ -16,63 +73,62 @@ def build_golden_layers(project_root='.'):
|
|
|
16
73
|
is itself project-root-relative, so it must be prefixed with this to
|
|
17
74
|
resolve correctly - passing project_root='.' (the default) only works
|
|
18
75
|
when the Tiltfile happens to live at the project root.
|
|
76
|
+
|
|
77
|
+
The build is skipped when every golden image already exists with a
|
|
78
|
+
`tdk.golden.hash` label matching the current Dockerfile. Set
|
|
79
|
+
TDK_GOLDEN_REBUILD=1 to force a rebuild. Otherwise all targets are built
|
|
80
|
+
with one `docker buildx bake`, falling back to serial `docker build` when
|
|
81
|
+
buildx is not installed.
|
|
19
82
|
"""
|
|
20
83
|
resource_name = 'golden-layers-build'
|
|
21
84
|
dockerfile_from_tiltfile = (
|
|
22
85
|
GOLDEN_DOCKERFILE if project_root in (None, '.', '')
|
|
23
86
|
else project_root.rstrip('/') + '/' + GOLDEN_DOCKERFILE
|
|
24
87
|
)
|
|
88
|
+
context = project_root or '.'
|
|
25
89
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
echo "
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
echo " ✓ {prefix}-l4-frontend:{tag}"
|
|
65
|
-
echo " ✓ {prefix}-l4-migrator:{tag}"
|
|
66
|
-
""".format(
|
|
67
|
-
dockerfile=dockerfile_from_tiltfile,
|
|
68
|
-
prefix=GOLDEN_IMAGE_PREFIX,
|
|
69
|
-
tag='latest',
|
|
70
|
-
context=project_root,
|
|
71
|
-
).strip()
|
|
90
|
+
images = []
|
|
91
|
+
summary = []
|
|
92
|
+
bake_targets = []
|
|
93
|
+
serial_builds = []
|
|
94
|
+
target_names = []
|
|
95
|
+
for key in GOLDEN_LAYERS:
|
|
96
|
+
layer = GOLDEN_LAYERS[key]
|
|
97
|
+
ref = layer['name'] + ':' + layer['tag']
|
|
98
|
+
images.append(ref)
|
|
99
|
+
summary.append('echo " ✓ ' + ref + '"')
|
|
100
|
+
target_names.append('"' + key + '"')
|
|
101
|
+
bake_targets.append('''target "{key}" {{
|
|
102
|
+
dockerfile = "$DF"
|
|
103
|
+
context = "$ROOT"
|
|
104
|
+
target = "{target}"
|
|
105
|
+
tags = ["{ref}"]
|
|
106
|
+
labels = {{
|
|
107
|
+
"{label}" = HASH
|
|
108
|
+
}}
|
|
109
|
+
}}
|
|
110
|
+
'''.format(key=key, target=layer['target'], ref=ref, label=GOLDEN_HASH_LABEL))
|
|
111
|
+
serial_builds.append(
|
|
112
|
+
'docker build --label "{label}=$HASH" -f "$DF" --target {target} -t {ref} "$ROOT" || exit 1'.format(
|
|
113
|
+
label=GOLDEN_HASH_LABEL, target=layer['target'], ref=ref,
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
cmd = (_GOLDEN_BUILD_TEMPLATE
|
|
118
|
+
.replace('@@DF_REL@@', _sh_quote(dockerfile_from_tiltfile))
|
|
119
|
+
.replace('@@ROOT_REL@@', _sh_quote(context))
|
|
120
|
+
.replace('@@IMAGES@@', ' '.join(images))
|
|
121
|
+
.replace('@@HASH_LABEL@@', GOLDEN_HASH_LABEL)
|
|
122
|
+
.replace('@@COUNT@@', str(len(images)))
|
|
123
|
+
.replace('@@TARGET_NAMES@@', ', '.join(target_names))
|
|
124
|
+
.replace('@@BAKE_TARGETS@@', '\n'.join(bake_targets))
|
|
125
|
+
.replace('@@SERIAL_BUILDS@@', '\n '.join(serial_builds))
|
|
126
|
+
.replace('@@SUMMARY@@', '\n'.join(summary))
|
|
127
|
+
)
|
|
72
128
|
|
|
73
129
|
local_resource(
|
|
74
130
|
resource_name,
|
|
75
|
-
cmd=
|
|
131
|
+
cmd=cmd,
|
|
76
132
|
labels=['infra.docker', 'golden-layers'],
|
|
77
133
|
deps=[dockerfile_from_tiltfile],
|
|
78
134
|
allow_parallel=True,
|
|
@@ -172,8 +172,10 @@ def L3_generate_frontend_builder(res_path, build_cmd = RUNTIME_CONFIG["bun"]["ba
|
|
|
172
172
|
# Args passed after the script name are appended to the end of the script
|
|
173
173
|
# command, so they reach `vite build` while any preceding steps in the
|
|
174
174
|
# script (e.g. `tsc --noEmit &&`) still run.
|
|
175
|
+
# A build script that already names a config (the CLI scaffold does) keeps its own; a second --config is not added.
|
|
176
|
+
config_flag = "" if "--config" in build_cmd else " --config " + AUTOGENERATED_CONFIG_PATHS['vite_build']
|
|
175
177
|
parts.append(
|
|
176
|
-
"RUN " + build_cmd +
|
|
178
|
+
"RUN " + build_cmd + config_flag + "\n"
|
|
177
179
|
)
|
|
178
180
|
return "".join(parts)
|
|
179
181
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# 🏗️ TILT SDK - BUILD CONCURRENCY
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Sets Tilt's maximum parallel updates from the Docker engine's CPU count and
|
|
5
|
+
# memory, instead of Tilt's default, so a warm `tdk up` on a machine with spare
|
|
6
|
+
# capacity builds more than three images at once.
|
|
7
|
+
#
|
|
8
|
+
# Override with TDK_MAX_PARALLEL_BUILDS=<positive integer>.
|
|
9
|
+
# =============================================================================
|
|
10
|
+
|
|
11
|
+
MIN_PARALLEL_BUILDS = 3
|
|
12
|
+
MAX_PARALLEL_BUILDS = 8
|
|
13
|
+
# CPUs kept free for Tilt, the Traefik/Postgres containers and the host.
|
|
14
|
+
RESERVED_CPUS = 2
|
|
15
|
+
# Rough peak memory per concurrent image build (Vite and tsc dominate).
|
|
16
|
+
BUILD_MEMORY_BUDGET_BYTES = 1610612736 # 1.5 GiB
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _is_positive_int(text):
|
|
20
|
+
return len(text) > 0 and text.isdigit() and int(text) > 0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _engine_resources():
|
|
24
|
+
"""Returns (cpus, memory_bytes) from `docker info`, or None when unavailable."""
|
|
25
|
+
out = str(local(
|
|
26
|
+
"docker info --format '{{.NCPU}} {{.MemTotal}}' 2>/dev/null || true",
|
|
27
|
+
quiet=True,
|
|
28
|
+
echo_off=True,
|
|
29
|
+
)).strip()
|
|
30
|
+
parts = out.split(' ')
|
|
31
|
+
if len(parts) != 2 or not parts[0].isdigit() or not parts[1].isdigit():
|
|
32
|
+
return None
|
|
33
|
+
return (int(parts[0]), int(parts[1]))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def compute_max_parallel_builds(cpus, memory_bytes):
|
|
37
|
+
"""The concurrency for an engine with `cpus` CPUs and `memory_bytes` of memory."""
|
|
38
|
+
by_cpu = cpus - RESERVED_CPUS
|
|
39
|
+
by_memory = memory_bytes // BUILD_MEMORY_BUDGET_BYTES
|
|
40
|
+
return max(MIN_PARALLEL_BUILDS, min(MAX_PARALLEL_BUILDS, min(by_cpu, by_memory)))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def configure_build_concurrency():
|
|
44
|
+
"""Calls update_settings once with the chosen max_parallel_updates. Call at Tiltfile load."""
|
|
45
|
+
override = os.environ.get('TDK_MAX_PARALLEL_BUILDS', '').strip()
|
|
46
|
+
if _is_positive_int(override):
|
|
47
|
+
value = int(override)
|
|
48
|
+
print("🧮 Parallel builds: " + str(value) + " (TDK_MAX_PARALLEL_BUILDS)")
|
|
49
|
+
else:
|
|
50
|
+
resources = _engine_resources()
|
|
51
|
+
if resources == None:
|
|
52
|
+
print("🧮 Parallel builds: Tilt default (docker info unavailable, so the engine's CPU and memory could not be read)")
|
|
53
|
+
return
|
|
54
|
+
value = compute_max_parallel_builds(resources[0], resources[1])
|
|
55
|
+
print("🧮 Parallel builds: " + str(value) + " (from Docker engine: " + str(resources[0]) + " CPUs)")
|
|
56
|
+
update_settings(max_parallel_updates=value)
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
# =============================================================================
|
|
22
22
|
|
|
23
23
|
load("../common/utils_debug.star", "debug_log")
|
|
24
|
+
load("./build_concurrency.star", "configure_build_concurrency")
|
|
24
25
|
load("../../platform/docker/constants.star", "PlatformDockerConstants")
|
|
25
26
|
load("../../platform/docker/compose/traefik_standalone.star", "generate_standalone_traefik_compose")
|
|
26
27
|
load("../../platform/docker/networking/sablier_container_cycle.star", "sablier_middleware_suffix")
|
|
@@ -442,6 +443,9 @@ def load_all_infrastructure(should_enable, fix_docker_networks_fn=None, docker_p
|
|
|
442
443
|
if should_enable('monitoring') and should_enable('elk'):
|
|
443
444
|
fail("Incompatible flags: 'monitoring' and 'elk' cannot both be enabled at the same time. Disable one of them.")
|
|
444
445
|
|
|
446
|
+
# Size Tilt's parallel image builds to the Docker engine before any build is queued
|
|
447
|
+
configure_build_concurrency()
|
|
448
|
+
|
|
445
449
|
# Build golden image before other infrastructure (if enabled)
|
|
446
450
|
golden_image_resource = None
|
|
447
451
|
if docker_provider and write_fn:
|
package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star
CHANGED
|
@@ -11,6 +11,7 @@ OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
|
|
|
11
11
|
|
|
12
12
|
load("../../common/utils_debug.star", "debug_log")
|
|
13
13
|
load('../../../platform/docker/index.star', 'Docker')
|
|
14
|
+
load('../../../platform/docker/build/frontend_build_plan.star', 'frontend_build_plan')
|
|
14
15
|
load('../../../platform/docker/constants.star', 'PlatformDockerConstants')
|
|
15
16
|
load('../../../tilt/manifest/constants.star', 'BASE_PORT_FRONTEND', 'BASE_PORT_BACKEND')
|
|
16
17
|
load('../../../platform/docker/networking/traefik_static_routes.star', 'generate_static_wake_route', 'normalize_abs_path')
|
|
@@ -575,11 +576,29 @@ def _should_skip_frontend(res, should_enable):
|
|
|
575
576
|
return res.get('frontend', False) and should_enable('backends-only')
|
|
576
577
|
|
|
577
578
|
|
|
578
|
-
def
|
|
579
|
+
def _register_frontend_typecheck(res, service_dir, typecheck_cmd):
|
|
580
|
+
"""Non-blocking `<service>-typecheck` resource: runs the planned tsc command so type errors show in Tilt without gating the image.
|
|
581
|
+
|
|
582
|
+
The command runs on the host, so TypeScript must resolve from the service directory (`bun install`, at the
|
|
583
|
+
service or workspace root). Without it the resource fails with that instruction instead of a bare module error.
|
|
584
|
+
"""
|
|
585
|
+
local_resource(
|
|
586
|
+
name=res['name'] + '-typecheck',
|
|
587
|
+
cmd="cd '" + service_dir + "' && { bunx tsc --version >/dev/null 2>&1 || { echo 'typecheck needs host dependencies: run bun install in " + service_dir + "'; exit 1; }; } && " + typecheck_cmd,
|
|
588
|
+
deps=[service_dir + '/src', service_dir + '/tsconfig.json'],
|
|
589
|
+
labels=['app.' + res['name'], 'typecheck'],
|
|
590
|
+
# tsc --noEmit writes no files, so it can run alongside the image builds.
|
|
591
|
+
allow_parallel=True,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _process_frontend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable, project_root=''):
|
|
579
596
|
"""Process a frontend resource and return compose entry."""
|
|
580
597
|
target_path = res.get('target_path', manifest.get('target_path', '/usr/share/nginx/html'))
|
|
581
598
|
use_nginx = 'nginx' in target_path or target_path == '/usr/share/nginx/html'
|
|
582
599
|
port = res.get('port', manifest.get('port', BASE_PORT_FRONTEND))
|
|
600
|
+
service_dir = (project_root.rstrip('/') + '/' if project_root else '') + full_res_path
|
|
601
|
+
plan = frontend_build_plan(service_dir + '/package.json')
|
|
583
602
|
Docker.frontend(
|
|
584
603
|
res_path=full_res_path,
|
|
585
604
|
resource_name=res['name'],
|
|
@@ -587,7 +606,10 @@ def _process_frontend_resource(res, manifest, full_res_path, resource_path, reso
|
|
|
587
606
|
target_path=target_path,
|
|
588
607
|
use_nginx=use_nginx,
|
|
589
608
|
use_golden=should_enable('golden-image'),
|
|
609
|
+
build_cmd=plan['build_cmd'],
|
|
590
610
|
)
|
|
611
|
+
if plan['typecheck']:
|
|
612
|
+
_register_frontend_typecheck(res, service_dir, plan['typecheck_cmd'])
|
|
591
613
|
return Docker.frontend_compose(resource_path=resource_path, resource_name=resource_name, res=res, manifest=manifest)
|
|
592
614
|
|
|
593
615
|
|
|
@@ -735,7 +757,7 @@ def register_compose_resources(resource_config, ctx, runtime_flags, manifest_sta
|
|
|
735
757
|
continue
|
|
736
758
|
|
|
737
759
|
if res.get('frontend', False):
|
|
738
|
-
entry = _process_frontend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable)
|
|
760
|
+
entry = _process_frontend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable, ctx.get('project_root', ''))
|
|
739
761
|
else:
|
|
740
762
|
entry = _process_backend_resource(res, manifest, full_res_path, resource_path, resource_name, should_enable)
|
|
741
763
|
# A deferStart resource has no container Traefik can discover via
|
|
@@ -32,6 +32,7 @@ load('../../../generators/nginx_conf.star', 'Nginx')
|
|
|
32
32
|
load('../../../generators/api_gateway.star', 'ApiGateway')
|
|
33
33
|
load('../../../generators/tsconfig.star', 'TSConfig')
|
|
34
34
|
load('../../../../platform/docker/index.star', 'Docker')
|
|
35
|
+
load('../../../../platform/docker/build/frontend_build_plan.star', 'frontend_build_plan')
|
|
35
36
|
load('../../../generators/npmrc.star', 'PackageConfig')
|
|
36
37
|
load('../../libs.star', 'Libs')
|
|
37
38
|
load('./env.star', 'EnvGenerators')
|
|
@@ -371,13 +372,16 @@ def _generate_all_configs_for_resource(
|
|
|
371
372
|
target_path = resource_config.get('target_path', '/usr/share/nginx/html')
|
|
372
373
|
use_nginx = 'nginx' in target_path or target_path == '/usr/share/nginx/html'
|
|
373
374
|
port = resource_config.get('port', manifest.get('port', BASE_PORT_FRONTEND))
|
|
375
|
+
root = ctx.get('project_root', '')
|
|
376
|
+
plan = frontend_build_plan((root.rstrip('/') + '/' if root else '') + resource_path + '/package.json')
|
|
374
377
|
dockerfile_content = Docker.frontend(
|
|
375
378
|
res_path=resource_path,
|
|
376
379
|
resource_name=resource_config['name'],
|
|
377
380
|
port=port,
|
|
378
381
|
target_path=target_path,
|
|
379
382
|
use_nginx=use_nginx,
|
|
380
|
-
use_golden=should_enable('golden-image')
|
|
383
|
+
use_golden=should_enable('golden-image'),
|
|
384
|
+
build_cmd=plan['build_cmd'],
|
|
381
385
|
)
|
|
382
386
|
elif provider_owned:
|
|
383
387
|
dockerfile_content = Docker.backend_language(
|
|
@@ -89,14 +89,14 @@ def setup_utility_resources():
|
|
|
89
89
|
)
|
|
90
90
|
|
|
91
91
|
local_resource('prune-images',
|
|
92
|
-
cmd='./.tdk/.tdk-out/ext/assets/scripts/cleanup-tilt-images.sh --keep 0 --force && docker image prune -f',
|
|
92
|
+
cmd='./.tdk/.tdk-out/tdk-cli-ext/engine/assets/scripts/cleanup-tilt-images.sh --keep 0 --force && docker image prune -f',
|
|
93
93
|
resource_deps=[],
|
|
94
94
|
labels=['dev.maintenance'],
|
|
95
95
|
auto_init=False
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
local_resource('memory-optimizer',
|
|
99
|
-
cmd='./.tdk/.tdk-out/ext/assets/scripts/mac-m1-resource-monitor.sh && ./.tdk/.tdk-out/ext/assets/scripts/cleanup-tilt-images.sh --keep 0 --force && docker image prune -f && docker builder prune -af --filter "until=24h"',
|
|
99
|
+
cmd='./.tdk/.tdk-out/tdk-cli-ext/engine/assets/scripts/mac-m1-resource-monitor.sh && ./.tdk/.tdk-out/tdk-cli-ext/engine/assets/scripts/cleanup-tilt-images.sh --keep 0 --force && docker image prune -f && docker builder prune -af --filter "until=24h"',
|
|
100
100
|
resource_deps=[],
|
|
101
101
|
labels=['dev.maintenance', 'mac-m1'],
|
|
102
102
|
auto_init=False
|
|
@@ -121,12 +121,15 @@ def setup_utility_resources():
|
|
|
121
121
|
else
|
|
122
122
|
echo "$(date +%H:%M:%S) ✓ $count dangling images (threshold: 5)"
|
|
123
123
|
fi
|
|
124
|
+
# Tilt tags every build as tilt-<hash>, so superseded builds stay tagged
|
|
125
|
+
# and are never dangling. Keep the newest 2 per service; running images are skipped.
|
|
126
|
+
./.tdk/.tdk-out/tdk-cli-ext/engine/assets/scripts/cleanup-tilt-images.sh --keep 2 --force >/dev/null 2>&1 || true
|
|
124
127
|
sleep 300
|
|
125
128
|
done
|
|
126
129
|
''',
|
|
127
130
|
resource_deps=[],
|
|
128
131
|
labels=['dev.maintenance', 'mac-m1'],
|
|
129
|
-
auto_init=
|
|
132
|
+
auto_init=True,
|
|
130
133
|
)
|
|
131
134
|
|
|
132
135
|
# Continuously reclaim heavyweight migrator images once migration succeeds.
|
|
@@ -134,7 +137,7 @@ def setup_utility_resources():
|
|
|
134
137
|
serve_cmd='''
|
|
135
138
|
echo "🧹 Migrator image cleanup started (every 45s)"
|
|
136
139
|
while true; do
|
|
137
|
-
./.tdk/.tdk-out/ext/assets/scripts/cleanup-completed-migrator-images.sh || true
|
|
140
|
+
./.tdk/.tdk-out/tdk-cli-ext/engine/assets/scripts/cleanup-completed-migrator-images.sh || true
|
|
138
141
|
sleep 45
|
|
139
142
|
done
|
|
140
143
|
''',
|
|
@@ -71,14 +71,10 @@
|
|
|
71
71
|
{title}
|
|
72
72
|
</h1>
|
|
73
73
|
<nav class="nav">
|
|
74
|
-
|
|
75
|
-
<a href="
|
|
76
|
-
|
|
77
|
-
<a href="
|
|
78
|
-
<!-- nosemgrep: html.security.plaintext-http-link.plaintext-http-link -- localhost dev tools; each listens on loopback over HTTP -->
|
|
79
|
-
<a href="http://localhost:9767" class="{active_inspector}">⚙️ Configs</a>
|
|
80
|
-
<!-- nosemgrep: html.security.plaintext-http-link.plaintext-http-link -- localhost dev tools; each listens on loopback over HTTP -->
|
|
81
|
-
<a href="http://localhost:9768" class="{active_terminal}" id="terminalLink" data-available="{terminal_available}">💻 Terminal</a>
|
|
74
|
+
<a href="//localhost:9765" class="{active_browser}" id="filesLink" data-available="{files_available}">📁 Files</a>
|
|
75
|
+
<a href="//localhost:9766" class="{active_viewer}" id="viewerLink" data-available="{viewer_available}">👁️ Code</a>
|
|
76
|
+
<a href="//localhost:9767" class="{active_inspector}">⚙️ Configs</a>
|
|
77
|
+
<a href="//localhost:9768" class="{active_terminal}" id="terminalLink" data-available="{terminal_available}">💻 Terminal</a>
|
|
82
78
|
</nav>
|
|
83
79
|
<button class="theme-toggle" id="themeToggle" title="Toggle theme">
|
|
84
80
|
🌓
|