@tdk-landscape/tdk-cli-core 1.3.50 → 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.
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/release-binaries.yml +76 -1
- package/CHANGELOG.md +50 -0
- package/README.md +54 -17
- package/cli/README.md +16 -43
- package/cli/biome.json +0 -4
- package/cli/dist/commands/help.d.ts.map +1 -1
- package/cli/dist/commands/help.js +22 -39
- package/cli/dist/commands/help.js.map +1 -1
- package/cli/dist/commands/project.d.ts +6 -0
- package/cli/dist/commands/project.d.ts.map +1 -1
- package/cli/dist/commands/project.js +16 -0
- package/cli/dist/commands/project.js.map +1 -1
- package/cli/dist/commands/up.d.ts.map +1 -1
- package/cli/dist/commands/up.js +5 -0
- package/cli/dist/commands/up.js.map +1 -1
- package/cli/dist/commands/upgrade.d.ts +3 -0
- package/cli/dist/commands/upgrade.d.ts.map +1 -1
- package/cli/dist/commands/upgrade.js +43 -1
- package/cli/dist/commands/upgrade.js.map +1 -1
- package/cli/dist/utils/constants.d.ts +1 -1
- package/cli/dist/utils/constants.d.ts.map +1 -1
- package/cli/dist/utils/constants.js +1 -1
- package/cli/dist/utils/constants.js.map +1 -1
- package/cli/dist/utils/doctor-runtime.js +5 -5
- package/cli/dist/utils/doctor-runtime.js.map +1 -1
- package/cli/dist/utils/prompt.d.ts.map +1 -1
- package/cli/dist/utils/prompt.js +6 -1
- package/cli/dist/utils/prompt.js.map +1 -1
- package/cli/dist/utils/service-urls.js +1 -1
- package/cli/dist/utils/service-urls.js.map +1 -1
- package/cli/package.json +2 -1
- package/cli/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +2 -2
- package/cli/src/commands/__tests__/doctor.test.ts +8 -8
- package/cli/src/commands/__tests__/upgrade.test.ts +90 -3
- package/cli/src/commands/help.ts +22 -39
- package/cli/src/commands/project.ts +20 -0
- package/cli/src/commands/up.ts +10 -0
- package/cli/src/commands/upgrade.ts +52 -1
- package/cli/src/utils/constants.ts +1 -1
- package/cli/src/utils/doctor-runtime.ts +5 -5
- package/cli/src/utils/prompt.ts +8 -1
- package/cli/src/utils/service-urls.ts +1 -1
- package/discovery/identity-management-backend/service.yaml +2 -2
- package/discovery/identity-management-frontend/service.yaml +1 -1
- package/discovery/registry.star +3 -1
- package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +2 -2
- package/docs/demo.svg +217 -0
- package/engine/assets/docker/golden-layers.Dockerfile +18 -18
- package/engine/assets/docker/wake-gateway/Dockerfile +4 -3
- package/engine/assets/docker/wake-gateway/decide.test.ts +72 -1
- package/engine/assets/docker/wake-gateway/decide.ts +51 -2
- package/engine/assets/docker/wake-gateway/server.ts +165 -28
- package/engine/assets/scripts/mac-m1-resource-monitor.sh +1 -1
- package/engine/scripts/debug-container.sh +8 -3
- package/engine/spec.master +2 -2
- package/engine/topologies/platform/docker/compose/traefik_standalone.star +23 -1
- package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +1 -1
- package/engine/topologies/platform/docker/networking/traefik_static_routes.star +62 -8
- package/engine/topologies/tilt/common/utils.star +1 -1
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +89 -12
- package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +20 -0
- package/install.sh +19 -242
- package/openspec/changes/prioritized-cold-start/design.md +27 -3
- package/openspec/changes/prioritized-cold-start/proposal.md +1 -0
- package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +9 -3
- package/openspec/changes/prioritized-cold-start/tasks.md +24 -1
- package/package.json +1 -1
- package/platform/services/platform/proxy/service.yaml +1 -1
- package/scripts/record-demo.sh +43 -0
- package/scripts/release-binaries.sh +8 -5
- package/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +2 -2
- package/tests/tilt-engine/test_sablier_defer_start.py +41 -0
- package/tests/tilt-engine/test_standalone_traefik.py +14 -0
- package/tests/tilt-engine/test_traefik_static_wake_route.py +68 -0
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/codebase-audit-report.md +0 -62
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/.openspec.yaml +0 -5
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/design.md +0 -94
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/proposal.md +0 -46
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CLEANUP_REPORT_8.md +0 -174
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/ai-slop-cleanup/CRITICAL_ASSESSMENT_8.md +0 -160
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/circular-deps/CRITICAL_ASSESSMENT_4.md +0 -381
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/dead-code/CRITICAL_ASSESSMENT_3.md +0 -316
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/CRITICAL_ASSESSMENT_1.md +0 -290
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/deduplication/IMPLEMENTATION_SUMMARY_1.md +0 -233
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/defensive-cleanup/CRITICAL_ASSESSMENT_6.md +0 -835
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/CRITICAL_ASSESSMENT_7.md +0 -254
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/legacy-cleanup/IMPLEMENTATION_SUMMARY_7.md +0 -187
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/strong-typing/CRITICAL_ASSESSMENT_5.md +0 -269
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/CRITICAL_ASSESSMENT_2.md +0 -312
- package/openspec/changes/archive/2026-05-04-comprehensive-cleanup-final/specs/type-consolidation/IMPLEMENTATION_SUMMARY.md +0 -170
- 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
|
-
"
|
|
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, "
|
|
357
|
+
const foreign = findForeignIngressHolders(holders, "tdk_project");
|
|
358
358
|
expect(foreign).toHaveLength(1);
|
|
359
359
|
|
|
360
|
-
const result = checkIngressPorts((() => dockerPs) as never, "
|
|
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 = "
|
|
368
|
-
const result = checkIngressPorts((() => dockerPs) as never, "
|
|
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("
|
|
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 @
|
|
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 "
|
|
675
|
+
return "tdk-project-verdaccio\n";
|
|
676
676
|
}
|
|
677
677
|
if (command.includes("curl")) {
|
|
678
678
|
return "200";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
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
|
+
});
|
package/cli/src/commands/help.ts
CHANGED
|
@@ -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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
|
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
|
|
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(
|
|
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("
|
|
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("
|
|
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
|
|
144
|
-
${chalk.cyan("$")} tdk resource api --type backend
|
|
145
|
-
${chalk.cyan("$")} tdk resource app --type frontend
|
|
146
|
-
${chalk.cyan("$")} tdk stacks --services
|
|
147
|
-
${chalk.cyan("$")} tdk up my-stack
|
|
148
|
-
${chalk.cyan("$")} tdk networks
|
|
149
|
-
${chalk.cyan("$")} tdk doctor
|
|
150
|
-
${chalk.cyan("$")} tdk upgrade
|
|
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
|
*/
|
package/cli/src/commands/up.ts
CHANGED
|
@@ -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 {
|
|
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 = "
|
|
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
|
-
//
|
|
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\/(
|
|
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\/(
|
|
209
|
+
/failed to resolve source metadata for docker\.io\/library\/(tdk-project-[^:\s]+)/i,
|
|
210
210
|
)?.[1];
|
|
211
|
-
return `base image ${image ?? "
|
|
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 @
|
|
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
|
}
|
package/cli/src/utils/prompt.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -9,8 +9,8 @@ features:
|
|
|
9
9
|
healthCheck: True
|
|
10
10
|
healthCheckPath: /api/v1/health
|
|
11
11
|
params:
|
|
12
|
-
BASE_URL: http://identity.backend.
|
|
13
|
-
EMAIL_FROM: noreply@
|
|
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
|
package/discovery/registry.star
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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.
|
|
25
|
+
host: orchestrator.backend.tdk-project.local
|
|
26
26
|
pathPrefix: /api/orchestrator
|