From 928e5385eb03e85e686902754e7144649e840470 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Feb 2026 13:19:45 +0000 Subject: [PATCH 1/3] feat: warn on major version upgrade in CLI install scripts Added a major version upgrade warning to all three CLI installation scripts (shell, PowerShell, and Python). When the installed CLI version has a different major version than the target version, the scripts now display a warning message and point users to the CHANGELOG in aptos-core: https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos/CHANGELOG.md The warning is placed at every upgrade code path in each script: - Shell: binary install, source install with version, source install latest - PowerShell: binary install path - Python: get_version(), run_from_source(), install_from_source() --- public/scripts/install_cli.ps1 | 25 +++++++++++++++++++++++ public/scripts/install_cli.py | 37 ++++++++++++++++++++++++++++++++++ public/scripts/install_cli.sh | 27 +++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/public/scripts/install_cli.ps1 b/public/scripts/install_cli.ps1 index d895bd10..8ce56f26 100644 --- a/public/scripts/install_cli.ps1 +++ b/public/scripts/install_cli.ps1 @@ -44,6 +44,30 @@ function Test-CommandExists { return [bool](Get-Command -Name $Command -ErrorAction SilentlyContinue) } +# Check for major version upgrade and warn user +function Test-MajorVersionUpgrade { + param( + [string]$OldVersion, + [string]$NewVersion + ) + + if (-not $OldVersion -or -not $NewVersion) { + return + } + + $oldMajor = $OldVersion.Split('.')[0] + $newMajor = $NewVersion.Split('.')[0] + + if ($oldMajor -ne $newMajor) { + Write-Host "" + Write-ColorMessage -Color $YELLOW -Message "WARNING: This is a major version upgrade (v${oldMajor}.x.x -> v${newMajor}.x.x)." + Write-ColorMessage -Color $YELLOW -Message "Major version upgrades may include breaking changes." + Write-ColorMessage -Color $YELLOW -Message "Please review the CHANGELOG before continuing:" + Write-ColorMessage -Color $CYAN -Message " https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos/CHANGELOG.md" + Write-Host "" + } +} + # Get the latest version from GitHub API function Get-LatestVersion { try { @@ -156,6 +180,7 @@ function Main { Write-ColorMessage -Color $YELLOW -Message "Aptos CLI version $VERSION is already installed." return } + Test-MajorVersionUpgrade -OldVersion $currentVersion -NewVersion $VERSION } # Install the CLI diff --git a/public/scripts/install_cli.py b/public/scripts/install_cli.py index cbd141b1..24a267bc 100644 --- a/public/scripts/install_cli.py +++ b/public/scripts/install_cli.py @@ -441,6 +441,39 @@ def display_post_message_unix(self, version: str) -> None: ) ) + def _warn_major_upgrade(self, current_version: str, new_version: str) -> None: + """Warn the user if this is a major version upgrade.""" + if not current_version or not new_version: + return + + try: + current_major = current_version.split(".")[0] + new_major = new_version.split(".")[0] + except (IndexError, AttributeError): + return + + if current_major != new_major: + self._write("") + self._write( + colorize( + "warning", + f"WARNING: This is a major version upgrade (v{current_major}.x.x -> v{new_major}.x.x).", + ) + ) + self._write( + colorize("warning", "Major version upgrades may include breaking changes.") + ) + self._write( + colorize("warning", "Please review the CHANGELOG before continuing:") + ) + self._write( + colorize( + "info", + " https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos/CHANGELOG.md", + ) + ) + self._write("") + def get_version(self): if self._version: version_to_install = self._version @@ -484,6 +517,8 @@ def get_version(self): return None, current_version else: + if current_version: + self._warn_major_upgrade(current_version, version_to_install) self._write(f"Installing {colorize('b', version_to_install)}") return version_to_install, current_version @@ -772,6 +807,7 @@ def install_from_source(self, version: Optional[str] = None) -> int: if current_version == latest_version: self._write(colorize("warning", f"Aptos CLI version {latest_version} is already installed.")) return 0 + self._warn_major_upgrade(current_version, latest_version) except (FileNotFoundError, PermissionError, subprocess.CalledProcessError, OSError): pass # CLI not installed, proceed @@ -876,6 +912,7 @@ def run_from_source(self) -> int: if current_version == version: self._write(colorize("warning", f"Aptos CLI version {version} is already installed.")) return 0 + self._warn_major_upgrade(current_version, version) except (FileNotFoundError, PermissionError, subprocess.CalledProcessError, OSError): # CLI not installed or not runnable, proceed with installation pass diff --git a/public/scripts/install_cli.sh b/public/scripts/install_cli.sh index 8cb1345e..e02eab98 100755 --- a/public/scripts/install_cli.sh +++ b/public/scripts/install_cli.sh @@ -105,6 +105,28 @@ validate_version() { fi } +# Check for major version upgrade and warn user +warn_major_upgrade() { + old_version=$1 + new_version=$2 + + if [ -z "$old_version" ] || [ -z "$new_version" ]; then + return + fi + + old_major=$(echo "$old_version" | cut -d. -f1) + new_major=$(echo "$new_version" | cut -d. -f1) + + if [ "$old_major" != "$new_major" ]; then + printf "\n" + print_message "$YELLOW" "WARNING: This is a major version upgrade (v${old_major}.x.x -> v${new_major}.x.x)." + print_message "$YELLOW" "Major version upgrades may include breaking changes." + print_message "$YELLOW" "Please review the CHANGELOG before continuing:" + print_message "$CYAN" " https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos/CHANGELOG.md" + printf "\n" + fi +} + # Sort version tags - with fallback for systems without GNU sort -V sort_version_tags() { # Try GNU sort -V first, fall back to basic sort if not available @@ -163,6 +185,9 @@ install_from_source() { print_message "$GREEN" "Aptos CLI version $version is already installed. Use --force to rebuild." exit 0 fi + if [ -n "$installed_version" ]; then + warn_major_upgrade "$installed_version" "$version" + fi fi print_message "$CYAN" "Checking out $latest_tag..." @@ -356,6 +381,7 @@ main() { print_message "$YELLOW" "Aptos CLI version $VERSION is already installed." exit 0 fi + warn_major_upgrade "$current_version" "$VERSION" fi fi @@ -376,6 +402,7 @@ main() { print_message "$YELLOW" "Aptos CLI version $VERSION is already installed." exit 0 fi + warn_major_upgrade "$current_version" "$VERSION" fi # Install the CLI From 447d88a428907de3dff79328b3bced152fb9efaa Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Feb 2026 13:25:08 +0000 Subject: [PATCH 2/3] feat: add backup and --undo support for CLI upgrades Before overwriting the CLI binary during an upgrade, the install scripts now back up the current binary to .backup (e.g., aptos.backup). Only one backup copy is kept at a time (overwrites any previous backup). A new --undo flag restores the backup as the current binary: Shell: curl ... | sh -s -- --undo PowerShell: ... --undo Python: python install_cli.py --undo The undo operation is lightweight - no network calls, package installs, or builds are needed. After restoring, the script reports the version that was restored to. Changes across all three scripts (shell, PowerShell, Python): - backup_current_binary / Backup-CurrentBinary / _backup_current_binary - undo_upgrade / Undo-Upgrade / _undo_upgrade - --undo flag in argument parsing - Backup called right before the binary is overwritten in both the binary download and build-from-source install paths --- public/scripts/install_cli.ps1 | 41 +++++++++++++++++++++ public/scripts/install_cli.py | 65 ++++++++++++++++++++++++++++++++++ public/scripts/install_cli.sh | 50 ++++++++++++++++++++++++-- 3 files changed, 153 insertions(+), 3 deletions(-) diff --git a/public/scripts/install_cli.ps1 b/public/scripts/install_cli.ps1 index 8ce56f26..77c2a7e1 100644 --- a/public/scripts/install_cli.ps1 +++ b/public/scripts/install_cli.ps1 @@ -21,6 +21,7 @@ $BIN_DIR = "$env:USERPROFILE\.aptoscli\bin" $FORCE = $false $ACCEPT_ALL = $false $VERSION = "" +$UNDO = $false # Print colored message function Write-ColorMessage { @@ -68,6 +69,36 @@ function Test-MajorVersionUpgrade { } } +# Backup the current CLI binary before overwriting (keeps only one backup) +function Backup-CurrentBinary { + $cliPath = Join-Path $BIN_DIR $SCRIPT + if (Test-Path $cliPath) { + $backupPath = "$cliPath.backup" + Write-ColorMessage -Color $CYAN -Message "Backing up current CLI binary to $backupPath..." + Copy-Item -Path $cliPath -Destination $backupPath -Force + Write-ColorMessage -Color $GREEN -Message "Backup complete. Use --undo to restore the previous version." + } +} + +# Restore the previously backed up CLI binary +function Undo-Upgrade { + $cliPath = Join-Path $BIN_DIR $SCRIPT + $backupPath = "$cliPath.backup" + if (-not (Test-Path $backupPath)) { + Die "No backup found at $backupPath. Nothing to undo." + } + + Write-ColorMessage -Color $CYAN -Message "Restoring previous CLI version..." + Move-Item -Path $backupPath -Destination $cliPath -Force + + $restoredVersion = (& $cliPath --version | Select-String -Pattern '\d+\.\d+\.\d+').Matches.Value + if ($restoredVersion) { + Write-ColorMessage -Color $GREEN -Message "Successfully restored Aptos CLI version $restoredVersion." + } else { + Write-ColorMessage -Color $GREEN -Message "Previous CLI version restored." + } +} + # Get the latest version from GitHub API function Get-LatestVersion { try { @@ -118,6 +149,9 @@ function Install-CLI { curl.exe -L $url -o $zipPath } + # Backup current binary before overwriting + Backup-CurrentBinary + # Extract the zip file Expand-Archive -Path $zipPath -DestinationPath $BIN_DIR -Force @@ -158,11 +192,18 @@ function Main { Die "No version specified for --cli-version" } } + '--undo' { $UNDO = $true } default { Die "Unknown option: $($args[$i])" } } } + + # Handle undo (no network needed) + if ($UNDO) { + Undo-Upgrade + return + } # Get version if not specified if (-not $VERSION) { diff --git a/public/scripts/install_cli.py b/public/scripts/install_cli.py index 24a267bc..edb47885 100644 --- a/public/scripts/install_cli.py +++ b/public/scripts/install_cli.py @@ -248,12 +248,14 @@ def __init__( accept_all: bool = False, bin_dir: Optional[str] = None, from_source: bool = False, + undo: bool = False, ) -> None: self._version = version self._force = force self._accept_all = accept_all self._bin_dir = Path(bin_dir).expanduser() if bin_dir else None self._from_source = from_source + self._undo = undo self._release_info = None self._latest_release_info = None @@ -268,6 +270,10 @@ def bin_dir(self) -> Path: def bin_path(self): return self.bin_dir.joinpath(SCRIPT) + @property + def backup_path(self): + return self.bin_dir.joinpath(SCRIPT + ".backup") + @property def release_info(self): if not self._release_info: @@ -283,6 +289,10 @@ def latest_release_info(self): raise RuntimeError("Failed to find latest CLI release") def run(self) -> int: + # Handle undo (no network needed) + if self._undo: + return self._undo_upgrade() + # Handle installation from source if self._from_source: return self.run_from_source() @@ -322,6 +332,10 @@ def install(self, version, target): self._install_comment(version, "Downloading...") self.bin_dir.mkdir(parents=True, exist_ok=True) + + # Backup current binary before overwriting + self._backup_current_binary() + if self.bin_path.exists(): self.bin_path.unlink() @@ -441,6 +455,47 @@ def display_post_message_unix(self, version: str) -> None: ) ) + def _backup_current_binary(self) -> None: + """Backup the current CLI binary before overwriting (keeps only one backup).""" + if self.bin_path.exists(): + self._write( + colorize("info", f"Backing up current CLI binary to {self.backup_path}...") + ) + shutil.copy2(self.bin_path, self.backup_path) + self._write( + colorize("success", "Backup complete. Use --undo to restore the previous version.") + ) + + def _undo_upgrade(self) -> int: + """Restore the previously backed up CLI binary.""" + if not self.backup_path.exists(): + self._write( + colorize("error", f"No backup found at {self.backup_path}. Nothing to undo.") + ) + return 1 + + self._write(colorize("info", "Restoring previous CLI version...")) + + # Replace current binary with backup + if self.bin_path.exists(): + self.bin_path.unlink() + shutil.move(str(self.backup_path), str(self.bin_path)) + os.chmod(self.bin_path, 0o755) + + try: + out = subprocess.check_output( + [self.bin_path, "--version"], + universal_newlines=True, + ) + restored_version = out.split(" ")[-1].rstrip().lstrip() + self._write( + colorize("success", f"Successfully restored Aptos CLI version {restored_version}.") + ) + except Exception: + self._write(colorize("success", "Previous CLI version restored.")) + + return 0 + def _warn_major_upgrade(self, current_version: str, new_version: str) -> None: """Warn the user if this is a major version upgrade.""" if not current_version or not new_version: @@ -862,6 +917,9 @@ def install_from_source(self, version: Optional[str] = None) -> int: # Move the binary to the bin directory dest_binary = self.bin_path try: + # Backup current binary before overwriting + self._backup_current_binary() + if dest_binary.exists(): dest_binary.unlink() shutil.copy2(source_binary, dest_binary) @@ -960,6 +1018,12 @@ def main(): action="store_true", default=False, ) + parser.add_argument( + "--undo", + help="Restore the previously installed CLI version from backup", + action="store_true", + default=False, + ) args = parser.parse_args() @@ -969,6 +1033,7 @@ def main(): bin_dir=args.bin_dir, version=args.cli_version, from_source=args.from_source, + undo=args.undo, ) try: diff --git a/public/scripts/install_cli.sh b/public/scripts/install_cli.sh index e02eab98..3c3ac2ce 100755 --- a/public/scripts/install_cli.sh +++ b/public/scripts/install_cli.sh @@ -23,6 +23,7 @@ ACCEPT_ALL=false VERSION="" GENERIC_LINUX=false FROM_SOURCE=false +UNDO=false UNIVERSAL_INSTALLER_URL="https://raw.githubusercontent.com/gregnazario/universal-installer/main/scripts/install_pkg.sh" APTOS_REPO_URL="https://github.com/aptos-labs/aptos-core.git" @@ -127,6 +128,35 @@ warn_major_upgrade() { fi } +# Backup the current CLI binary before overwriting (keeps only one backup) +backup_current_binary() { + if [ -x "$BIN_DIR/$SCRIPT" ]; then + backup_path="$BIN_DIR/$SCRIPT.backup" + print_message "$CYAN" "Backing up current CLI binary to $backup_path..." + cp "$BIN_DIR/$SCRIPT" "$backup_path" || die "Failed to backup current binary" + print_message "$GREEN" "Backup complete. Use --undo to restore the previous version." + fi +} + +# Restore the previously backed up CLI binary +undo_upgrade() { + backup_path="$BIN_DIR/$SCRIPT.backup" + if [ ! -f "$backup_path" ]; then + die "No backup found at $backup_path. Nothing to undo." + fi + + print_message "$CYAN" "Restoring previous CLI version..." + mv "$backup_path" "$BIN_DIR/$SCRIPT" || die "Failed to restore backup" + chmod +x "$BIN_DIR/$SCRIPT" + + if "$BIN_DIR/$SCRIPT" --version >/dev/null 2>&1; then + restored_version=$("$BIN_DIR/$SCRIPT" --version 2>/dev/null | awk '{print $NF}') + print_message "$GREEN" "Successfully restored Aptos CLI version $restored_version." + else + print_message "$GREEN" "Previous CLI version restored." + fi +} + # Sort version tags - with fallback for systems without GNU sort -V sort_version_tags() { # Try GNU sort -V first, fall back to basic sort if not available @@ -211,6 +241,7 @@ install_from_source() { # Move the binary to the bin directory if [ -f "target/release/aptos" ]; then + backup_current_binary mv "target/release/aptos" "$BIN_DIR/" || die "Failed to move binary to $BIN_DIR" chmod +x "$BIN_DIR/aptos" print_message "$GREEN" "Aptos CLI version $version installed successfully from source!" @@ -325,6 +356,9 @@ install_cli() { die "unzip is not installed. Please install it." fi + # Backup current binary before overwriting + backup_current_binary + # Move the binary to the bin directory mv "$tmp_dir/aptos" "$BIN_DIR/" chmod +x "$BIN_DIR/aptos" @@ -334,9 +368,6 @@ install_cli() { # Main installation process main() { - # Install required packages first - install_required_packages - # Parse command line arguments while [ $# -gt 0 ]; do case "$1" in @@ -364,11 +395,24 @@ main() { FROM_SOURCE=true shift ;; + --undo) + UNDO=true + shift + ;; *) die "Unknown option: $1" ;; esac done + + # Handle undo (no packages or network needed) + if [ "$UNDO" = true ]; then + undo_upgrade + exit 0 + fi + + # Install required packages + install_required_packages # Handle installation from source if [ "$FROM_SOURCE" = true ]; then From 94e817b85020d739c6437c931ef96d0683c23fcd Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Feb 2026 14:06:03 +0000 Subject: [PATCH 3/3] docs: add --help documentation to CLI install scripts Added comprehensive --help output to all three install scripts: Shell (install_cli.sh): - New show_usage() function with full options reference - -h/--help flag in argument parser - Improved error message for unknown options to mention --help PowerShell (install_cli.ps1): - New Show-Usage function with full options reference - -h/--help handling in argument parser - Improved error message for unknown options to mention --help Python (install_cli.py): - Enhanced argparse description and added epilog section - RawDescriptionHelpFormatter for clean formatting of epilog - Improved all individual help strings for clarity and consistency All three help outputs document: - Every available flag with descriptions - The automatic backup behavior during upgrades - The major version upgrade warning and CHANGELOG link - The --undo rollback mechanism - Usage examples --- public/scripts/install_cli.ps1 | 46 +++++++++++++++++++++++++++++++++- public/scripts/install_cli.py | 32 +++++++++++++++++------ public/scripts/install_cli.sh | 45 ++++++++++++++++++++++++++++++++- 3 files changed, 114 insertions(+), 9 deletions(-) diff --git a/public/scripts/install_cli.ps1 b/public/scripts/install_cli.ps1 index 77c2a7e1..0476c3b0 100644 --- a/public/scripts/install_cli.ps1 +++ b/public/scripts/install_cli.ps1 @@ -23,6 +23,42 @@ $ACCEPT_ALL = $false $VERSION = "" $UNDO = $false +# Show usage information +function Show-Usage { + Write-Host @" +Usage: install_cli.ps1 [OPTIONS] + +Installs the latest version of the Aptos CLI on Windows. + +During upgrades, the installer automatically backs up the current binary +so you can roll back with --undo. If the upgrade crosses a major version +boundary (e.g. v1.x.x -> v2.x.x), a warning is displayed with a link to +the CHANGELOG for breaking changes: + https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos/CHANGELOG.md + +OPTIONS: + -f, --force Install even if the same version is already installed + -y, --yes Accept all prompts automatically + --bin-dir DIR Install the CLI binary to DIR + (default: %USERPROFILE%\.aptoscli\bin) + --cli-version VER Install a specific version instead of the latest + --undo Restore the previous CLI version from the backup + created during the last upgrade. Only one backup is + kept at a time. No network access is required. + -h, --help Show this help message and exit + +EXAMPLES: + # Install the latest version + .\install_cli.ps1 + + # Install a specific version + .\install_cli.ps1 --cli-version 3.5.0 + + # Roll back to the previously installed version + .\install_cli.ps1 --undo +"@ +} + # Print colored message function Write-ColorMessage { param( @@ -193,8 +229,16 @@ function Main { } } '--undo' { $UNDO = $true } + '-h' { + Show-Usage + return + } + '--help' { + Show-Usage + return + } default { - Die "Unknown option: $($args[$i])" + Die "Unknown option: $($args[$i]). Use --help for usage information." } } } diff --git a/public/scripts/install_cli.py b/public/scripts/install_cli.py index edb47885..bf471fe7 100644 --- a/public/scripts/install_cli.py +++ b/public/scripts/install_cli.py @@ -987,40 +987,58 @@ def main(): return 1 parser = argparse.ArgumentParser( - description="Installs the latest version of the Aptos CLI" + description="Installs the latest version of the Aptos CLI.", + epilog=( + "UPGRADE BEHAVIOR:\n" + " During upgrades, the installer automatically backs up the current\n" + " binary so you can roll back with --undo. If the upgrade crosses a\n" + " major version boundary (e.g. v1.x.x -> v2.x.x), a warning is\n" + " displayed with a link to the CHANGELOG for breaking changes:\n" + " https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos/CHANGELOG.md\n" + "\n" + "EXAMPLES:\n" + " python install_cli.py # Install latest version\n" + " python install_cli.py --cli-version 3.5.0 # Install specific version\n" + " python install_cli.py --undo # Roll back last upgrade\n" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument( "-f", "--force", - help="Forcibly install on top of existing version", + help="install even if the same version is already installed", action="store_true", default=False, ) parser.add_argument( "-y", "--yes", - help="Accept all prompts", + help="accept all prompts automatically", dest="accept_all", action="store_true", default=False, ) parser.add_argument( "--bin-dir", - help="If given, the CLI binary will be downloaded here instead", + help="install the CLI binary to this directory instead of the default", ) parser.add_argument( "--cli-version", - help="If given, the CLI version to install", + help="install a specific CLI version instead of the latest", ) parser.add_argument( "--from-source", - help="Build and install from source instead of downloading pre-built binary", + help="build and install from source instead of downloading a pre-built binary (requires git)", action="store_true", default=False, ) parser.add_argument( "--undo", - help="Restore the previously installed CLI version from backup", + help=( + "restore the previous CLI version from the backup created during " + "the last upgrade. Only one backup is kept at a time. " + "No network access is required" + ), action="store_true", default=False, ) diff --git a/public/scripts/install_cli.sh b/public/scripts/install_cli.sh index 3c3ac2ce..3d1353e6 100755 --- a/public/scripts/install_cli.sh +++ b/public/scripts/install_cli.sh @@ -27,6 +27,45 @@ UNDO=false UNIVERSAL_INSTALLER_URL="https://raw.githubusercontent.com/gregnazario/universal-installer/main/scripts/install_pkg.sh" APTOS_REPO_URL="https://github.com/aptos-labs/aptos-core.git" +# Show usage information +show_usage() { + cat < v2.x.x), a warning is displayed with a link to +the CHANGELOG for breaking changes: + https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos/CHANGELOG.md + +OPTIONS: + -f, --force Install even if the same version is already installed + -y, --yes Accept all prompts automatically + --bin-dir DIR Install the CLI binary to DIR (default: ~/.local/bin) + --cli-version VER Install a specific version instead of the latest + --generic-linux Use the generic Linux binary instead of a + distro-specific build + --from-source Build and install from source instead of downloading + a pre-built binary (requires git) + --undo Restore the previous CLI version from the backup + created during the last upgrade. Only one backup is + kept at a time. No network access is required. + -h, --help Show this help message and exit + +EXAMPLES: + # Install the latest version + curl -fsSL https://aptos.dev/scripts/install_cli.sh | sh + + # Install a specific version + curl -fsSL https://aptos.dev/scripts/install_cli.sh | sh -s -- --cli-version 3.5.0 + + # Roll back to the previously installed version + curl -fsSL https://aptos.dev/scripts/install_cli.sh | sh -s -- --undo +EOF +} + # Print colored message print_message() { color=$1 @@ -399,8 +438,12 @@ main() { UNDO=true shift ;; + -h|--help) + show_usage + exit 0 + ;; *) - die "Unknown option: $1" + die "Unknown option: $1. Use --help for usage information." ;; esac done