Jesús Pérez 41455c5b3e
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
feat: Add complete Nushell full distribution system
## 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
2025-09-24 18:52:07 +01:00

528 lines
16 KiB
PowerShell
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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