436 lines
14 KiB
Plaintext
436 lines
14 KiB
Plaintext
![]() |
#!/usr/bin/env nu
|
||
|
|
||
|
# Nushell Auto-Updater - Nushell version
|
||
|
# Downloads latest nushell release from GitHub, extracts, and manages symlinks
|
||
|
# Usage: nu update_nushell.nu [command]
|
||
|
|
||
|
# Configuration
|
||
|
let GITHUB_API_URL = "https://api.github.com/repos/nushell/nushell/releases/latest"
|
||
|
let GITHUB_RELEASES_URL = "https://github.com/nushell/nushell/releases/download"
|
||
|
let NUSHELL_SYMLINK = "nushell"
|
||
|
|
||
|
# Function to show usage
|
||
|
def show_usage [] {
|
||
|
print $"(ansi blue)Nushell Auto-Updater(ansi reset)"
|
||
|
print ""
|
||
|
print "Usage: nu update_nushell.nu [command]"
|
||
|
print ""
|
||
|
print "Commands:"
|
||
|
print $" (ansi green)check(ansi reset) Check for updates without installing"
|
||
|
print $" (ansi green)update(ansi reset) Download and install latest version"
|
||
|
print $" (ansi green)status(ansi reset) Show current installation status"
|
||
|
print $" (ansi green)help, -h(ansi reset) Show this help message"
|
||
|
print ""
|
||
|
print "If no command is specified, 'update' is assumed."
|
||
|
print ""
|
||
|
}
|
||
|
|
||
|
# Function to get latest version from GitHub API
|
||
|
def get_latest_version [] {
|
||
|
try {
|
||
|
http get $GITHUB_API_URL | get tag_name
|
||
|
} catch {
|
||
|
null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Function to check if nushell directory is a git repository
|
||
|
def is_git_repository [nushell_dir: string] {
|
||
|
($nushell_dir | path join ".git" "config" | path exists)
|
||
|
}
|
||
|
|
||
|
# Function to get git repository status with origin information
|
||
|
def get_git_status [nushell_dir: string] {
|
||
|
if not (is_git_repository $nushell_dir) {
|
||
|
return null
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
cd $nushell_dir
|
||
|
let current_commit = (^git rev-parse --short HEAD | str trim)
|
||
|
let current_branch = (^git branch --show-current | str trim)
|
||
|
let remote_status = (^git status --porcelain=v1 | lines | length)
|
||
|
let origin_url = (^git remote get-url origin | str trim)
|
||
|
cd ..
|
||
|
|
||
|
{
|
||
|
branch: $current_branch,
|
||
|
commit: $current_commit,
|
||
|
changes: $remote_status,
|
||
|
origin: $origin_url
|
||
|
}
|
||
|
} catch {
|
||
|
null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Function to get currently installed version
|
||
|
def get_current_version [] {
|
||
|
try {
|
||
|
if ($NUSHELL_SYMLINK | path exists) and (($NUSHELL_SYMLINK | path type) == "symlink") {
|
||
|
let target = $NUSHELL_SYMLINK | path expand | path basename
|
||
|
# Check if target is a git repository
|
||
|
if (is_git_repository $target) {
|
||
|
"git-repo"
|
||
|
} else {
|
||
|
let version_match = ($target | parse "nushell-{version}")
|
||
|
if ($version_match | length) > 0 {
|
||
|
$version_match.0.version
|
||
|
} else {
|
||
|
null
|
||
|
}
|
||
|
}
|
||
|
} else if ($NUSHELL_SYMLINK | path exists) and (($NUSHELL_SYMLINK | path type) == "dir") and (is_git_repository $NUSHELL_SYMLINK) {
|
||
|
# Handle case where nushell is a directory (git repository)
|
||
|
"git-repo"
|
||
|
} else {
|
||
|
null
|
||
|
}
|
||
|
} catch {
|
||
|
null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Function to get current directory name
|
||
|
def get_current_directory [] {
|
||
|
try {
|
||
|
if ($NUSHELL_SYMLINK | path exists) and (($NUSHELL_SYMLINK | path type) == "symlink") {
|
||
|
$NUSHELL_SYMLINK | path expand | path basename
|
||
|
} else if ($NUSHELL_SYMLINK | path exists) and (($NUSHELL_SYMLINK | path type) == "dir") {
|
||
|
# Return the directory name itself if it's a directory
|
||
|
$NUSHELL_SYMLINK
|
||
|
} else {
|
||
|
null
|
||
|
}
|
||
|
} catch {
|
||
|
null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Function to check installation status
|
||
|
def check_status [] {
|
||
|
print $"(ansi blue)Nushell Installation Status(ansi reset)"
|
||
|
print ""
|
||
|
|
||
|
let current_version = get_current_version
|
||
|
let current_dir = get_current_directory
|
||
|
|
||
|
# Handle git repository case
|
||
|
if $current_version == "git-repo" {
|
||
|
print $"(ansi cyan)Git Repository Detected(ansi reset)"
|
||
|
print $"Currently installed: (ansi cyan)Git repository(ansi reset)"
|
||
|
|
||
|
let git_status = get_git_status $current_dir
|
||
|
if ($git_status | is-not-empty) {
|
||
|
print $"Repository: (ansi cyan)($current_dir)(ansi reset)"
|
||
|
print $"Origin: (ansi cyan)($git_status.origin)(ansi reset)"
|
||
|
print $"Branch: (ansi cyan)($git_status.branch)(ansi reset)"
|
||
|
print $"Commit: (ansi cyan)($git_status.commit)(ansi reset)"
|
||
|
if $git_status.changes > 0 {
|
||
|
print $"Status: (ansi yellow)($git_status.changes) uncommitted changes(ansi reset)"
|
||
|
} else {
|
||
|
print $"Status: (ansi green)Clean working directory(ansi reset)"
|
||
|
}
|
||
|
}
|
||
|
print ""
|
||
|
print $"(ansi yellow)⚠️ Git Repository Management(ansi reset)"
|
||
|
print "This nushell installation is a git repository."
|
||
|
print $"Use (ansi cyan)git pull(ansi reset) to update instead of this script."
|
||
|
print $"To switch to release versions, remove the current directory and use (ansi cyan)nu update_nushell.nu update(ansi reset)"
|
||
|
return 3
|
||
|
}
|
||
|
|
||
|
let latest_version = get_latest_version
|
||
|
|
||
|
if ($latest_version | is-empty) {
|
||
|
print $"(ansi red)Error: Could not fetch latest version from GitHub(ansi reset)"
|
||
|
print "Please check your internet connection and try again."
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
print $"Latest available version: (ansi green)($latest_version)(ansi reset)"
|
||
|
|
||
|
if ($current_version | is-empty) {
|
||
|
print $"Currently installed: (ansi yellow)None(ansi reset)"
|
||
|
print $"Status: (ansi yellow)Fresh installation needed(ansi reset)"
|
||
|
return 2
|
||
|
} else {
|
||
|
print $"Currently installed: (ansi cyan)($current_version)(ansi reset)"
|
||
|
|
||
|
if $current_version == $latest_version {
|
||
|
print $"Status: (ansi green)Up to date(ansi reset)"
|
||
|
return 0
|
||
|
} else {
|
||
|
print $"Status: (ansi yellow)Update available(ansi reset)"
|
||
|
return 2
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Function to download file
|
||
|
def download_file [url: string, filename: string] {
|
||
|
try {
|
||
|
print $"(ansi blue)Downloading from: ($url)(ansi reset)"
|
||
|
http get $url | save $filename
|
||
|
true
|
||
|
} catch { |e|
|
||
|
print $"(ansi red)Error downloading file: ($e.msg)(ansi reset)"
|
||
|
false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Function to download and extract nushell
|
||
|
def download_and_extract [version: string] {
|
||
|
let filename = $"nushell-($version)-source.tar.gz"
|
||
|
let url = $"($GITHUB_RELEASES_URL)/($version)/($filename)"
|
||
|
let target_dir = $"nushell-($version)"
|
||
|
|
||
|
print $"(ansi blue)Downloading nushell ($version)...(ansi reset)"
|
||
|
|
||
|
# Download the file
|
||
|
if not (download_file $url $filename) {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if not ($filename | path exists) {
|
||
|
print $"(ansi red)Error: Download failed - file not found(ansi reset)"
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
print $"(ansi blue)Extracting ($filename)...(ansi reset)"
|
||
|
|
||
|
# Extract the tar.gz file
|
||
|
try {
|
||
|
^tar -xzf $filename
|
||
|
} catch { |e|
|
||
|
print $"(ansi red)Error: Failed to extract ($filename): ($e.msg)(ansi reset)"
|
||
|
try { rm $filename } catch { }
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
# Check if extraction created the expected directory
|
||
|
if not ($target_dir | path exists) {
|
||
|
print $"(ansi red)Error: Expected directory ($target_dir) not found after extraction(ansi reset)"
|
||
|
try { rm $filename } catch { }
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
print $"(ansi green)Successfully downloaded and extracted nushell ($version)(ansi reset)"
|
||
|
true
|
||
|
}
|
||
|
|
||
|
# Function to clean up old installations
|
||
|
def cleanup_old_installation [old_dir: string, old_version: string] {
|
||
|
if not ($old_dir | is-empty) and ($old_dir | path exists) {
|
||
|
print $"(ansi blue)Removing old installation: ($old_dir)(ansi reset)"
|
||
|
try {
|
||
|
rm -rf $old_dir
|
||
|
} catch { |e|
|
||
|
print $"(ansi yellow)Warning: Could not remove old directory ($old_dir): ($e.msg)(ansi reset)"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Remove old tar.gz files
|
||
|
let old_tarball = $"nushell-($old_version)-source.tar.gz"
|
||
|
if ($old_tarball | path exists) {
|
||
|
print $"(ansi blue)Removing old tarball: ($old_tarball)(ansi reset)"
|
||
|
try {
|
||
|
rm $old_tarball
|
||
|
} catch { |e|
|
||
|
print $"(ansi yellow)Warning: Could not remove old tarball ($old_tarball): ($e.msg)(ansi reset)"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Function to create symlink
|
||
|
def create_symlink [target_dir: string] {
|
||
|
# Remove existing symlink
|
||
|
if ($NUSHELL_SYMLINK | path exists) {
|
||
|
if (($NUSHELL_SYMLINK | path type) == "symlink") {
|
||
|
try { rm $NUSHELL_SYMLINK } catch { }
|
||
|
} else {
|
||
|
print $"(ansi red)Error: ($NUSHELL_SYMLINK) exists but is not a symlink(ansi reset)"
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Create new symlink
|
||
|
try {
|
||
|
^ln -s $target_dir $NUSHELL_SYMLINK
|
||
|
print $"(ansi green)Created symlink: ($NUSHELL_SYMLINK) -> ($target_dir)(ansi reset)"
|
||
|
true
|
||
|
} catch { |e|
|
||
|
print $"(ansi red)Error: Failed to create symlink: ($e.msg)(ansi reset)"
|
||
|
false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Function to update git repository
|
||
|
def update_git_repository [] {
|
||
|
let current_dir = get_current_directory
|
||
|
|
||
|
if ($current_dir | is-empty) or not (is_git_repository $current_dir) {
|
||
|
print $"(ansi red)Error: Git repository not found or invalid(ansi reset)"
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
print $"(ansi blue)Updating Git Repository(ansi reset)"
|
||
|
print $"Repository: (ansi cyan)($current_dir)(ansi reset)"
|
||
|
|
||
|
# Show origin and branch info
|
||
|
let git_status = get_git_status $current_dir
|
||
|
if ($git_status | is-not-empty) {
|
||
|
print $"Origin: (ansi cyan)($git_status.origin)(ansi reset)"
|
||
|
print $"Branch: (ansi cyan)($git_status.branch)(ansi reset)"
|
||
|
}
|
||
|
print ""
|
||
|
|
||
|
try {
|
||
|
cd $current_dir
|
||
|
|
||
|
# Check if there are uncommitted changes
|
||
|
let changes = (^git status --porcelain=v1 | lines | length)
|
||
|
if $changes > 0 {
|
||
|
print $"(ansi yellow)Warning: ($changes) uncommitted changes detected(ansi reset)"
|
||
|
print "Stashing changes before update..."
|
||
|
let stash_message = $"Auto-stash before nushell update (date now | format date '%Y-%m-%d %H:%M:%S')"
|
||
|
^git stash push -m $stash_message
|
||
|
}
|
||
|
|
||
|
# Update repository
|
||
|
print $"(ansi blue)Fetching latest changes...(ansi reset)"
|
||
|
^git fetch origin
|
||
|
|
||
|
print $"(ansi blue)Pulling latest changes...(ansi reset)"
|
||
|
^git pull origin
|
||
|
|
||
|
# Restore stashed changes if any
|
||
|
if $changes > 0 {
|
||
|
print $"(ansi blue)Restoring stashed changes...(ansi reset)"
|
||
|
try {
|
||
|
^git stash pop
|
||
|
} catch {
|
||
|
print $"(ansi yellow)Warning: Failed to restore stashed changes(ansi reset)"
|
||
|
print "Your changes are saved in the stash. Use 'git stash list' to see them."
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cd ..
|
||
|
|
||
|
print ""
|
||
|
print $"(ansi green)✓ Git repository updated successfully!(ansi reset)"
|
||
|
let new_commit = (^git -C $current_dir rev-parse --short HEAD | str trim)
|
||
|
print $"Current commit: (ansi cyan)($new_commit)(ansi reset)"
|
||
|
|
||
|
0
|
||
|
} catch { |e|
|
||
|
print $"(ansi red)Error updating git repository: ($e.msg)(ansi reset)"
|
||
|
try { cd .. } catch { }
|
||
|
1
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Function to perform update
|
||
|
def perform_update [force_update: bool = false] {
|
||
|
print $"(ansi blue)Nushell Auto-Updater(ansi reset)"
|
||
|
print ""
|
||
|
|
||
|
# Check status first
|
||
|
let status_result = check_status
|
||
|
|
||
|
match $status_result {
|
||
|
0 => {
|
||
|
if not $force_update {
|
||
|
print ""
|
||
|
print $"(ansi green)Nushell is already up to date!(ansi reset)"
|
||
|
return 0
|
||
|
}
|
||
|
print ""
|
||
|
print $"(ansi yellow)Forcing update even though current version is latest...(ansi reset)"
|
||
|
}
|
||
|
1 => { return 1 }
|
||
|
2 => {
|
||
|
print ""
|
||
|
print $"(ansi yellow)Proceeding with installation/update...(ansi reset)"
|
||
|
}
|
||
|
3 => {
|
||
|
# Git repository detected
|
||
|
print ""
|
||
|
print $"(ansi blue)Git repository detected. Running git update...(ansi reset)"
|
||
|
print ""
|
||
|
return (update_git_repository)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
print ""
|
||
|
|
||
|
# Get versions
|
||
|
let latest_version = get_latest_version
|
||
|
let current_version = get_current_version
|
||
|
let current_dir = get_current_directory
|
||
|
|
||
|
# Download and extract
|
||
|
if not (download_and_extract $latest_version) {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
let new_dir = $"nushell-($latest_version)"
|
||
|
let new_tarball = $"nushell-($latest_version)-source.tar.gz"
|
||
|
|
||
|
# Create symlink
|
||
|
if not (create_symlink $new_dir) {
|
||
|
print $"(ansi red)Installation failed at symlink creation(ansi reset)"
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
# Clean up old installation (only if different from new one)
|
||
|
if (not ($current_version | is-empty)) and ($current_version != $latest_version) {
|
||
|
cleanup_old_installation $current_dir $current_version
|
||
|
}
|
||
|
|
||
|
# Remove the new tarball (keep the extracted directory)
|
||
|
if ($new_tarball | path exists) {
|
||
|
try { rm $new_tarball } catch { }
|
||
|
}
|
||
|
|
||
|
print ""
|
||
|
print $"(ansi green)✓ Nushell installation completed successfully!(ansi reset)"
|
||
|
print $"Version: (ansi cyan)($latest_version)(ansi reset)"
|
||
|
print $"Location: (ansi cyan)(pwd)/($new_dir)(ansi reset)"
|
||
|
print $"Symlink: (ansi cyan)(pwd)/($NUSHELL_SYMLINK)(ansi reset)"
|
||
|
|
||
|
0
|
||
|
}
|
||
|
|
||
|
# Main function
|
||
|
def main [command?: string] {
|
||
|
let cmd = ($command | default "update")
|
||
|
|
||
|
match $cmd {
|
||
|
"check" => { check_status }
|
||
|
"update" => { perform_update }
|
||
|
"force" => { perform_update true }
|
||
|
"status" => { check_status }
|
||
|
"help" | "-h" | "--help" => { show_usage }
|
||
|
_ => {
|
||
|
print $"(ansi red)Error: Unknown command '($cmd)'(ansi reset)"
|
||
|
print ""
|
||
|
show_usage
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Export functions for use
|
||
|
export def "nushell-updater check" [] { check_status }
|
||
|
export def "nushell-updater update" [] { perform_update }
|
||
|
export def "nushell-updater force" [] { perform_update true }
|
||
|
export def "nushell-updater status" [] { check_status }
|
||
|
export def "nushell-updater help" [] { show_usage }
|
||
|
|
||
|
# Main entry point function
|
||
|
export def "nushell-updater" [command?: string] {
|
||
|
main ($command | default "help")
|
||
|
}
|
||
|
|
||
|
# When run as script, don't execute main automatically
|
||
|
# Use the exported functions instead
|