@tdk-landscape/tdk-cli-core 1.3.55 → 1.3.56

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,11 @@ Notable changes to the `tdk` CLI. Versions match [npm](https://www.npmjs.com/pac
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ - `tdk project` adds `.env`, `.tdk/.tdk-out/`, `.tdk/.project-id` and `node_modules/` to `.gitignore`. `.env` holds a generated database password and was easy to commit by accident.
8
+ - `tdk doctor` checks that host ports 80, 443 and 5432 are free and names what holds them (usually a local Postgres). `tdk up` prints the same warning before starting Tilt.
9
+ - `tdk project` no longer tells you to edit `.env` first: the defaults work, and the Verdaccio variables are marked as belonging to the paid registry.
10
+ ## 1.3.55 (2026-09-27)
11
+
7
12
  - Dropped the `ora` dependency (only `tdk upgrade` used it) in favor of a small built-in spinner: 50 installed packages instead of 61, and no more duplicate versions of cli-cursor, restore-cursor, onetime and signal-exit.
8
13
  - `tdk doctor` outside a project checks only Docker, Compose and Tilt, and reports "This machine is ready for TDK". Before, running it right after installing (as the installer suggests) failed with "Master configs missing … Environment not ready".
9
14
 
package/README.md CHANGED
@@ -26,7 +26,7 @@ tdk up shop # run it with hot reload
26
26
 
27
27
  ![tdk scaffolding a backend and a frontend, then listing the stack](docs/demo.svg)
28
28
 
29
- **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 the CLI. macOS and Linux are supported. Windows is untested (WSL2 may work). Run `tdk doctor` to check all of it.
29
+ **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 the CLI. Ports 80, 443 and 5432 must be free (a local Postgres is the usual conflict). macOS and Linux are supported. Windows is untested (WSL2 may work). Run `tdk doctor` to check all of it.
30
30
 
31
31
  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.
32
32
 
@@ -144,6 +144,7 @@ TDK has no telemetry or analytics. The CLI only goes online when you ask it to:
144
144
  - `tdk upgrade` checks the latest release on GitHub.
145
145
  - 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.
146
146
  - Docker and Tilt pull images and packages as they normally would.
147
+ - Tilt, which TDK runs, has its own anonymous usage analytics. Turn them off with `tilt analytics opt out`.
147
148
 
148
149
  ## FAQ
149
150
 
@@ -4,7 +4,7 @@ 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 { checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, } from "../utils/doctor-runtime.js";
7
+ import { checkHostPorts, checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, } 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";
@@ -697,6 +697,8 @@ export const doctorCommand = new Command("doctor")
697
697
  checkEnvironmentVariables,
698
698
  // Preflight: catch "port 80 already allocated" BEFORE claiming ready.
699
699
  checkIngressPorts,
700
+ // Preflight: a local Postgres or web server on 5432/80/443.
701
+ () => checkHostPorts(),
700
702
  // Preflight: Verdaccio down causes ImageBuild bun install ConnectionRefused.
701
703
  checkPrivateNpmRegistry,
702
704
  // Runtime checks: skip gracefully if the stack isn't started yet.
@@ -8,7 +8,7 @@ import { generateMasterConfigs, readProjectConfig } from "../generator/template-
8
8
  import { MASTER_CONFIG_FILES } from "../utils/constants.js";
9
9
  import { ensureEnvFile, validateEnvFile } from "../utils/env-validator.js";
10
10
  import { errorFactories, runCommand, showErrorAndExit } from "../utils/errors.js";
11
- import { ensureDirectory, writeJsonFile } from "../utils/file-helpers.js";
11
+ import { ensureDirectory, ensureGitignore, writeJsonFile } from "../utils/file-helpers.js";
12
12
  import { showCancelled, showCommandHeader, showDetail, showStep, showSuccess, } from "../utils/formatting.js";
13
13
  import { findProjectRoot } from "../utils/paths.js";
14
14
  import { PROJECT_FEATURES } from "../utils/project-features.js";
@@ -218,6 +218,10 @@ export const projectCommand = new Command("project")
218
218
  }
219
219
  showStep("\nšŸ“‹ Regenerating master configuration files...\n");
220
220
  await generateMasterConfigs(projectRoot);
221
+ const ignored = ensureGitignore(projectRoot);
222
+ if (ignored.length > 0) {
223
+ showSuccess(`Added to .gitignore: ${ignored.join(", ")}`);
224
+ }
221
225
  console.log(chalk.green("\nāœ… Project configuration regenerated!"));
222
226
  showDetail("\nGenerated in .tdk/.tdk-out/:", 0);
223
227
  for (const file of MASTER_CONFIG_FILES) {
@@ -354,8 +358,7 @@ export const projectCommand = new Command("project")
354
358
  showStep("\nšŸ”§ Setting up environment variables...\n");
355
359
  const envCreated = ensureEnvFile(projectRoot);
356
360
  if (envCreated) {
357
- showSuccess("Created: .env (with required variables)");
358
- showDetail("→ Edit .env to set VERDACCIO_URL_DOCKER, TILT_ENV, etc.");
361
+ showSuccess("Created: .env (the defaults work as-is)");
359
362
  }
360
363
  else {
361
364
  showSuccess("Found: .env (environment already configured)");
@@ -368,6 +371,10 @@ export const projectCommand = new Command("project")
368
371
  }
369
372
  showDetail("→ Edit .env and fill in these values before running `tdk up`", 1);
370
373
  }
374
+ const ignored = ensureGitignore(projectRoot);
375
+ if (ignored.length > 0) {
376
+ showSuccess(`Added to .gitignore: ${ignored.join(", ")}`);
377
+ }
371
378
  console.log(chalk.green("\nāœ… Project configuration complete!"));
372
379
  showDetail("\nGenerated files in .tdk/.tdk-out/:", 0);
373
380
  for (const file of MASTER_CONFIG_FILES) {
@@ -378,9 +385,9 @@ export const projectCommand = new Command("project")
378
385
  showDetail("\nSource file:", 0);
379
386
  showDetail("- .tdk/project.json (edit this to change project structure)");
380
387
  showDetail("\nNext steps:", 0);
381
- showDetail("1. Edit .env to set required values (VERDACCIO_URL_DOCKER, TILT_ENV)");
382
- showDetail("2. Run `tdk config regenerate` after editing .tdk/project.json");
383
- showDetail("3. Run `tdk up` to start development");
388
+ showDetail("1. tdk resource api --type backend --stack app # scaffold a service");
389
+ showDetail("2. tdk up app # start it");
390
+ showDetail("\nAfter editing .tdk/project.json, run `tdk config regenerate`.", 0);
384
391
  });
385
392
  });
386
393
  //# sourceMappingURL=project.js.map
@@ -3,6 +3,7 @@ import chalk from "chalk";
3
3
  import { Command } from "commander";
4
4
  import { ensureProjectRuntimeAssets } from "../generator/template-engine.js";
5
5
  import { handleDryRun } from "../utils/command-helpers.js";
6
+ import { checkHostPorts } from "../utils/doctor-runtime.js";
6
7
  import { errorFactories, handleTiltFailure, withTiltCheck } from "../utils/errors.js";
7
8
  import { formatCount } from "../utils/formatting.js";
8
9
  import { findProjectRoot } from "../utils/paths.js";
@@ -84,6 +85,14 @@ export const upCommand = new Command("up")
84
85
  if (handleDryRun(options, "not starting services", dryRunCommand)) {
85
86
  return;
86
87
  }
88
+ // Warn rather than block: a stale local Postgres on 5432 makes Tilt fail
89
+ // halfway through startup with a bind error that's hard to read.
90
+ const hostPorts = await checkHostPorts();
91
+ if (!hostPorts.didPass && !options.quiet) {
92
+ console.log(chalk.yellow(`\nāš ļø ${hostPorts.message}`));
93
+ if (hostPorts.fix)
94
+ console.log(chalk.gray(` ${hostPorts.fix}\n`));
95
+ }
87
96
  if (options.force && !options.quiet) {
88
97
  console.log(chalk.yellow("Force flag set - killing any existing Tilt processes..."));
89
98
  execSync("killall tilt 2>/dev/null || true", { shell: "/bin/sh", stdio: "pipe" });
@@ -1,5 +1,6 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
+ import { createServer } from "node:net";
3
4
  import { join } from "node:path";
4
5
  import { formatCount } from "./formatting.js";
5
6
  import { findProjectRoot } from "./paths.js";
@@ -322,6 +323,90 @@ export function checkIngressPorts(exec = execSync, projectName = getProjectName(
322
323
  fix: `Stop the foreign container(s), e.g. \`docker stop ${foreign[0]?.name}\`, or shut down the other TDK/Tilt project using that Traefik. Then re-run \`tdk up\`.`,
323
324
  };
324
325
  }
326
+ /** Host ports the generated stack publishes: Traefik (80, 443) and Postgres (5432). */
327
+ export const HOST_PORTS = { 80: "HTTP", 443: "HTTPS", 5432: "Postgres" };
328
+ function probeAddress(port, host) {
329
+ return new Promise((resolve) => {
330
+ const server = createServer();
331
+ server.once("error", (err) =>
332
+ // EACCES: ports below 1024 need root on Linux, so we can't tell.
333
+ resolve(err.code === "EADDRINUSE" ? "in-use" : "unknown"));
334
+ server.once("listening", () => server.close(() => resolve("free")));
335
+ server.listen(port, host);
336
+ });
337
+ }
338
+ /**
339
+ * Probes both the wildcard and loopback address: a Homebrew Postgres listens on
340
+ * 127.0.0.1 only, which a wildcard bind alone doesn't detect.
341
+ */
342
+ export async function probeHostPort(port) {
343
+ const results = await Promise.all([
344
+ probeAddress(port, "0.0.0.0"),
345
+ probeAddress(port, "127.0.0.1"),
346
+ ]);
347
+ if (results.includes("in-use"))
348
+ return "in-use";
349
+ return results.includes("free") ? "free" : "unknown";
350
+ }
351
+ /**
352
+ * Catches host ports taken by something other than this project's containers,
353
+ * most often a local Postgres on 5432 or a web server on 80. Docker-held 80/443
354
+ * are reported by checkIngressPorts, so only 5432 is checked against other
355
+ * containers here.
356
+ */
357
+ export async function checkHostPorts(exec = execSync, projectName = getProjectName(), probe = probeHostPort) {
358
+ const ports = Object.keys(HOST_PORTS).map(Number);
359
+ const inUse = [];
360
+ for (const port of ports) {
361
+ if ((await probe(port)) === "in-use")
362
+ inUse.push(port);
363
+ }
364
+ if (inUse.length === 0) {
365
+ return {
366
+ name: "Host Ports",
367
+ didPass: true,
368
+ message: `Host ports ${ports.join(", ")} are free`,
369
+ };
370
+ }
371
+ let dockerPs = "";
372
+ try {
373
+ dockerPs = exec("docker ps --format '{{.Names}}\\t{{.Ports}}'", {
374
+ stdio: "pipe",
375
+ encoding: "utf-8",
376
+ timeout: EXEC_TIMEOUT_MS,
377
+ });
378
+ }
379
+ catch {
380
+ // Can't attribute ports without Docker; treat them as held by the host.
381
+ }
382
+ const holders = parsePublishedPortHolders(dockerPs, inUse);
383
+ const prefix = toComposeProjectPrefix(projectName).toLowerCase();
384
+ const isOwn = (name) => {
385
+ const lower = name.toLowerCase();
386
+ return lower.startsWith(`${prefix}_`) || lower.startsWith(`${prefix}-`);
387
+ };
388
+ const problems = [];
389
+ for (const port of inUse) {
390
+ const holder = holders.find((h) => h.publishedPorts.includes(port));
391
+ if (!holder) {
392
+ problems.push(`${port} (${HOST_PORTS[port]}) is used by a program on this machine`);
393
+ }
394
+ else if (port === 5432 && !isOwn(holder.name)) {
395
+ problems.push(`${port} (${HOST_PORTS[port]}) is published by container ${holder.name}`);
396
+ }
397
+ }
398
+ if (problems.length === 0) {
399
+ return { name: "Host Ports", didPass: true, message: "Host ports are held by this project" };
400
+ }
401
+ const first = inUse.find((port) => problems.some((p) => p.startsWith(`${port} `))) ?? inUse[0];
402
+ return {
403
+ name: "Host Ports",
404
+ didPass: false,
405
+ message: `Ports TDK needs are taken:\n ${problems.join("\n ")}`,
406
+ fix: `Find what holds a port with \`lsof -nP -iTCP:${first} -sTCP:LISTEN\` and stop it ` +
407
+ "(for a local Postgres: `brew services stop postgresql` or quit Postgres.app), then run `tdk up`.",
408
+ };
409
+ }
325
410
  /**
326
411
  * When a Tilt session is active, surface resource update errors (Traefik port
327
412
  * binds, image builds, etc.) instead of claiming the environment is ready
@@ -4,14 +4,14 @@ import { join } from "node:path";
4
4
  const REQUIRED_ENV_VARS = [
5
5
  {
6
6
  name: "VERDACCIO_URL_DOCKER",
7
- description: "Docker-internal URL for npm registry",
7
+ description: "Docker-internal URL of the private npm registry (Verdaccio, a paid feature)",
8
8
  required: true,
9
9
  default: "http://verdaccio:4873",
10
10
  example: "http://verdaccio:4873",
11
11
  },
12
12
  {
13
13
  name: "VERDACCIO_URL",
14
- description: "Local development URL for npm registry",
14
+ description: "Local URL of the private npm registry (Verdaccio, a paid feature)",
15
15
  required: false,
16
16
  default: "http://localhost:4873",
17
17
  },
@@ -44,8 +44,8 @@ function generateDbPassword() {
44
44
  export function generateEnvFile(_projectRoot) {
45
45
  const lines = [
46
46
  "# šŸ”§ TDK Environment Configuration",
47
- "# Auto-generated by TDK CLI - edit as needed",
48
- "# These variables are required for services to run",
47
+ "# Generated by `tdk project`. The defaults work as-is; edit only to change them.",
48
+ "# Keep this file out of git (it holds a generated database password).",
49
49
  "#",
50
50
  "# After editing, run: tdk config regenerate && tdk up",
51
51
  "",
@@ -53,7 +53,7 @@ export function generateEnvFile(_projectRoot) {
53
53
  for (const envVar of REQUIRED_ENV_VARS) {
54
54
  lines.push(`# ${envVar.description}`);
55
55
  if (envVar.required) {
56
- lines.push(`# āš ļø REQUIRED - set this or services will fail`);
56
+ lines.push("# Required (the value below is a working default)");
57
57
  }
58
58
  if (envVar.example) {
59
59
  lines.push(`# Example: ${envVar.example}`);
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import chalk from "chalk";
4
4
  export function writeFilesWithProgress(basePath, tasks, onProgress) {
@@ -42,4 +42,25 @@ export function ensureDirectory(dirPath) {
42
42
  mkdirSync(dirPath, { recursive: true });
43
43
  }
44
44
  }
45
+ /** Generated files that must never be committed: .env holds a generated DB password. */
46
+ const GITIGNORE_ENTRIES = [".env", ".tdk/.tdk-out/", ".tdk/.project-id", "node_modules/"];
47
+ /**
48
+ * Appends the TDK entries missing from the project's .gitignore (creating it
49
+ * if needed), so the first `git add .` doesn't commit .env or generated
50
+ * output. Returns the entries it added.
51
+ */
52
+ export function ensureGitignore(projectRoot) {
53
+ const gitignorePath = resolve(projectRoot, ".gitignore");
54
+ const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : "";
55
+ const lines = new Set(existing.split("\n").map((line) => line.trim().replace(/\/$/, "")));
56
+ const missing = GITIGNORE_ENTRIES.filter((entry) => !lines.has(entry.replace(/\/$/, "")));
57
+ if (missing.length === 0)
58
+ return [];
59
+ const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
60
+ const header = existing
61
+ ? "\n# TDK (generated files and secrets)\n"
62
+ : "# TDK (generated files and secrets)\n";
63
+ appendFileSync(gitignorePath, `${prefix}${header}${missing.join("\n")}\n`);
64
+ return missing;
65
+ }
45
66
  //# sourceMappingURL=file-helpers.js.map
package/cli/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tdk-cli-internal",
3
3
  "private": true,
4
- "version": "1.3.55",
4
+ "version": "1.3.56",
5
5
  "description": "TDK CLI - Tilt Development Kit command line interface",
6
6
  "keywords": [
7
7
  "tdk",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdk-landscape/tdk-cli-core",
3
- "version": "1.3.55",
3
+ "version": "1.3.56",
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": {