
Some checks failed
Build and Test / Validate Setup (push) Has been cancelled
Build and Test / Build (darwin-amd64) (push) Has been cancelled
Build and Test / Build (darwin-arm64) (push) Has been cancelled
Build and Test / Build (linux-amd64) (push) Has been cancelled
Build and Test / Build (windows-amd64) (push) Has been cancelled
Build and Test / Build (linux-arm64) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Package Results (push) Has been cancelled
Build and Test / Quality Gate (push) Has been cancelled
## Major Features Added - **Complete distribution infrastructure**: Build, package, and distribute Nushell binary alongside plugins - **Zero-prerequisite installation**: Bootstrap installers work on fresh systems without Rust/Cargo/Nushell - **Cross-platform support**: Linux, macOS, Windows (x86_64, ARM64) - **Self-contained packages**: Everything needed for complete Nushell environment ## New Components ### Build System - `scripts/build_nushell.nu` - Build nushell with all workspace plugins - `scripts/collect_full_binaries.nu` - Collect nu binary + all plugins - `justfiles/full_distro.just` - 40+ new recipes for distribution workflows ### Bootstrap Installers (Zero Prerequisites) - `installers/bootstrap/install.sh` - Universal POSIX installer (900+ lines) - `installers/bootstrap/install.ps1` - Windows PowerShell installer (800+ lines) - Complete platform detection, PATH setup, plugin registration ### Distribution System - `scripts/create_distribution_packages.nu` - Multi-platform package creator - `scripts/install_full_nushell.nu` - Advanced nu-based installer - `scripts/verify_installation.nu` - Installation verification suite - `scripts/lib/common_lib.nu` - Shared utilities and logging ### Configuration Management - `scripts/templates/default_config.nu` - Complete nushell configuration (500+ lines) - `scripts/templates/default_env.nu` - Cross-platform environment setup - `etc/distribution_config.toml` - Central distribution settings - `scripts/templates/uninstall.sh` & `uninstall.ps1` - Clean removal ## Key Workflows ```bash just build-full # Build nushell + all plugins just pack-full-all # Create packages for all platforms just verify-full # Verify installation just release-full-cross # Complete cross-platform release ``` ## Installation Experience - One-liner: `curl -sSf https://your-url/install.sh | sh` - Multiple modes: user, system, portable installation - Automatic plugin registration with verification - Professional uninstall capability ## Benefits - ✅ Solves bootstrap problem - no prerequisites needed - ✅ Production-ready distribution system - ✅ Complete cross-platform support - ✅ Professional installation experience - ✅ Integrates seamlessly with existing plugin workflows - ✅ Enterprise-grade verification and logging
528 lines
16 KiB
PowerShell
528 lines
16 KiB
PowerShell
# Universal Nushell + Plugins Uninstaller for Windows
|
||
# PowerShell script that cleanly removes Nushell and plugins installation
|
||
#
|
||
# This script:
|
||
# - Detects installation locations (user or system Program Files)
|
||
# - Removes Nushell binary and plugin binaries
|
||
# - Cleans up configuration files (with backup option)
|
||
# - Removes PATH entries from environment variables
|
||
# - Unregisters plugins from Nushell
|
||
# - Provides detailed removal report
|
||
|
||
param(
|
||
[switch]$Help,
|
||
[switch]$Yes,
|
||
[switch]$KeepConfig,
|
||
[switch]$BackupConfig,
|
||
[switch]$System,
|
||
[switch]$User,
|
||
[switch]$DryRun,
|
||
[switch]$Debug
|
||
)
|
||
|
||
# Configuration
|
||
$InstallDirUser = "$env:USERPROFILE\.local\bin"
|
||
$InstallDirSystem = "$env:ProgramFiles\nushell"
|
||
$ConfigDir = "$env:APPDATA\nushell"
|
||
$BackupSuffix = "uninstall-backup-$(Get-Date -Format 'yyyyMMdd_HHmmss')"
|
||
|
||
# Logging functions
|
||
function Write-Info {
|
||
param([string]$Message)
|
||
Write-Host "ℹ️ $Message" -ForegroundColor Blue
|
||
}
|
||
|
||
function Write-Success {
|
||
param([string]$Message)
|
||
Write-Host "✅ $Message" -ForegroundColor Green
|
||
}
|
||
|
||
function Write-Warning {
|
||
param([string]$Message)
|
||
Write-Host "⚠️ $Message" -ForegroundColor Yellow
|
||
}
|
||
|
||
function Write-Error {
|
||
param([string]$Message)
|
||
Write-Host "❌ $Message" -ForegroundColor Red
|
||
}
|
||
|
||
function Write-Debug {
|
||
param([string]$Message)
|
||
if ($Debug) {
|
||
Write-Host "🐛 DEBUG: $Message" -ForegroundColor Magenta
|
||
}
|
||
}
|
||
|
||
# Usage information
|
||
function Show-Usage {
|
||
Write-Host @"
|
||
Nushell Full Distribution Uninstaller for Windows
|
||
|
||
USAGE:
|
||
.\uninstall.ps1 [OPTIONS]
|
||
|
||
OPTIONS:
|
||
-Help Show this help message
|
||
-Yes Non-interactive mode (assume yes to prompts)
|
||
-KeepConfig Keep configuration files (don't remove $ConfigDir)
|
||
-BackupConfig Backup configuration before removal
|
||
-System Remove from system location (Program Files) - requires admin
|
||
-User Remove from user location (~\.local\bin) - default
|
||
-DryRun Show what would be removed without actually removing
|
||
-Debug Enable debug output
|
||
|
||
EXAMPLES:
|
||
.\uninstall.ps1 # Interactive removal from user location
|
||
.\uninstall.ps1 -Yes # Non-interactive removal
|
||
.\uninstall.ps1 -BackupConfig -Yes # Remove with config backup
|
||
.\uninstall.ps1 -System # Remove system installation (needs admin)
|
||
.\uninstall.ps1 -DryRun # Show what would be removed
|
||
|
||
"@
|
||
}
|
||
|
||
# Show usage and exit if help requested
|
||
if ($Help) {
|
||
Show-Usage
|
||
exit 0
|
||
}
|
||
|
||
# Determine installation directory
|
||
$Interactive = -not $Yes
|
||
$IsSystemInstall = $System
|
||
|
||
if ($IsSystemInstall) {
|
||
$InstallDir = $InstallDirSystem
|
||
Write-Info "Targeting system installation: $InstallDir"
|
||
|
||
# Check if we have admin privileges
|
||
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
||
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||
|
||
if (-not $isAdmin -and -not $DryRun) {
|
||
Write-Warning "System installation requires administrative privileges"
|
||
if ($Interactive) {
|
||
$response = Read-Host "Continue with elevated privileges? [y/N]"
|
||
if ($response -notmatch '^[yY]([eE][sS])?$') {
|
||
Write-Info "Uninstallation cancelled"
|
||
exit 0
|
||
}
|
||
}
|
||
|
||
# Re-run as administrator
|
||
if (-not $isAdmin) {
|
||
Write-Info "Re-running with elevated privileges..."
|
||
$arguments = $MyInvocation.BoundParameters.Keys | ForEach-Object { "-$_" }
|
||
Start-Process PowerShell.exe -Argument "-File `"$PSCommandPath`" $($arguments -join ' ')" -Verb RunAs
|
||
exit 0
|
||
}
|
||
}
|
||
} else {
|
||
$InstallDir = $InstallDirUser
|
||
Write-Info "Targeting user installation: $InstallDir"
|
||
}
|
||
|
||
# Detection functions
|
||
function Get-NushellInstallation {
|
||
param([string]$InstallDir)
|
||
|
||
$foundItems = @()
|
||
Write-Debug "Detecting Nushell installation in $InstallDir"
|
||
|
||
# Check for nu binary
|
||
$nuPath = Join-Path $InstallDir "nu.exe"
|
||
if (Test-Path $nuPath) {
|
||
$foundItems += "nu.exe"
|
||
Write-Debug "Found nu binary: $nuPath"
|
||
}
|
||
|
||
# Check for plugin binaries
|
||
$pluginPattern = Join-Path $InstallDir "nu_plugin_*.exe"
|
||
$pluginBinaries = Get-ChildItem -Path $pluginPattern -ErrorAction SilentlyContinue
|
||
foreach ($plugin in $pluginBinaries) {
|
||
$foundItems += $plugin.Name
|
||
Write-Debug "Found plugin binary: $($plugin.FullName)"
|
||
}
|
||
|
||
return $foundItems
|
||
}
|
||
|
||
function Get-ConfigInstallation {
|
||
param([string]$ConfigDir)
|
||
|
||
$foundItems = @()
|
||
Write-Debug "Detecting configuration in $ConfigDir"
|
||
|
||
if (Test-Path $ConfigDir) {
|
||
# Check for main config files
|
||
$configFiles = @("config.nu", "env.nu", "distribution_config.toml")
|
||
foreach ($configFile in $configFiles) {
|
||
$configPath = Join-Path $ConfigDir $configFile
|
||
if (Test-Path $configPath) {
|
||
$foundItems += $configFile
|
||
Write-Debug "Found config file: $configPath"
|
||
}
|
||
}
|
||
|
||
# Check for plugin registration
|
||
$pluginFiles = @("plugin.nu", "registry.dat")
|
||
foreach ($pluginFile in $pluginFiles) {
|
||
$pluginPath = Join-Path $ConfigDir $pluginFile
|
||
if (Test-Path $pluginPath) {
|
||
$foundItems += "plugin-registry"
|
||
Write-Debug "Found plugin registry files"
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
return $foundItems
|
||
}
|
||
|
||
function Get-PathEntries {
|
||
param([string]$InstallDir)
|
||
|
||
Write-Debug "Detecting PATH entries for $InstallDir"
|
||
$foundIn = @()
|
||
|
||
# Check user PATH
|
||
$userPath = [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::User)
|
||
if ($userPath -and $userPath.Contains($InstallDir)) {
|
||
$foundIn += "User PATH"
|
||
Write-Debug "Found in User PATH"
|
||
}
|
||
|
||
# Check system PATH (if we have access)
|
||
try {
|
||
$systemPath = [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine)
|
||
if ($systemPath -and $systemPath.Contains($InstallDir)) {
|
||
$foundIn += "System PATH"
|
||
Write-Debug "Found in System PATH"
|
||
}
|
||
} catch {
|
||
Write-Debug "Cannot access System PATH (insufficient privileges)"
|
||
}
|
||
|
||
return $foundIn
|
||
}
|
||
|
||
# Removal functions
|
||
function Remove-Binaries {
|
||
param(
|
||
[string]$InstallDir,
|
||
[array]$Binaries
|
||
)
|
||
|
||
Write-Info "Removing binaries from $InstallDir..."
|
||
$removedCount = 0
|
||
|
||
foreach ($binary in $Binaries) {
|
||
$binaryPath = Join-Path $InstallDir $binary
|
||
if (Test-Path $binaryPath) {
|
||
Write-Info "Removing binary: $binary"
|
||
if (-not $DryRun) {
|
||
try {
|
||
Remove-Item -Path $binaryPath -Force
|
||
Write-Success "Removed: $binary"
|
||
$removedCount++
|
||
} catch {
|
||
Write-Error "Failed to remove: $binary - $($_.Exception.Message)"
|
||
}
|
||
} else {
|
||
Write-Info "[DRY RUN] Would remove: $binaryPath"
|
||
$removedCount++
|
||
}
|
||
}
|
||
}
|
||
|
||
Write-Success "Removed $removedCount binaries"
|
||
}
|
||
|
||
function Backup-Configuration {
|
||
param([string]$ConfigDir)
|
||
|
||
if (Test-Path $ConfigDir) {
|
||
$backupDir = "$ConfigDir.$BackupSuffix"
|
||
Write-Info "Backing up configuration to: $backupDir"
|
||
|
||
if (-not $DryRun) {
|
||
try {
|
||
Copy-Item -Path $ConfigDir -Destination $backupDir -Recurse -Force
|
||
Write-Success "Configuration backed up successfully"
|
||
} catch {
|
||
Write-Error "Failed to backup configuration: $($_.Exception.Message)"
|
||
return $false
|
||
}
|
||
} else {
|
||
Write-Info "[DRY RUN] Would backup configuration to: $backupDir"
|
||
}
|
||
return $true
|
||
} else {
|
||
Write-Info "No configuration directory to backup"
|
||
return $true
|
||
}
|
||
}
|
||
|
||
function Remove-Configuration {
|
||
param(
|
||
[string]$ConfigDir,
|
||
[array]$ConfigFiles
|
||
)
|
||
|
||
if ($KeepConfig) {
|
||
Write-Info "Keeping configuration files as requested"
|
||
return
|
||
}
|
||
|
||
if ($BackupConfig) {
|
||
Backup-Configuration $ConfigDir
|
||
}
|
||
|
||
if (Test-Path $ConfigDir) {
|
||
Write-Info "Removing configuration directory: $ConfigDir"
|
||
|
||
# Ask for confirmation if interactive and not just removing empty dir
|
||
if ($Interactive -and $ConfigFiles.Count -gt 0) {
|
||
$response = Read-Host "Remove configuration directory $ConfigDir? [y/N]"
|
||
if ($response -notmatch '^[yY]([eE][sS])?$') {
|
||
Write-Info "Keeping configuration directory"
|
||
return
|
||
}
|
||
}
|
||
|
||
if (-not $DryRun) {
|
||
try {
|
||
Remove-Item -Path $ConfigDir -Recurse -Force
|
||
Write-Success "Configuration directory removed"
|
||
} catch {
|
||
Write-Error "Failed to remove configuration directory: $($_.Exception.Message)"
|
||
}
|
||
} else {
|
||
Write-Info "[DRY RUN] Would remove configuration directory: $ConfigDir"
|
||
}
|
||
} else {
|
||
Write-Info "No configuration directory found"
|
||
}
|
||
}
|
||
|
||
function Remove-PathEntries {
|
||
param(
|
||
[string]$InstallDir,
|
||
[array]$PathLocations
|
||
)
|
||
|
||
if ($PathLocations.Count -eq 0) {
|
||
Write-Info "No PATH entries found"
|
||
return
|
||
}
|
||
|
||
Write-Info "Removing PATH entries..."
|
||
|
||
foreach ($location in $PathLocations) {
|
||
Write-Info "Cleaning PATH entries from: $location"
|
||
|
||
$target = if ($location -eq "User PATH") {
|
||
[EnvironmentVariableTarget]::User
|
||
} else {
|
||
[EnvironmentVariableTarget]::Machine
|
||
}
|
||
|
||
if (-not $DryRun) {
|
||
try {
|
||
$currentPath = [Environment]::GetEnvironmentVariable("Path", $target)
|
||
$pathEntries = $currentPath -split ';'
|
||
$newPathEntries = $pathEntries | Where-Object { $_ -ne $InstallDir }
|
||
$newPath = $newPathEntries -join ';'
|
||
|
||
[Environment]::SetEnvironmentVariable("Path", $newPath, $target)
|
||
Write-Success "Cleaned PATH entries from: $location"
|
||
} catch {
|
||
Write-Error "Failed to clean PATH entries from: $location - $($_.Exception.Message)"
|
||
}
|
||
} else {
|
||
Write-Info "[DRY RUN] Would remove PATH entries from: $location"
|
||
}
|
||
}
|
||
}
|
||
|
||
function Unregister-Plugins {
|
||
param([string]$InstallDir)
|
||
|
||
# Only try to unregister if nu is still available somewhere
|
||
$nuBinary = $null
|
||
|
||
if (Get-Command nu -ErrorAction SilentlyContinue) {
|
||
$nuBinary = (Get-Command nu).Path
|
||
} elseif (Test-Path (Join-Path $InstallDir "nu.exe")) {
|
||
$nuBinary = Join-Path $InstallDir "nu.exe"
|
||
} else {
|
||
Write-Info "Nushell not available for plugin unregistration"
|
||
return
|
||
}
|
||
|
||
Write-Info "Attempting to unregister plugins..."
|
||
|
||
if (-not $DryRun) {
|
||
try {
|
||
# Try to get list of registered plugins
|
||
$registeredPlugins = & $nuBinary -c "plugin list | get name" 2>$null
|
||
|
||
if ($registeredPlugins) {
|
||
Write-Info "Unregistering plugins from nushell..."
|
||
foreach ($plugin in $registeredPlugins) {
|
||
try {
|
||
& $nuBinary -c "plugin rm $plugin" 2>$null
|
||
} catch {
|
||
Write-Warning "Could not unregister plugin: $plugin"
|
||
}
|
||
}
|
||
Write-Success "Plugin unregistration completed"
|
||
} else {
|
||
Write-Info "No registered plugins found"
|
||
}
|
||
} catch {
|
||
Write-Warning "Could not retrieve plugin list"
|
||
}
|
||
} else {
|
||
Write-Info "[DRY RUN] Would attempt to unregister plugins"
|
||
}
|
||
}
|
||
|
||
# Main uninstallation process
|
||
function Main {
|
||
Write-Info "🗑️ Nushell Full Distribution Uninstaller"
|
||
Write-Info "========================================"
|
||
|
||
if ($DryRun) {
|
||
Write-Warning "DRY RUN MODE - No files will be modified"
|
||
}
|
||
|
||
# Detect current installation
|
||
Write-Info ""
|
||
Write-Info "🔍 Detecting current installation..."
|
||
|
||
$userBinaries = Get-NushellInstallation $InstallDirUser
|
||
$systemBinaries = Get-NushellInstallation $InstallDirSystem
|
||
$configFiles = Get-ConfigInstallation $ConfigDir
|
||
$userPathEntries = Get-PathEntries $InstallDirUser
|
||
$systemPathEntries = Get-PathEntries $InstallDirSystem
|
||
|
||
# Determine what we're removing based on target
|
||
if ($IsSystemInstall) {
|
||
$binaries = $systemBinaries
|
||
$pathEntries = $systemPathEntries
|
||
} else {
|
||
$binaries = $userBinaries
|
||
$pathEntries = $userPathEntries
|
||
}
|
||
|
||
# Show detection results
|
||
Write-Info "Installation Status:"
|
||
if ($userBinaries.Count -gt 0) {
|
||
Write-Info " 📁 User binaries ($InstallDirUser): $($userBinaries -join ', ')"
|
||
} else {
|
||
Write-Info " 📁 User binaries ($InstallDirUser): none found"
|
||
}
|
||
|
||
if ($systemBinaries.Count -gt 0) {
|
||
Write-Info " 📁 System binaries ($InstallDirSystem): $($systemBinaries -join ', ')"
|
||
} else {
|
||
Write-Info " 📁 System binaries ($InstallDirSystem): none found"
|
||
}
|
||
|
||
if ($configFiles.Count -gt 0) {
|
||
Write-Info " ⚙️ Configuration ($ConfigDir): $($configFiles -join ', ')"
|
||
} else {
|
||
Write-Info " ⚙️ Configuration ($ConfigDir): none found"
|
||
}
|
||
|
||
if ($pathEntries.Count -gt 0) {
|
||
Write-Info " 🛣️ PATH entries: $($pathEntries -join ', ')"
|
||
} else {
|
||
Write-Info " 🛣️ PATH entries: none found"
|
||
}
|
||
|
||
# Check if anything was found
|
||
if ($binaries.Count -eq 0 -and $configFiles.Count -eq 0 -and $pathEntries.Count -eq 0) {
|
||
Write-Warning "No Nushell installation detected"
|
||
if ($Interactive) {
|
||
$response = Read-Host "Continue anyway? [y/N]"
|
||
if ($response -notmatch '^[yY]([eE][sS])?$') {
|
||
Write-Info "Uninstallation cancelled"
|
||
exit 0
|
||
}
|
||
} else {
|
||
Write-Info "Nothing to remove"
|
||
exit 0
|
||
}
|
||
}
|
||
|
||
# Confirmation prompt
|
||
if ($Interactive) {
|
||
Write-Info ""
|
||
Write-Warning "This will remove the detected Nushell installation components."
|
||
if ($KeepConfig) {
|
||
Write-Info "Configuration files will be kept as requested."
|
||
} elseif ($BackupConfig) {
|
||
Write-Info "Configuration files will be backed up before removal."
|
||
}
|
||
|
||
$response = Read-Host "Proceed with uninstallation? [y/N]"
|
||
if ($response -notmatch '^[yY]([eE][sS])?$') {
|
||
Write-Info "Uninstallation cancelled"
|
||
exit 0
|
||
}
|
||
}
|
||
|
||
# Perform uninstallation
|
||
Write-Info ""
|
||
Write-Info "🗑️ Starting uninstallation..."
|
||
|
||
# Unregister plugins before removing binaries
|
||
if ($binaries.Count -gt 0) {
|
||
Unregister-Plugins $InstallDir
|
||
}
|
||
|
||
# Remove binaries
|
||
if ($binaries.Count -gt 0) {
|
||
Remove-Binaries $InstallDir $binaries
|
||
}
|
||
|
||
# Remove configuration
|
||
if ($configFiles.Count -gt 0) {
|
||
Remove-Configuration $ConfigDir $configFiles
|
||
}
|
||
|
||
# Clean PATH entries
|
||
if ($pathEntries.Count -gt 0) {
|
||
Remove-PathEntries $InstallDir $pathEntries
|
||
}
|
||
|
||
# Final summary
|
||
Write-Info ""
|
||
Write-Success "🎉 Uninstallation completed!"
|
||
Write-Info "Summary:"
|
||
Write-Info " 🗑️ Removed from: $InstallDir"
|
||
if ($configFiles.Count -gt 0 -and -not $KeepConfig) {
|
||
Write-Info " 🗑️ Configuration removed: $ConfigDir"
|
||
} elseif ($KeepConfig) {
|
||
Write-Info " 💾 Configuration kept: $ConfigDir"
|
||
}
|
||
if ($BackupConfig) {
|
||
Write-Info " 💾 Configuration backed up with suffix: .$BackupSuffix"
|
||
}
|
||
|
||
Write-Info ""
|
||
Write-Info "🔄 To complete removal:"
|
||
Write-Info "1. Restart your terminal or refresh environment variables"
|
||
Write-Info "2. Verify removal: Get-Command nu (should return nothing)"
|
||
|
||
if ($KeepConfig -or $BackupConfig) {
|
||
Write-Info ""
|
||
Write-Info "📝 Note: Configuration files were preserved as requested"
|
||
}
|
||
}
|
||
|
||
# Run main function
|
||
Main |