@tdk-landscape/tdk-cli-core 1.3.61 → 1.3.62
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 +4 -0
- package/cli/dist/commands/doctor.js +3 -37
- package/cli/dist/utils/doctor-runtime.js +55 -0
- package/cli/package.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@ Notable changes to the `tdk` CLI. Versions match [npm](https://www.npmjs.com/pac
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
- `tdk doctor` no longer skips the service health check when Traefik answers with an error. A 404 (no route yet) or 502/503/504 (route found, container not answering) is reported as a failure that names the service and URL, instead of being read as "Traefik never bound :80". It is skipped only when every probe fails to connect.
|
|
8
|
+
|
|
9
|
+
## 1.3.61 (2026-09-28)
|
|
10
|
+
|
|
7
11
|
- `tdk doctor` and `tdk up` no longer report free host ports (80, 443, 5432) as "taken" on Linux. The port check bound `0.0.0.0` and `127.0.0.1` at the same time, and on Linux the second bind collided with our own first probe. It now probes them one after the other, which still catches a loopback-only Postgres on macOS. Found by the nightly clean-machine quickstart run.
|
|
8
12
|
|
|
9
13
|
## 1.3.60 (2026-09-27)
|
|
@@ -4,13 +4,13 @@ import { dirname, join, normalize } from "node:path";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { MASTER_CONFIG_FILES, QUICKSTART_DOCS_URL, REQUIRED_PACKAGE_SCRIPTS, } from "../utils/constants.js";
|
|
7
|
-
import { checkHostPorts, checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, } from "../utils/doctor-runtime.js";
|
|
7
|
+
import { checkHostPorts, checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, summarizeServiceProbes, } from "../utils/doctor-runtime.js";
|
|
8
8
|
import { validateEnvFile } from "../utils/env-validator.js";
|
|
9
9
|
import { formatCount } from "../utils/formatting.js";
|
|
10
10
|
import { findProjectRoot } from "../utils/paths.js";
|
|
11
11
|
import { buildHealthTargets, pingHealthTargets } from "../utils/service-urls.js";
|
|
12
12
|
import { discoverResourcesFromRoot } from "../utils/services.js";
|
|
13
|
-
export { checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, summarizeTiltBuildError, } from "../utils/doctor-runtime.js";
|
|
13
|
+
export { checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, summarizeServiceProbes, summarizeTiltBuildError, } from "../utils/doctor-runtime.js";
|
|
14
14
|
// A wedged Docker daemon makes `docker ps` block forever instead of failing,
|
|
15
15
|
// and doctor is exactly the tool people run when their environment is broken.
|
|
16
16
|
const EXEC_TIMEOUT_MS = 10_000;
|
|
@@ -658,41 +658,7 @@ async function checkServiceHealth(timeoutMs) {
|
|
|
658
658
|
message: "No routable services to ping",
|
|
659
659
|
};
|
|
660
660
|
}
|
|
661
|
-
|
|
662
|
-
const reachable = probes.filter((probe) => probe.ok);
|
|
663
|
-
const failed = probes.filter((probe) => !probe.ok);
|
|
664
|
-
// Nothing answered at all: either the stack was never started, or ingress
|
|
665
|
-
// never came up (Traefik port conflict). Prefer failing via
|
|
666
|
-
// checkTiltResourceHealth / checkIngressPorts when Tilt is up; here we only
|
|
667
|
-
// skip when there is truly nothing to probe yet.
|
|
668
|
-
if (reachable.length === 0) {
|
|
669
|
-
return {
|
|
670
|
-
name: "Service Health",
|
|
671
|
-
didPass: true,
|
|
672
|
-
isSkipped: true,
|
|
673
|
-
message: `No services responded - skipped ping of ${formatCount(targets.length, "service")} (stack may be down, or Traefik never bound :80)`,
|
|
674
|
-
fix: "If `tdk up` is already running, check Traefik/port 80 in the Tilt UI. Otherwise start with: tdk up",
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
if (failed.length === 0) {
|
|
678
|
-
return {
|
|
679
|
-
name: "Service Health",
|
|
680
|
-
didPass: true,
|
|
681
|
-
message: `All ${formatCount(targets.length, "service")} responding on /health`,
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
const details = failed
|
|
685
|
-
.map((probe) => {
|
|
686
|
-
const reason = probe.status ? `HTTP ${probe.status}` : (probe.error ?? "no response");
|
|
687
|
-
return `${probe.name} (${reason})\n ${probe.url}`;
|
|
688
|
-
})
|
|
689
|
-
.join("\n ");
|
|
690
|
-
return {
|
|
691
|
-
name: "Service Health",
|
|
692
|
-
didPass: false,
|
|
693
|
-
message: `${formatCount(failed.length, "service")} not responding (${reachable.length}/${probes.length} healthy):\n ${details}`,
|
|
694
|
-
fix: "Check container state and routing: docker ps, then tdk networks to compare the advertised URLs against Traefik's routers",
|
|
695
|
-
};
|
|
661
|
+
return summarizeServiceProbes(await pingHealthTargets(targets, timeoutMs));
|
|
696
662
|
}
|
|
697
663
|
export const doctorCommand = new Command("doctor")
|
|
698
664
|
.description("Check environment readiness for TDK")
|
|
@@ -349,6 +349,61 @@ export async function probeHostPort(port) {
|
|
|
349
349
|
return wildcard;
|
|
350
350
|
return probeAddress(port, "127.0.0.1");
|
|
351
351
|
}
|
|
352
|
+
/** What an HTTP status from a service's health URL means, in the words a user needs. */
|
|
353
|
+
function describeProbeFailure(probe) {
|
|
354
|
+
const { status } = probe;
|
|
355
|
+
if (status === undefined)
|
|
356
|
+
return probe.error ?? "no response";
|
|
357
|
+
if (status === 404) {
|
|
358
|
+
return "HTTP 404: Traefik is answering but has no route for this URL (container not started yet, or not running)";
|
|
359
|
+
}
|
|
360
|
+
if (status === 502 || status === 503 || status === 504) {
|
|
361
|
+
return `HTTP ${status}: Traefik found the route but the container isn't answering (still starting, or unhealthy)`;
|
|
362
|
+
}
|
|
363
|
+
return `HTTP ${status}`;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Turns health-URL probes into a doctor result.
|
|
367
|
+
*
|
|
368
|
+
* "Nothing answered" (connection refused or timeout on every probe) means the
|
|
369
|
+
* stack is down or Traefik never bound :80, so the check is skipped. Any HTTP
|
|
370
|
+
* response counts as an answer, including a 404 or 502 from Traefik: that means
|
|
371
|
+
* the ingress is up and the service behind it is not reachable, which is a
|
|
372
|
+
* failure. Treating only 2xx as "responded" reported a running Traefik with no
|
|
373
|
+
* route as "Traefik never bound :80" and skipped the check.
|
|
374
|
+
*/
|
|
375
|
+
export function summarizeServiceProbes(probes) {
|
|
376
|
+
const answered = probes.filter((probe) => probe.status !== undefined);
|
|
377
|
+
const failed = probes.filter((probe) => !probe.ok);
|
|
378
|
+
if (answered.length === 0) {
|
|
379
|
+
return {
|
|
380
|
+
name: "Service Health",
|
|
381
|
+
didPass: true,
|
|
382
|
+
isSkipped: true,
|
|
383
|
+
message: `No services responded - skipped ping of ${formatCount(probes.length, "service")} (stack may be down, or Traefik never bound :80)`,
|
|
384
|
+
fix: "If `tdk up` is already running, check Traefik/port 80 in the Tilt UI. Otherwise start with: tdk up",
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
if (failed.length === 0) {
|
|
388
|
+
return {
|
|
389
|
+
name: "Service Health",
|
|
390
|
+
didPass: true,
|
|
391
|
+
message: `All ${formatCount(probes.length, "service")} responding on /health`,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const details = failed
|
|
395
|
+
.map((probe) => `${probe.name} (${describeProbeFailure(probe)})\n ${probe.url}`)
|
|
396
|
+
.join("\n ");
|
|
397
|
+
const notRouted = failed.some((probe) => probe.status === 404);
|
|
398
|
+
return {
|
|
399
|
+
name: "Service Health",
|
|
400
|
+
didPass: false,
|
|
401
|
+
message: `${formatCount(failed.length, "service")} not healthy (${probes.length - failed.length}/${probes.length} healthy):\n ${details}`,
|
|
402
|
+
fix: notRouted
|
|
403
|
+
? "If `tdk up` just started, images may still be building: re-run `tdk doctor` in a minute. Otherwise check `docker ps` and the resource in the Tilt UI, and compare `tdk networks` with the URL above."
|
|
404
|
+
: "Check container state and routing: docker ps, then tdk networks to compare the advertised URLs against Traefik's routers",
|
|
405
|
+
};
|
|
406
|
+
}
|
|
352
407
|
/**
|
|
353
408
|
* Catches host ports taken by something other than this project's containers,
|
|
354
409
|
* most often a local Postgres on 5432 or a web server on 80. Docker-held 80/443
|
package/cli/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tdk-landscape/tdk-cli-core",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.62",
|
|
4
4
|
"description": "Run 100 microservices on a 16 GB laptop without Kubernetes. Scaffold services and run them locally with hot reload, built on Tilt.",
|
|
5
5
|
"homepage": "https://tdk-landscape.github.io/tdk-website",
|
|
6
6
|
"repository": {
|