@tdk-landscape/tdk-cli-core 1.3.78 → 1.3.80

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
+ - Add `tdk doctor --json` with schema-versioned readiness, checks, and structured errors; diagnostics remain on stderr.
8
+ - Stabilize doctor exits: 0 ready, 1 blocking findings, 2 invalid arguments/internal check failures. Invalid `--ping-timeout` now exits 2 instead of 1 and rejects fractional/unit-suffixed values.
9
+ - Add versioned `--json` envelopes to `tdk status`, `tdk resources`, and `tdk networks`; existing `networks --json` array consumers can migrate with `--json-legacy` through CLI 1.4.x. The compatibility flag is scheduled for removal in 1.5.0.
10
+ - Add `tdk config verify --json` with drift summaries and unified diffs for missing or stale master outputs; verification remains read-only, and structured command/internal failures use exits 1/2.
11
+
12
+
7
13
  ## 1.3.77 (2026-09-30)
8
14
 
9
15
  - `tdk up` no longer leaves `nats` (and any other service in `services/platform/messaging/docker-compose.yml`, e.g. a local mail catcher) disabled in Tilt. The pre-alpha default always goes through a focus-filter path that only re-enabled `init-networks`/`postgres`/`traefik`/`golden-layers-build`, so a resource with the `nats` feature retried its broker connection forever until you ran `tilt enable nats` by hand. Fixes #155.
package/README.md CHANGED
@@ -1,27 +1,6 @@
1
- <div align="center">
2
-
3
1
  # TDK — Tilt Development Kit
4
2
 
5
- TDK is a local development kit. It scaffolds services and runs a stack on your
6
- laptop with Docker and Tilt (hot reload, health, Traefik, Postgres).
7
-
8
- It is not a Kubernetes packager. It does not replace Helm, Argo CD,
9
- Kustomize, or your production charts.
10
-
11
- Use TDK when local bring-up of many services is painful and you do not want a
12
- cluster on the laptop.
13
-
14
- Skip TDK if `helm install` (or your existing compose/Tilt/Skaffold) already
15
- gives you a working local or shared-dev environment.
16
-
17
- ## Status
18
-
19
- Supported: macOS, Linux, Windows via WSL2 Ubuntu.
20
- Native Windows: CLI inspect only; `tdk up` unsupported.
21
- WSL2 smoke: local script only (no GitHub WSL runner).
22
- Stability: 1.x — generated files are a contract; verify in CI.
23
- Check your machine: `tdk doctor`
24
- Known limits: [docs/compare-honest.md](docs/compare-honest.md)
3
+ TDK runs many services on your laptop with Docker + Tilt. Helm still deploys the cluster.
25
4
 
26
5
  [![npm version](https://img.shields.io/npm/v/@tdk-landscape/tdk-cli-core.svg?style=flat&color=blue)](https://www.npmjs.com/package/@tdk-landscape/tdk-cli-core)
27
6
  [![CI](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/ci.yml/badge.svg)](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/ci.yml)
@@ -32,216 +11,51 @@ Known limits: [docs/compare-honest.md](docs/compare-honest.md)
32
11
  [![Socket Badge](https://badge.socket.dev/npm/package/@tdk-landscape/tdk-cli-core/latest)](https://socket.dev/npm/package/@tdk-landscape/tdk-cli-core/overview)
33
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
34
13
 
35
- [Website](https://tdk-landscape.github.io/tdk-website) · [Quickstart](https://tdk-landscape.github.io/tdk-website/docs/quickstart/) · [Examples](https://tdk-landscape.github.io/tdk-website/docs/examples/) · [Demo](https://tdk-landscape.github.io/tdk-demo-animation/) · [Report a bug](https://github.com/tdk-landscape/tdk-cli-core/issues/new/choose)
36
-
37
- </div>
38
-
39
- ## When not to use TDK
40
-
41
- - Production or shared-cluster deploy is Helm/Argo/Kustomize — keep it.
42
- - Your local or namespace-per-dev environment already works.
43
- - You will not adopt `service.json` and generated files.
14
+ ## Installation
44
15
 
45
- ## TDK is for you if
46
-
47
- - You have many services and want `tdk up <one stack>` on Docker.
48
- - You do not want kind or minikube as your daily path.
49
- - You want to generate services, run `tdk config verify`, and use Traefik `*.localhost` routes.
50
-
51
- Start with the [bundled product example](examples/tdk-example/README.md); its write path is covered by [example-e2e](.github/workflows/example-e2e.yml).
16
+ Install the CLI from npm (requires Node.js 22.12+) or use the prebuilt binary (no Node.js or Bun required):
52
17
 
53
18
  ```bash
54
19
  npm install -g @tdk-landscape/tdk-cli-core
55
- npx @tdk-landscape/tdk-cli-core
56
- mkdir shop && cd shop # tdk writes files into the current directory
57
- tdk project --yes # set up the project
58
- tdk resource orders-api --type backend --stack shop # scaffold a service
59
- tdk up shop # run it with hot reload
60
- ```
61
-
62
- ![tdk scaffolding a backend and a frontend, then listing the stack](docs/demo.svg)
63
-
64
- Want to inspect a stack before starting containers? `tdk up shop --dry-run` shows the services and local URLs without Docker or Tilt installed.
65
-
66
- ![npm downloads, last month](docs/npm-downloads.svg)
67
-
68
- **Requirements:** Docker (Desktop, OrbStack or Colima; Engine 25+, Compose 2.20+), [Tilt](https://docs.tilt.dev/install.html), [Bun](https://bun.sh) for the generated services, and Node.js 22.12+ for npm installs. Ports 80, 443 and 5432 must be free (a local Postgres is the usual conflict). macOS and Linux releases are available. Windows native is unsupported. WSL2 Ubuntu is supported. See docs/wsl2.md. Run `tdk doctor` to check your setup.
69
-
70
- This is the core monorepo for TDK: the `tdk` CLI, the Starlark-based Tilt orchestration engine that powers it, and the discovery system that turns a directory of services into a running local landscape.
71
-
72
- ## Why TDK?
73
-
74
- | | docker-compose | Local Kubernetes (kind, minikube) | Plain Tilt | **TDK** |
75
- |---|---|---|---|---|
76
- | Scaffold a new service in one command | ❌ | ❌ | ❌ | ✅ `tdk resource` |
77
- | Hot reload on file change | ⚠️ `compose watch` config | ⚠️ extra tooling | ✅ | ✅ |
78
- | Health-checked startup order | ✅ `depends_on: condition: service_healthy` | ✅ | ⚠️ write it yourself | ✅ |
79
- | Proxy, Postgres, monitoring included | ❌ | ❌ | ❌ | ✅ |
80
- | Needs a cluster | No | Yes | Optional | **No** |
81
- | Start only one stack of a large system | ⚠️ profiles | ⚠️ | ⚠️ | ✅ `tdk up <stack>` |
82
-
83
- TDK is not a replacement for Tilt: it generates the Tiltfile, Dockerfiles and compose files and then runs Tilt. If you already have a hand-written Tiltfile or compose setup you're happy with, TDK mostly saves you the boilerplate for the next service.
84
-
85
- ## Fixture bench: 100 generated services on one laptop
86
-
87
- Measured with [`scripts/benchmark/container-scale.ts`](scripts/benchmark/README.md) against the 100-service [`tdk-erp-system` fixture bench](https://github.com/tdk-landscape/tdk-erp-system), a scale fixture of generated `/health` services rather than an ERP product, on a 16 GB machine (Docker VM: 7.75 GiB). Raw results are in [`benchmarks/results/`](benchmarks/results).
88
-
89
- | Services | All healthy after | Total memory | Avg memory / service | Crashes / OOM kills |
90
- |---:|---:|---:|---:|---:|
91
- | 10 | 8 s | 161 MiB | 16 MiB | 0 / 0 |
92
- | 50 | 24 s | 829 MiB | 17 MiB | 0 / 0 |
93
- | 100 | 112 s | 1.6 GiB | 17 MiB | 0 / 0 |
94
-
95
- What this does and doesn't measure:
96
-
97
- - 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.
98
- - Images were already built, so build time isn't included. The clean-machine run below includes it.
99
- - 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.
100
- - Numbers vary between runs (a later run reached 100 healthy in 74 s). Run it yourself with `bun scripts/benchmark/container-scale.ts`.
101
-
102
- ### Cold boot on a clean machine (CI)
103
-
104
- 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:
105
-
106
- - [**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.
107
- - [**ERP fixture scale E2E**](.github/workflows/erp-scale-e2e.yml): the 100-service generated-health fixture bench. [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.
108
-
109
- Both are Linux/Ubuntu CI fixtures: 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 clean Ubuntu runner. Windows now has a separate smoke workflow for CLI startup and unit tests, but it does not boot a full landscape on Windows. These CI runs also don't cover Apple Silicon or an existing repo you bring yourself.
110
-
111
- ## What is TDK?
112
-
113
- TDK organizes microservices using a **Project → Stack → Resource** hierarchy, then uses Tilt to build, run, and hot-reload them locally:
114
-
115
- ```
116
- Project (1 per repo)
117
- ├── TILT_RESOURCE_DEFAULTS.star # Ports, health checks, memory
118
- ├── TILT_TECH_STACK.star # Bun, Vite, Prisma, NATS
119
- │
120
- └── Stacks (deployment groups)
121
- ├── api-stack
122
- │ ├── api-backend # Resource
123
- │ └── web-frontend # Resource
124
- │
125
- └── worker-stack
126
- ├── worker-backend
127
- └── web-frontend
128
- ```
129
-
130
- `.tdk/project.json` also groups stacks into phases (`pre_alpha`, `alpha`, `beta`, `out_of_scope`) so a large system can be brought up in stages. `tdk up` runs stacks from the first three and adds any new stack to `pre_alpha`.
131
-
132
- Running `tdk resource` scaffolds a service (Dockerfile, TypeScript config, starter code, tests); `tdk up` hands the whole landscape to Tilt for orchestration, live-reload, and health checking — sized to fit dozens of services on a single laptop.
133
-
134
- ## Install
135
-
136
- ```bash
137
- npm install -g @tdk-landscape/tdk-cli-core
138
- # or
139
- bun install -g @tdk-landscape/tdk-cli-core
140
- ```
141
-
142
- Or install the prebuilt binary (no Node or Bun needed; verified against the release checksums):
143
-
144
- ```bash
20
+ # or install the prebuilt binary
145
21
  curl -fsSL https://tdk-landscape.github.io/install.sh | sh
146
22
  ```
147
23
 
148
- Premium features don't need a different install: set `TDK_LICENSE_KEY` and any of these installs unlocks them. See [docs/FEATURES.md](docs/FEATURES.md).
149
-
150
24
  ## Quick start
151
25
 
152
26
  ```bash
153
27
  mkdir shop && cd shop
154
- tdk project --yes # writes .tdk/project.json and generated configs
155
- tdk resource orders-api --type backend --stack shop # asks you to confirm, then scaffolds services/shop/orders-api
156
- tdk resource storefront --type frontend --stack shop
157
- tdk up shop # starts Tilt; UI at http://localhost:10350
158
- tdk networks # lists the *.localhost URLs Traefik routes
159
- tdk down # stops everything
160
- ```
161
-
162
- See the [CLI reference](cli/README.md) for the full command set (`stack`, `resources`, `doctor`, `ui`, and more).
163
-
164
- ## Repository layout
165
-
166
- This is a monorepo — most day-to-day CLI work happens under `cli/`, while `engine/` and `discovery/` implement the Tilt-side orchestration that the CLI drives.
167
-
168
- | Path | What it is |
169
- |------|------------|
170
- | [`cli/`](cli) | The `tdk` CLI source (TypeScript/Bun) — commands, UI, templates. See [cli/README.md](cli/README.md). |
171
- | [`engine/`](engine) | The Starlark Tilt framework: topology modules for Docker, networking, database virtualization, secrets, observability. See [engine/README.md](engine/README.md). |
172
- | [`discovery/`](discovery) | Manifest-driven service discovery — scans `service.json` files and builds the resource/dependency graph consumed by the engine. |
173
- | [`ext/`](ext) | Tilt extension (`ext://tdk-cli`) plus IDE/UI enhancement components. |
174
- | [`scripts/`](scripts) | Release, benchmarking, and dev-environment scripts (git hooks, pre-commit, idle-footprint measurement). |
175
- | [`benchmarks/`](benchmarks) | Container/landscape scale benchmarks. |
176
- | [`docs/`](docs) | Reference docs, including [FEATURES.md](docs/FEATURES.md) for project- and resource-level feature flags. |
177
- | [`Tiltfile`](Tiltfile) | Entry point that wires the engine into `tilt up`. |
178
- | [`install.sh`](install.sh) | Legacy installer URL; hands off to the official installer at `tdk-landscape.github.io/install.sh`. |
179
-
180
- ## Features
181
-
182
- TDK ships a set of always-on infrastructure services (Traefik proxy, PostgreSQL) plus opt-in features — monitoring, ELK, Debezium CDC, a local npm registry, and more. Enable/disable them per-project via `.tdk/project.json`, or per-resource via each service's `service.json`. Full reference: [docs/FEATURES.md](docs/FEATURES.md).
183
-
184
- ## Free and paid features
185
-
186
- The CLI, engine and everything in this repository are MIT-licensed and work without an account or key. A few extras are paid and need a `TDK_LICENSE_KEY`: on-demand start/stop of idle services (Sablier), a local npm registry (Verdaccio), DDD scaffolding, Playwright config, C4 diagrams, synthetic monitoring, and a few generators (see `KNOWN_RESOURCES` in [extension-fetch.ts](cli/src/generator/extension-fetch.ts)). In this repo those are disabled stubs; with a key set, the CLI downloads the real implementations. [docs/FEATURES.md](docs/FEATURES.md) marks which features are which. To get a key, use the [request form on the website](https://tdk-landscape.github.io/tdk-website/#waitlist) or open a [Premium license request](https://github.com/tdk-landscape/tdk-cli-core/issues/new?template=premium_license.yml).
187
-
188
- ## Network access and telemetry
189
-
190
- TDK has no telemetry or analytics. The CLI only goes online when you ask it to:
191
-
192
- - `tdk upgrade` checks the latest release on GitHub.
193
- - With `TDK_LICENSE_KEY` set, `tdk project` and `tdk up` download the paid bundle from `tdk-extension-dist.oranguman.workers.dev` and re-check the key every 12 hours. Without a key, nothing is sent.
194
- - Docker and Tilt pull images and packages as they normally would.
195
- - Tilt, which TDK runs, has its own anonymous usage analytics. Turn them off with `tilt analytics opt out`.
196
-
197
- ## FAQ
198
-
199
- **Why not just docker compose?** For a handful of services, compose is fine. TDK generates the compose files for you. It adds hot reload through Tilt, one-command scaffolding with matching Dockerfiles, and a stack-level `tdk up` for large systems where starting everything is too slow.
200
-
201
- **How is this different from Skaffold, Garden, DevSpace or Tilt itself?** Skaffold, Garden and DevSpace are built around Kubernetes, and plain Tilt leaves the Tiltfile to you. TDK runs on plain Docker with no cluster, and generates both the services and the Tilt config from a `service.json` per service.
202
-
203
- See the [honest comparison](docs/compare-honest.md) for tradeoffs and cases where TDK may not fit.
204
-
205
- **Do I need 100 microservices?** No. Two services work the same way. The 100-service fixture bench is a stress test to show the overhead stays flat; it is not an ERP product example.
206
-
207
- **Is TDK affiliated with Tilt or Docker?** No. TDK is an independent project built on top of [Tilt](https://tilt.dev), which is maintained by Docker, Inc.
208
-
209
- ## Development
210
-
211
- ```bash
212
- make help # list all make targets
213
- make test # run the full test suite
214
- make test-fast # fast unit tests, no external deps
215
- make pre-commit-run # run pre-commit hooks on all files
28
+ tdk project --yes
29
+ tdk resource orders-api --type backend --stack shop --yes
30
+ tdk up shop
31
+ tdk networks
216
32
  ```
217
33
 
218
- `npm run typecheck` / `npm run lint` / `npm run test` delegate to the `cli/` workspace. If you're contributing, start with the [step-by-step contributor guide](docs/contributing/README.md) for subsystem-specific instructions.
34
+ `tdk up shop --dry-run` previews selected local services and URLs before starting containers. The default starter is Bun/TypeScript; TDK's core role is running local containers through Docker + Tilt, not providing a Node.js application framework. See the [one-backend example](examples/one-backend/README.md).
219
35
 
220
- ## Environment check
36
+ ## When not to use TDK
221
37
 
222
- ### Cold npx / first run
38
+ - Your existing Compose or Tilt workflow already gives you a working local environment.
39
+ - You do not want `service.json` manifests and generated local configuration.
223
40
 
224
- ```bash
225
- npx @tdk-landscape/tdk-cli-core
226
- npx @tdk-landscape/tdk-cli-core doctor
227
- ```
228
-
229
- Ranked first failures: Node 22.12+, Docker Engine 25+, Compose 2.20.2+, Tilt 0.25+, Bun 1.2+, and ports 80/443/5432.
230
- Install Bun 1.2+ as a machine prerequisite. `tdk project` generates the stack configuration; Prisma and NATS are project-specific, and NATS is required only when the feature is enabled.
41
+ ## Docs
231
42
 
232
- ```bash
233
- tdk doctor
234
- ```
43
+ - [Documentation index](docs/README.md)
44
+ - [Working alongside Helm](docs/with-helm.md)
45
+ - [Configuration and editor schemas](docs/configuration.md)
46
+ - [Runnable one-backend example](examples/one-backend/README.md)
47
+ - [Full multi-service example](examples/tdk-example/README.md)
48
+ - [Features and license limits](docs/FEATURES.md)
49
+ - [Honest comparison and known limits](docs/compare-honest.md)
50
+ - [Architecture and repository map](docs/project-overview.md)
51
+ - [Scale fixture measurements and caveats](docs/scale-bench.md)
235
52
 
236
- Checks Docker, Compose and Tilt. Inside a project it also checks the generated configs, `.env`, service scripts and that the ingress ports are free, before you run `tdk up`. It also catches wiring mistakes: a resource without `package.json`, a `params` URL that uses the wrong port for another service, a frontend calling a backend on `localhost:<port>`, the `nats` feature without a broker to start, duplicate Tilt processes, and Docker running out of network address space.
53
+ ## Requirements and support
237
54
 
238
- ## Community
55
+ For the local runtime, install Docker (Desktop, OrbStack, or Colima; Engine 25+, Compose 2.20+) and [Tilt](https://docs.tilt.dev/install.html). Bun 1.2+ is used by the default generated services. Ports 80, 443, and 5432 must be free. TDK supports macOS, Linux, and Windows through WSL2 Ubuntu; native Windows supports CLI inspection only. Run `tdk doctor` to check local readiness. See [WSL2 setup](docs/wsl2.md).
239
56
 
240
- - **Questions and ideas:** [open an issue](https://github.com/tdk-landscape/tdk-cli-core/issues/new/choose).
241
- - **Contributing:** see [CONTRIBUTING.md](CONTRIBUTING.md) and the [step-by-step guide](docs/contributing/README.md). Issues labeled [`good first issue`](https://github.com/tdk-landscape/tdk-cli-core/labels/good%20first%20issue) are a good place to start.
242
- - **Security:** see [SECURITY.md](SECURITY.md). Please don't report vulnerabilities in public issues.
243
- - **Examples:** bundled [TDK product example](examples/tdk-example), [ERP fixture bench (100 generated services)](https://github.com/tdk-landscape/tdk-erp-system), [SaaS starter](https://github.com/tdk-landscape/tdk-saas-starter), [restaurant](https://github.com/tdk-landscape/tdk-restaurant-example), [ecommerce (Vue + Hono)](https://github.com/tdk-landscape/tdk-ecommerce-example).
57
+ ## Contributing and license
244
58
 
245
- ## License
59
+ Contributions are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md) and the [contributor guide](docs/contributing/README.md). Report security issues using [SECURITY.md](SECURITY.md).
246
60
 
247
- [MIT](LICENSE) © [TDK Landscape](https://github.com/tdk-landscape)
61
+ TDK is MIT-licensed; see [LICENSE](LICENSE).
@@ -8,8 +8,10 @@ import { generateMasterConfigs, readProjectConfig, TemplateEngine, verifyMasterC
8
8
  import { isMasterConfigFileName } from "../types/index.js";
9
9
  import { assertValid } from "../utils/command-helpers.js";
10
10
  import { MASTER_CONFIG_FILES } from "../utils/constants.js";
11
- import { requireProjectRoot, runCommand } from "../utils/errors.js";
11
+ import { errorFactories, requireProjectRoot, runCommand } from "../utils/errors.js";
12
12
  import { writeJsonFile } from "../utils/file-helpers.js";
13
+ import { createMachineEnvelope, writeMachineError } from "../utils/machine-output.js";
14
+ import { findProjectRoot } from "../utils/paths.js";
13
15
  import { SERVICE_MANIFEST_SCHEMA_VERSION, validateServiceManifest, } from "../utils/service-manifest.js";
14
16
  import { discoverServiceManifestPaths } from "../utils/services.js";
15
17
  import { validateOptionalInfraService } from "../utils/validation.js";
@@ -173,11 +175,25 @@ export const configCommand = new Command("config")
173
175
  }))
174
176
  .addCommand(new Command("verify")
175
177
  .description("Verify that generated files match .tdk/project.json")
176
- .action(async () => {
177
- await runCommand(async () => {
178
- const projectRoot = requireProjectRoot();
179
- console.log(chalk.blue("🔍 Verifying configuration...\n"));
178
+ .option("--json", "Emit a machine-readable verification report")
179
+ .action(async (options) => {
180
+ const action = async () => {
181
+ const projectRoot = options.json ? findProjectRoot() : requireProjectRoot();
182
+ if (!projectRoot)
183
+ throw errorFactories.notInProject();
180
184
  const result = verifyMasterConfigs(projectRoot);
185
+ if (options.json) {
186
+ console.log(JSON.stringify(createMachineEnvelope({
187
+ valid: result.valid,
188
+ errors: result.errors,
189
+ warnings: result.warnings,
190
+ diffs: result.diffs,
191
+ })));
192
+ if (!result.valid)
193
+ process.exit(1);
194
+ return;
195
+ }
196
+ console.log(chalk.blue("🔍 Verifying configuration...\n"));
181
197
  for (const warning of result.warnings) {
182
198
  console.warn(chalk.yellow(`⚠️ ${warning}`));
183
199
  }
@@ -190,10 +206,22 @@ export const configCommand = new Command("config")
190
206
  for (const error of result.errors) {
191
207
  console.log(chalk.gray(` - ${error}`));
192
208
  }
209
+ for (const { diff } of result.diffs)
210
+ console.log(chalk.gray(`\n${diff}`));
193
211
  console.log(chalk.gray("\nRun `tdk config regenerate` to fix."));
194
212
  process.exit(1);
195
213
  }
196
- });
214
+ };
215
+ if (options.json) {
216
+ try {
217
+ await action();
218
+ }
219
+ catch (error) {
220
+ writeMachineError(error);
221
+ }
222
+ return;
223
+ }
224
+ await runCommand(action);
197
225
  }))
198
226
  .addCommand(new Command("edit").description("Open .tdk/project.json in your $EDITOR").action(async () => {
199
227
  await runCommand(async () => {
@@ -6,6 +6,7 @@ import { Command } from "commander";
6
6
  import { hasVerdaccioLicense } from "../generator/extension-fetch.js";
7
7
  import { MASTER_CONFIG_FILES, REQUIRED_PACKAGE_SCRIPTS } from "../utils/constants.js";
8
8
  import { isPathDiscovered, readDiscoveryPaths } from "../utils/discovery-paths.js";
9
+ import { collectDoctorChecks, createDoctorReport, getDoctorExitCode, } from "../utils/doctor-report.js";
9
10
  import { checkHealthRoutes, checkHostPorts, checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, projectConfigEnablesVerdaccio, summarizeServiceProbes, } from "../utils/doctor-runtime.js";
10
11
  import { checkDockerNetworkCapacity, checkFrontendBackendUrls, checkNatsBroker, checkResourcePackageJson, checkServiceUrlPorts, checkTiltInstances, } from "../utils/doctor-wiring.js";
11
12
  import { validateEnvFile } from "../utils/env-validator.js";
@@ -66,8 +67,8 @@ export function checkWslProjectLocation(projectPath, strict, isWsl = process.pla
66
67
  }
67
68
  /** Failures are shown before passing statuses, with a 5432 conflict first. */
68
69
  export function orderDoctorResults(results) {
69
- const failures = results.filter((result) => !result.didPass && !result.isSkipped);
70
- const remaining = results.filter((result) => result.didPass || result.isSkipped);
70
+ const failures = results.filter((result) => !result.didPass && !result.isSkipped && !result.isWarning);
71
+ const remaining = results.filter((result) => result.didPass || result.isSkipped || result.isWarning);
71
72
  failures.sort((left, right) => {
72
73
  const rank = (result) => result.name === "Host Ports" && /\b5432\b/.test(result.message)
73
74
  ? 0
@@ -968,21 +969,52 @@ async function checkServiceHealth(timeoutMs) {
968
969
  }
969
970
  export const doctorCommand = new Command("doctor")
970
971
  .description("Check environment readiness for TDK")
972
+ .option("--json", "Output a versioned JSON readiness report", false)
973
+ .exitOverride((error) => {
974
+ if (error.exitCode === 0)
975
+ process.exit(0);
976
+ if (doctorCommand.opts().json) {
977
+ console.log(JSON.stringify(createDoctorReport([], false, [{ code: "USAGE", message: error.message }])));
978
+ }
979
+ process.exit(2);
980
+ })
971
981
  .option("--no-ping", "Skip pinging running services' /health endpoints")
972
982
  .option("--strict", "Fail warnings such as a WSL project under /mnt/c")
973
983
  .option("--ping-timeout <ms>", "Per-service ping timeout in milliseconds", String(DEFAULT_PING_TIMEOUT_MS))
974
984
  .action(async (options) => {
985
+ // Validate before running any probe; parseInt would silently accept "100ms" or "1.5".
986
+ const pingTimeout = Number(options.pingTimeout);
987
+ if (!/^\d+$/.test(options.pingTimeout) ||
988
+ !Number.isSafeInteger(pingTimeout) ||
989
+ pingTimeout <= 0) {
990
+ const message = "--ping-timeout must be a positive integer number of milliseconds";
991
+ if (options.json)
992
+ console.log(JSON.stringify(createDoctorReport([], false, [{ code: "USAGE", message }])));
993
+ console.error(message);
994
+ process.exit(2);
995
+ return;
996
+ }
997
+ if (!options.json) {
998
+ console.log("TDK doctor checks your local Docker + Tilt development environment; it does not check cluster deployments.");
999
+ }
975
1000
  if (process.platform === "win32") {
1001
+ if (options.json) {
1002
+ console.log(JSON.stringify(createDoctorReport([
1003
+ {
1004
+ name: "Native Windows support",
1005
+ didPass: false,
1006
+ message: NATIVE_WINDOWS_DOCTOR_MESSAGE,
1007
+ fix: "Use WSL2 Ubuntu with Docker Desktop integration. Guide: docs/wsl2.md",
1008
+ },
1009
+ ], Boolean(findProjectRoot()))));
1010
+ }
976
1011
  console.error(NATIVE_WINDOWS_DOCTOR_MESSAGE);
977
1012
  process.exit(1);
978
1013
  return;
979
1014
  }
980
- console.log(`\n${chalk.bold("🔍 TDK Doctor")}\n`);
981
- console.log("Checking environment...\n");
982
- const pingTimeout = Number.parseInt(options.pingTimeout, 10);
983
- if (!Number.isFinite(pingTimeout) || pingTimeout <= 0) {
984
- console.log(`${chalk.red("✗")} --ping-timeout must be a positive number of milliseconds`);
985
- process.exit(1);
1015
+ if (!options.json) {
1016
+ console.log(`\n${chalk.bold("🔍 TDK Doctor")}\n`);
1017
+ console.log("Checking environment...\n");
986
1018
  }
987
1019
  const machineChecks = [
988
1020
  () => checkHostPorts(),
@@ -1035,20 +1067,22 @@ export const doctorCommand = new Command("doctor")
1035
1067
  // project. Only the machine checks mean anything there; the project checks
1036
1068
  // would all fail with "run tdk project".
1037
1069
  const inProject = Boolean(findProjectRoot());
1038
- const checks = inProject ? [...machineChecks, ...projectChecks] : [...machineChecks];
1039
- // Machine checks are independent and mostly wait on child processes, so run
1040
- // them concurrently. Project checks stay sequential; rendering ranks failures first.
1041
- const startedMachineChecks = machineChecks.map((checkFn) => Promise.resolve().then(checkFn));
1042
- const results = [];
1043
- for (const [index, checkFn] of checks.entries()) {
1044
- const result = await (startedMachineChecks[index] ?? checkFn());
1045
- results.push(result);
1046
- }
1047
- // Let machine checks finish before process.exit so their probes can clean up.
1048
- await Promise.allSettled(startedMachineChecks);
1049
- const allPassed = results.every((result) => result.didPass || result.isSkipped);
1070
+ const { checks: results, errors } = await collectDoctorChecks(machineChecks, inProject ? projectChecks : []);
1071
+ const report = createDoctorReport(orderDoctorResults(results), inProject, errors);
1072
+ const exitCode = getDoctorExitCode(report);
1073
+ const allPassed = report.data.ready;
1074
+ if (options.json) {
1075
+ console.log(JSON.stringify(report));
1076
+ for (const error of errors)
1077
+ console.error(error.message);
1078
+ if (exitCode !== 0)
1079
+ process.exit(exitCode);
1080
+ return;
1081
+ }
1082
+ for (const error of errors)
1083
+ console.error(`${chalk.red("✗")} Doctor check failed: ${error.message}`);
1050
1084
  for (const result of orderDoctorResults(results)) {
1051
- if (!result.didPass && !result.isSkipped) {
1085
+ if (!result.didPass && !result.isSkipped && !result.isWarning) {
1052
1086
  console.log(`${chalk.red("✗")} ${result.message}`);
1053
1087
  if (result.fix)
1054
1088
  console.log(`${chalk.blue("ℹ")} Fix: ${result.fix}`);
@@ -1074,7 +1108,7 @@ export const doctorCommand = new Command("doctor")
1074
1108
  }
1075
1109
  const outcome = getDoctorOutcomeMessage(inProject, allPassed);
1076
1110
  console.log(`${allPassed ? chalk.green(chalk.bold("✓")) : chalk.red(chalk.bold("✗"))} ${outcome}`);
1077
- if (!allPassed)
1078
- process.exit(1);
1111
+ if (exitCode !== 0)
1112
+ process.exit(exitCode);
1079
1113
  });
1080
1114
  //# sourceMappingURL=doctor.js.map
@@ -1,7 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import pkg from "../../package.json" with { type: "json" };
3
3
  const TDK_BANNER = `
4
- ${chalk.bold.cyan("tdk")} ${chalk.gray(`v${pkg.version}`)} ${chalk.white("Tilt Development Kit: run your microservices locally")}
4
+ ${chalk.bold.cyan("tdk")} ${chalk.gray(`v${pkg.version}`)} ${chalk.white("TDK runs many services locally with Docker + Tilt; Helm deploys the cluster")}
5
5
  `;
6
6
  const COMMAND_GROUPS = [
7
7
  {
@@ -78,7 +78,8 @@ function formatCommand(name, desc, alias, color) {
78
78
  }
79
79
  export function showHelp() {
80
80
  console.log(TDK_BANNER);
81
- console.log(chalk.bold.white("\n Project → Phase → Stack → Resource\n"));
81
+ console.log(chalk.gray("\n Local development inner loop; TDK does not replace Helm for cluster deployments.\n"));
82
+ console.log(chalk.bold.white(" Project → Phase → Stack → Resource\n"));
82
83
  console.log(chalk.gray(" Usage: tdk [command] [options]\n"));
83
84
  console.log(chalk.bold.yellow(" Global Options:"));
84
85
  console.log(` ${chalk.cyan("-v, --version".padEnd(20))} ${chalk.white("Display version number")}`);