Jesús Pérez 2f6089caaf # Summary
feat: bootstrap installer with robust archive extraction, version mismatch handling, and improved PATH messaging

## Detailed Description

This commit implements a production-ready bootstrap installer with comprehensive error handling, version-agnostic archive extraction, and clear user messaging. All improvements follow DRY principles using symlink-based architecture for single-source-of-truth maintenance.

## Major Changes (Session 2025-10-19)

### 0. Bootstrap Installer & Distribution Improvements (NEW)

#### 0a. Install Script Architecture - DRY Design

**Issue**: Code duplication across installation paths
- `./install.sh`, `./scripts/templates/install.sh`, `installers/bootstrap/install.sh` were separate copies
- Changes required updating multiple files
- Risk of divergence and inconsistency

**Solution**: Implemented symlink-based DRY architecture
- Single source of truth: `installers/bootstrap/install.sh` (1,247 lines)
- Symlinks created:
  - `./install.sh` → `installers/bootstrap/install.sh`
  - `./scripts/templates/install.sh` → `installers/bootstrap/install.sh`
- All changes automatically propagate through symlinks
- No code duplication

**Impact**:
-  Single maintenance point for all install scripts
-  Consistent behavior across all paths
-  Reduced maintenance burden
-  No divergence risk

#### 0b. Archive Extraction - Version-Agnostic Binary Detection

**Issue**: "No Nushell binary found in extracted archive"
- Script failed when extracting from `nushell-full-0.108.0-darwin-arm64.tar.gz`
- Error occurred even though binaries were present at `nushell-full-0.108.0/bin/nu`

**Root Cause**: `find` command returning parent directory itself in results
```bash
#  OLD - find returns parent directory first
local extracted=$(find "$extract_dir" -maxdepth 1 -type d -name "nushell-*" | head -1)
# Returns: /tmp/nushell-install-22919/nushell-extract (parent!)
# Should return: /tmp/nushell-install-22919/nushell-extract/nushell-full-0.108.0 (subdirectory)
```

**Solution**: Added `-not -path` to exclude starting directory
```bash
#  NEW - exclude parent directory
local extracted=$(find "$extract_dir" -maxdepth 1 -type d -name "nushell-*" -not -path "$extract_dir" | head -1)
# Now correctly returns the subdirectory
```

**Additional Improvements**:
- 4-level fallback detection strategy:
  1. Check `$extracted/bin/nu` (subdirectory structure)
  2. Check `$extracted/nu` (flat structure)
  3. Fallback search for any `nushell-*` subdirectory with `bin/nu`
  4. Last resort recursive search for any executable named `nu`
- Validates binaries exist before using them
- Clear error reporting with all search locations

**Version-Agnostic**:
- Uses `nushell-*` pattern (not hardcoded version numbers)
- Works with any Nushell version: 0.107, 0.108, 0.109, etc.
- Supports both `.tar.gz` and `.zip` archive formats

**Impact**:
-  Archive extraction works reliably
-  Works with any Nushell version
-  Clear error messages guide users
-  Multiple archive structure support

#### 0c. Plugin Registration - Version Mismatch Handling

**Issue**: Plugin registration failed with version incompatibility errors
```
Error: nu:🐚:plugin_failed_to_load
× Plugin `polars` is compiled for nushell version 0.107.1, which is not compatible with version 0.108.0
```

**Solution**: Implemented intelligent error classification
- Captures both stdout and stderr from plugin add commands
- Detects version incompatibility: "is not compatible with version" or "is compiled for nushell version"
- Classifies errors into three categories:
  1. **Success**: Plugin registered successfully 
  2. **Incompatible**: Version mismatch (skipped gracefully) ⚠️
  3. **Failed**: Other registration failures 
- Reports summary with counts of each category
- Installation continues successfully even with version mismatches

**New Error Reporting**:
```
 Registered nu_plugin_auth
⚠️  Skipping nu_plugin_polars: Version mismatch (built for different Nushell version)
 Successfully registered 13 plugins
⚠️  Skipped 1 incompatible plugins (version mismatch):
  - nu_plugin_polars
```

**Impact**:
-  No installation failures due to version mismatches
-  Users informed of incompatible plugins
-  Clear distinction between error types
-  Installation completes successfully

#### 0d. Shell Configuration PATH Update - Clear Messaging

**Issue**: Confusing PATH update messages
- User sees: "PATH already updated" for all files
- Then sees: "No shell configuration files were updated" warning
- Then sees: "Please manually add to your PATH" error
- **Problem**: Contradictory messages when PATH is already configured everywhere

**Root Cause**: Script conflated two states
- State 1: "Was PATH found in files?" (skips updating if found)
- State 2: "Did we add PATH to any file?" (used for messaging)
- Both states ignored means no update was made, but PATH might already exist

**Solution**: Track two separate states
```bash
local updated=false        # Was PATH ADDED to any file?
local path_found=false     # Was PATH FOUND in any file?

# In loop:
if grep -q "$install_dir" "$config_file"; then
    path_found=true        # Found it! Mark as true
    continue
fi

# After loop:
if [ "$updated" = "true" ]; then
    log_success "Shell configuration updated"
elif [ "$path_found" = "true" ]; then
    log_success "PATH is already configured in your shell configuration files"
else
    log_warn "Could not find or update shell configuration"
fi
```

**New Clear Messages**:
-  "PATH is already configured in your shell configuration files" (when found everywhere)
-  "Shell configuration updated" (when just added)
- ⚠️ "Could not find or update shell configuration" (when file missing)

**Impact**:
-  Non-contradictory messages
-  Users understand what happened
-  No false warnings when PATH already configured
-  Clear guidance when manual action needed

#### 0e. Installation Features

**`--source-path` Option** (Local Installation):
- Install from local archive: `./install.sh --source-path archive.tar.gz`
- Install from local directory: `./install.sh --source-path /path/to/binaries`
- Default behavior: `./install.sh --source-path` uses `./bin_archives`
- Works with custom `--install-dir` paths
- No download needed, offline installation support

**`--uninstall` with Configuration Management**:
- Prompts user: "Remove ~/.config/nushell? [y/N]"
- Removes all installed binaries and plugins
- Preserves user choice for configuration
- Clean uninstall before fresh reinstall

#### 0f. Documentation Updates

**CLAUDE.md**:
- Added "Install Script Architecture (DRY Design)" section
- Documents source of truth and symlink structure
- Explains `--source-path` feature
- Shows version-agnostic archive detection
- Lists DRY architecture benefits

**README.md**:
- Added "Install Script Architecture (DRY Design)" subsection
- Shows symlink structure with arrows
- Provides `--source-path` usage examples
- Explains version-agnostic detection
- "How DRY Works" 3-step explanation

#### 0g. Files Modified

Core Changes:
- `installers/bootstrap/install.sh` (+3 lines for PATH messaging fix)
  - Archive extraction fix with `-not -path`
  - Plugin registration error classification
  - Clear PATH update messaging
  - Total: 1,247 lines (unified)

Auto-Updated via Symlinks:
- `./install.sh` - Auto-updated (1,247 lines)
- `./scripts/templates/install.sh` - Auto-updated (1,247 lines)

Documentation:
- `CLAUDE.md` - Added install architecture section
- `README.md` - Added install architecture subsection
- `CHANGELOG.md` - Added comprehensive entry (+100 lines)

#### 0h. Testing & Verification

All scenarios tested and verified:
- [x] Archive extraction works with version-agnostic detection
- [x] Installation to `~/.local` successful (16 binaries)
- [x] Installation to `~/.local/bin` successful (21 plugins loaded)
- [x] Plugin registration handles version mismatches gracefully
- [x] PATH messaging is clear and non-contradictory
- [x] Clean uninstall followed by fresh reinstall works perfectly

#### 0i. Impact

User-Facing Benefits:
-  Users can install from any version of nushell-full archive
-  Version mismatch plugins skipped without breaking installation
-  Clear, honest error messages
-  Non-confusing PATH update messages
-  Offline installation support via `--source-path`
-  Clean uninstall/reinstall workflow

Developer Benefits:
-  Single source of truth eliminates code duplication
-  Changes propagate automatically through symlinks
-  Reduced maintenance burden
-  Consistent behavior across all paths
-  Production-ready installation process

---

### 1. Help System Integration (New Feature)

**Issue**: Version-update module recipes were not discoverable
- Not shown in `just help modules`
- Not referenced in `just help`
- Not included in help navigation system
- Users had to manually run `just --list` to find update commands

**Solution**:
- Added version-update module to all help outputs
- Updated `justfiles/help.just` to document all 30+ version-update recipes
- Created new `just commands` recipe as discoverable alias for `just --list`
- Integrated version-update into help-all workflow

**Impact**:
- Version-update commands now fully discoverable via help system
- Users can find update commands with: `just help modules`, `just help`, `just commands`
- Improved overall help system navigation

**Files Modified**:
- `justfiles/help.just` (+23 lines)
  - Added version-update module to help sections
  - Added to modules list
  - Added to help-all workflow
  - New `commands` recipe showing all recipes by group

### 2. Build Process Fixes (Phase 3: Bin Archives)

#### 2a. Plugin Archive Collection Bug

**Issue**: "No plugins found to package" warning in Phase 3
- Collected 26 plugin binaries but reported 0
- Archive creation skipped because count was wrong

**Root Cause**: `each` command returns null, so `| length` returned 0
```nushell
#  OLD - each returns null
let plugin_count = (ls nu_plugin_*/target/release/nu_plugin_* | each {|p|
    cp $p.name $"($temp_dir)/"
} | length)  # Returns 0!
```

**Solution**: Separated counting from copying with proper filtering
```nushell
#  NEW - count before operations
let plugins_to_copy = (ls nu_plugin_*/target/release/nu_plugin_* | where type == "file")
let plugin_count = ($plugins_to_copy | length)
```

**Impact**:
- Now correctly collects and reports 26 plugins
- Filters out .d dependency files automatically
- Warning eliminated

#### 2b. Tar Archive Path Handling

**Issue**: Tar command failing silently with relative paths in subshell
- `cd $temp_dir` changes context unpredictably
- Relative path `../$archive_name` fails in subshell
- Archive file not created despite exit code 0

**Root Cause**: Shell context and relative path issues in Nushell `do` block

**Solution**: Used `tar -C` with absolute paths instead of `cd`
```nushell
#  OLD - unreliable context switching
do {
    cd $temp_dir
    tar -czf ../$archive_name .
}

#  NEW - absolute paths, no context switching
tar -C $temp_dir -czf $archive_path .
```

**Additional Improvements**:
- Absolute path construction using `pwd | path join`
- Better error diagnostics with exit code and stderr output
- File verification after creation

**Impact**:
- Tar archives now created successfully
- Robust path handling across platforms
- Clear error messages for debugging

#### 2c. File Size Calculation Type Error

**Issue**: Runtime error when calculating archive size
```
Error: The '/' operator does not work on values of type 'list<filesize>'
```

**Root Cause**: `ls` returns list of records, so `.size` was a list
```nushell
#  OLD - returns list<filesize>
(ls $archive_path).size / 1024 / 1024

#  NEW - returns filesize
(ls $archive_path | get 0.size) / 1024 / 1024
```

**Impact**:
- Proper file size calculation in MB
- No more type errors

**Files Modified**:
- `scripts/create_full_distribution.nu` (+58 lines, refactored plugin collection)
  - Fixed plugin counting logic
  - Improved path handling with absolute paths
  - Enhanced error diagnostics

### 3. Plugin Rebuild Optimization

**Issue**: All plugins marked for rebuild even when dependencies unchanged
- Step 4 (`update_all_plugins.nu`) touched all Cargo.toml files at 01:00:32
- Step 5 saw all files as "newer" than binaries
- Marked ALL plugins for rebuild, though cargo only rebuilt changed ones

**Root Cause**: Script always saved files, even when no changes made
```nushell
#  OLD - always saves, touching file timestamp
$updated_content | to toml | save -f $cargo_toml
```

**Solution**: Only save if content actually changed
```nushell
#  NEW - compare before writing
let original_toml = $content | to toml
let new_toml = $updated_content | to toml

if $original_toml != $new_toml {
    $updated_content | to toml | save -f $cargo_toml
}
```

**Impact**:
- Unchanged files preserve original timestamps
- Only plugins with actual dependency changes are rebuilt
- Efficient rebuild process with accurate file modification detection

**Files Modified**:
- `scripts/update_all_plugins.nu` (+12 lines, added content comparison)
  - Only touches files with real changes
  - Preserves timestamps for efficiency
  - Clearer logic and comments

### 4. Documentation

**Files Modified**:
- `CHANGELOG.md` (+56 lines)
  - Added comprehensive 2025-10-19 entry
  - Documented all fixes with root causes
  - Listed files modified and impact summary

## Technical Details

### Nushell Patterns Used

1. **Proper List Handling**:
   - `ls` returns list of records, access with `| get 0.size`
   - Filter with `where type == "file"` to exclude metadata

2. **Absolute Path Construction**:
   - `pwd | append "path" | path join` for cross-platform paths
   - Safer than string concatenation with `/`

3. **Content Comparison**:
   - Compare TOML string representation before saving
   - Preserves file timestamps for efficiency

4. **Error Diagnostics**:
   - Capture `stderr` from commands
   - Report exit codes and error messages separately

## Testing

- [x] Help system shows version-update module
- [x] `just commands` displays all recipes by group
- [x] Phase 3 bin archive creation works
- [x] Plugin collection reports correct count (26)
- [x] Tar archives created successfully
- [x] File size calculated correctly
- [x] Plugin rebuild only touches changed files
- [x] CHANGELOG updated with all changes

## Files Changed

```
38 files changed, 2721 insertions(+), 2548 deletions(-)

Core Changes:
- justfiles/help.just                  (+23)  Help system integration
- scripts/create_full_distribution.nu  (+58)  Build process fixes
- scripts/update_all_plugins.nu        (+12)  Rebuild optimization
- CHANGELOG.md                         (+56)  Documentation

Dependency Updates:
- All plugin Cargo.toml and Cargo.lock files (version consistency)
```

## Breaking Changes

None. These are bug fixes and optimizations that maintain backward compatibility.

## Migration Notes

No migration needed. Improvements are transparent to users.

## Related Issues

- Help system discoverability
- Build process Phase 3 failures
- Unnecessary plugin rebuilds
- Build process reliability

## Checklist

- [x] Changes follow Rust/Nushell idioms
- [x] Code is well-commented
- [x] Error handling is comprehensive
- [x] Documentation is updated
- [x] All changes tested
- [x] No breaking changes introduced
2025-10-19 02:39:31 +01:00

598 lines
17 KiB
Bash
Executable File
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.

#!/bin/bash
# Universal Nushell + Plugins Uninstaller
# POSIX compliant shell script that cleanly removes Nushell and plugins installation
#
# This script:
# - Detects installation locations (user ~/.local/bin or system /usr/local/bin)
# - Removes Nushell binary and plugin binaries
# - Cleans up configuration files (with backup option)
# - Removes PATH entries from shell configuration files
# - Unregisters plugins from Nushell
# - Provides detailed removal report
set -e # Exit on error
# Configuration
INSTALL_DIR_USER="$HOME/.local/bin"
INSTALL_DIR_SYSTEM="/usr/local/bin"
CONFIG_DIR="$HOME/.config/nushell"
BACKUP_SUFFIX="uninstall-backup-$(date +%Y%m%d_%H%M%S)"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
printf "${BLUE} %s${NC}\\n" "$1"
}
log_success() {
printf "${GREEN}✅ %s${NC}\\n" "$1"
}
log_warn() {
printf "${YELLOW}⚠️ %s${NC}\\n" "$1"
}
log_error() {
printf "${RED}❌ %s${NC}\\n" "$1" >&2
}
log_debug() {
if [ "$DEBUG" = "1" ]; then
printf "${PURPLE}🐛 DEBUG: %s${NC}\\n" "$1"
fi
}
# Usage information
usage() {
cat << EOF
Nushell Full Distribution Uninstaller
USAGE:
$0 [OPTIONS]
OPTIONS:
-h, --help Show this help message
-y, --yes Non-interactive mode (assume yes to prompts)
-k, --keep-config Keep configuration files (don't remove ~/.config/nushell)
-b, --backup-config Backup configuration before removal
--system Remove from system location (/usr/local/bin)
⚠️ Requires sudo: sudo $0 --system
--user Remove from user location (~/.local/bin) [default]
No sudo required - recommended
--uninstall-dir PATH Remove from custom directory (PATH must be writable)
Bypasses interactive prompts when supplied explicitly
Example: --uninstall-dir ~/.local/bin
--dry-run Show what would be removed without actually removing
--debug Enable debug output
EXAMPLES:
# Default removal (from user location, interactive)
$0
# Non-interactive removal (from user location)
$0 -y
# Remove with config backup
$0 --backup-config -y
# Remove from custom directory
$0 --uninstall-dir ~/.local/bin -y
# Remove system installation (requires sudo)
sudo $0 --system -y
# Preview what would be removed
$0 --dry-run
TROUBLESHOOTING:
• Permission denied to /usr/local/bin?
→ Default uses ~/.local/bin (no sudo needed)
→ Or: --uninstall-dir ~/.local/bin (explicit custom path)
• Unsure what to do?
→ Run with --dry-run first to see what would be removed
→ Default removal is safest: $0 -y
EOF
}
# Parse command line arguments
INTERACTIVE=1
KEEP_CONFIG=0
BACKUP_CONFIG=0
SYSTEM_INSTALL=0
DRY_RUN=0
CUSTOM_UNINSTALL_DIR=""
while [ $# -gt 0 ]; do
case $1 in
-h|--help)
usage
exit 0
;;
-y|--yes)
INTERACTIVE=0
shift
;;
-k|--keep-config)
KEEP_CONFIG=1
shift
;;
-b|--backup-config)
BACKUP_CONFIG=1
shift
;;
--system)
SYSTEM_INSTALL=1
shift
;;
--user)
SYSTEM_INSTALL=0
shift
;;
--uninstall-dir)
CUSTOM_UNINSTALL_DIR="$2"
shift 2
;;
--dry-run)
DRY_RUN=1
shift
;;
--debug)
DEBUG=1
shift
;;
*)
log_error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Determine installation directory
if [ -n "$CUSTOM_UNINSTALL_DIR" ]; then
# Custom directory provided - use it directly (bypass all checks)
INSTALL_DIR="$CUSTOM_UNINSTALL_DIR"
log_info "Using custom uninstall directory (via --uninstall-dir): $INSTALL_DIR"
elif [ "$SYSTEM_INSTALL" = "1" ]; then
INSTALL_DIR="$INSTALL_DIR_SYSTEM"
log_info "Targeting system installation: $INSTALL_DIR"
# Check if we need sudo
if [ "$(id -u)" -ne 0 ] && [ "$DRY_RUN" = "0" ]; then
log_warn "System installation requires administrative privileges"
if [ "$INTERACTIVE" = "1" ]; then
printf "Continue with sudo? [y/N]: "
read -r response
case "$response" in
[yY]|[yY][eE][sS])
;;
*)
log_info "Uninstallation cancelled"
exit 0
;;
esac
fi
# Re-run with sudo if not already root
if [ "$(id -u)" -ne 0 ]; then
log_info "Re-running with sudo..."
exec sudo "$0" "$@"
fi
fi
else
INSTALL_DIR="$INSTALL_DIR_USER"
log_info "Targeting user installation: $INSTALL_DIR"
fi
# Detection functions
detect_nushell_installation() {
local install_dir="$1"
local found_items=""
log_debug "Detecting Nushell installation in $install_dir"
# Check for nu binary
if [ -f "$install_dir/nu" ]; then
found_items="$found_items nu"
log_debug "Found nu binary: $install_dir/nu"
fi
# Check for plugin binaries
for plugin_binary in "$install_dir"/nu_plugin_*; do
if [ -f "$plugin_binary" ]; then
local plugin_name=$(basename "$plugin_binary")
found_items="$found_items $plugin_name"
log_debug "Found plugin binary: $plugin_binary"
fi
done
echo "$found_items"
}
detect_config_installation() {
local config_dir="$1"
local found_items=""
log_debug "Detecting configuration in $config_dir"
if [ -d "$config_dir" ]; then
# Check for main config files
for config_file in config.nu env.nu distribution_config.toml; do
if [ -f "$config_dir/$config_file" ]; then
found_items="$found_items $config_file"
log_debug "Found config file: $config_dir/$config_file"
fi
done
# Check for plugin registration
if [ -f "$config_dir/plugin.nu" ] || [ -f "$config_dir/registry.dat" ]; then
found_items="$found_items plugin-registry"
log_debug "Found plugin registry files"
fi
fi
echo "$found_items"
}
detect_shell_profile_entries() {
local install_dir="$1"
local found_profiles=""
log_debug "Detecting shell profile entries for $install_dir"
# Check common shell profile files
for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile" "$HOME/.bash_profile"; do
if [ -f "$profile" ]; then
if grep -q "$install_dir" "$profile" 2>/dev/null; then
found_profiles="$found_profiles $(basename "$profile")"
log_debug "Found PATH entry in: $profile"
fi
fi
done
echo "$found_profiles"
}
# Removal functions
remove_binaries() {
local install_dir="$1"
local binaries="$2"
local removed_count=0
log_info "Removing binaries from $install_dir..."
for binary in $binaries; do
local binary_path="$install_dir/$binary"
if [ -f "$binary_path" ]; then
log_info "Removing binary: $binary"
if [ "$DRY_RUN" = "0" ]; then
rm -f "$binary_path"
if [ $? -eq 0 ]; then
log_success "Removed: $binary"
removed_count=$((removed_count + 1))
else
log_error "Failed to remove: $binary"
fi
else
log_info "[DRY RUN] Would remove: $binary_path"
removed_count=$((removed_count + 1))
fi
fi
done
log_success "Removed $removed_count binaries"
}
backup_configuration() {
local config_dir="$1"
local backup_dir="$config_dir.$BACKUP_SUFFIX"
if [ -d "$config_dir" ]; then
log_info "Backing up configuration to: $backup_dir"
if [ "$DRY_RUN" = "0" ]; then
cp -r "$config_dir" "$backup_dir"
if [ $? -eq 0 ]; then
log_success "Configuration backed up successfully"
else
log_error "Failed to backup configuration"
return 1
fi
else
log_info "[DRY RUN] Would backup configuration to: $backup_dir"
fi
else
log_info "No configuration directory to backup"
fi
}
remove_configuration() {
local config_dir="$1"
local config_files="$2"
if [ "$KEEP_CONFIG" = "1" ]; then
log_info "Keeping configuration files as requested"
return 0
fi
if [ "$BACKUP_CONFIG" = "1" ]; then
backup_configuration "$config_dir"
fi
if [ -d "$config_dir" ]; then
log_info "Removing configuration directory: $config_dir"
# Ask for confirmation if interactive and not just removing empty dir
if [ "$INTERACTIVE" = "1" ] && [ -n "$config_files" ]; then
printf "Remove configuration directory $config_dir? [y/N]: "
read -r response
case "$response" in
[yY]|[yY][eE][sS])
;;
*)
log_info "Keeping configuration directory"
return 0
;;
esac
fi
if [ "$DRY_RUN" = "0" ]; then
rm -rf "$config_dir"
if [ $? -eq 0 ]; then
log_success "Configuration directory removed"
else
log_error "Failed to remove configuration directory"
fi
else
log_info "[DRY RUN] Would remove configuration directory: $config_dir"
fi
else
log_info "No configuration directory found"
fi
}
remove_shell_profile_entries() {
local install_dir="$1"
local profiles="$2"
if [ -z "$profiles" ]; then
log_info "No shell profile entries found"
return 0
fi
log_info "Removing PATH entries from shell profiles..."
for profile_name in $profiles; do
local profile_path="$HOME/.$profile_name"
if [ -f "$profile_path" ]; then
log_info "Cleaning PATH entries from: $profile_name"
if [ "$DRY_RUN" = "0" ]; then
# Create backup
cp "$profile_path" "$profile_path.$BACKUP_SUFFIX"
# Remove lines containing the install directory
grep -v "$install_dir" "$profile_path.$BACKUP_SUFFIX" > "$profile_path"
if [ $? -eq 0 ]; then
log_success "Cleaned PATH entries from: $profile_name"
else
log_error "Failed to clean PATH entries from: $profile_name"
# Restore backup
mv "$profile_path.$BACKUP_SUFFIX" "$profile_path"
fi
else
log_info "[DRY RUN] Would remove PATH entries from: $profile_path"
fi
fi
done
}
# Unregister plugins from nushell (if nu is still available)
unregister_plugins() {
local install_dir="$1"
# Only try to unregister if nu is still available somewhere
local nu_binary=""
if command -v nu >/dev/null 2>&1; then
nu_binary=$(command -v nu)
elif [ -f "$install_dir/nu" ]; then
nu_binary="$install_dir/nu"
else
log_info "Nushell not available for plugin unregistration"
return 0
fi
log_info "Attempting to unregister plugins..."
if [ "$DRY_RUN" = "0" ]; then
# Try to get list of registered plugins
local registered_plugins=""
registered_plugins=$("$nu_binary" -c "plugin list | get name" 2>/dev/null) || {
log_warn "Could not retrieve plugin list"
return 0
}
if [ -n "$registered_plugins" ]; then
log_info "Unregistering plugins from nushell..."
for plugin in $registered_plugins; do
"$nu_binary" -c "plugin rm $plugin" 2>/dev/null || {
log_warn "Could not unregister plugin: $plugin"
}
done
log_success "Plugin unregistration completed"
else
log_info "No registered plugins found"
fi
else
log_info "[DRY RUN] Would attempt to unregister plugins"
fi
}
# Main uninstallation process
main() {
log_info "🗑️ Nushell Full Distribution Uninstaller"
log_info "========================================"
if [ "$DRY_RUN" = "1" ]; then
log_warn "DRY RUN MODE - No files will be modified"
fi
# Detect current installation
log_info ""
log_info "🔍 Detecting current installation..."
local binaries=""
local config_files=""
local shell_profiles=""
# Check user installation
local user_binaries=$(detect_nushell_installation "$INSTALL_DIR_USER")
local user_config=$(detect_config_installation "$CONFIG_DIR")
local user_profiles=$(detect_shell_profile_entries "$INSTALL_DIR_USER")
# Check system installation
local system_binaries=$(detect_nushell_installation "$INSTALL_DIR_SYSTEM")
local system_profiles=$(detect_shell_profile_entries "$INSTALL_DIR_SYSTEM")
# Determine what we're removing based on target
if [ "$SYSTEM_INSTALL" = "1" ]; then
binaries="$system_binaries"
shell_profiles="$system_profiles"
# Config is always user-level
config_files="$user_config"
else
binaries="$user_binaries"
shell_profiles="$user_profiles"
config_files="$user_config"
fi
# Show detection results
log_info "Installation Status:"
if [ -n "$user_binaries" ]; then
log_info " 📁 User binaries ($INSTALL_DIR_USER): $user_binaries"
else
log_info " 📁 User binaries ($INSTALL_DIR_USER): none found"
fi
if [ -n "$system_binaries" ]; then
log_info " 📁 System binaries ($INSTALL_DIR_SYSTEM): $system_binaries"
else
log_info " 📁 System binaries ($INSTALL_DIR_SYSTEM): none found"
fi
if [ -n "$config_files" ]; then
log_info " ⚙️ Configuration ($CONFIG_DIR): $config_files"
else
log_info " ⚙️ Configuration ($CONFIG_DIR): none found"
fi
if [ -n "$shell_profiles" ]; then
log_info " 🐚 Shell profiles: $shell_profiles"
else
log_info " 🐚 Shell profiles: no PATH entries found"
fi
# Check if anything was found
if [ -z "$binaries" ] && [ -z "$config_files" ] && [ -z "$shell_profiles" ]; then
log_warn "No Nushell installation detected"
if [ "$INTERACTIVE" = "1" ]; then
printf "Continue anyway? [y/N]: "
read -r response
case "$response" in
[yY]|[yY][eE][sS])
;;
*)
log_info "Uninstallation cancelled"
exit 0
;;
esac
else
log_info "Nothing to remove"
exit 0
fi
fi
# Confirmation prompt
if [ "$INTERACTIVE" = "1" ]; then
log_info ""
log_warn "This will remove the detected Nushell installation components."
if [ "$KEEP_CONFIG" = "1" ]; then
log_info "Configuration files will be kept as requested."
elif [ "$BACKUP_CONFIG" = "1" ]; then
log_info "Configuration files will be backed up before removal."
fi
printf "Proceed with uninstallation? [y/N]: "
read -r response
case "$response" in
[yY]|[yY][eE][sS])
;;
*)
log_info "Uninstallation cancelled"
exit 0
;;
esac
fi
# Perform uninstallation
log_info ""
log_info "🗑️ Starting uninstallation..."
# Unregister plugins before removing binaries
if [ -n "$binaries" ]; then
unregister_plugins "$INSTALL_DIR"
fi
# Remove binaries
if [ -n "$binaries" ]; then
remove_binaries "$INSTALL_DIR" "$binaries"
fi
# Remove configuration
if [ -n "$config_files" ]; then
remove_configuration "$CONFIG_DIR" "$config_files"
fi
# Clean shell profiles
if [ -n "$shell_profiles" ]; then
remove_shell_profile_entries "$INSTALL_DIR" "$shell_profiles"
fi
# Final summary
log_info ""
log_success "🎉 Uninstallation completed!"
log_info "Summary:"
log_info " 🗑️ Removed from: $INSTALL_DIR"
if [ -n "$config_files" ] && [ "$KEEP_CONFIG" = "0" ]; then
log_info " 🗑️ Configuration removed: $CONFIG_DIR"
elif [ "$KEEP_CONFIG" = "1" ]; then
log_info " 💾 Configuration kept: $CONFIG_DIR"
fi
if [ "$BACKUP_CONFIG" = "1" ]; then
log_info " 💾 Configuration backed up with suffix: .$BACKUP_SUFFIX"
fi
log_info ""
log_info "🔄 To complete removal:"
log_info "1. Restart your terminal or run: source ~/.bashrc (or equivalent)"
log_info "2. Verify removal: command -v nu (should return nothing)"
if [ "$KEEP_CONFIG" = "1" ] || [ "$BACKUP_CONFIG" = "1" ]; then
log_info ""
log_info "📝 Note: Configuration files were preserved as requested"
fi
}
# Run main function
main "$@"