@tdk-landscape/tdk-cli-core 1.3.51 → 1.3.52

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 (84) hide show
  1. package/.github/workflows/ci.yml +13 -0
  2. package/.github/workflows/release-binaries.yml +76 -1
  3. package/CHANGELOG.md +50 -0
  4. package/README.md +52 -17
  5. package/cli/README.md +16 -43
  6. package/cli/biome.json +0 -4
  7. package/cli/dist/commands/help.d.ts.map +1 -1
  8. package/cli/dist/commands/help.js +22 -39
  9. package/cli/dist/commands/help.js.map +1 -1
  10. package/cli/dist/commands/project.d.ts +6 -0
  11. package/cli/dist/commands/project.d.ts.map +1 -1
  12. package/cli/dist/commands/project.js +16 -0
  13. package/cli/dist/commands/project.js.map +1 -1
  14. package/cli/dist/commands/up.d.ts.map +1 -1
  15. package/cli/dist/commands/up.js +5 -0
  16. package/cli/dist/commands/up.js.map +1 -1
  17. package/cli/dist/commands/upgrade.d.ts +3 -0
  18. package/cli/dist/commands/upgrade.d.ts.map +1 -1
  19. package/cli/dist/commands/upgrade.js +43 -1
  20. package/cli/dist/commands/upgrade.js.map +1 -1
  21. package/cli/dist/utils/constants.d.ts +1 -1
  22. package/cli/dist/utils/constants.d.ts.map +1 -1
  23. package/cli/dist/utils/constants.js +1 -1
  24. package/cli/dist/utils/constants.js.map +1 -1
  25. package/cli/dist/utils/doctor-runtime.js +5 -5
  26. package/cli/dist/utils/doctor-runtime.js.map +1 -1
  27. package/cli/dist/utils/prompt.d.ts.map +1 -1
  28. package/cli/dist/utils/prompt.js +6 -1
  29. package/cli/dist/utils/prompt.js.map +1 -1
  30. package/cli/dist/utils/service-urls.js +1 -1
  31. package/cli/dist/utils/service-urls.js.map +1 -1
  32. package/cli/package.json +2 -1
  33. package/cli/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +2 -2
  34. package/cli/src/commands/__tests__/doctor.test.ts +8 -8
  35. package/cli/src/commands/__tests__/upgrade.test.ts +90 -3
  36. package/cli/src/commands/help.ts +22 -39
  37. package/cli/src/commands/project.ts +20 -0
  38. package/cli/src/commands/up.ts +10 -0
  39. package/cli/src/commands/upgrade.ts +52 -1
  40. package/cli/src/utils/constants.ts +1 -1
  41. package/cli/src/utils/doctor-runtime.ts +5 -5
  42. package/cli/src/utils/prompt.ts +8 -1
  43. package/cli/src/utils/service-urls.ts +1 -1
  44. package/discovery/identity-management-backend/service.yaml +2 -2
  45. package/discovery/identity-management-frontend/service.yaml +1 -1
  46. package/discovery/registry.star +3 -1
  47. package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +2 -2
  48. package/engine/assets/docker/golden-layers.Dockerfile +18 -18
  49. package/engine/assets/docker/wake-gateway/decide.test.ts +25 -0
  50. package/engine/assets/docker/wake-gateway/decide.ts +19 -0
  51. package/engine/assets/docker/wake-gateway/server.ts +60 -14
  52. package/engine/assets/scripts/mac-m1-resource-monitor.sh +1 -1
  53. package/engine/scripts/debug-container.sh +8 -3
  54. package/engine/spec.master +2 -2
  55. package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +1 -1
  56. package/engine/topologies/tilt/common/utils.star +1 -1
  57. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +53 -11
  58. package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +20 -0
  59. package/install.sh +19 -242
  60. package/openspec/changes/prioritized-cold-start/design.md +5 -1
  61. package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +9 -3
  62. package/openspec/changes/prioritized-cold-start/tasks.md +13 -0
  63. package/package.json +1 -1
  64. package/platform/services/platform/proxy/service.yaml +1 -1
  65. package/scripts/release-binaries.sh +8 -5
  66. package/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +2 -2
  67. package/tests/tilt-engine/test_sablier_defer_start.py +41 -0
  68. package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/codebase-audit-report.md +0 -62
  69. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/.openspec.yaml +0 -5
  70. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/design.md +0 -94
  71. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/proposal.md +0 -46
  72. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CLEANUP_REPORT_8.md +0 -174
  73. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CRITICAL_ASSESSMENT_8.md +0 -160
  74. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/circular-deps/CRITICAL_ASSESSMENT_4.md +0 -381
  75. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/dead-code/CRITICAL_ASSESSMENT_3.md +0 -316
  76. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/CRITICAL_ASSESSMENT_1.md +0 -290
  77. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/IMPLEMENTATION_SUMMARY_1.md +0 -233
  78. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/defensive-cleanup/CRITICAL_ASSESSMENT_6.md +0 -835
  79. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/CRITICAL_ASSESSMENT_7.md +0 -254
  80. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/IMPLEMENTATION_SUMMARY_7.md +0 -187
  81. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/strong-typing/CRITICAL_ASSESSMENT_5.md +0 -269
  82. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/CRITICAL_ASSESSMENT_2.md +0 -312
  83. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/IMPLEMENTATION_SUMMARY.md +0 -170
  84. package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/tasks.md +0 -137
@@ -346,7 +346,7 @@ describe("doctor ingress + tilt runtime checks", () => {
346
346
  it("detects a foreign Traefik holding host port 80", () => {
347
347
  const dockerPs = [
348
348
  "tdk_saas_starter_traefik\t0.0.0.0:80->80/tcp,:::80->80/tcp,0.0.0.0:8080->8080/tcp",
349
- "beauty_crm_postgres\t0.0.0.0:5432->5432/tcp",
349
+ "tdk_project_postgres\t0.0.0.0:5432->5432/tcp",
350
350
  ].join("\n");
351
351
 
352
352
  const holders = parsePublishedPortHolders(dockerPs, [80, 443]);
@@ -354,20 +354,20 @@ describe("doctor ingress + tilt runtime checks", () => {
354
354
  expect(holders[0]?.name).toBe("tdk_saas_starter_traefik");
355
355
  expect(holders[0]?.publishedPorts).toContain(80);
356
356
 
357
- const foreign = findForeignIngressHolders(holders, "beauty_crm");
357
+ const foreign = findForeignIngressHolders(holders, "tdk_project");
358
358
  expect(foreign).toHaveLength(1);
359
359
 
360
- const result = checkIngressPorts((() => dockerPs) as never, "beauty-crm");
360
+ const result = checkIngressPorts((() => dockerPs) as never, "tdk-project");
361
361
  expect(result.didPass).toBe(false);
362
362
  expect(result.message).toContain("tdk_saas_starter_traefik");
363
363
  expect(result.fix).toContain("docker stop");
364
364
  });
365
365
 
366
366
  it("passes when this project's Traefik owns the ingress ports", () => {
367
- const dockerPs = "beauty_crm_traefik\t0.0.0.0:80->80/tcp,:::80->80/tcp,0.0.0.0:443->443/tcp";
368
- const result = checkIngressPorts((() => dockerPs) as never, "beauty-crm");
367
+ const dockerPs = "tdk_project_traefik\t0.0.0.0:80->80/tcp,:::80->80/tcp,0.0.0.0:443->443/tcp";
368
+ const result = checkIngressPorts((() => dockerPs) as never, "tdk-project");
369
369
  expect(result.didPass).toBe(true);
370
- expect(result.message).toContain("beauty_crm_traefik");
370
+ expect(result.message).toContain("tdk_project_traefik");
371
371
  });
372
372
 
373
373
  it("fails doctor when Tilt reports Traefik update errors that block apps", () => {
@@ -481,7 +481,7 @@ describe("doctor ingress + tilt runtime checks", () => {
481
481
  it("summarizes ConnectionRefused package manifest errors from build logs", () => {
482
482
  const raw = [
483
483
  "error: ConnectionRefused downloading package manifest @tdk-landscape/eventing",
484
- "error: ConnectionRefused downloading package manifest @beauty-crm/identity-js-sdk",
484
+ "error: ConnectionRefused downloading package manifest @tdk-landscape/identity-js-sdk",
485
485
  "error: ConnectionRefused downloading package manifest @tdk-landscape/observability",
486
486
  'ImageBuild: process "/bin/sh -c bun install --production --cache-dir=/cache/bun" did not complete successfully: exit code: 1',
487
487
  ].join("\n");
@@ -672,7 +672,7 @@ describe("doctor ingress + tilt runtime checks", () => {
672
672
 
673
673
  const execUp = ((command: string) => {
674
674
  if (command.startsWith("docker ps")) {
675
- return "beauty-crm-verdaccio\n";
675
+ return "tdk-project-verdaccio\n";
676
676
  }
677
677
  if (command.includes("curl")) {
678
678
  return "200";
@@ -1,4 +1,5 @@
1
- import { chmodSync, mkdtempSync, rmSync } from "node:fs";
1
+ import { createHash } from "node:crypto";
2
+ import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
3
  import { tmpdir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -8,7 +9,7 @@ vi.mock("node:child_process", () => ({
8
9
  execSync: (...args: unknown[]) => execSyncMock(...args),
9
10
  }));
10
11
 
11
- const { upgradeViaBinary, isWritable } = await import("../upgrade.js");
12
+ const { upgradeViaBinary, isWritable, parseChecksums } = await import("../upgrade.js");
12
13
 
13
14
  import type { BinaryRelease } from "../upgrade.js";
14
15
 
@@ -16,10 +17,31 @@ import type { BinaryRelease } from "../upgrade.js";
16
17
  // and leave the bundled tdk-cli/ engine+templates folder next to it stale or
17
18
  // missing, so an upgraded binary still crashed with the loadTemplate ENOENT
18
19
  // (the exact error this was meant to fix). It must refresh both, every time.
20
+ const BINARY_BYTES = "fake tdk binary";
21
+ const ENGINE_BYTES = "fake engine tarball";
22
+ const sha256 = (text: string) => createHash("sha256").update(text).digest("hex");
23
+
19
24
  describe("upgradeViaBinary", () => {
20
25
  let installDir: string;
21
26
  let tdkPath: string;
22
27
  let release: BinaryRelease;
28
+ let checksums: string;
29
+ const downloaded: string[] = [];
30
+
31
+ // Stands in for curl: `curl -fsSL -o "<path>" "<url>"` writes the asset's
32
+ // bytes to <path>; `curl -fsSL "<checksums url>"` returns checksums.txt.
33
+ function fakeExec(command: string): string {
34
+ const download = command.match(/^curl -fsSL -o (".*?") (".*?")$/);
35
+ if (download) {
36
+ const path = JSON.parse(download[1]) as string;
37
+ const url = JSON.parse(download[2]) as string;
38
+ writeFileSync(path, url === release.engineDownloadUrl ? ENGINE_BYTES : BINARY_BYTES);
39
+ downloaded.push(path);
40
+ return "";
41
+ }
42
+ if (command.includes(release.checksumsUrl)) return checksums;
43
+ return "";
44
+ }
23
45
 
24
46
  beforeEach(() => {
25
47
  installDir = mkdtempSync(join(tmpdir(), "tdk-upgrade-test-"));
@@ -29,13 +51,20 @@ describe("upgradeViaBinary", () => {
29
51
  assetName: "tdk-darwin-arm64",
30
52
  downloadUrl: "https://example.test/tdk-darwin-arm64",
31
53
  engineDownloadUrl: "https://example.test/tdk-cli-engine.tar.gz",
54
+ checksumsUrl: "https://example.test/checksums.txt",
32
55
  };
56
+ checksums = [
57
+ `${sha256(BINARY_BYTES)} tdk-darwin-arm64`,
58
+ `${sha256("other platform")} tdk-linux-amd64`,
59
+ `${sha256(ENGINE_BYTES)} tdk-cli-engine.tar.gz`,
60
+ ].join("\n");
33
61
  execSyncMock.mockReset();
34
- execSyncMock.mockReturnValue("");
62
+ execSyncMock.mockImplementation(fakeExec);
35
63
  });
36
64
 
37
65
  afterEach(() => {
38
66
  rmSync(installDir, { recursive: true, force: true });
67
+ for (const path of downloaded.splice(0)) rmSync(path, { force: true });
39
68
  });
40
69
 
41
70
  function commandsRun(): string[] {
@@ -79,6 +108,50 @@ describe("upgradeViaBinary", () => {
79
108
  expect(tarIndex).toBeGreaterThan(engineCurlIndex);
80
109
  });
81
110
 
111
+ it("verifies checksums after both downloads and before installing anything", async () => {
112
+ await upgradeViaBinary(tdkPath, release);
113
+
114
+ const commands = commandsRun();
115
+ const checksumsIndex = commands.findIndex((c) => c.includes(release.checksumsUrl));
116
+ const engineCurlIndex = commands.findIndex((c) => c.includes(release.engineDownloadUrl));
117
+ const mvIndex = commands.findIndex((c) => c.startsWith("mv "));
118
+
119
+ expect(checksumsIndex).toBeGreaterThan(engineCurlIndex);
120
+ expect(mvIndex).toBeGreaterThan(checksumsIndex);
121
+ });
122
+
123
+ it.each([
124
+ [
125
+ "the binary does not match",
126
+ () => checksums.replace(sha256(BINARY_BYTES), sha256("tampered")),
127
+ ],
128
+ [
129
+ "the engine tarball does not match",
130
+ () => checksums.replace(sha256(ENGINE_BYTES), sha256("x")),
131
+ ],
132
+ ["checksums.txt has no entry for the binary", () => checksums.replace("tdk-darwin-arm64", "x")],
133
+ ])("refuses to install when %s", async (_case, makeChecksums) => {
134
+ checksums = makeChecksums();
135
+ const ok = await upgradeViaBinary(tdkPath, release);
136
+
137
+ expect(ok).toBe(false);
138
+ const commands = commandsRun();
139
+ expect(commands.some((c) => c.startsWith("mv "))).toBe(false);
140
+ expect(commands.some((c) => c.includes("tar -xzf"))).toBe(false);
141
+ expect(commands.some((c) => c.startsWith("rm -rf"))).toBe(false);
142
+ });
143
+
144
+ it("still installs releases whose checksums.txt predates the engine entry", async () => {
145
+ checksums = checksums
146
+ .split("\n")
147
+ .filter((line) => !line.endsWith("tdk-cli-engine.tar.gz"))
148
+ .join("\n");
149
+ const ok = await upgradeViaBinary(tdkPath, release);
150
+
151
+ expect(ok).toBe(true);
152
+ expect(commandsRun().some((c) => c.startsWith("mv "))).toBe(true);
153
+ });
154
+
82
155
  // root ignores the write-permission bit (accessSync(W_OK) legitimately
83
156
  // returns true even on a 0o555 dir), so these checks only hold for
84
157
  // non-root users.
@@ -109,3 +182,17 @@ describe("upgradeViaBinary", () => {
109
182
  }
110
183
  });
111
184
  });
185
+
186
+ describe("parseChecksums", () => {
187
+ it("reads both text-mode and binary-mode (*name) shasum lines and ignores junk", () => {
188
+ const a = "a".repeat(64);
189
+ const b = "B".repeat(64);
190
+ const sums = parseChecksums(
191
+ `${a} tdk-linux-amd64\n${b} *tdk-cli-engine.tar.gz\n\nnot a checksum line\n`,
192
+ );
193
+
194
+ expect(sums.get("tdk-linux-amd64")).toBe(a);
195
+ expect(sums.get("tdk-cli-engine.tar.gz")).toBe("b".repeat(64));
196
+ expect(sums.size).toBe(2);
197
+ });
198
+ });
@@ -1,24 +1,13 @@
1
1
  import chalk from "chalk";
2
+ import pkg from "../../package.json" with { type: "json" };
2
3
 
3
4
  const TDK_BANNER = `
4
- ${chalk.cyan("╔════════════════════════════════════════════════════════════╗")}
5
- ${chalk.cyan("║")} ${chalk.cyan("║")}
6
- ${chalk.cyan("║")} ${chalk.bold.white("████████╗██████╗ ██╗ ██╗")} ${chalk.bold.white("██████╗██╗ ██╗")} ${chalk.cyan("║")}
7
- ${chalk.cyan("║")} ${chalk.bold.white("╚══██╔══╝██╔══██╗██║ ██╔╝")} ${chalk.bold.white("██╔════╝██║ ██║")} ${chalk.cyan("║")}
8
- ${chalk.cyan("║")} ${chalk.bold.white("██║ ██║ ██║█████╔╝")} ${chalk.bold.white("██║ ██║ ██║")} ${chalk.cyan("║")}
9
- ${chalk.cyan("║")} ${chalk.bold.white("██║ ██║ ██║██╔═██╗")} ${chalk.bold.white("██║ ██║ ██║")} ${chalk.cyan("║")}
10
- ${chalk.cyan("║")} ${chalk.bold.white("██║ ██████╔╝██║ ██╗")}${chalk.yellow("██╗")}${chalk.bold.white("╚██████╗███████╗██║")} ${chalk.cyan("║")}
11
- ${chalk.cyan("║")} ${chalk.white("╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝")} ${chalk.white("╚═════╝╚══════╝╚═╝")} ${chalk.cyan("║")}
12
- ${chalk.cyan("║")} ${chalk.cyan("║")}
13
- ${chalk.cyan("║")} ${chalk.bold.yellow("🚀 Tilt Development Kit - Local Microservices")} ${chalk.cyan("║")}
14
- ${chalk.cyan("║")} ${chalk.cyan("║")}
15
- ${chalk.cyan("╚════════════════════════════════════════════════════════════╝")}
5
+ ${chalk.bold.cyan("tdk")} ${chalk.gray(`v${pkg.version}`)} ${chalk.white("Tilt Development Kit: run your microservices locally")}
16
6
  `;
17
7
 
18
8
  const COMMAND_GROUPS = [
19
9
  {
20
- title: "🌍 Project Level",
21
- emoji: "📁",
10
+ title: "Project Level",
22
11
  color: chalk.blue,
23
12
  commands: [
24
13
  {
@@ -30,8 +19,7 @@ const COMMAND_GROUPS = [
30
19
  ],
31
20
  },
32
21
  {
33
- title: "📦 Stack Level",
34
- emoji: "📦",
22
+ title: "Stack Level",
35
23
  color: chalk.magenta,
36
24
  commands: [
37
25
  { name: "stack", desc: "Organize resources into stacks (interactive)", alias: "" },
@@ -39,8 +27,7 @@ const COMMAND_GROUPS = [
39
27
  ],
40
28
  },
41
29
  {
42
- title: "⚡ Resource Level",
43
- emoji: "⚡",
30
+ title: "Resource Level",
44
31
  color: chalk.yellow,
45
32
  commands: [
46
33
  { name: "resource", desc: "Create new resource with templates", alias: "" },
@@ -48,8 +35,7 @@ const COMMAND_GROUPS = [
48
35
  ],
49
36
  },
50
37
  {
51
- title: "🔄 Lifecycle",
52
- emoji: "🔄",
38
+ title: "Lifecycle",
53
39
  color: chalk.green,
54
40
  commands: [
55
41
  { name: "up, deploy", desc: "Start services (optionally by stack)", alias: "[stack-name]" },
@@ -58,8 +44,7 @@ const COMMAND_GROUPS = [
58
44
  ],
59
45
  },
60
46
  {
61
- title: "🌐 Networking",
62
- emoji: "🌐",
47
+ title: "Networking",
63
48
  color: chalk.blueBright,
64
49
  commands: [
65
50
  {
@@ -70,8 +55,7 @@ const COMMAND_GROUPS = [
70
55
  ],
71
56
  },
72
57
  {
73
- title: "🛠️ Utilities",
74
- emoji: "🛠️",
58
+ title: "Utilities",
75
59
  color: chalk.cyan,
76
60
  commands: [
77
61
  { name: "ui, interactive", desc: "Interactive TUI for managing services", alias: "" },
@@ -84,7 +68,7 @@ const COMMAND_GROUPS = [
84
68
  { name: "completion", desc: "Generate shell completions", alias: "--install --shell zsh" },
85
69
  { name: "upgrade, update", desc: "Self-update to latest version", alias: "--force" },
86
70
  { name: "version, -v", desc: "Display version number", alias: "" },
87
- { name: "help", desc: "Show this colorful help", alias: "[command]" },
71
+ { name: "help", desc: "Show help", alias: "[command]" },
88
72
  ],
89
73
  },
90
74
  ];
@@ -104,7 +88,7 @@ function formatCommand(
104
88
  export function showHelp(): void {
105
89
  console.log(TDK_BANNER);
106
90
 
107
- console.log(chalk.bold.white("\n Project → Phase → Stack → Resource (PPSR) Model\n"));
91
+ console.log(chalk.bold.white("\n Project → Phase → Stack → Resource\n"));
108
92
 
109
93
  console.log(chalk.gray(" Usage: tdk [command] [options]\n"));
110
94
 
@@ -117,7 +101,7 @@ export function showHelp(): void {
117
101
  console.log();
118
102
 
119
103
  for (const group of COMMAND_GROUPS) {
120
- console.log(group.color.bold(`${group.emoji} ${group.title}`));
104
+ console.log(group.color.bold(` ${group.title}`));
121
105
  console.log(group.color(` ${"─".repeat(50)}`));
122
106
 
123
107
  for (const cmd of group.commands) {
@@ -126,7 +110,7 @@ export function showHelp(): void {
126
110
 
127
111
  console.log();
128
112
  }
129
- console.log(chalk.bold.yellow(" 🚀 Quick Start:"));
113
+ console.log(chalk.bold.yellow(" Quick start:"));
130
114
  console.log(chalk.gray(` ${"─".repeat(50)}`));
131
115
  console.log(
132
116
  chalk.white(`
@@ -136,25 +120,24 @@ export function showHelp(): void {
136
120
  ${chalk.cyan("4.")} Start development: ${chalk.green("tdk up my-stack")}
137
121
  `),
138
122
  );
139
- console.log(chalk.bold.yellow(" 💡 Examples:"));
123
+ console.log(chalk.bold.yellow(" Examples:"));
140
124
  console.log(chalk.gray(` ${"─".repeat(50)}`));
141
125
  console.log(
142
126
  chalk.white(`
143
- ${chalk.cyan("$")} tdk project # Create master configs
144
- ${chalk.cyan("$")} tdk resource api --type backend # Create backend service
145
- ${chalk.cyan("$")} tdk resource app --type frontend # Create frontend app
146
- ${chalk.cyan("$")} tdk stacks --services # List stacks with resources
147
- ${chalk.cyan("$")} tdk up my-stack # Start a stack
148
- ${chalk.cyan("$")} tdk networks # Show Traefik-routed URLs
149
- ${chalk.cyan("$")} tdk doctor # Check environment
150
- ${chalk.cyan("$")} tdk upgrade # Self-update
127
+ ${chalk.cyan("$")} tdk project # Create master configs
128
+ ${chalk.cyan("$")} tdk resource api --type backend # Create a backend service
129
+ ${chalk.cyan("$")} tdk resource app --type frontend # Create a frontend app
130
+ ${chalk.cyan("$")} tdk stacks --services # List stacks with their resources
131
+ ${chalk.cyan("$")} tdk up my-stack # Start a stack
132
+ ${chalk.cyan("$")} tdk networks # Show Traefik-routed URLs
133
+ ${chalk.cyan("$")} tdk doctor # Check your environment
134
+ ${chalk.cyan("$")} tdk upgrade # Self-update
151
135
  `),
152
136
  );
153
137
  console.log(chalk.gray(` ${"═".repeat(50)}`));
154
- console.log(chalk.gray(` For more help: ${chalk.cyan("tdk help [command]}")}`));
138
+ console.log(chalk.gray(` For more help: ${chalk.cyan("tdk help [command]")}`));
155
139
  console.log(
156
140
  chalk.gray(` GitHub: ${chalk.cyan("https://github.com/tdk-landscape/tdk-cli-core")}`),
157
141
  );
158
142
  console.log();
159
- console.log(chalk.green.bold(" Happy coding! 🚀\n"));
160
143
  }
@@ -69,6 +69,26 @@ function syncDiscoveredStacksToPreAlpha(
69
69
  return missingStacks;
70
70
  }
71
71
 
72
+ /**
73
+ * Adds stacks that have service.json files but aren't in any phase to
74
+ * pre_alpha, so a resource scaffolded after `tdk project` is picked up by
75
+ * `tdk up` without re-running `tdk project`. Returns the stacks it enabled.
76
+ */
77
+ export function enableDiscoveredStacks(projectRoot: string): string[] {
78
+ const projectJsonPath = join(projectRoot, ".tdk", "project.json");
79
+ if (!existsSync(projectJsonPath)) return [];
80
+
81
+ const projectConfig = readProjectConfig(projectRoot);
82
+ const enabled = syncDiscoveredStacksToPreAlpha(
83
+ projectConfig,
84
+ discoverExistingServiceStacks(projectRoot),
85
+ );
86
+ if (enabled.length > 0) {
87
+ writeJsonFile(projectJsonPath, projectConfig);
88
+ }
89
+ return enabled;
90
+ }
91
+
72
92
  /**
73
93
  * Get core infrastructure services from PROJECT_FEATURES that are enabled by default
74
94
  */
@@ -15,6 +15,7 @@ import {
15
15
  stackExists,
16
16
  } from "../utils/services.js";
17
17
  import { buildTiltUpArgs, runTilt } from "../utils/tilt.js";
18
+ import { enableDiscoveredStacks } from "./project.js";
18
19
 
19
20
  export const upCommand = new Command("up")
20
21
  .description("Start all services (optionally filtered by stack)")
@@ -32,6 +33,15 @@ export const upCommand = new Command("up")
32
33
  console.log(chalk.gray(`Refreshed runtime assets: ${copiedAssets.join(", ")}`));
33
34
  }
34
35
 
36
+ const newlyEnabledStacks = enableDiscoveredStacks(projectRoot);
37
+ if (newlyEnabledStacks.length > 0 && !options.quiet) {
38
+ console.log(
39
+ chalk.gray(
40
+ `Added ${newlyEnabledStacks.join(", ")} to the pre_alpha phase in .tdk/project.json`,
41
+ ),
42
+ );
43
+ }
44
+
35
45
  let servicesToStart: Awaited<ReturnType<typeof discoverResources>>;
36
46
  let stackDescription: string;
37
47
  let focusServiceNames: string[] = [];
@@ -1,5 +1,6 @@
1
1
  import { execSync } from "node:child_process";
2
- import { accessSync, constants, existsSync, realpathSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import { accessSync, constants, existsSync, readFileSync, realpathSync } from "node:fs";
3
4
  import { dirname, join, resolve } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import chalk from "chalk";
@@ -78,12 +79,48 @@ function binaryAssetName(): string | null {
78
79
  }
79
80
 
80
81
  const ENGINE_ASSET_NAME = "tdk-cli-engine.tar.gz";
82
+ const CHECKSUMS_ASSET_NAME = "checksums.txt";
81
83
 
82
84
  export interface BinaryRelease {
83
85
  tag: string;
84
86
  assetName: string;
85
87
  downloadUrl: string;
86
88
  engineDownloadUrl: string;
89
+ checksumsUrl: string;
90
+ }
91
+
92
+ /** Parses `shasum -a 256` / `sha256sum` output ("<hash> <name>" or "<hash> *<name>"). */
93
+ export function parseChecksums(text: string): Map<string, string> {
94
+ const sums = new Map<string, string>();
95
+ for (const line of text.split("\n")) {
96
+ const match = line.trim().match(/^([0-9a-f]{64})\s+\*?(.+)$/i);
97
+ if (match) sums.set(match[2].trim(), match[1].toLowerCase());
98
+ }
99
+ return sums;
100
+ }
101
+
102
+ /**
103
+ * Throws unless `filePath` hashes to the checksum listed for `assetName`.
104
+ * A missing entry is an error when `required`; otherwise it is skipped, so
105
+ * releases published before an asset was added to checksums.txt (older
106
+ * releases list only the binaries, not the engine tarball) still install.
107
+ */
108
+ function verifyChecksum(
109
+ filePath: string,
110
+ assetName: string,
111
+ sums: Map<string, string>,
112
+ required: boolean,
113
+ ): void {
114
+ const expected = sums.get(assetName);
115
+ if (!expected) {
116
+ if (required) throw new Error(`${CHECKSUMS_ASSET_NAME} has no entry for ${assetName}`);
117
+ logVerbose(`${CHECKSUMS_ASSET_NAME} has no entry for ${assetName}; skipping verification`);
118
+ return;
119
+ }
120
+ const actual = createHash("sha256").update(readFileSync(filePath)).digest("hex");
121
+ if (actual !== expected) {
122
+ throw new Error(`Checksum mismatch for ${assetName} (expected ${expected}, got ${actual})`);
123
+ }
87
124
  }
88
125
 
89
126
  async function getLatestBinaryRelease(): Promise<BinaryRelease | null> {
@@ -103,6 +140,7 @@ async function getLatestBinaryRelease(): Promise<BinaryRelease | null> {
103
140
  assetName,
104
141
  downloadUrl: `https://github.com/${BINARY_RELEASE_REPO}/releases/download/${data.tag_name}/${assetName}`,
105
142
  engineDownloadUrl: `https://github.com/${BINARY_RELEASE_REPO}/releases/download/${data.tag_name}/${ENGINE_ASSET_NAME}`,
143
+ checksumsUrl: `https://github.com/${BINARY_RELEASE_REPO}/releases/download/${data.tag_name}/${CHECKSUMS_ASSET_NAME}`,
106
144
  };
107
145
  } catch (err: unknown) {
108
146
  logVerbose("Binary release lookup error", err);
@@ -158,6 +196,19 @@ export async function upgradeViaBinary(tdkPath: string, release: BinaryRelease):
158
196
  { stdio: "pipe", timeout: 120000 },
159
197
  );
160
198
 
199
+ // Verify both downloads against the release's checksums.txt before
200
+ // touching the installed binary or engine.
201
+ spinner.text = `Verifying checksums (${release.tag})...`;
202
+ const sums = parseChecksums(
203
+ execSync(`curl -fsSL ${JSON.stringify(release.checksumsUrl)}`, {
204
+ encoding: "utf-8",
205
+ stdio: "pipe",
206
+ timeout: 30000,
207
+ }),
208
+ );
209
+ verifyChecksum(tmpPath, release.assetName, sums, true);
210
+ verifyChecksum(engineTmpPath, ENGINE_ASSET_NAME, sums, false);
211
+
161
212
  execSync(`mv ${JSON.stringify(tmpPath)} ${JSON.stringify(tdkPath)}`);
162
213
  execSync(`rm -rf ${JSON.stringify(engineDir)}`);
163
214
  execSync(`mkdir -p ${JSON.stringify(engineDir)}`);
@@ -54,7 +54,7 @@ export const STANDARD_PORTS = {
54
54
 
55
55
  export const QUICKSTART_DOCS_URL = "https://tdk-landscape.github.io/tdk-website/docs/quickstart/";
56
56
 
57
- export const BASE_DOMAIN_DEFAULT = "beauty-crm.localhost";
57
+ export const BASE_DOMAIN_DEFAULT = "tdk-project.localhost";
58
58
  export const APP_SUBDOMAIN_PREFIX = "app";
59
59
  export const API_SUBDOMAIN_PREFIX = "api";
60
60
 
@@ -62,7 +62,7 @@ export function parsePublishedPortHolders(
62
62
  export function isOwnIngressContainer(containerName: string, projectPrefix: string): boolean {
63
63
  const name = containerName.toLowerCase();
64
64
  const prefix = projectPrefix.toLowerCase();
65
- // beauty_crm_traefik, beauty_crm-traefik-1, etc.
65
+ // tdk_project_traefik, tdk_project-traefik-1, etc.
66
66
  return name === `${prefix}_traefik` || name.startsWith(`${prefix}_traefik`);
67
67
  }
68
68
 
@@ -201,14 +201,14 @@ export function summarizeTiltBuildError(error: string): string {
201
201
  }
202
202
 
203
203
  if (
204
- /failed to resolve source metadata for docker\.io\/library\/(beauty-crm-[^:\s]+)/i.test(
204
+ /failed to resolve source metadata for docker\.io\/library\/(tdk-project-[^:\s]+)/i.test(
205
205
  normalized,
206
206
  )
207
207
  ) {
208
208
  const image = normalized.match(
209
- /failed to resolve source metadata for docker\.io\/library\/(beauty-crm-[^:\s]+)/i,
209
+ /failed to resolve source metadata for docker\.io\/library\/(tdk-project-[^:\s]+)/i,
210
210
  )?.[1];
211
- return `base image ${image ?? "beauty-crm-*"} missing locally (golden-layer build not finished or failed)`;
211
+ return `base image ${image ?? "tdk-project-*"} missing locally (golden-layer build not finished or failed)`;
212
212
  }
213
213
 
214
214
  if (
@@ -364,7 +364,7 @@ export function checkPrivateNpmRegistry(
364
364
  return {
365
365
  name: "Private npm registry",
366
366
  didPass: false,
367
- message: `Private npm registry (Verdaccio) is not reachable at ${registryUrl}. Docker ImageBuilds will fail with ConnectionRefused downloading package manifests for @beauty-crm/* and @tdk-landscape/* packages.`,
367
+ message: `Private npm registry (Verdaccio) is not reachable at ${registryUrl}. Docker ImageBuilds will fail with ConnectionRefused downloading package manifests for @tdk-landscape/* and project-scoped packages.`,
368
368
  fix: "Start the registry with `bun run verdaccio:start` (or `docker compose -f docker-compose.verdaccio.yml up -d`), publish packages with `bun run publish:all` / `scripts/build/publish-to-verdaccio.sh`, then `tilt trigger` failed resources or re-run `tdk up`.",
369
369
  };
370
370
  }
@@ -32,7 +32,14 @@ interface MultiSelectPromptOptions<T extends string> extends ChoicePromptOptions
32
32
  async function question(prompt: string): Promise<string> {
33
33
  const rl = createInterface({ input, output });
34
34
  try {
35
- return (await rl.question(prompt)).trim();
35
+ // Without this, a closed stdin (CI, `< /dev/null`, a pipe) leaves the
36
+ // question pending forever and Node exits 0 having done nothing.
37
+ return await new Promise<string>((resolve, reject) => {
38
+ rl.once("close", () =>
39
+ reject(new Error("No input available to answer the prompt. Run this in a terminal.")),
40
+ );
41
+ rl.question(prompt).then((answer) => resolve(answer.trim()), reject);
42
+ });
36
43
  } finally {
37
44
  rl.close();
38
45
  }
@@ -28,7 +28,7 @@ export function getProjectName(): string {
28
28
  }
29
29
  } catch {}
30
30
  }
31
- return "beauty-crm";
31
+ return "tdk-project";
32
32
  }
33
33
 
34
34
  export function resolveSubdomainBases(): { appBase: string; apiBase: string } {
@@ -9,8 +9,8 @@ features:
9
9
  healthCheck: True
10
10
  healthCheckPath: /api/v1/health
11
11
  params:
12
- BASE_URL: http://identity.backend.beauty.local
13
- EMAIL_FROM: noreply@beauty-crm.local
12
+ BASE_URL: http://identity.backend.tdk-project.local
13
+ EMAIL_FROM: noreply@tdk-project.local
14
14
  UPSTASH_REDIS_REST_URL: http://redis:6379
15
15
  path: identity-management-backend
16
16
  playwright: True
@@ -18,7 +18,7 @@ federation:
18
18
  shared:
19
19
  - react
20
20
  - react-dom
21
- - @beauty-crm/introvertic-ui
21
+ - @tdk-landscape/introvertic-ui
22
22
  healthCheck: True
23
23
  path: identity-management-frontend
24
24
  playwright: True
@@ -423,7 +423,9 @@ def _find_manifest_files(root, manifest_filename):
423
423
  full_path = root
424
424
  else:
425
425
  full_path = project_root + "/" + root
426
- cmd = "find " + full_path + " -type f -name '" + manifest_filename + "' 2>/dev/null"
426
+ # A stack listed in project.json may not have a directory yet (a fresh
427
+ # project lists "proxy"); find exits 1 on a missing path and local() fails.
428
+ cmd = "if [ -d '" + full_path + "' ]; then find '" + full_path + "' -type f -name '" + manifest_filename + "' 2>/dev/null; fi"
427
429
  result = str(local(cmd, quiet=True, echo_off=True)).strip()
428
430
 
429
431
  if result:
@@ -2,7 +2,7 @@ appName: orchestrator-glue-backend
2
2
  appType: backend
3
3
  stack: platform
4
4
  port: 5001
5
- databaseName: beauty_crm_orchestrator
5
+ databaseName: tdk_project_orchestrator
6
6
  features:
7
7
  - nats
8
8
  - prisma
@@ -22,5 +22,5 @@ resources:
22
22
  memory: 256Mi
23
23
  runtime: bun
24
24
  traefik:
25
- host: orchestrator.backend.beauty.local
25
+ host: orchestrator.backend.tdk-project.local
26
26
  pathPrefix: /api/orchestrator