@tdk-landscape/tdk-cli-core 1.3.154 → 1.3.156
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/cli/dist/commands/completion.js +2 -0
- package/cli/dist/commands/doctor.js +106 -1
- package/cli/dist/commands/help.js +2 -1
- package/cli/dist/commands/ui.js +7 -5
- package/cli/dist/commands/up.js +27 -4
- package/cli/dist/commands/upgrade.js +188 -12
- package/cli/dist/generator/template-engine.js +23 -0
- package/cli/dist/utils/doctor-crashloop.js +160 -0
- package/cli/dist/utils/doctor-fix.js +211 -0
- package/cli/dist/utils/tilt-port-owner.js +49 -0
- package/cli/dist/utils/which.js +29 -0
- package/cli/dist-bundle/chunk-I3NXJOZC.js +377 -0
- package/cli/dist-bundle/cli.js +323 -362
- package/cli/dist-bundle/{cold-preflight-VC2AAPTQ.js → cold-preflight-A2DOE5ZP.js} +1 -1
- package/cli/package.json +1 -1
- package/discovery/manifest/loading.star +7 -1
- package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +5 -4
- package/engine/topologies/platform/docker/compose/compose.star +10 -5
- package/engine/topologies/platform/docker/compose/traefik_standalone.star +8 -2
- package/engine/topologies/platform/docker/generators/golden_docker_generator_v2.star +1 -1
- package/engine/topologies/platform/docker/layers/l1_base_layers.star +1 -1
- package/engine/topologies/platform/docker/layers/l3_builder_layers.star +1 -1
- package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +14 -1
- package/engine/topologies/tilt/manifest/constants.star +1 -1
- package/package.json +1 -1
- package/cli/dist-bundle/chunk-DL7Q7MQT.js +0 -322
|
@@ -122,6 +122,7 @@ _tdk() {
|
|
|
122
122
|
upgrade)
|
|
123
123
|
_arguments
|
|
124
124
|
'--force[Force upgrade even if on latest]'
|
|
125
|
+
'--all[Upgrade every TDK installation found on PATH]'
|
|
125
126
|
'--dry-run[Show what would be upgraded]'
|
|
126
127
|
;;
|
|
127
128
|
esac
|
|
@@ -242,6 +243,7 @@ complete -c tdk -n '__fish_seen_subcommand_from runtime' -l json -d 'Print the c
|
|
|
242
243
|
|
|
243
244
|
# upgrade command options
|
|
244
245
|
complete -c tdk -n '__fish_seen_subcommand_from upgrade' -l force -d 'Force upgrade even if on latest'
|
|
246
|
+
complete -c tdk -n '__fish_seen_subcommand_from upgrade' -l all -d 'Upgrade every TDK installation found on PATH'
|
|
245
247
|
complete -c tdk -n '__fish_seen_subcommand_from upgrade' -l dry-run -d 'Show what would be upgraded'
|
|
246
248
|
`;
|
|
247
249
|
const POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName tdk -ScriptBlock {
|
|
@@ -6,24 +6,29 @@ import { join, normalize, relative } from "node:path";
|
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import { hasVerdaccioLicense } from "../generator/extension-fetch.js";
|
|
9
|
+
import { readProjectConfig } from "../generator/template-engine.js";
|
|
9
10
|
import { DEVCONTAINER_DOCKER_FIX, detectHost, isContainerHost, WEBCONTAINER_DOCS, WEBCONTAINER_UP_MESSAGE, } from "../utils/agent-host.js";
|
|
10
11
|
import { projectNeedsBun } from "../utils/bun-requirement.js";
|
|
11
12
|
import { AUTOGENERATED_DIR, CONTAINER_RUNTIME_CHECK, MASTER_CONFIG_FILES, PACKAGE_JSON, REQUIRED_PACKAGE_SCRIPTS, } from "../utils/constants.js";
|
|
12
13
|
import { isPathDiscovered, readDiscoveryPaths } from "../utils/discovery-paths.js";
|
|
14
|
+
import { checkCrashLoopingContainers } from "../utils/doctor-crashloop.js";
|
|
15
|
+
import { applyDoctorFixes, checkDuplicateTdkInstalls, hasFailedActions, planDoctorFixes, } from "../utils/doctor-fix.js";
|
|
13
16
|
import { checkDiskSpace, dockerDesktopStuck } from "../utils/doctor-host.js";
|
|
14
17
|
import { collectDoctorChecks, createDoctorReport, getDoctorExitCode, } from "../utils/doctor-report.js";
|
|
15
18
|
import { checkHealthRoutes, checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, projectConfigEnablesVerdaccio, summarizeAdvertisedProbes, summarizeServiceProbes, summarizeSmokeResults, } from "../utils/doctor-runtime.js";
|
|
16
19
|
import { checkStarlarkLoadExports } from "../utils/doctor-starlark.js";
|
|
17
20
|
import { checkDockerNetworkCapacity, checkDuplicateResourceNames, checkDuplicateResourcePorts, checkFrontendBackendUrls, checkMigrationsInApi, checkNatsBroker, checkPrismaConsistency, checkResourcePackageJson, checkServiceUrlPorts, checkSharedPlatformPostgres, checkSharedStackRoutes, checkTiltInstances, } from "../utils/doctor-wiring.js";
|
|
21
|
+
import { findProjectTiltUpPids } from "../utils/down-cleanup.js";
|
|
18
22
|
import { validateEnvFile } from "../utils/env-validator.js";
|
|
19
23
|
import { execAsync, isExecTimeout } from "../utils/exec-async.js";
|
|
20
24
|
import { formatCount } from "../utils/formatting.js";
|
|
21
25
|
import { getHostPortPlan, resolveRuntimeIngressPort } from "../utils/host-port-config.js";
|
|
22
26
|
import { createHostPortPlan } from "../utils/host-port-plan.js";
|
|
23
27
|
import { findProjectRoot, getPackageVersion } from "../utils/paths.js";
|
|
28
|
+
import { promptConfirm } from "../utils/prompt.js";
|
|
24
29
|
import { EnvUnreadableError, envSecretValues, redactSecrets, redactValue, } from "../utils/secret-redaction.js";
|
|
25
30
|
import { checkCircularDependencies, checkDependsOnShape, checkSchemaVersions, checkServicePorts, } from "../utils/service-config-checks.js";
|
|
26
|
-
import { buildAdvertisedTargets, buildHealthTargets, pingHealthTargets, } from "../utils/service-urls.js";
|
|
31
|
+
import { buildAdvertisedTargets, buildHealthTargets, getProjectName, pingHealthTargets, } from "../utils/service-urls.js";
|
|
27
32
|
import { discoverResourcesFromRoot } from "../utils/services.js";
|
|
28
33
|
import { buildSmokePlans, readOnlySmokePlans, runSmokePlans } from "../utils/smoke.js";
|
|
29
34
|
import { evaluateTdkVersionFloor } from "../utils/tdk-version.js";
|
|
@@ -978,6 +983,26 @@ async function checkAdvertisedEndpoints(timeoutMs, ingressPort) {
|
|
|
978
983
|
}
|
|
979
984
|
return summarizeAdvertisedProbes(await pingHealthTargets(targets, timeoutMs));
|
|
980
985
|
}
|
|
986
|
+
function printFixActions(actions, dryRun) {
|
|
987
|
+
if (actions.length === 0) {
|
|
988
|
+
console.log(`${chalk.green("✓")} Nothing to repair\n`);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
for (const action of actions) {
|
|
992
|
+
const label = action.status === "planned"
|
|
993
|
+
? `${chalk.cyan("would run")}`
|
|
994
|
+
: action.status === "applied"
|
|
995
|
+
? chalk.green("ran")
|
|
996
|
+
: action.status === "failed"
|
|
997
|
+
? chalk.red("failed")
|
|
998
|
+
: chalk.gray("skipped");
|
|
999
|
+
const detail = action.status === "planned" ? "" : ` (${action.reason})`;
|
|
1000
|
+
console.log(`${label}: ${action.command}${detail}`);
|
|
1001
|
+
}
|
|
1002
|
+
if (dryRun)
|
|
1003
|
+
console.log(chalk.gray("\nDry run: nothing was changed."));
|
|
1004
|
+
console.log("");
|
|
1005
|
+
}
|
|
981
1006
|
export const doctorCommand = new Command("doctor")
|
|
982
1007
|
.description("Check environment readiness for TDK")
|
|
983
1008
|
.option("--json", "Output a versioned JSON readiness report", false)
|
|
@@ -991,6 +1016,9 @@ export const doctorCommand = new Command("doctor")
|
|
|
991
1016
|
})
|
|
992
1017
|
.option("--no-ping", "Skip pinging running services' /health endpoints")
|
|
993
1018
|
.option("--strict", "Fail warnings such as a WSL project under /mnt/c")
|
|
1019
|
+
.option("--fix", "Repair this project's unused Docker networks and leftover doctor probes", false)
|
|
1020
|
+
.option("--dry-run", "With --fix: list the repairs without changing anything", false)
|
|
1021
|
+
.option("-y, --yes", "With --fix: apply every repair without asking", false)
|
|
994
1022
|
.option("--ping-timeout <ms>", "Per-service ping timeout in milliseconds", String(DEFAULT_PING_TIMEOUT_MS))
|
|
995
1023
|
.action(async (options) => {
|
|
996
1024
|
// Validate before running any probe; parseInt would silently accept "100ms" or "1.5".
|
|
@@ -1005,6 +1033,14 @@ export const doctorCommand = new Command("doctor")
|
|
|
1005
1033
|
process.exit(2);
|
|
1006
1034
|
return;
|
|
1007
1035
|
}
|
|
1036
|
+
if ((options.dryRun || options.yes) && !options.fix) {
|
|
1037
|
+
const message = `${options.dryRun ? "--dry-run" : "--yes"} only applies with --fix`;
|
|
1038
|
+
if (options.json)
|
|
1039
|
+
console.log(JSON.stringify(createDoctorReport([], false, [{ code: "USAGE", message }])));
|
|
1040
|
+
console.error(message);
|
|
1041
|
+
process.exit(2);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1008
1044
|
if (!options.json) {
|
|
1009
1045
|
console.log("TDK doctor checks your local Docker + Tilt development environment; it does not check cluster deployments.");
|
|
1010
1046
|
}
|
|
@@ -1051,6 +1087,51 @@ export const doctorCommand = new Command("doctor")
|
|
|
1051
1087
|
console.log("Checking environment...\n");
|
|
1052
1088
|
}
|
|
1053
1089
|
const projectRoot = findProjectRoot();
|
|
1090
|
+
// Repairs run before the checks so the checks report the state after the fix.
|
|
1091
|
+
let fixActions;
|
|
1092
|
+
if (options.fix) {
|
|
1093
|
+
try {
|
|
1094
|
+
let projectName;
|
|
1095
|
+
if (projectRoot) {
|
|
1096
|
+
try {
|
|
1097
|
+
projectName = readProjectConfig(projectRoot).project.name;
|
|
1098
|
+
}
|
|
1099
|
+
catch {
|
|
1100
|
+
// An unreadable config is reported by the project checks below.
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const tiltPids = projectRoot
|
|
1104
|
+
? findProjectTiltUpPids(join(projectRoot, ".tdk", ".tdk-out", "Tiltfile"))
|
|
1105
|
+
: [];
|
|
1106
|
+
const planned = await planDoctorFixes(projectName, undefined, tiltPids.length > 0
|
|
1107
|
+
? `Tilt is running for this project (pid ${tiltPids.join(", ")}); run tdk down first`
|
|
1108
|
+
: undefined);
|
|
1109
|
+
fixActions = await applyDoctorFixes(planned, {
|
|
1110
|
+
dryRun: options.dryRun,
|
|
1111
|
+
confirm: options.yes || (!options.json && process.stdin.isTTY)
|
|
1112
|
+
? async (action) => options.yes ||
|
|
1113
|
+
(await promptConfirm({
|
|
1114
|
+
message: `Run \`${action.command}\` (${action.check}, ${action.reason})?`,
|
|
1115
|
+
}))
|
|
1116
|
+
: undefined,
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
catch (error) {
|
|
1120
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1121
|
+
fixActions = [
|
|
1122
|
+
{
|
|
1123
|
+
id: "plan-fixes",
|
|
1124
|
+
check: "Docker networks",
|
|
1125
|
+
scope: "project",
|
|
1126
|
+
command: "docker network ls",
|
|
1127
|
+
status: "failed",
|
|
1128
|
+
reason: `could not list Docker networks: ${reason.split(/\r?\n/)[0]}`,
|
|
1129
|
+
},
|
|
1130
|
+
];
|
|
1131
|
+
}
|
|
1132
|
+
if (!options.json)
|
|
1133
|
+
printFixActions(fixActions, options.dryRun);
|
|
1134
|
+
}
|
|
1054
1135
|
let hostPortPlan;
|
|
1055
1136
|
let portPlanError;
|
|
1056
1137
|
try {
|
|
@@ -1094,6 +1175,7 @@ export const doctorCommand = new Command("doctor")
|
|
|
1094
1175
|
() => checkPublishedBindAddress(process.env, options.strict),
|
|
1095
1176
|
// Each project needs several networks; a full address pool fails `tdk up` late.
|
|
1096
1177
|
() => checkDockerNetworkCapacity(),
|
|
1178
|
+
() => checkDuplicateTdkInstalls(),
|
|
1097
1179
|
];
|
|
1098
1180
|
const projectChecks = [
|
|
1099
1181
|
() => checkTdkVersion(),
|
|
@@ -1134,6 +1216,24 @@ export const doctorCommand = new Command("doctor")
|
|
|
1134
1216
|
},
|
|
1135
1217
|
// Runtime checks: skip gracefully if the stack isn't started yet.
|
|
1136
1218
|
checkContainerResourceHealth,
|
|
1219
|
+
// A container restarted thousands of times is invisible in `docker ps` until someone looks.
|
|
1220
|
+
() => {
|
|
1221
|
+
const root = findProjectRoot() ?? process.cwd();
|
|
1222
|
+
let secrets = [];
|
|
1223
|
+
try {
|
|
1224
|
+
secrets = envSecretValues(root);
|
|
1225
|
+
}
|
|
1226
|
+
catch {
|
|
1227
|
+
// Without the secret values, log lines cannot be shown safely.
|
|
1228
|
+
return {
|
|
1229
|
+
name: "Crash-looping containers",
|
|
1230
|
+
didPass: true,
|
|
1231
|
+
isSkipped: true,
|
|
1232
|
+
message: "Could not read .env - skipped crash-loop check",
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
return checkCrashLoopingContainers(getProjectName(), secrets);
|
|
1236
|
+
},
|
|
1137
1237
|
// When Tilt is up, surface red resources (Traefik/apps never started).
|
|
1138
1238
|
checkTiltResourceHealth,
|
|
1139
1239
|
];
|
|
@@ -1160,6 +1260,11 @@ export const doctorCommand = new Command("doctor")
|
|
|
1160
1260
|
}
|
|
1161
1261
|
}
|
|
1162
1262
|
const report = createDoctorReport(orderDoctorResults(results), inProject, errors, hostPortPlan, host);
|
|
1263
|
+
if (fixActions)
|
|
1264
|
+
report.data.actions = fixActions;
|
|
1265
|
+
const fixFailed = fixActions ? hasFailedActions(fixActions) : false;
|
|
1266
|
+
if (fixFailed)
|
|
1267
|
+
report.data.ready = false;
|
|
1163
1268
|
const exitCode = getDoctorExitCode(report);
|
|
1164
1269
|
const allPassed = report.data.ready;
|
|
1165
1270
|
if (options.json) {
|
|
@@ -83,7 +83,7 @@ const COMMAND_GROUPS = [
|
|
|
83
83
|
alias: "--check-assets",
|
|
84
84
|
},
|
|
85
85
|
{ name: "completion", desc: "Generate shell completions", alias: "--install --shell zsh" },
|
|
86
|
-
{ name: "upgrade, update", desc: "Self-update to latest version", alias: "--force" },
|
|
86
|
+
{ name: "upgrade, update", desc: "Self-update to latest version", alias: "--force --all" },
|
|
87
87
|
{ name: "version, -v", desc: "Display version number", alias: "" },
|
|
88
88
|
{ name: "help", desc: "Show help", alias: "[command]" },
|
|
89
89
|
],
|
|
@@ -136,6 +136,7 @@ export function showHelp() {
|
|
|
136
136
|
${chalk.cyan("$")} tdk networks # Show Traefik-routed URLs
|
|
137
137
|
${chalk.cyan("$")} tdk doctor # Check your environment
|
|
138
138
|
${chalk.cyan("$")} tdk upgrade # Self-update
|
|
139
|
+
${chalk.cyan("$")} tdk upgrade --all # Update all TDK installs on PATH
|
|
139
140
|
`));
|
|
140
141
|
console.log(chalk.gray(` ${"═".repeat(50)}`));
|
|
141
142
|
console.log(chalk.gray(` For more help: ${chalk.cyan("tdk help [command]")}`));
|
package/cli/dist/commands/ui.js
CHANGED
|
@@ -348,11 +348,13 @@ export const TUIApp = ({ animated = true }) => {
|
|
|
348
348
|
// Parse SGR 1006 mouse protocol: ESC[<btn;x;yM or ESC[<btn;x;ym
|
|
349
349
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: \\x1b ESC is intentional (ANSI SGR/CSI sequence parsing)
|
|
350
350
|
const sgrMatch = str.match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const
|
|
351
|
+
const btnText = sgrMatch?.[1];
|
|
352
|
+
const yText = sgrMatch?.[3];
|
|
353
|
+
const action = sgrMatch?.[4];
|
|
354
|
+
if (btnText !== undefined && yText !== undefined && action !== undefined) {
|
|
355
|
+
const btn = parseInt(btnText, 10);
|
|
356
|
+
const y = parseInt(yText, 10);
|
|
357
|
+
const release = action === "m";
|
|
356
358
|
// Check if it's a left click (btn & 0b11 == 0 means left button)
|
|
357
359
|
const isLeftClick = (btn & 0b11) === 0;
|
|
358
360
|
if (isLeftClick && !release) {
|
package/cli/dist/commands/up.js
CHANGED
|
@@ -29,6 +29,7 @@ import { buildSmokePlans, formatSmokeFailure, runSmokePlans } from "../utils/smo
|
|
|
29
29
|
import { buildStartupReport, formatStartupReport, isStartupStalled, } from "../utils/startup-report.js";
|
|
30
30
|
import { evaluateTdkVersionFloor } from "../utils/tdk-version.js";
|
|
31
31
|
import { buildTiltUpArgs, runTilt } from "../utils/tilt.js";
|
|
32
|
+
import { findTiltPortOwner, isForeignTilt } from "../utils/tilt-port-owner.js";
|
|
32
33
|
import { stopTiltOnPort } from "../utils/tilt-process.js";
|
|
33
34
|
import { parseTiltPort, resolveTiltPort, secondUpAction, stopTiltForUp, } from "../utils/tilt-startup.js";
|
|
34
35
|
import { findUnknownServices, resolveOnlySelection } from "../utils/up-only.js";
|
|
@@ -369,13 +370,35 @@ export const upCommand = new Command("up")
|
|
|
369
370
|
const watchedPort = configuredTiltPort ?? basePort;
|
|
370
371
|
const candidatePorts = options.only ? [...new Set([basePort, watchedPort])] : [watchedPort];
|
|
371
372
|
const running = [];
|
|
373
|
+
const foreign = [];
|
|
372
374
|
for (const candidate of candidatePorts) {
|
|
373
|
-
if ((await tiltGetUiResources(candidate))
|
|
375
|
+
if ((await tiltGetUiResources(candidate)) === null)
|
|
376
|
+
continue;
|
|
377
|
+
// A Tilt on this port may belong to another project; that is not "this environment is running".
|
|
378
|
+
const owner = findTiltPortOwner(candidate);
|
|
379
|
+
if (owner && isForeignTilt(owner, projectRoot)) {
|
|
380
|
+
foreign.push({ port: candidate, root: owner.root });
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
374
383
|
running.push(candidate);
|
|
384
|
+
}
|
|
375
385
|
}
|
|
386
|
+
if (foreign.length > 0 && configuredTiltPort !== undefined) {
|
|
387
|
+
const other = foreign[0];
|
|
388
|
+
const message = `TILT_PORT=${other?.port} is used by another project's Tilt (${other?.root}). Choose a free port, or run tdk down in that project.`;
|
|
389
|
+
emit?.({ ok: false }, [{ code: "TILT_PORT_IN_USE", message }]);
|
|
390
|
+
showErrorAndExit(message);
|
|
391
|
+
}
|
|
392
|
+
if (!options.quiet) {
|
|
393
|
+
for (const other of foreign) {
|
|
394
|
+
console.log(chalk.yellow(`⚠️ Tilt on port ${other.port} belongs to another project (${other.root}); this project will use another port.`));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// --force replaces this project's Tilt; it must never stop another project's.
|
|
398
|
+
const force = options.force && foreign.length === 0;
|
|
376
399
|
const decision = secondUpAction({
|
|
377
400
|
runningPorts: running,
|
|
378
|
-
force
|
|
401
|
+
force,
|
|
379
402
|
only: Boolean(options.only),
|
|
380
403
|
});
|
|
381
404
|
if (decision.action === "already-running") {
|
|
@@ -400,7 +423,7 @@ export const upCommand = new Command("up")
|
|
|
400
423
|
}
|
|
401
424
|
const resolution = await resolveTiltPort({
|
|
402
425
|
configuredPort: configuredTiltPort,
|
|
403
|
-
force
|
|
426
|
+
force,
|
|
404
427
|
basePort,
|
|
405
428
|
findAvailablePort,
|
|
406
429
|
});
|
|
@@ -410,7 +433,7 @@ export const upCommand = new Command("up")
|
|
|
410
433
|
console.log(chalk.blue(`🔄 Auto-switching to port ${port}\n`));
|
|
411
434
|
}
|
|
412
435
|
process.env.TILT_PORT = port.toString();
|
|
413
|
-
await stopTiltForUp({ force
|
|
436
|
+
await stopTiltForUp({ force, quiet: options.quiet, port }, {
|
|
414
437
|
stop: stopTiltOnPort,
|
|
415
438
|
log: (message) => console.log(chalk.yellow(message)),
|
|
416
439
|
wait: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
@@ -15,7 +15,7 @@ import { binaryAssetName, executableName, isWindows } from "../utils/platform.js
|
|
|
15
15
|
import { promptConfirm } from "../utils/prompt.js";
|
|
16
16
|
import { startSpinner } from "../utils/spinner.js";
|
|
17
17
|
import { extractTarball } from "../utils/tar.js";
|
|
18
|
-
import { findOnPath } from "../utils/which.js";
|
|
18
|
+
import { findAllOnPath, findOnPath } from "../utils/which.js";
|
|
19
19
|
function isStandaloneBinary(tdkPath) {
|
|
20
20
|
if (isWindows() && tdkPath.toLowerCase().endsWith(".exe"))
|
|
21
21
|
return true;
|
|
@@ -88,6 +88,45 @@ function detectInstallation() {
|
|
|
88
88
|
return { method: "unknown" };
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
function detectInstallationForPath(tdkPath) {
|
|
92
|
+
try {
|
|
93
|
+
const realPath = realpathSync(tdkPath);
|
|
94
|
+
if (realPath.includes("tdk-cli")) {
|
|
95
|
+
const possibleGitRoot = resolve(realPath, "..", "..", "..");
|
|
96
|
+
if (existsSync(join(possibleGitRoot, ".git"))) {
|
|
97
|
+
return { method: "git", path: possibleGitRoot };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (tdkPath.includes("node_modules") || tdkPath.includes(".npm") || tdkPath.includes(".bun")) {
|
|
101
|
+
return { method: tdkPath.includes(".bun") ? "bun" : "npm", path: tdkPath };
|
|
102
|
+
}
|
|
103
|
+
if (isStandaloneBinary(realPath))
|
|
104
|
+
return { method: "binary", path: realPath };
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
logVerbose(`Installation detection failed for ${tdkPath}`, err);
|
|
108
|
+
}
|
|
109
|
+
return { method: "unknown", path: tdkPath };
|
|
110
|
+
}
|
|
111
|
+
function detectAllInstallations() {
|
|
112
|
+
const candidates = findAllOnPath(executableName()).map(detectInstallationForPath);
|
|
113
|
+
const current = detectInstallation();
|
|
114
|
+
if (current.method !== "unknown")
|
|
115
|
+
candidates.unshift(current);
|
|
116
|
+
const unique = new Map();
|
|
117
|
+
for (const install of candidates) {
|
|
118
|
+
if (install.method === "unknown" || !install.path)
|
|
119
|
+
continue;
|
|
120
|
+
let key = install.path;
|
|
121
|
+
try {
|
|
122
|
+
if (install.method === "binary")
|
|
123
|
+
key = realpathSync(install.path);
|
|
124
|
+
}
|
|
125
|
+
catch { }
|
|
126
|
+
unique.set(`${install.method}:${key}`, install);
|
|
127
|
+
}
|
|
128
|
+
return [...unique.values()];
|
|
129
|
+
}
|
|
91
130
|
const BINARY_RELEASE_REPO = "tdk-landscape/tdk-cli-releases";
|
|
92
131
|
const ENGINE_ASSET_NAME = "tdk-cli-engine.tar.gz";
|
|
93
132
|
const CHECKSUMS_ASSET_NAME = "checksums.txt";
|
|
@@ -355,8 +394,75 @@ export async function upgradeViaBun() {
|
|
|
355
394
|
return false;
|
|
356
395
|
}
|
|
357
396
|
}
|
|
397
|
+
async function upgradeAllInstallations(dryRun) {
|
|
398
|
+
const installations = detectAllInstallations();
|
|
399
|
+
if (!installations.length) {
|
|
400
|
+
console.error(chalk.red("Could not find any TDK installations on PATH."));
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
console.log(chalk.blue(`Found ${installations.length} TDK installation${installations.length === 1 ? "" : "s"}:`));
|
|
404
|
+
for (const install of installations) {
|
|
405
|
+
console.log(chalk.gray(` ${install.method}: ${install.path}`));
|
|
406
|
+
}
|
|
407
|
+
if (dryRun) {
|
|
408
|
+
console.log(chalk.yellow("\nDry run: no installations were changed."));
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
const binaryInstallations = installations.filter((install) => install.method === "binary" && install.path);
|
|
412
|
+
const release = binaryInstallations.length ? await getLatestBinaryRelease() : null;
|
|
413
|
+
if (binaryInstallations.length && !release) {
|
|
414
|
+
console.error(chalk.red("Could not find a compatible TDK binary release for this platform."));
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
let success = true;
|
|
418
|
+
const completed = new Set();
|
|
419
|
+
for (const install of installations) {
|
|
420
|
+
const key = install.method === "npm" || install.method === "bun"
|
|
421
|
+
? install.method
|
|
422
|
+
: `${install.method}:${install.path}`;
|
|
423
|
+
if (completed.has(key))
|
|
424
|
+
continue;
|
|
425
|
+
completed.add(key);
|
|
426
|
+
if (install.method === "git" && install.path) {
|
|
427
|
+
console.log(chalk.cyan(`\nUpdating git install at ${install.path}`));
|
|
428
|
+
success = (await upgradeViaGit(install.path)) && success;
|
|
429
|
+
}
|
|
430
|
+
else if (install.method === "binary" && install.path && release) {
|
|
431
|
+
const latest = release.tag.replace(/^v/, "").split("-")[0];
|
|
432
|
+
let current = "";
|
|
433
|
+
try {
|
|
434
|
+
current = execFileSync(install.path, ["version"], {
|
|
435
|
+
encoding: "utf-8",
|
|
436
|
+
timeout: 10_000,
|
|
437
|
+
}).trim();
|
|
438
|
+
}
|
|
439
|
+
catch { }
|
|
440
|
+
if (current === latest) {
|
|
441
|
+
console.log(chalk.green(`\n${install.path} is already current (${current}).`));
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
console.log(chalk.cyan(`\nUpdating ${install.path} (${current || "unknown"} → ${latest})`));
|
|
445
|
+
success = (await upgradeViaBinary(install.path, release)) && success;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else if (install.method === "npm") {
|
|
449
|
+
console.log(chalk.cyan("\nUpdating the npm installation..."));
|
|
450
|
+
success = (await upgradeViaNpm()) && success;
|
|
451
|
+
}
|
|
452
|
+
else if (install.method === "bun") {
|
|
453
|
+
console.log(chalk.cyan("\nUpdating the Bun installation..."));
|
|
454
|
+
success = (await upgradeViaBun()) && success;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (success)
|
|
458
|
+
console.log(chalk.green.bold("\nAll TDK installations on PATH are up to date."));
|
|
459
|
+
else
|
|
460
|
+
console.error(chalk.red("\nAt least one TDK installation could not be updated."));
|
|
461
|
+
return success;
|
|
462
|
+
}
|
|
358
463
|
async function upgradeViaGit(path) {
|
|
359
|
-
const spinner = startSpinner("
|
|
464
|
+
const spinner = startSpinner("Updating the git installation from the default branch...");
|
|
465
|
+
let rebaseInProgress = false;
|
|
360
466
|
try {
|
|
361
467
|
execFileSync("git", ["rev-parse", "--git-dir"], {
|
|
362
468
|
cwd: path,
|
|
@@ -368,16 +474,57 @@ async function upgradeViaGit(path) {
|
|
|
368
474
|
stdio: "pipe",
|
|
369
475
|
timeout: 30000,
|
|
370
476
|
});
|
|
371
|
-
const
|
|
477
|
+
const dirty = execFileSync("git", ["status", "--porcelain"], {
|
|
372
478
|
cwd: path,
|
|
373
479
|
encoding: "utf-8",
|
|
374
480
|
}).trim();
|
|
375
|
-
|
|
376
|
-
|
|
481
|
+
if (dirty) {
|
|
482
|
+
throw new Error("The TDK source checkout has local changes. Commit or stash them, then run `tdk upgrade` again.");
|
|
483
|
+
}
|
|
484
|
+
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
377
485
|
cwd: path,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
486
|
+
encoding: "utf-8",
|
|
487
|
+
}).trim();
|
|
488
|
+
let defaultRef = "";
|
|
489
|
+
try {
|
|
490
|
+
defaultRef = execFileSync("git", ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], {
|
|
491
|
+
cwd: path,
|
|
492
|
+
encoding: "utf-8",
|
|
493
|
+
stdio: "pipe",
|
|
494
|
+
}).trim();
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
for (const candidate of ["origin/main", "origin/master"]) {
|
|
498
|
+
try {
|
|
499
|
+
execFileSync("git", ["rev-parse", "--verify", candidate], { cwd: path, stdio: "pipe" });
|
|
500
|
+
defaultRef = candidate;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
catch { }
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (!defaultRef?.startsWith("origin/")) {
|
|
507
|
+
throw new Error("Could not find the default branch for origin (expected origin/HEAD, origin/main, or origin/master).");
|
|
508
|
+
}
|
|
509
|
+
const defaultBranch = defaultRef.slice("origin/".length);
|
|
510
|
+
if (branch === defaultBranch) {
|
|
511
|
+
spinner.text = `Fast-forwarding ${branch} from origin/${defaultBranch}...`;
|
|
512
|
+
execFileSync("git", ["pull", "--ff-only", "origin", defaultBranch], {
|
|
513
|
+
cwd: path,
|
|
514
|
+
stdio: "pipe",
|
|
515
|
+
timeout: 30000,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
spinner.text = `Rebasing ${branch} onto origin/${defaultBranch}...`;
|
|
520
|
+
rebaseInProgress = true;
|
|
521
|
+
execFileSync("git", ["rebase", defaultRef], {
|
|
522
|
+
cwd: path,
|
|
523
|
+
stdio: "pipe",
|
|
524
|
+
timeout: 60000,
|
|
525
|
+
});
|
|
526
|
+
rebaseInProgress = false;
|
|
527
|
+
}
|
|
381
528
|
if (existsSync(join(path, "cli", PACKAGE_JSON))) {
|
|
382
529
|
spinner.text = "Rebuilding CLI...";
|
|
383
530
|
execFileSync(findOnPath("bun") ?? "bun", ["install"], {
|
|
@@ -397,10 +544,16 @@ async function upgradeViaGit(path) {
|
|
|
397
544
|
stdio: "pipe",
|
|
398
545
|
timeout: 30000,
|
|
399
546
|
});
|
|
400
|
-
spinner.succeed(
|
|
547
|
+
spinner.succeed(`Upgraded successfully from origin/${defaultBranch}`);
|
|
401
548
|
return true;
|
|
402
549
|
}
|
|
403
550
|
catch (err) {
|
|
551
|
+
if (rebaseInProgress) {
|
|
552
|
+
try {
|
|
553
|
+
execFileSync("git", ["rebase", "--abort"], { cwd: path, stdio: "pipe" });
|
|
554
|
+
}
|
|
555
|
+
catch { }
|
|
556
|
+
}
|
|
404
557
|
spinner.fail(`Git upgrade failed: ${getErrorMessage(err)}`);
|
|
405
558
|
return false;
|
|
406
559
|
}
|
|
@@ -409,6 +562,7 @@ export const upgradeCommand = new Command("upgrade")
|
|
|
409
562
|
.alias("update")
|
|
410
563
|
.description("Upgrade TDK CLI to the latest version")
|
|
411
564
|
.option("-f, --force", "Force upgrade even if already on latest", false)
|
|
565
|
+
.option("--all", "Upgrade every TDK installation found on PATH", false)
|
|
412
566
|
.option("--dry-run", "Show what would be upgraded without actually doing it", false)
|
|
413
567
|
.option("-y, --yes", "Skip confirmation prompt", false)
|
|
414
568
|
.action(async (options) => {
|
|
@@ -418,6 +572,27 @@ export const upgradeCommand = new Command("upgrade")
|
|
|
418
572
|
const installInfo = detectInstallation();
|
|
419
573
|
console.log(chalk.gray(`Installation method: ${installInfo.method}`));
|
|
420
574
|
console.log();
|
|
575
|
+
if (options.all) {
|
|
576
|
+
const installations = detectAllInstallations();
|
|
577
|
+
if (!installations.length) {
|
|
578
|
+
console.error(chalk.red("Could not find any TDK installations on PATH."));
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
if (!options.dryRun && !options.yes) {
|
|
582
|
+
const confirm = await promptConfirm({
|
|
583
|
+
message: `Upgrade all ${installations.length} TDK installations on PATH?`,
|
|
584
|
+
initial: true,
|
|
585
|
+
});
|
|
586
|
+
if (!confirm) {
|
|
587
|
+
showCancelled();
|
|
588
|
+
process.exit(0);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const success = await upgradeAllInstallations(options.dryRun);
|
|
592
|
+
if (!success)
|
|
593
|
+
process.exit(1);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
421
596
|
const windowsNpmMessage = windowsNpmUpgradeMessage(process.platform, installInfo);
|
|
422
597
|
if (windowsNpmMessage) {
|
|
423
598
|
console.log(windowsNpmMessage);
|
|
@@ -459,7 +634,7 @@ export const upgradeCommand = new Command("upgrade")
|
|
|
459
634
|
}
|
|
460
635
|
}
|
|
461
636
|
else if (installInfo.method === "git" && installInfo.path) {
|
|
462
|
-
console.log(chalk.blue("📦 Git installation detected - will
|
|
637
|
+
console.log(chalk.blue("📦 Git installation detected - will update from origin's default branch"));
|
|
463
638
|
try {
|
|
464
639
|
execFileSync("git", ["fetch", "origin"], { cwd: installInfo.path, stdio: "pipe" });
|
|
465
640
|
const localHash = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
@@ -516,7 +691,7 @@ export const upgradeCommand = new Command("upgrade")
|
|
|
516
691
|
console.log(chalk.gray(` Path: ${installInfo.path}`));
|
|
517
692
|
}
|
|
518
693
|
if (installInfo.method === "git") {
|
|
519
|
-
console.log(chalk.gray(" Action:
|
|
694
|
+
console.log(chalk.gray(" Action: fetch origin, update from origin/HEAD, rebuild, and relink"));
|
|
520
695
|
}
|
|
521
696
|
else if (installInfo.method === "binary" && binaryRelease) {
|
|
522
697
|
console.log(chalk.gray(` Action: Download and replace with ${binaryRelease.downloadUrl}`));
|
|
@@ -565,7 +740,8 @@ export const upgradeCommand = new Command("upgrade")
|
|
|
565
740
|
if (!success) {
|
|
566
741
|
console.error(chalk.red("\nUpgrade failed"));
|
|
567
742
|
if (installInfo.method === "git") {
|
|
568
|
-
console.log(chalk.cyan(`
|
|
743
|
+
console.log(chalk.cyan(` Check local changes with: git -C ${installInfo.path} status --short`));
|
|
744
|
+
console.log(chalk.cyan(" Commit or stash them, then retry: tdk upgrade"));
|
|
569
745
|
}
|
|
570
746
|
else if (installInfo.method === "binary" && binaryRelease && installInfo.path) {
|
|
571
747
|
const engineDir = join(dirname(installInfo.path), "tdk-cli");
|
|
@@ -505,6 +505,26 @@ export async function generateMasterConfigs(projectRoot, options = {}) {
|
|
|
505
505
|
// vendor a working copy into .tdk/.tdk-out/tdk-cli-ext/ at generation time.
|
|
506
506
|
// This makes `tdk up` work offline and on any machine - no hardcoded paths,
|
|
507
507
|
// no dependency on a private GitHub repo.
|
|
508
|
+
// The engine's own unit tests (for example wake-gateway/decide.test.ts, which imports bun:test) are
|
|
509
|
+
// not needed to run a stack. Vendoring them under .tdk/.tdk-out makes a Node project's test runner
|
|
510
|
+
// (Vitest, Jest) discover them and fail, so they are left out of the vendored copy.
|
|
511
|
+
const TEST_FILE_PATTERN = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
512
|
+
export function isTestFile(filePath) {
|
|
513
|
+
return TEST_FILE_PATTERN.test(filePath);
|
|
514
|
+
}
|
|
515
|
+
function removeVendoredTestFiles(dir) {
|
|
516
|
+
if (!fs.existsSync(dir))
|
|
517
|
+
return;
|
|
518
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
519
|
+
const entryPath = path.join(dir, entry.name);
|
|
520
|
+
if (entry.isDirectory()) {
|
|
521
|
+
removeVendoredTestFiles(entryPath);
|
|
522
|
+
}
|
|
523
|
+
else if (isTestFile(entryPath)) {
|
|
524
|
+
fs.rmSync(entryPath, { force: true });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
508
528
|
const TDK_EXTENSION_DIRS = ["engine", "discovery", "specs", "ext"];
|
|
509
529
|
const PROJECT_RUNTIME_ASSET_DIRS = [["shared-platform-engineering", "docker-templates"]];
|
|
510
530
|
const REQUIRED_DOCKER_TEMPLATE_FILES = [
|
|
@@ -618,8 +638,11 @@ async function vendorTdkExtension(projectRoot) {
|
|
|
618
638
|
}
|
|
619
639
|
fs.copyFileSync(path.join(sourceRoot, "Tiltfile"), path.join(vendoredDir, "Tiltfile"));
|
|
620
640
|
for (const dir of TDK_EXTENSION_DIRS) {
|
|
641
|
+
// A project vendored by an older TDK already holds test files; cpSync only adds, so drop them.
|
|
642
|
+
removeVendoredTestFiles(path.join(vendoredDir, dir));
|
|
621
643
|
fs.cpSync(path.join(sourceRoot, dir), path.join(vendoredDir, dir), {
|
|
622
644
|
recursive: true,
|
|
645
|
+
filter: (source) => !isTestFile(source),
|
|
623
646
|
});
|
|
624
647
|
}
|
|
625
648
|
console.log(`✓ Vendored TDK extension → .tdk/.tdk-out/tdk-cli-ext/`);
|