@tdk-landscape/tdk-cli-core 1.3.153 → 1.3.155

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.
@@ -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 {
@@ -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]")}`));
@@ -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
- if (sgrMatch) {
352
- const btn = parseInt(sgrMatch[1], 10);
353
- // sgrMatch[2] is the x coordinate, which the handler does not use
354
- const y = parseInt(sgrMatch[3], 10);
355
- const release = sgrMatch[4] === "m";
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) {
@@ -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("Pulling latest changes from git...");
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 branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
477
+ const dirty = execFileSync("git", ["status", "--porcelain"], {
372
478
  cwd: path,
373
479
  encoding: "utf-8",
374
480
  }).trim();
375
- spinner.text = `Pulling latest on ${branch}...`;
376
- execFileSync("git", ["pull", "origin", branch], {
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
- stdio: "pipe",
379
- timeout: 30000,
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("Upgraded successfully via git pull");
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 pull latest from origin"));
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: git pull origin main && bun install && bun link --force"));
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(` cd ${installInfo.path} && git pull && bun link --force`));
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");
@@ -25,3 +25,32 @@ export function findOnPath(command) {
25
25
  }
26
26
  return null;
27
27
  }
28
+ export function findAllOnPath(command) {
29
+ const pathEnv = process.env.PATH ?? process.env.Path ?? "";
30
+ const windows = isWindows();
31
+ const names = pathLookups(command, windows);
32
+ if (windows && !extname(command)) {
33
+ for (const extension of (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")) {
34
+ const suffix = extension.toLowerCase();
35
+ if (suffix && !names.includes(`${command}${suffix}`))
36
+ names.push(`${command}${suffix}`);
37
+ }
38
+ }
39
+ const found = [];
40
+ const seen = new Set();
41
+ for (const dir of pathEnv.split(delimiter)) {
42
+ if (!dir)
43
+ continue;
44
+ for (const name of names) {
45
+ const candidate = join(dir, name);
46
+ if (!existsSync(candidate))
47
+ continue;
48
+ const key = windows ? candidate.toLowerCase() : candidate;
49
+ if (!seen.has(key)) {
50
+ seen.add(key);
51
+ found.push(candidate);
52
+ }
53
+ }
54
+ }
55
+ return found;
56
+ }