From 6de4d2637d00861c27a1a8116b8ae2767a6f99f5 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Tue, 9 Sep 2025 23:44:56 +0300 Subject: [PATCH 1/4] chore: windows scripts --- README.md | 43 ++++++++++++- install.bat | 124 +++++++++++++++++++++++++++++++++++ install.ps1 | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 install.bat create mode 100644 install.ps1 diff --git a/README.md b/README.md index e801a78..fbf7263 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ A command-line interface for interacting with JuliaHub, a platform for Julia com ## Installation -### Quick Install (Recommended) +### Quick Install + +#### Linux and macOS Install the latest release automatically: @@ -38,6 +40,45 @@ chmod +x install.sh curl -sSfL https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.sh | sh -s -- --install-dir /usr/local/bin ``` +#### Windows + +**Option 1: PowerShell (Recommended)** + +```powershell +# Download and run the PowerShell installer +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.ps1" -OutFile "install.ps1"; .\install.ps1; Remove-Item install.ps1 +``` + +Or download and run manually: +```powershell +# Download the installer +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.ps1" -OutFile "install.ps1" + +# Run the installer +.\install.ps1 + +# Clean up +Remove-Item install.ps1 +``` + +**Option 2: Command Prompt (CMD)** + +```cmd +curl -L "https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.bat" -o install.bat && install.bat && del install.bat +``` + +Or download and run manually: +```cmd +curl -L "https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.bat" -o install.bat +install.bat +del install.bat +``` + +**Windows Installation Notes:** +- PowerShell script supports custom install directory: `.\install.ps1 -InstallDir "C:\tools\bin"` +- Default install location: `%USERPROFILE%\.local\bin` +- CMD script requires curl (available in Windows 10 1803+ and Windows 11) + ### Download Binary Manually Download the latest release from the [GitHub releases page](https://github.com/JuliaComputing/gojuliahub/releases). diff --git a/install.bat b/install.bat new file mode 100644 index 0000000..4b26e92 --- /dev/null +++ b/install.bat @@ -0,0 +1,124 @@ +@echo off +setlocal enabledelayedexpansion + +:: JuliaHub CLI (jh) installer script for Windows +:: This script downloads and installs the latest release of jh from GitHub + +:: Configuration +set REPO_OWNER=JuliaComputing +set REPO_NAME=jh +set BINARY_NAME=jh +if "%INSTALL_DIR%"=="" set INSTALL_DIR=%USERPROFILE%\.local\bin + +:: Create install directory if it doesn't exist +if not exist "%INSTALL_DIR%" mkdir "%INSTALL_DIR%" + +echo JuliaHub CLI (%BINARY_NAME%) Installer for Windows +echo ================================================ + +:: Check if curl is available (Windows 10 1803+ has curl built-in) +curl --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo ERROR: curl is required but not found. Please install curl or use PowerShell. + echo You can install curl from: https://curl.se/download.html + echo Or use the PowerShell install script instead. + exit /b 1 +) + +echo INFO: Fetching latest release information... + +:: Get latest version from GitHub API +for /f "tokens=*" %%i in ('curl -s "https://api.github.com/repos/%REPO_OWNER%/%REPO_NAME%/releases/latest" ^| findstr "tag_name" ^| for /f "tokens=2 delims=:," %%j in ('findstr "tag_name"') do @echo %%~j') do set VERSION=%%i +set VERSION=%VERSION:"=% +set VERSION=%VERSION: =% + +if "%VERSION%"=="" ( + echo ERROR: Failed to get latest version information + exit /b 1 +) + +echo INFO: Latest version: %VERSION% + +:: Detect architecture +set ARCH=amd64 +if "%PROCESSOR_ARCHITECTURE%"=="ARM64" set ARCH=arm64 + +:: Construct download URL and filenames +set BINARY_FILE=%BINARY_NAME%-windows-%ARCH%.exe +set DOWNLOAD_URL=https://github.com/%REPO_OWNER%/%REPO_NAME%/releases/download/%VERSION%/%BINARY_FILE% +set TEMP_FILE=%INSTALL_DIR%\%BINARY_FILE%.tmp +set FINAL_FILE=%INSTALL_DIR%\%BINARY_NAME%.exe + +echo INFO: Downloading %BINARY_NAME% %VERSION% for windows-%ARCH%... +echo INFO: Download URL: %DOWNLOAD_URL% + +:: Check if binary already exists +if exist "%FINAL_FILE%" ( + echo INFO: Checking current installation... + for /f "tokens=*" %%i in ('"%FINAL_FILE%" --version 2^>nul') do set CURRENT_VERSION=%%i + if not "%CURRENT_VERSION%"=="" ( + echo INFO: Current installation: !CURRENT_VERSION! + echo !CURRENT_VERSION! | findstr "%VERSION%" >nul + if !errorlevel! equ 0 ( + echo INFO: Latest version is already installed + exit /b 0 + ) + ) + echo WARNING: Existing installation found. It will be replaced. +) + +:: Download binary +curl -L -o "%TEMP_FILE%" "%DOWNLOAD_URL%" +if %errorlevel% neq 0 ( + echo ERROR: Failed to download binary from %DOWNLOAD_URL% + if exist "%TEMP_FILE%" del "%TEMP_FILE%" + exit /b 1 +) + +:: Verify download was successful +if not exist "%TEMP_FILE%" ( + echo ERROR: Downloaded file not found + exit /b 1 +) + +:: Move to final location +move "%TEMP_FILE%" "%FINAL_FILE%" >nul +if %errorlevel% neq 0 ( + echo ERROR: Failed to install binary to %FINAL_FILE% + exit /b 1 +) + +echo SUCCESS: Installed %BINARY_NAME% to %FINAL_FILE% + +:: Check if install directory is in PATH +echo %PATH% | findstr /C:"%INSTALL_DIR%" >nul +if %errorlevel% neq 0 ( + echo WARNING: %INSTALL_DIR% is not in your PATH. + echo To add it permanently, run: + echo setx PATH "%%PATH%%;%INSTALL_DIR%" + echo Or add it to your current session: + echo set PATH=%%PATH%%;%INSTALL_DIR% + echo. +) + +:: Verify installation +if exist "%FINAL_FILE%" ( + echo INFO: Verifying installation... + for /f "tokens=*" %%i in ('"%FINAL_FILE%" --version 2^>nul') do set VERSION_OUTPUT=%%i + if not "!VERSION_OUTPUT!"=="" ( + echo SUCCESS: Installation verified: !VERSION_OUTPUT! + echo INFO: Run '%BINARY_NAME% --help' to get started + ) else ( + echo WARNING: Binary installed but version check failed + ) +) else ( + echo ERROR: Installation failed: binary not found + exit /b 1 +) + +echo. +echo SUCCESS: Installation complete! +echo INFO: You can now use '%BINARY_NAME%' to interact with JuliaHub +echo INFO: Start with: %BINARY_NAME% auth login + +endlocal diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..82df339 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,181 @@ +#!/usr/bin/env pwsh + +# JuliaHub CLI (jh) installer script for Windows PowerShell +# This script downloads and installs the latest release of jh from GitHub + +param( + [string]$InstallDir = "$env:USERPROFILE\.local\bin", + [switch]$Help +) + +# Configuration +$RepoOwner = "JuliaComputing" +$RepoName = "jh" +$BinaryName = "jh" + +# Colors for output +$Colors = @{ + Red = "Red" + Green = "Green" + Yellow = "Yellow" + Blue = "Cyan" +} + +# Logging functions +function Write-Info { + param([string]$Message) + Write-Host "INFO: $Message" -ForegroundColor $Colors.Blue +} + +function Write-Success { + param([string]$Message) + Write-Host "SUCCESS: $Message" -ForegroundColor $Colors.Green +} + +function Write-Warning { + param([string]$Message) + Write-Host "WARNING: $Message" -ForegroundColor $Colors.Yellow +} + +function Write-Error { + param([string]$Message) + Write-Host "ERROR: $Message" -ForegroundColor $Colors.Red + exit 1 +} + +# Show help +if ($Help) { + Write-Host "JuliaHub CLI Installer for PowerShell" + Write-Host "" + Write-Host "Usage: .\install.ps1 [options]" + Write-Host "" + Write-Host "Parameters:" + Write-Host " -InstallDir DIR Install directory (default: `$env:USERPROFILE\.local\bin)" + Write-Host " -Help Show this help message" + Write-Host "" + Write-Host "Environment variables:" + Write-Host " INSTALL_DIR Same as -InstallDir" + exit 0 +} + +# Override with environment variable if set +if ($env:INSTALL_DIR) { + $InstallDir = $env:INSTALL_DIR +} + +Write-Host "JuliaHub CLI ($BinaryName) Installer for PowerShell" +Write-Host "==================================================" + +# Detect architecture +$Arch = "amd64" +if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { + $Arch = "arm64" +} + +Write-Info "Detected platform: windows-$Arch" + +# Get latest version from GitHub API +Write-Info "Fetching latest release information..." +try { + $ApiUrl = "https://api.github.com/repos/$RepoOwner/$RepoName/releases/latest" + $Response = Invoke-RestMethod -Uri $ApiUrl -Method Get + $Version = $Response.tag_name + + if (-not $Version) { + Write-Error "Failed to get latest version information" + } + + Write-Info "Latest version: $Version" +} +catch { + Write-Error "Failed to fetch release information: $($_.Exception.Message)" +} + +# Construct download URL and filenames +$BinaryFile = "$BinaryName-windows-$Arch.exe" +$DownloadUrl = "https://github.com/$RepoOwner/$RepoName/releases/download/$Version/$BinaryFile" +$TempFile = Join-Path $InstallDir "$BinaryFile.tmp" +$FinalFile = Join-Path $InstallDir "$BinaryName.exe" + +# Create install directory if it doesn't exist +if (-not (Test-Path $InstallDir)) { + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null +} + +# Check if binary already exists +if (Test-Path $FinalFile) { + Write-Info "Checking current installation..." + try { + $CurrentVersion = & $FinalFile --version 2>$null + if ($CurrentVersion -and $CurrentVersion -match $Version) { + Write-Info "Current installation: $CurrentVersion" + Write-Info "Latest version is already installed" + exit 0 + } + if ($CurrentVersion) { + Write-Info "Current installation: $CurrentVersion" + } + } + catch { + # Ignore version check errors + } + Write-Warning "Existing installation found. It will be replaced." +} + +Write-Info "Downloading $BinaryName $Version for windows-$Arch..." +Write-Info "Download URL: $DownloadUrl" + +# Download binary +try { + Invoke-WebRequest -Uri $DownloadUrl -OutFile $TempFile -UseBasicParsing + + # Verify download was successful + if (-not (Test-Path $TempFile) -or (Get-Item $TempFile).Length -eq 0) { + Remove-Item $TempFile -ErrorAction SilentlyContinue + Write-Error "Failed to download binary from $DownloadUrl" + } + + # Move to final location + Move-Item $TempFile $FinalFile -Force + + Write-Success "Installed $BinaryName to $FinalFile" +} +catch { + Remove-Item $TempFile -ErrorAction SilentlyContinue + Write-Error "Failed to download or install binary: $($_.Exception.Message)" +} + +# Check if install directory is in PATH +$PathDirs = $env:PATH -split ";" +if ($InstallDir -notin $PathDirs) { + Write-Warning "$InstallDir is not in your PATH." + Write-Host "To add it permanently for current user, run:" + Write-Host " `$env:PATH += ';$InstallDir'; [Environment]::SetEnvironmentVariable('PATH', `$env:PATH, 'User')" -ForegroundColor Yellow + Write-Host "Or add it to your current session:" + Write-Host " `$env:PATH += ';$InstallDir'" -ForegroundColor Yellow + Write-Host "" +} + +# Verify installation +if (Test-Path $FinalFile) { + Write-Info "Verifying installation..." + try { + $VersionOutput = & $FinalFile --version 2>$null + if ($VersionOutput) { + Write-Success "Installation verified: $VersionOutput" + Write-Info "Run '$BinaryName --help' to get started" + } else { + Write-Warning "Binary installed but version check failed" + } + } + catch { + Write-Warning "Binary installed but version check failed" + } +} else { + Write-Error "Installation failed: binary not found" +} + +Write-Host "" +Write-Success "Installation complete!" +Write-Info "You can now use '$BinaryName' to interact with JuliaHub" +Write-Info "Start with: $BinaryName auth login" From f0d268bd18c7821ec7b3e8bf42b98a072f35a8d8 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Tue, 9 Sep 2025 23:51:10 +0300 Subject: [PATCH 2/4] fix install --- README.md | 23 +++++++++++++---------- install.ps1 | 44 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index fbf7263..c30e806 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,13 @@ A command-line interface for interacting with JuliaHub, a platform for Julia com Install the latest release automatically: ```bash -curl -sSfL https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.sh | sh +curl -sSfL https://raw.githubusercontent.com/JuliaComputing/jh/main/install.sh | sh ``` Or download and run the script manually: ```bash -wget https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.sh +wget https://raw.githubusercontent.com/JuliaComputing/jh/main/install.sh chmod +x install.sh ./install.sh ``` @@ -37,7 +37,7 @@ chmod +x install.sh **Custom installation directory example:** ```bash -curl -sSfL https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.sh | sh -s -- --install-dir /usr/local/bin +curl -sSfL https://raw.githubusercontent.com/JuliaComputing/jh/main/install.sh | sh -s -- --install-dir /usr/local/bin ``` #### Windows @@ -46,13 +46,13 @@ curl -sSfL https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/inst ```powershell # Download and run the PowerShell installer -Invoke-WebRequest -Uri "https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.ps1" -OutFile "install.ps1"; .\install.ps1; Remove-Item install.ps1 +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/JuliaComputing/jh/main/install.ps1" -OutFile "install.ps1"; .\install.ps1; Remove-Item install.ps1 ``` Or download and run manually: ```powershell # Download the installer -Invoke-WebRequest -Uri "https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.ps1" -OutFile "install.ps1" +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/JuliaComputing/jh/main/install.ps1" -OutFile "install.ps1" # Run the installer .\install.ps1 @@ -64,24 +64,27 @@ Remove-Item install.ps1 **Option 2: Command Prompt (CMD)** ```cmd -curl -L "https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.bat" -o install.bat && install.bat && del install.bat +curl -L "https://raw.githubusercontent.com/JuliaComputing/jh/main/install.bat" -o install.bat && install.bat && del install.bat ``` Or download and run manually: ```cmd -curl -L "https://raw.githubusercontent.com/JuliaComputing/gojuliahub/main/install.bat" -o install.bat +curl -L "https://raw.githubusercontent.com/JuliaComputing/jh/main/install.bat" -o install.bat install.bat del install.bat ``` **Windows Installation Notes:** - PowerShell script supports custom install directory: `.\install.ps1 -InstallDir "C:\tools\bin"` +- PowerShell script can automatically add to PATH: will prompt unless you use `-NoPrompt` +- For automated installs: `.\install.ps1 -NoPrompt` (won't add to PATH automatically) - Default install location: `%USERPROFILE%\.local\bin` - CMD script requires curl (available in Windows 10 1803+ and Windows 11) +- After installation, restart your terminal or run `refreshenv` to use `jh` command ### Download Binary Manually -Download the latest release from the [GitHub releases page](https://github.com/JuliaComputing/gojuliahub/releases). +Download the latest release from the [GitHub releases page](https://github.com/JuliaComputing/jh/releases). Available for: - Linux (amd64, arm64) @@ -91,8 +94,8 @@ Available for: ### Build from Source ```bash -git clone https://github.com/JuliaComputing/gojuliahub -cd gojuliahub +git clone https://github.com/JuliaComputing/jh +cd jh go build -o jh . ``` diff --git a/install.ps1 b/install.ps1 index 82df339..108e7b4 100644 --- a/install.ps1 +++ b/install.ps1 @@ -5,7 +5,8 @@ param( [string]$InstallDir = "$env:USERPROFILE\.local\bin", - [switch]$Help + [switch]$Help, + [switch]$NoPrompt ) # Configuration @@ -51,6 +52,7 @@ if ($Help) { Write-Host "" Write-Host "Parameters:" Write-Host " -InstallDir DIR Install directory (default: `$env:USERPROFILE\.local\bin)" + Write-Host " -NoPrompt Don't prompt to add to PATH (for automated installs)" Write-Host " -Help Show this help message" Write-Host "" Write-Host "Environment variables:" @@ -149,10 +151,42 @@ catch { $PathDirs = $env:PATH -split ";" if ($InstallDir -notin $PathDirs) { Write-Warning "$InstallDir is not in your PATH." - Write-Host "To add it permanently for current user, run:" - Write-Host " `$env:PATH += ';$InstallDir'; [Environment]::SetEnvironmentVariable('PATH', `$env:PATH, 'User')" -ForegroundColor Yellow - Write-Host "Or add it to your current session:" - Write-Host " `$env:PATH += ';$InstallDir'" -ForegroundColor Yellow + + # Ask user if they want to add it automatically (unless -NoPrompt is used) + $AddToPath = "n" + if (-not $NoPrompt) { + $AddToPath = Read-Host "Add $InstallDir to your PATH permanently? (y/N)" + } + + if ($AddToPath -match '^[Yy]') { + try { + # Add to current session + $env:PATH += ";$InstallDir" + + # Add permanently for current user + $UserPath = [Environment]::GetEnvironmentVariable('PATH', 'User') + if ($UserPath -and (-not $UserPath.Contains($InstallDir))) { + $NewUserPath = "$UserPath;$InstallDir" + [Environment]::SetEnvironmentVariable('PATH', $NewUserPath, 'User') + Write-Success "Added $InstallDir to your PATH permanently" + } elseif (-not $UserPath) { + [Environment]::SetEnvironmentVariable('PATH', $InstallDir, 'User') + Write-Success "Added $InstallDir to your PATH permanently" + } else { + Write-Info "$InstallDir already in permanent PATH" + } + } + catch { + Write-Warning "Failed to update PATH automatically: $($_.Exception.Message)" + Write-Host "To add it manually:" + Write-Host " For current session: `$env:PATH += ';$InstallDir'" -ForegroundColor Yellow + Write-Host " Permanently: [Environment]::SetEnvironmentVariable('PATH', `$env:PATH + ';$InstallDir', 'User')" -ForegroundColor Yellow + } + } else { + Write-Host "To add it manually:" + Write-Host " For current session: `$env:PATH += ';$InstallDir'" -ForegroundColor Yellow + Write-Host " Permanently: [Environment]::SetEnvironmentVariable('PATH', `$env:PATH + ';$InstallDir', 'User')" -ForegroundColor Yellow + } Write-Host "" } From 7e3957abc9742d52014a19288306cdde0a54a925 Mon Sep 17 00:00:00 2001 From: Panagiotis Georgakopoulos Date: Wed, 10 Sep 2025 00:17:21 +0300 Subject: [PATCH 3/4] feat: ./jh update --- README.md | 5 ++ install.sh | 4 +- main.go | 23 ++++++++- update.go | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 update.go diff --git a/README.md b/README.md index c30e806..0dd7a1d 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,11 @@ go build -o jh . - `jh user info` - Show detailed user information +### Update (`jh update`) + +- `jh update` - Check for updates and automatically install the latest version +- `jh update --force` - Force update even if current version is newer than latest release + ## Configuration Configuration is stored in `~/.juliahub` with 0600 permissions. The file contains: diff --git a/install.sh b/install.sh index 2873233..afd775e 100755 --- a/install.sh +++ b/install.sh @@ -7,7 +7,7 @@ set -e # Configuration REPO_OWNER="JuliaComputing" -REPO_NAME="gojuliahub" +REPO_NAME="jh" BINARY_NAME="jh" INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" @@ -206,7 +206,7 @@ main() { } # Parse command line arguments -while [[ $# -gt 0 ]]; do +while [ $# -gt 0 ]; do case $1 in --install-dir) INSTALL_DIR="$2" diff --git a/main.go b/main.go index eaf58e5..48ddecb 100644 --- a/main.go +++ b/main.go @@ -912,6 +912,26 @@ without needing to use the 'jh' wrapper commands.`, }, } +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update jh to the latest version", + Long: `Check for updates and automatically download and install the latest version of jh. + +This command fetches the latest release information from GitHub and compares +it with the current version. If an update is available, it downloads and runs +the appropriate install script for your platform. + +The update process will replace the current installation with the latest version.`, + Example: " jh update\n jh update --force", + Run: func(cmd *cobra.Command, args []string) { + force, _ := cmd.Flags().GetBool("force") + if err := runUpdate(force); err != nil { + fmt.Printf("Update failed: %v\n", err) + os.Exit(1) + } + }, +} + func init() { authLoginCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server") jobListCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server") @@ -928,6 +948,7 @@ func init() { pushCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server") fetchCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server") pullCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server") + updateCmd.Flags().Bool("force", false, "Force update even if current version is newer than latest release") authCmd.AddCommand(authLoginCmd, authRefreshCmd, authStatusCmd, authEnvCmd) jobCmd.AddCommand(jobListCmd, jobStartCmd) @@ -937,7 +958,7 @@ func init() { juliaCmd.AddCommand(juliaInstallCmd) gitCredentialCmd.AddCommand(gitCredentialHelperCmd, gitCredentialGetCmd, gitCredentialStoreCmd, gitCredentialEraseCmd, gitCredentialSetupCmd) - rootCmd.AddCommand(authCmd, jobCmd, datasetCmd, projectCmd, userCmd, juliaCmd, cloneCmd, pushCmd, fetchCmd, pullCmd, runCmd, gitCredentialCmd) + rootCmd.AddCommand(authCmd, jobCmd, datasetCmd, projectCmd, userCmd, juliaCmd, cloneCmd, pushCmd, fetchCmd, pullCmd, runCmd, gitCredentialCmd, updateCmd) } func main() { diff --git a/update.go b/update.go new file mode 100644 index 0000000..aee372a --- /dev/null +++ b/update.go @@ -0,0 +1,143 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "os/exec" + "runtime" + "strings" +) + +// GitHubRelease represents a GitHub release response +type GitHubRelease struct { + TagName string `json:"tag_name"` + Name string `json:"name"` + Body string `json:"body"` +} + +// getLatestRelease fetches the latest release info from GitHub +func getLatestRelease() (*GitHubRelease, error) { + url := "https://api.github.com/repos/JuliaComputing/jh/releases/latest" + + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("failed to fetch release info: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("GitHub API returned status %d", resp.StatusCode) + } + + var release GitHubRelease + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return nil, fmt.Errorf("failed to parse release info: %w", err) + } + + return &release, nil +} + +// compareVersions compares two version strings (e.g., "v1.2.3") +// Returns: -1 if current < latest, 0 if equal, 1 if current > latest +func compareVersions(current, latest string) int { + // Remove 'v' prefix if present + current = strings.TrimPrefix(current, "v") + latest = strings.TrimPrefix(latest, "v") + + // Handle "dev" version + if current == "dev" { + return -1 // Always consider dev as older + } + + // Simple string comparison for now (works for semantic versions) + if current == latest { + return 0 + } else if current < latest { + return -1 + } + return 1 +} + +// getInstallScript returns the appropriate install script URL and command for the current platform +func getInstallScript() (string, []string, error) { + switch runtime.GOOS { + case "windows": + // Prefer PowerShell on Windows + powershellCmd, err := exec.LookPath("powershell") + if err == nil { + return "https://raw.githubusercontent.com/JuliaComputing/jh/main/install.ps1", + []string{powershellCmd, "-ExecutionPolicy", "Bypass", "-Command", + "Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/JuliaComputing/jh/main/install.ps1' -OutFile 'install.ps1'; ./install.ps1 -NoPrompt; Remove-Item install.ps1"}, nil + } + // Fallback to cmd + return "https://raw.githubusercontent.com/JuliaComputing/jh/main/install.bat", + []string{"cmd", "/c", "curl -L https://raw.githubusercontent.com/JuliaComputing/jh/main/install.bat -o install.bat && install.bat && del install.bat"}, nil + case "darwin", "linux": + // Prefer bash if available, fallback to sh + shell := "bash" + if _, err := exec.LookPath("bash"); err != nil { + shell = "sh" + } + return "https://raw.githubusercontent.com/JuliaComputing/jh/main/install.sh", + []string{shell, "-c", fmt.Sprintf("curl -sSfL https://raw.githubusercontent.com/JuliaComputing/jh/main/install.sh -o /tmp/jh_install.sh && %s /tmp/jh_install.sh && rm -f /tmp/jh_install.sh", shell)}, nil + default: + return "", nil, fmt.Errorf("unsupported platform: %s", runtime.GOOS) + } +} + +// runUpdate performs the actual update by executing the install script +func runUpdate(force bool) error { + // Check current version vs latest + fmt.Printf("Current version: %s\n", version) + + latest, err := getLatestRelease() + if err != nil { + return fmt.Errorf("failed to check for updates: %w", err) + } + + fmt.Printf("Latest version: %s\n", latest.TagName) + + // Compare versions + comparison := compareVersions(version, latest.TagName) + + if comparison == 0 && !force { + fmt.Println("You are already running the latest version!") + return nil + } else if comparison > 0 && !force { + fmt.Printf("Your version (%s) is newer than the latest release (%s)\n", version, latest.TagName) + fmt.Println("Use --force to downgrade to the latest release") + return nil + } + + if comparison < 0 { + fmt.Printf("Update available: %s -> %s\n", version, latest.TagName) + } else if force { + fmt.Printf("Force updating: %s -> %s\n", version, latest.TagName) + } + + // Get install script for current platform + scriptURL, command, err := getInstallScript() + if err != nil { + return fmt.Errorf("failed to determine install script: %w", err) + } + + fmt.Printf("Downloading and running install script from: %s\n", scriptURL) + fmt.Println("This will replace the current installation...") + + // Execute the install command + cmd := exec.Command(command[0], command[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + if err := cmd.Run(); err != nil { + return fmt.Errorf("update failed: %w", err) + } + + fmt.Println("\nUpdate completed successfully!") + fmt.Println("You may need to restart your terminal for the changes to take effect.") + + return nil +} From 265016c77a20919755cfb3612797aec7db50dbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=A0=CE=B1=CE=BD=CE=B1=CE=B3=CE=B9=CF=8E=CF=84=CE=B7?= =?UTF-8?q?=CF=82=20=CE=93=CE=B5=CF=89=CF=81=CE=B3=CE=B1=CE=BA=CF=8C=CF=80?= =?UTF-8?q?=CE=BF=CF=85=CE=BB=CE=BF=CF=82?= Date: Mon, 15 Sep 2025 10:14:30 +0300 Subject: [PATCH 4/4] chore: apply suggestions from code review Co-authored-by: Morten Piibeleht --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9213e93..f9d638f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ A command-line interface for interacting with JuliaHub, a platform for Julia com Install the latest release automatically: ```bash - curl -sSfL https://raw.githubusercontent.com/JuliaComputing/jh/main/install.sh | sh +curl -sSfL https://raw.githubusercontent.com/JuliaComputing/jh/main/install.sh | sh ``` Or download and run the script manually: @@ -37,8 +37,7 @@ chmod +x install.sh **Custom installation directory example:** ```bash - -curl -sSfL https://raw.githubusercontent.com/JuliaComputing/jh/main/install.sh | bassh -s -- --install-dir /usr/local/bin +curl -sSfL https://raw.githubusercontent.com/JuliaComputing/jh/main/install.sh | sh -s -- --install-dir /usr/local/bin ``` #### Windows