@tdk-landscape/tdk-cli-core 1.3.72 → 1.3.74
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
CHANGED
|
@@ -4,6 +4,12 @@ Notable changes to the `tdk` CLI. Versions match [npm](https://www.npmjs.com/pac
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 1.3.73 (2026-09-28)
|
|
8
|
+
|
|
9
|
+
- Free-tier projects no longer start the `sablier` and `wake-gateway` containers, and Traefik no longer downloads the Sablier plugin from GitHub on every start. They were always launched (the `sablier` one with a read-write Docker socket) even though the license only gated the per-service labels, so nothing could use them. They are now generated only when a license grants Sablier. Fewer containers, one image build less, and no network fetch at Traefik boot. Spotted in the ERP CI run log.
|
|
10
|
+
|
|
11
|
+
## 1.3.72 (2026-09-28)
|
|
12
|
+
|
|
7
13
|
- `tdk doctor` no longer fails with "Verdaccio is not reachable" when the project config enables Verdaccio but no Premium license grants it. TDK does not start Verdaccio in that case, so the check could never pass; it is now skipped with a note. A registry your own `.npmrc` points at (`:4873`) is still checked. Found by the ERP CI workflow.
|
|
8
14
|
|
|
9
15
|
## 1.3.71 (2026-09-28)
|
package/README.md
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
# TDK — Tilt Development Kit
|
|
4
4
|
|
|
5
|
-
**Run
|
|
5
|
+
**Run your whole microservice landscape locally. No Kubernetes.**
|
|
6
6
|
|
|
7
|
-
One CLI that scaffolds your services and runs the whole landscape locally with hot reload, health checks, a proxy, and Postgres, all built on [Tilt](https://tilt.dev).
|
|
7
|
+
One CLI that scaffolds your services and runs the whole landscape locally with hot reload, health checks, a proxy, and Postgres, all built on [Tilt](https://tilt.dev). Tested at scale: a 100-service fixture (small generated `/health` services) boots from nothing on a clean CI runner in about 8 minutes, and holds about 1.7 GiB of memory ([details](#cold-boot-on-a-clean-machine-ci)).
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@tdk-landscape/tdk-cli-core)
|
|
10
10
|
[](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/ci.yml)
|
|
11
|
+
[](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/quickstart-e2e.yml)
|
|
12
|
+
[](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/erp-scale-e2e.yml)
|
|
11
13
|
[](https://snyk.io/test/github/tdk-landscape/tdk-cli-core)
|
|
12
14
|
[](https://socket.dev/npm/package/@tdk-landscape/tdk-cli-core/overview)
|
|
13
15
|
[](LICENSE)
|
|
@@ -56,10 +58,19 @@ Measured with [`scripts/benchmark/container-scale.ts`](scripts/benchmark/README.
|
|
|
56
58
|
What this does and doesn't measure:
|
|
57
59
|
|
|
58
60
|
- The ERP services are small generated Bun HTTP services (about 20 lines each) answering `/health`. Real services with real dependencies use more memory; the point is that TDK's per-service overhead (runtime image, healthchecks, proxy routing) stays small.
|
|
59
|
-
- Images were already built
|
|
61
|
+
- Images were already built, so build time isn't included. The clean-machine run below includes it.
|
|
60
62
|
- The script starts the service containers with the settings `tdk up` generates (512 MiB / 0.5 CPU limits, healthchecks), without Tilt running. The memory column counts only the service containers, not Postgres or Traefik.
|
|
61
63
|
- Numbers vary between runs (a later run reached 100 healthy in 74 s). Run it yourself with `bun scripts/benchmark/container-scale.ts`.
|
|
62
64
|
|
|
65
|
+
### Cold boot on a clean machine (CI)
|
|
66
|
+
|
|
67
|
+
The table above is a warm start. Two workflows install the published CLI on a fresh GitHub-hosted Ubuntu runner and run `tdk up` from nothing, building every image:
|
|
68
|
+
|
|
69
|
+
- [**Quickstart E2E**](.github/workflows/quickstart-e2e.yml): the commands from the top of this README, one scaffolded backend, until it answers `/health` through Traefik. About 4 minutes. Runs after each release and daily.
|
|
70
|
+
- [**ERP scale E2E**](.github/workflows/erp-scale-e2e.yml): the 100-service example. [Run 36395860088](https://github.com/tdk-landscape/tdk-cli-core/actions/runs/36395860088) (CLI 1.3.72, 4 vCPU / 15 GB): **100 / 100 backends healthy through Traefik after 472 s**, 111 containers, 1.8 GiB used by all containers. Runs weekly.
|
|
71
|
+
|
|
72
|
+
Both are the same kind of fixture: generated services answering `/health`, not a real business app. A green run means TDK can generate, build, route and health-check a landscape of that size on a machine that has never seen it. It doesn't cover Windows, Apple Silicon, or an existing repo you bring yourself.
|
|
73
|
+
|
|
63
74
|
## What is TDK?
|
|
64
75
|
|
|
65
76
|
TDK organizes microservices using a **Project → Stack → Resource** hierarchy, then uses Tilt to build, run, and hot-reload them locally:
|
package/cli/package.json
CHANGED
|
@@ -10,8 +10,14 @@ load("../config/healthcheck.star", "compose_healthcheck_timing")
|
|
|
10
10
|
load("../networking/traefik_static_routes.star", "normalize_abs_path")
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def generate_standalone_traefik_compose():
|
|
14
|
-
"""Minimal Traefik that routes Docker labels on api.{project}.localhost.
|
|
13
|
+
def generate_standalone_traefik_compose(sablier_enabled = False):
|
|
14
|
+
"""Minimal Traefik that routes Docker labels on api.{project}.localhost.
|
|
15
|
+
|
|
16
|
+
The Sablier plugin, the sablier container and the wake gateway are only
|
|
17
|
+
emitted when sablier_enabled is True (a license grants "sablier"). Without
|
|
18
|
+
it they would run for nothing: a container holding a read-write Docker
|
|
19
|
+
socket, an image build, and a plugin download from GitHub on every start.
|
|
20
|
+
"""
|
|
15
21
|
network = PlatformDockerConstants.NETWORK_TRAEFIK_PUBLIC
|
|
16
22
|
name = PlatformDockerConstants.PROJECT_NAME
|
|
17
23
|
return """###############################################################################
|
|
@@ -24,10 +30,7 @@ services:
|
|
|
24
30
|
image: traefik:v3.6.8
|
|
25
31
|
container_name: {name}_traefik
|
|
26
32
|
restart: unless-stopped
|
|
27
|
-
|
|
28
|
-
- sablier
|
|
29
|
-
- wake-gateway
|
|
30
|
-
command:
|
|
33
|
+
{traefik_depends_on} command:
|
|
31
34
|
- "--api.insecure=true"
|
|
32
35
|
- "--ping=true"
|
|
33
36
|
- "--providers.docker=true"
|
|
@@ -44,12 +47,7 @@ services:
|
|
|
44
47
|
- "--entrypoints.web.address=:80"
|
|
45
48
|
- "--entrypoints.websecure.address=:443"
|
|
46
49
|
- "--log.level=INFO"
|
|
47
|
-
|
|
48
|
-
# resource's `sablier:` manifest block), keeps idle services from
|
|
49
|
-
# burning memory. Requires Traefik >=3.6 (see allownonrunning below).
|
|
50
|
-
- "--experimental.plugins.sablier.moduleName=github.com/sablierapp/sablier-traefik-plugin"
|
|
51
|
-
- "--experimental.plugins.sablier.version=v1.3.1"
|
|
52
|
-
ports:
|
|
50
|
+
{sablier_flags} ports:
|
|
53
51
|
- "80:80"
|
|
54
52
|
- "8080:8080"
|
|
55
53
|
volumes:
|
|
@@ -60,7 +58,66 @@ services:
|
|
|
60
58
|
healthcheck:
|
|
61
59
|
test: ["CMD", "traefik", "healthcheck", "--ping"]
|
|
62
60
|
{healthcheck_timing}
|
|
63
|
-
|
|
61
|
+
{sablier_services}networks:
|
|
62
|
+
traefik-public:
|
|
63
|
+
name: {network}
|
|
64
|
+
external: true
|
|
65
|
+
""".format(
|
|
66
|
+
name=name,
|
|
67
|
+
network=network,
|
|
68
|
+
healthcheck_timing=compose_healthcheck_timing(10),
|
|
69
|
+
traefik_depends_on=_SABLIER_DEPENDS_ON if sablier_enabled else "",
|
|
70
|
+
sablier_flags=_SABLIER_FLAGS if sablier_enabled else "",
|
|
71
|
+
sablier_services=_SABLIER_SERVICES.format(
|
|
72
|
+
name=name,
|
|
73
|
+
gateway_container=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_CONTAINER,
|
|
74
|
+
gateway_port=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_PORT,
|
|
75
|
+
vendored_gateway_dir="tdk-cli-ext/engine/assets/docker/wake-gateway",
|
|
76
|
+
tilt_dev_dir=os.environ.get("HOME", "") + "/.tilt-dev",
|
|
77
|
+
project_root_mount=_project_root_mount(),
|
|
78
|
+
) if sablier_enabled else "",
|
|
79
|
+
dynamic_dir_name=PlatformDockerConstants.TRAEFIK_DYNAMIC_DIR_REL.split("/")[-1],
|
|
80
|
+
vendored_gateway_dir="tdk-cli-ext/engine/assets/docker/wake-gateway",
|
|
81
|
+
gateway_container=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_CONTAINER,
|
|
82
|
+
gateway_port=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_PORT,
|
|
83
|
+
tilt_dev_dir=os.environ.get("HOME", "") + "/.tilt-dev",
|
|
84
|
+
project_root_mount=_project_root_mount(),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _project_root_mount():
|
|
89
|
+
"""Read-only mount of the project at its own absolute host path.
|
|
90
|
+
|
|
91
|
+
The gateway runs `docker compose up --no-build` for a deferred resource
|
|
92
|
+
itself (bypassing Tilt's build queue), using the compose files Tilt uses.
|
|
93
|
+
Compose resolves relative paths in them (env_file etc.) client-side, so
|
|
94
|
+
they must exist at the same absolute paths inside the gateway container.
|
|
95
|
+
Empty when TDK_PROJECT_ROOT is unset (the generated Tiltfile sets it).
|
|
96
|
+
"""
|
|
97
|
+
root = project_root_abspath()
|
|
98
|
+
if not root:
|
|
99
|
+
return ""
|
|
100
|
+
return "\n - " + root + ":" + root + ":ro"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def project_root_abspath():
|
|
104
|
+
"""Absolute, normalized project root (TDK_PROJECT_ROOT), or "" when unset."""
|
|
105
|
+
return normalize_abs_path(os.environ.get("TDK_PROJECT_ROOT", ""))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_SABLIER_DEPENDS_ON = """ depends_on:
|
|
109
|
+
- sablier
|
|
110
|
+
- wake-gateway
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
_SABLIER_FLAGS = """ # On-demand scaling: routes to stopped containers (opt-in via a
|
|
114
|
+
# resource's `sablier:` manifest block), keeps idle services from
|
|
115
|
+
# burning memory. Requires Traefik >=3.6 (see allownonrunning below).
|
|
116
|
+
- "--experimental.plugins.sablier.moduleName=github.com/sablierapp/sablier-traefik-plugin"
|
|
117
|
+
- "--experimental.plugins.sablier.version=v1.3.1"
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
_SABLIER_SERVICES = """ sablier:
|
|
64
121
|
image: sablierapp/sablier:1.18.0
|
|
65
122
|
container_name: {name}_sablier
|
|
66
123
|
restart: unless-stopped
|
|
@@ -96,38 +153,4 @@ services:
|
|
|
96
153
|
networks:
|
|
97
154
|
- traefik-public
|
|
98
155
|
|
|
99
|
-
|
|
100
|
-
traefik-public:
|
|
101
|
-
name: {network}
|
|
102
|
-
external: true
|
|
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", ""))
|
|
156
|
+
"""
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
load("../../platform/docker/constants.star", "PlatformDockerConstants")
|
|
22
22
|
load("../../platform/docker/compose/traefik_standalone.star", "generate_standalone_traefik_compose")
|
|
23
|
+
load("../../platform/docker/networking/sablier_container_cycle.star", "sablier_middleware_suffix")
|
|
23
24
|
load("../../platform/registries/verdaccio_loader.star", "load_verdaccio")
|
|
24
25
|
|
|
25
26
|
# =============================================================================
|
|
@@ -194,7 +195,11 @@ def _load_standalone_traefik(root_prefix, env_file, write_fn):
|
|
|
194
195
|
"""Generate and load a self-contained Traefik compose for standalone projects."""
|
|
195
196
|
_stop_conflicting_traefik()
|
|
196
197
|
compose_rel = ".tdk/.tdk-out/docker-compose.traefik.yml"
|
|
197
|
-
|
|
198
|
+
# The free-tier stub always reports "disabled"; the licensed module reports
|
|
199
|
+
# enabled for a manifest that opts in. Probing it keeps this file free of any
|
|
200
|
+
# license logic while starting the sablier containers only when they can work.
|
|
201
|
+
_, sablier_enabled = sablier_middleware_suffix({"sablier": {"enable": True}}, "probe")
|
|
202
|
+
content = generate_standalone_traefik_compose(sablier_enabled)
|
|
198
203
|
if write_fn:
|
|
199
204
|
write_fn(compose_rel, content)
|
|
200
205
|
compose_file = root_prefix + compose_rel if root_prefix else compose_rel
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tdk-landscape/tdk-cli-core",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "Run
|
|
3
|
+
"version": "1.3.74",
|
|
4
|
+
"description": "Run your whole microservice landscape locally without Kubernetes. Scaffold services and run them with hot reload, built on Tilt.",
|
|
5
5
|
"homepage": "https://tdk-landscape.github.io/tdk-website",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|