@tdk-landscape/tdk-cli-core 1.3.75 → 1.3.77

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +59 -13
  3. package/cli/bin/tdk.js +22 -2
  4. package/cli/dist/cli.js +9 -2
  5. package/cli/dist/commands/completion.js +30 -3
  6. package/cli/dist/commands/config.js +59 -1
  7. package/cli/dist/commands/doctor.js +295 -98
  8. package/cli/dist/commands/eject.js +77 -0
  9. package/cli/dist/commands/networks.js +17 -4
  10. package/cli/dist/commands/project.js +28 -5
  11. package/cli/dist/commands/resource.js +118 -10
  12. package/cli/dist/commands/up.js +93 -21
  13. package/cli/dist/commands/upgrade.js +163 -68
  14. package/cli/dist/generator/template-engine.js +77 -13
  15. package/cli/dist/types/index.js +6 -1
  16. package/cli/dist/utils/cold-preflight.js +198 -0
  17. package/cli/dist/utils/constants.js +4 -0
  18. package/cli/dist/utils/docker.js +15 -1
  19. package/cli/dist/utils/doctor-runtime.js +48 -16
  20. package/cli/dist/utils/doctor-wiring.js +269 -0
  21. package/cli/dist/utils/errors.js +5 -5
  22. package/cli/dist/utils/exec-async.js +30 -0
  23. package/cli/dist/utils/generated-paths.js +49 -0
  24. package/cli/dist/utils/platform.js +53 -0
  25. package/cli/dist/utils/port-assignment.js +13 -20
  26. package/cli/dist/utils/project-templates.js +7 -2
  27. package/cli/dist/utils/resource-features.js +6 -2
  28. package/cli/dist/utils/service-manifest.js +73 -0
  29. package/cli/dist/utils/services.js +8 -1
  30. package/cli/dist/utils/tar.js +27 -5
  31. package/cli/dist/utils/tilt-process.js +53 -0
  32. package/cli/dist/utils/tilt.js +3 -1
  33. package/cli/dist/utils/which.js +26 -0
  34. package/cli/dist/utils/windows-doctor.js +175 -0
  35. package/cli/package.json +1 -1
  36. package/cli/templates/TILT_RESOURCE_DEFAULTS.star.hbs +1 -2
  37. package/cli/templates/TILT_TECH_STACK.star.hbs +0 -1
  38. package/cli/templates/Tiltfile.hbs +24 -4
  39. package/cli/templates/spec.master.hbs +1 -2
  40. package/discovery/AGENTS.md +1 -1
  41. package/discovery/constants.star +2 -2
  42. package/discovery/discovery_orchestrator.star +2 -2
  43. package/discovery/manifest/constants.star +1 -1
  44. package/discovery/manifest/loading.star +7 -3
  45. package/discovery/registry.star +17 -3
  46. package/engine/README.md +2 -0
  47. package/engine/schemas/service-schema.json +47 -21
  48. package/engine/scripts/generate-manifest.sh +2 -1
  49. package/engine/topologies/platform/docker/compose/compose.star +37 -5
  50. package/engine/topologies/tilt/common/utils.star +54 -11
  51. package/engine/topologies/tilt/manifest/AGENTS.md +1 -1
  52. package/engine/topologies/tilt/manifest/constants.star +3 -0
  53. package/engine/topologies/tilt/manifest/schema.star +2 -4
  54. package/engine/topologies/tilt/manifest/validator.star +1 -1
  55. package/engine/topologies/tilt/resources/infra-loader.star +14 -3
  56. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +15 -11
  57. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +15 -6
  58. package/examples/tdk-example/LICENSE +21 -0
  59. package/examples/tdk-example/README.md +23 -0
  60. package/examples/tdk-example/TILT_SERVICE_DEFAULTS.star +42 -0
  61. package/examples/tdk-example/TILT_TECH_STACK.star +67 -0
  62. package/examples/tdk-example/Tiltfile +25 -0
  63. package/examples/tdk-example/services/platform/messaging/docker-compose.yml +26 -0
  64. package/examples/tdk-example/services/shop/orders-api/package.json +20 -0
  65. package/examples/tdk-example/services/shop/orders-api/service.json +14 -0
  66. package/examples/tdk-example/services/shop/orders-api/src/index.ts +81 -0
  67. package/examples/tdk-example/services/shop/orders-app/index.html +12 -0
  68. package/examples/tdk-example/services/shop/orders-app/package.json +20 -0
  69. package/examples/tdk-example/services/shop/orders-app/public/.gitkeep +0 -0
  70. package/examples/tdk-example/services/shop/orders-app/service.json +9 -0
  71. package/examples/tdk-example/services/shop/orders-app/src/main.tsx +9 -0
  72. package/examples/tdk-example/services/shop/orders-app/src/orders.tsx +67 -0
  73. package/examples/tdk-example/services/shop/orders-app/src/style.css +15 -0
  74. package/examples/tdk-example/services/shop/orders-worker/package.json +18 -0
  75. package/examples/tdk-example/services/shop/orders-worker/service.json +13 -0
  76. package/examples/tdk-example/services/shop/orders-worker/src/index.ts +33 -0
  77. package/package.json +2 -1
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
+ - `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.
8
+
9
+ - `tdk doctor` now catches wiring mistakes that used to surface minutes into `tdk up`: a resource with no `package.json` (the image build stops at "COPY ... not found"), a `params` URL such as `http://auth-emulator:4000` whose port is not the one the target listens on (its `service.json` `port`), a frontend calling a backend through `localhost:<port>` (TDK publishes no backend ports on localhost; use the Traefik route), the `nats` feature without `services/platform/messaging/docker-compose.yml` (TDK does not generate it, so no NATS server starts), two Tilt processes running for one project, and Docker having no address pool left for the networks a project needs. Found while building [tdk-ecommerce-example](https://github.com/tdk-landscape/tdk-ecommerce-example) and a queue/auth example.
10
+
11
+ - `tdk project ecommerce` clones [tdk-ecommerce-example](https://github.com/tdk-landscape/tdk-ecommerce-example), a Vue 3 storefront and Hono catalog API. Before, only restaurant, saas, erp, user-management and example were accepted.
12
+
7
13
  ## 1.3.73 (2026-09-28)
8
14
 
9
15
  - 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.
package/README.md CHANGED
@@ -2,14 +2,31 @@
2
2
 
3
3
  # TDK — Tilt Development Kit
4
4
 
5
- **Run your whole microservice landscape locally. No Kubernetes.**
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).
6
7
 
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
+ 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)
8
25
 
9
26
  [![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)
10
27
  [![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)
11
28
  [![Quickstart E2E](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/quickstart-e2e.yml/badge.svg)](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/quickstart-e2e.yml)
12
- [![ERP scale E2E](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/erp-scale-e2e.yml/badge.svg)](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/erp-scale-e2e.yml)
29
+ [![ERP fixture scale E2E](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/erp-scale-e2e.yml/badge.svg)](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/erp-scale-e2e.yml)
13
30
  [![Example apps E2E](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/examples-e2e.yml/badge.svg?branch=main)](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/examples-e2e.yml)
14
31
  [![Known Vulnerabilities](https://snyk.io/test/github/tdk-landscape/tdk-cli-core/badge.svg)](https://snyk.io/test/github/tdk-landscape/tdk-cli-core)
15
32
  [![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)
@@ -19,8 +36,23 @@ One CLI that scaffolds your services and runs the whole landscape locally with h
19
36
 
20
37
  </div>
21
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.
44
+
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).
52
+
22
53
  ```bash
23
54
  npm install -g @tdk-landscape/tdk-cli-core
55
+ npx @tdk-landscape/tdk-cli-core
24
56
  mkdir shop && cd shop # tdk writes files into the current directory
25
57
  tdk project --yes # set up the project
26
58
  tdk resource orders-api --type backend --stack shop # scaffold a service
@@ -29,9 +61,11 @@ tdk up shop # run it with hot reload
29
61
 
30
62
  ![tdk scaffolding a backend and a frontend, then listing the stack](docs/demo.svg)
31
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
+
32
66
  ![npm downloads, last month](docs/npm-downloads.svg)
33
67
 
34
- **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.
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.
35
69
 
36
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.
37
71
 
@@ -48,9 +82,9 @@ This is the core monorepo for TDK: the `tdk` CLI, the Starlark-based Tilt orches
48
82
 
49
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.
50
84
 
51
- ## Benchmark: 100 services on one laptop
85
+ ## Fixture bench: 100 generated services on one laptop
52
86
 
53
- Measured with [`scripts/benchmark/container-scale.ts`](scripts/benchmark/README.md) against the 100-service [ERP example](https://github.com/tdk-landscape/tdk-erp-system) on a 16 GB machine (Docker VM: 7.75 GiB). Raw results are in [`benchmarks/results/`](benchmarks/results).
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).
54
88
 
55
89
  | Services | All healthy after | Total memory | Avg memory / service | Crashes / OOM kills |
56
90
  |---:|---:|---:|---:|---:|
@@ -70,9 +104,9 @@ What this does and doesn't measure:
70
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:
71
105
 
72
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.
73
- - [**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.
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.
74
108
 
75
- 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.
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.
76
110
 
77
111
  ## What is TDK?
78
112
 
@@ -166,7 +200,9 @@ TDK has no telemetry or analytics. The CLI only goes online when you ask it to:
166
200
 
167
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.
168
202
 
169
- **Do I need 100 microservices?** No. Two services work the same way. The 100-service example is a stress test to show the overhead stays flat.
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.
170
206
 
171
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.
172
208
 
@@ -179,22 +215,32 @@ make test-fast # fast unit tests, no external deps
179
215
  make pre-commit-run # run pre-commit hooks on all files
180
216
  ```
181
217
 
182
- `npm run typecheck` / `npm run lint` / `npm run test` delegate to the `cli/` workspace. See [cli/AGENTS.md](cli/AGENTS.md) for coding conventions if you're contributing to the CLI, and [engine/AGENTS.md](engine/AGENTS.md) for the Starlark topology framework.
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.
183
219
 
184
220
  ## Environment check
185
221
 
222
+ ### Cold npx / first run
223
+
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.
231
+
186
232
  ```bash
187
233
  tdk doctor
188
234
  ```
189
235
 
190
- 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`.
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.
191
237
 
192
238
  ## Community
193
239
 
194
240
  - **Questions and ideas:** [open an issue](https://github.com/tdk-landscape/tdk-cli-core/issues/new/choose).
195
- - **Contributing:** see [CONTRIBUTING.md](CONTRIBUTING.md). Issues labeled [`good first issue`](https://github.com/tdk-landscape/tdk-cli-core/labels/good%20first%20issue) are a good place to start.
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.
196
242
  - **Security:** see [SECURITY.md](SECURITY.md). Please don't report vulnerabilities in public issues.
197
- - **Examples:** [ERP system (100 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).
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).
198
244
 
199
245
  ## License
200
246
 
package/cli/bin/tdk.js CHANGED
@@ -1,14 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { dirname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
5
 
6
6
  const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = dirname(__filename);
8
8
 
9
+ function nodeTooOld(version) {
10
+ const [major = 0, minor = 0] = String(version)
11
+ .split(".")
12
+ .map((part) => Number.parseInt(part, 10));
13
+ return (
14
+ !Number.isFinite(major) || !Number.isFinite(minor) || major < 22 || (major === 22 && minor < 12)
15
+ );
16
+ }
17
+
18
+ if (nodeTooOld(process.versions.node)) {
19
+ console.error(
20
+ "TDK CLI needs Node.js 22.12+ (current: " +
21
+ process.versions.node +
22
+ ").\n" +
23
+ "This is the first cold-npx failure. Bun/Prisma/NATS are not involved yet.\n" +
24
+ "Install Node 22.12+ and rerun.",
25
+ );
26
+ process.exit(1);
27
+ }
28
+
9
29
  const cliPath = join(__dirname, "..", "dist", "cli.js");
10
30
 
11
- import(cliPath).catch((err) => {
31
+ import(pathToFileURL(cliPath).href).catch((err) => {
12
32
  console.error("Failed to start TDK:", err);
13
33
  process.exit(1);
14
34
  });
package/cli/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import { completionCommand } from "./commands/completion.js";
6
6
  import { configCommand } from "./commands/config.js";
7
7
  import { doctorCommand } from "./commands/doctor.js";
8
8
  import { downCommand } from "./commands/down.js";
9
+ import { ejectCommand } from "./commands/eject.js";
9
10
  import { showHelp } from "./commands/help.js";
10
11
  import { networksCommand } from "./commands/networks.js";
11
12
  import { projectCommand } from "./commands/project.js";
@@ -19,6 +20,7 @@ import { uiCommand } from "./commands/ui.js";
19
20
  import { upCommand } from "./commands/up.js";
20
21
  import { upgradeCommand } from "./commands/upgrade.js";
21
22
  import { versionCommand } from "./commands/version.js";
23
+ import { formatColdPreflight, runColdPreflight } from "./utils/cold-preflight.js";
22
24
  const program = new Command();
23
25
  program
24
26
  .name("tdk")
@@ -40,6 +42,7 @@ program.addCommand(resourcesCommand);
40
42
  program.addCommand(projectsCommand);
41
43
  program.addCommand(upCommand);
42
44
  program.addCommand(downCommand);
45
+ program.addCommand(ejectCommand);
43
46
  program.addCommand(statusCommand);
44
47
  program.addCommand(stackCommand);
45
48
  program.addCommand(resourceCommand);
@@ -53,9 +56,13 @@ program.addCommand(upgradeCommand);
53
56
  program.addCommand(networksCommand);
54
57
  // Default: show help if no command provided
55
58
  if (process.argv.length === 2) {
56
- showHelp();
57
- process.exit(0);
59
+ const result = await runColdPreflight();
60
+ console.log(formatColdPreflight(result));
61
+ console.log("\nCommands: tdk doctor | tdk project --yes | tdk --help");
62
+ process.exit(result.ok ? 0 : 1);
58
63
  }
64
+ if (process.argv[2] === "--doctor")
65
+ process.argv[2] = "doctor";
59
66
  if (process.argv.length === 3 && ["-h", "--help", "help"].includes(process.argv[2])) {
60
67
  showHelp();
61
68
  process.exit(0);
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import chalk from "chalk";
@@ -213,9 +213,16 @@ complete -c tdk -n '__fish_seen_subcommand_from doctor' -l fix -d 'Attempt to fi
213
213
  complete -c tdk -n '__fish_seen_subcommand_from upgrade' -l force -d 'Force upgrade even if on latest'
214
214
  complete -c tdk -n '__fish_seen_subcommand_from upgrade' -l dry-run -d 'Show what would be upgraded'
215
215
  `;
216
+ const POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName tdk -ScriptBlock {
217
+ param($wordToComplete)
218
+ $cmds = @('up','down','doctor','project','resource','stacks','status','networks','upgrade','version','ui','config')
219
+ $cmds | Where-Object { $_ -like "$wordToComplete*" }
220
+ }`;
221
+ const POWERSHELL_START = "# TDK-CLI-COMPLETION-START";
222
+ const POWERSHELL_END = "# TDK-CLI-COMPLETION-END";
216
223
  export const completionCommand = new Command("completion")
217
224
  .description("Generate shell completion scripts")
218
- .option("-s, --shell <shell>", "Target shell (bash, zsh, fish)", "bash")
225
+ .option("-s, --shell <shell>", "Target shell (bash, zsh, fish, powershell)", "bash")
219
226
  .option("-o, --output <path>", "Output file path (default: stdout)")
220
227
  .option("--install", "Install to shell config automatically")
221
228
  .action((options) => {
@@ -235,9 +242,13 @@ export const completionCommand = new Command("completion")
235
242
  completionScript = FISH_COMPLETION;
236
243
  filename = "tdk.fish";
237
244
  break;
245
+ case "powershell":
246
+ completionScript = `${POWERSHELL_START}\n${POWERSHELL_COMPLETION}\n${POWERSHELL_END}`;
247
+ filename = "Microsoft.PowerShell_profile.ps1";
248
+ break;
238
249
  default:
239
250
  console.error(chalk.red(`❌ Unsupported shell: ${shell}`));
240
- console.log(chalk.gray("Supported shells: bash, zsh, fish"));
251
+ console.log(chalk.gray("Supported shells: bash, zsh, fish, powershell"));
241
252
  process.exit(1);
242
253
  }
243
254
  if (options.install) {
@@ -245,6 +256,22 @@ export const completionCommand = new Command("completion")
245
256
  let installPath;
246
257
  let installInstructions;
247
258
  switch (shell) {
259
+ case "powershell": {
260
+ const docs = join(home, "Documents");
261
+ const ps7 = process.env.PSModulePath?.includes(join("Documents", "PowerShell", "Modules"));
262
+ const profileDir = join(docs, ps7 ? "PowerShell" : "WindowsPowerShell");
263
+ installPath = join(profileDir, filename);
264
+ mkdirSync(profileDir, { recursive: true });
265
+ const existing = existsSync(installPath) ? readFileSync(installPath, "utf-8") : "";
266
+ const block = `${POWERSHELL_START}\n${POWERSHELL_COMPLETION}\n${POWERSHELL_END}`;
267
+ const markerPattern = new RegExp(`${POWERSHELL_START}[\\s\\S]*?${POWERSHELL_END}`, "m");
268
+ const updated = markerPattern.test(existing)
269
+ ? existing.replace(markerPattern, block)
270
+ : `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${block}\n`;
271
+ writeFileSync(installPath, updated, "utf-8");
272
+ installInstructions = "\n# Close and reopen PowerShell to load the completion.";
273
+ break;
274
+ }
248
275
  case "bash": {
249
276
  installPath = join(home, ".bash_completion.d", filename);
250
277
  const bashDir = join(home, ".bash_completion.d");
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { existsSync, readFileSync } from "node:fs";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import chalk from "chalk";
5
5
  import { Command } from "commander";
@@ -10,6 +10,8 @@ import { assertValid } from "../utils/command-helpers.js";
10
10
  import { MASTER_CONFIG_FILES } from "../utils/constants.js";
11
11
  import { requireProjectRoot, runCommand } from "../utils/errors.js";
12
12
  import { writeJsonFile } from "../utils/file-helpers.js";
13
+ import { SERVICE_MANIFEST_SCHEMA_VERSION, validateServiceManifest, } from "../utils/service-manifest.js";
14
+ import { discoverServiceManifestPaths } from "../utils/services.js";
13
15
  import { validateOptionalInfraService } from "../utils/validation.js";
14
16
  /**
15
17
  * Serialize ProjectConfig to JSON-safe value.
@@ -115,6 +117,59 @@ export const configCommand = new Command("config")
115
117
  await generateMasterConfigs(projectRoot);
116
118
  console.log(chalk.green("\n✅ Configuration regenerated!"));
117
119
  });
120
+ }))
121
+ .addCommand(new Command("migrate")
122
+ .description("Migrate service.json files to the current schema version")
123
+ .action(async () => {
124
+ await runCommand(async () => {
125
+ const projectRoot = requireProjectRoot();
126
+ const failures = [];
127
+ const pendingMigrations = [];
128
+ for (const filePath of discoverServiceManifestPaths(projectRoot)) {
129
+ let manifest;
130
+ try {
131
+ manifest = JSON.parse(readFileSync(filePath, "utf-8"));
132
+ }
133
+ catch (error) {
134
+ failures.push(`${filePath}: ${error instanceof Error ? error.message : String(error)}`);
135
+ continue;
136
+ }
137
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
138
+ failures.push(`${filePath}: expected a JSON object`);
139
+ continue;
140
+ }
141
+ const record = manifest;
142
+ if (record.appType === undefined && typeof record.type === "string") {
143
+ record.appType = record.type;
144
+ }
145
+ if (record.schemaVersion === SERVICE_MANIFEST_SCHEMA_VERSION)
146
+ continue;
147
+ if (record.schemaVersion !== undefined) {
148
+ failures.push(`${filePath}.schemaVersion: cannot migrate unsupported version ${String(record.schemaVersion)} automatically`);
149
+ continue;
150
+ }
151
+ const validation = validateServiceManifest({ ...record, schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION }, filePath);
152
+ if (validation.errors.length > 0) {
153
+ failures.push(...validation.errors);
154
+ continue;
155
+ }
156
+ for (const warning of validation.warnings) {
157
+ console.warn(chalk.yellow(`⚠️ ${warning}`));
158
+ }
159
+ record.schemaVersion = SERVICE_MANIFEST_SCHEMA_VERSION;
160
+ pendingMigrations.push({ filePath, manifest: record });
161
+ }
162
+ for (const failure of failures)
163
+ console.error(chalk.red(`❌ ${failure}`));
164
+ if (failures.length > 0)
165
+ process.exit(1);
166
+ for (const migration of pendingMigrations) {
167
+ writeFileSync(migration.filePath, `${JSON.stringify(migration.manifest, null, 2)}\n`);
168
+ }
169
+ const migrated = pendingMigrations.length;
170
+ console.log(chalk.green(`✅ Migrated ${migrated} service.json file${migrated === 1 ? "" : "s"} to schema version ${SERVICE_MANIFEST_SCHEMA_VERSION}.`));
171
+ console.log(chalk.yellow("Review the diff; unknown fields were preserved."));
172
+ });
118
173
  }))
119
174
  .addCommand(new Command("verify")
120
175
  .description("Verify that generated files match .tdk/project.json")
@@ -123,6 +178,9 @@ export const configCommand = new Command("config")
123
178
  const projectRoot = requireProjectRoot();
124
179
  console.log(chalk.blue("🔍 Verifying configuration...\n"));
125
180
  const result = verifyMasterConfigs(projectRoot);
181
+ for (const warning of result.warnings) {
182
+ console.warn(chalk.yellow(`⚠️ ${warning}`));
183
+ }
126
184
  if (result.valid) {
127
185
  console.log(chalk.green("✅ All files are in sync!"));
128
186
  return;