feat: Add complete Nushell full distribution system
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
This commit is contained in:
Jesús Pérez 2025-09-24 18:52:07 +01:00
parent 741efa1e70
commit 41455c5b3e
19 changed files with 10313 additions and 0 deletions

View File

@ -2,6 +2,65 @@
## Changes since commit 0a460ce (2024-09-20)
### 🚀 Major Feature: Complete Nushell Distribution System
#### Full Distribution Infrastructure
- **Complete Nushell binary distribution**: Added capability to build, package, and distribute Nushell itself alongside plugins
- **Zero-prerequisite installation**: Users can install complete Nushell environment without having Rust, Cargo, or Nushell pre-installed
- **Cross-platform bootstrap installers**: Universal POSIX shell installer (`install.sh`) and Windows PowerShell installer (`install.ps1`)
- **Multi-platform packages**: Support for Linux x86_64/ARM64, macOS x86_64/ARM64, Windows x86_64
- **Self-contained distribution packages**: Complete packages including binaries, configuration, documentation, and installers
#### New Build System
- **`scripts/build_nushell.nu`**: Comprehensive script to build nushell with all workspace plugins
- **`scripts/collect_full_binaries.nu`**: Advanced binary collection system for nushell + all plugins
- **`scripts/create_distribution_packages.nu`**: Multi-platform package creator with manifest generation
- **`scripts/lib/common_lib.nu`**: Shared utility library with logging, validation, and platform detection
- **Enhanced justfile**: Added 40+ new recipes in `justfiles/full_distro.just` for complete distribution workflows
#### Installation and Configuration System
- **`scripts/install_full_nushell.nu`**: Advanced nu-based installer with plugin selection and configuration options
- **`scripts/verify_installation.nu`**: Comprehensive installation verification with detailed reporting
- **`scripts/templates/default_config.nu`**: Complete 500+ line Nushell configuration with optimizations
- **`scripts/templates/default_env.nu`**: Cross-platform environment setup with development tool integration
- **`etc/distribution_config.toml`**: Central distribution configuration management
#### Bootstrap Installers (Zero Prerequisites)
- **`installers/bootstrap/install.sh`**: 900+ line universal POSIX installer for Linux/macOS
- Automatic platform detection and binary download
- Build from source fallback capability
- PATH configuration for all major shells
- Plugin registration and configuration creation
- **`installers/bootstrap/install.ps1`**: 800+ line Windows PowerShell installer with equivalent features
- **Complete documentation**: Installation guides, troubleshooting, and security considerations
#### Uninstall System
- **`scripts/templates/uninstall.sh`** and **`uninstall.ps1`**: Clean removal scripts for all platforms
- **Complete cleanup**: Removes binaries, configurations, PATH entries with optional backup
- **Plugin unregistration**: Clean removal from nushell registry
#### Key Distribution Workflows
```bash
# Build complete distribution
just build-full # Build nushell + all plugins
# Create packages
just pack-full # Current platform
just pack-full-all # All platforms
just pack-full-checksums # With SHA256 checksums
# Installation and verification
just verify-full # Verify installation
just test-install-full # Test complete workflow
just release-full-cross # Full cross-platform release
```
#### Installation Experience
- **One-liner installation**: `curl -sSf https://your-url/install.sh | sh`
- **Multiple installation modes**: User (~/.local/bin), system (/usr/local/bin), portable
- **Automatic plugin registration**: All plugins registered and verified with `nu -c "plugin list"`
- **Configuration management**: Sensible defaults with backup and update capabilities
### 🎯 Major Updates
#### Documentation and Repository Structure

View File

@ -0,0 +1,352 @@
# Nushell + Plugins Distribution Configuration
# Configuration file for the full distribution system
[distribution]
# Distribution metadata
name = "Nushell + Plugins"
description = "Complete Nushell distribution with built-in and custom plugins"
version = "0.107.1"
homepage = "https://nushell.sh"
repository = "https://github.com/your-org/nushell-plugins"
# Distribution maintainer information
[distribution.maintainer]
name = "Nushell Plugins Team"
email = "maintainer@your-domain.com"
organization = "Your Organization"
# Release information
[distribution.release]
# Release channels: stable, beta, nightly
channel = "stable"
# Build types: release, debug, profiling
build_type = "release"
# Supported architectures
architectures = [
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc"
]
# Platform-specific configurations
[platforms.linux]
package_formats = ["tar.gz"]
install_prefix_user = "$HOME/.local"
install_prefix_system = "/usr/local"
shell_configs = [
"$HOME/.bashrc",
"$HOME/.zshrc",
"$HOME/.config/fish/config.fish",
"$HOME/.profile"
]
[platforms.macos]
package_formats = ["tar.gz"]
install_prefix_user = "$HOME/.local"
install_prefix_system = "/usr/local"
shell_configs = [
"$HOME/.bash_profile",
"$HOME/.zshrc",
"$HOME/.config/fish/config.fish",
"$HOME/.profile"
]
[platforms.windows]
package_formats = ["zip"]
install_prefix_user = "%USERPROFILE%\\.local"
install_prefix_system = "%ProgramFiles%\\Nushell"
registry_keys = [
"HKCU\\Environment",
"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"
]
# Nushell configuration
[nushell]
# Nushell source directory (relative to project root)
source_dir = "nushell"
# Features to enable when building nushell
features = [
"plugin",
"network",
"sqlite",
"trash-support",
"rustls-tls",
"system-clipboard"
]
# Build profile
profile = "release"
# Additional build flags
build_flags = ["--locked"]
# Workspace plugins (built with nushell)
[plugins.workspace]
# Enable all workspace plugins by default
enabled = true
# Specific workspace plugins to include
plugins = [
"nu_plugin_custom_values",
"nu_plugin_example",
"nu_plugin_formats",
"nu_plugin_gstat",
"nu_plugin_inc",
"nu_plugin_polars",
"nu_plugin_query",
"nu_plugin_stress_internals"
]
# Custom plugins (external to nushell workspace)
[plugins.custom]
# Enable custom plugins by default
enabled = true
# Specific custom plugins to include
plugins = [
"nu_plugin_clipboard",
"nu_plugin_desktop_notifications",
"nu_plugin_fluent",
"nu_plugin_hashes",
"nu_plugin_highlight",
"nu_plugin_image",
"nu_plugin_kcl",
"nu_plugin_port_extension",
"nu_plugin_qr_maker",
"nu_plugin_tera"
]
# Plugin-specific configurations
[plugins.custom.nu_plugin_clipboard]
description = "Cross-platform clipboard access"
required_system_deps = ["xclip", "wl-clipboard"] # Linux only
[plugins.custom.nu_plugin_desktop_notifications]
description = "Desktop notification support"
required_system_deps = ["libnotify"] # Linux only
[plugins.custom.nu_plugin_highlight]
description = "Syntax highlighting for code files"
optional_deps = ["bat", "pygments"]
[plugins.custom.nu_plugin_image]
description = "Image processing and viewing"
optional_deps = ["imagemagick", "ffmpeg"]
[plugins.custom.nu_plugin_tera]
description = "Template processing with Tera"
# Build configuration
[build]
# Parallel jobs for building
jobs = 0 # 0 = auto-detect
# Target directory for builds
target_dir = "target"
# Whether to use cargo workspaces
use_workspace = true
# Additional cargo flags
cargo_flags = ["--release", "--locked"]
# Environment variables for build
env_vars = {}
# Cross-compilation settings
[build.cross]
# Enable cross-compilation
enabled = true
# Cross-compilation tool (cross, cargo-zigbuild, etc.)
tool = "cross"
# Docker image for cross-compilation
docker_image = "ghcr.io/cross-rs/cross"
# Collection settings
[collection]
# Output directory for collected binaries
output_dir = "distribution"
# Whether to include nushell binary
include_nushell = true
# Whether to include workspace plugins
include_workspace_plugins = true
# Whether to include custom plugins
include_custom_plugins = true
# Files to copy to distribution
additional_files = [
"LICENSE",
"README.md",
"CHANGELOG.md"
]
# Directories to copy
additional_dirs = [
"scripts/templates"
]
# Packaging settings
[packaging]
# Output directory for packages
output_dir = "bin_archives"
# Package formats per platform
formats = {
linux = ["tar.gz"],
macos = ["tar.gz"],
windows = ["zip"]
}
# Compression level (0-9)
compression_level = 6
# Whether to generate checksums
generate_checksums = true
# Checksum algorithms
checksum_algorithms = ["sha256", "sha512"]
# Manifest settings
[packaging.manifest]
# Include detailed manifest in packages
enabled = true
# Manifest format (json, toml, yaml)
format = "json"
# Include build information
include_build_info = true
# Include system information
include_system_info = true
# Include plugin information
include_plugin_info = true
# Installation settings
[installation]
# Default configuration templates
config_templates = {
env = "scripts/templates/default_env.nu",
config = "scripts/templates/default_config.nu"
}
# Whether to create config directory
create_config_dir = true
# Whether to update PATH
update_path = true
# Whether to auto-register plugins
auto_register_plugins = true
# Shell integration scripts
shell_scripts = [
"installers/bootstrap/install.sh",
"installers/bootstrap/install.ps1"
]
# Verification settings
[verification]
# Enable installation verification
enabled = true
# Tests to run during verification
tests = [
"version_check",
"basic_functionality",
"plugin_list",
"path_check"
]
# Timeout for verification tests (seconds)
timeout = 30
# Update and maintenance
[maintenance]
# Automatic dependency updates
auto_update_deps = false
# Security advisory checks
security_checks = true
# Cleanup old builds
cleanup_builds = true
# Maximum build artifacts to keep
max_build_artifacts = 5
# CI/CD integration
[ci]
# Supported CI platforms
platforms = ["github", "gitlab", "jenkins"]
# Artifact retention (days)
artifact_retention = 30
# Test matrix
test_matrix = [
"ubuntu-latest",
"macos-latest",
"windows-latest"
]
# Release automation
[release]
# Automatic releases on tag
auto_release = true
# Release notes generation
generate_release_notes = true
# Pre-release validation
validate_before_release = true
# Release channels
channels = {
stable = "main",
beta = "develop",
nightly = "nightly"
}
# Documentation settings
[documentation]
# Generate documentation during build
generate_docs = true
# Documentation formats
formats = ["html", "markdown"]
# Include API documentation
include_api_docs = true
# Documentation output directory
output_dir = "docs"
# Logging and monitoring
[logging]
# Log level (error, warn, info, debug, trace)
level = "info"
# Log format (compact, full, json)
format = "compact"
# Log to file
file_logging = false
# Log file path
log_file = "build.log"
# Security settings
[security]
# Code signing (for releases)
code_signing = false
# GPG signing key
gpg_key = ""
# Vulnerability scanning
vulnerability_scan = true
# Supply chain verification
supply_chain_check = true
# Analytics and telemetry
[analytics]
# Usage analytics (anonymized)
enabled = false
# Analytics endpoint
endpoint = ""
# Data retention period
retention_days = 90
# Custom hooks and scripts
[hooks]
# Pre-build hooks
pre_build = []
# Post-build hooks
post_build = []
# Pre-package hooks
pre_package = []
# Post-package hooks
post_package = []
# Pre-release hooks
pre_release = []
# Post-release hooks
post_release = []
# Feature flags
[features]
# Experimental features
experimental_features = false
# Beta features
beta_features = false
# Development features
dev_features = false
# Custom settings (user-configurable)
[custom]
# User can add any additional settings here
# These will be preserved during updates

View File

@ -0,0 +1,377 @@
# Nushell + Plugins Bootstrap Installers
Universal installers for Nushell and plugins that work without any prerequisites. These scripts solve the bootstrap problem by providing one-command installation that works on fresh systems.
## Quick Start
### Linux/macOS (POSIX Shell)
```bash
curl -L https://your-url/install.sh | sh
```
### Windows (PowerShell)
```powershell
Invoke-WebRequest -Uri "https://your-url/install.ps1" | Invoke-Expression
```
## Features
- **Zero Prerequisites**: Works on fresh systems without Rust, Git, or other dependencies
- **Multi-Platform**: Supports Linux, macOS, and Windows (x86_64, ARM64)
- **Complete Distribution**: Installs Nushell + all workspace and custom plugins
- **Automatic Configuration**: Sets up PATH, creates default config, registers plugins
- **Installation Modes**: User (~/.local/bin) or system (/usr/local/bin) installation
- **Build Options**: Pre-built binaries or build from source
- **Verification**: Optional installation verification and testing
- **Uninstallation**: Clean removal of all installed components
## Installation Options
### Basic Installation
Default installation to user directory with all plugins:
```bash
# Linux/macOS
curl -L install-url/install.sh | sh
# Windows
iwr install-url/install.ps1 | iex
```
### System Installation
Install to system directories (requires admin privileges):
```bash
# Linux/macOS (requires sudo)
curl -L install-url/install.sh | sudo sh -s -- --system
# Windows (requires admin PowerShell)
iwr install-url/install.ps1 | iex -ArgumentList "-System"
```
### Build from Source
Build Nushell and plugins from source code:
```bash
# Linux/macOS
curl -L install-url/install.sh | sh -s -- --build-from-source
# Windows
iwr install-url/install.ps1 | iex -ArgumentList "-BuildFromSource"
```
### Minimal Installation
Install only Nushell without plugins:
```bash
# Linux/macOS
curl -L install-url/install.sh | sh -s -- --no-plugins
# Windows
iwr install-url/install.ps1 | iex -ArgumentList "-NoPlugins"
```
## Command Line Options
### Linux/macOS (install.sh)
| Option | Description |
|--------|-------------|
| `--system` | Install to system directory (/usr/local/bin, requires sudo) |
| `--user` | Install to user directory (~/.local/bin) [default] |
| `--no-path` | Don't modify shell PATH configuration |
| `--no-config` | Don't create initial nushell configuration |
| `--no-plugins` | Install only nushell, skip plugins |
| `--build-from-source` | Build from source instead of downloading binaries |
| `--verify` | Verify installation after completion |
| `--uninstall` | Remove nushell and plugins |
| `--version VERSION` | Install specific version (default: latest) |
| `--help` | Show help message |
### Windows (install.ps1)
| Parameter | Description |
|-----------|-------------|
| `-System` | Install to system directory (C:\Program Files\Nushell, requires admin) |
| `-User` | Install to user directory (~\.local\bin) [default] |
| `-NoPath` | Don't modify PATH environment variable |
| `-NoConfig` | Don't create initial nushell configuration |
| `-NoPlugins` | Install only nushell, skip plugins |
| `-BuildFromSource` | Build from source instead of downloading binaries |
| `-Verify` | Verify installation after completion |
| `-Uninstall` | Remove nushell and plugins |
| `-Version <version>` | Install specific version (default: latest) |
| `-Help` | Show help message |
## Installation Locations
### User Installation (Default)
- **Linux/macOS**: `~/.local/bin`
- **Windows**: `%USERPROFILE%\.local\bin`
- No admin privileges required
- Affects only current user
### System Installation
- **Linux/macOS**: `/usr/local/bin`
- **Windows**: `C:\Program Files\Nushell\bin`
- Requires admin privileges
- Available to all users
### Configuration Directory
- **Linux/macOS**: `~/.config/nushell`
- **Windows**: `%USERPROFILE%\.config\nushell`
## What Gets Installed
### Core Components
- **nushell binary**: Main shell executable
- **Workspace plugins**: Built-in nushell plugins
- `nu_plugin_custom_values`
- `nu_plugin_example`
- `nu_plugin_formats`
- `nu_plugin_gstat`
- `nu_plugin_inc`
- `nu_plugin_polars`
- `nu_plugin_query`
- `nu_plugin_stress_internals`
- **Custom plugins**: Additional community plugins
- `nu_plugin_clipboard`
- `nu_plugin_desktop_notifications`
- `nu_plugin_hashes`
- `nu_plugin_highlight`
- `nu_plugin_image`
- `nu_plugin_kcl`
- `nu_plugin_tera`
- And more...
### Configuration Files
- `config.nu`: Main nushell configuration
- `env.nu`: Environment configuration
- Scripts directory for custom commands
- Plugins directory for plugin configurations
### PATH Integration
Automatically updates shell configuration files:
- **Bash**: `~/.bashrc`, `~/.bash_profile`
- **Zsh**: `~/.zshrc`
- **Fish**: `~/.config/fish/config.fish`
- **Nushell**: `~/.config/nushell/env.nu`
- **Generic**: `~/.profile`
- **Windows**: System/User PATH environment variable
## Examples
### Standard Installation with Verification
```bash
# Linux/macOS
curl -L install-url/install.sh | sh -s -- --verify
# Windows
iwr install-url/install.ps1 | iex -ArgumentList "-Verify"
```
### Corporate/Enterprise Installation
```bash
# System installation without auto-config
curl -L install-url/install.sh | sudo sh -s -- --system --no-config
# Windows system installation
# Run as Administrator:
iwr install-url/install.ps1 | iex -ArgumentList "-System", "-NoConfig"
```
### Development Installation
```bash
# Build from source with verification
curl -L install-url/install.sh | sh -s -- --build-from-source --verify
# Windows development
iwr install-url/install.ps1 | iex -ArgumentList "-BuildFromSource", "-Verify"
```
### Specific Version Installation
```bash
# Install specific version
curl -L install-url/install.sh | sh -s -- --version v0.107.1
# Windows specific version
iwr install-url/install.ps1 | iex -ArgumentList "-Version", "v0.107.1"
```
## Troubleshooting
### Common Issues
#### "Command not found" after installation
- Restart your terminal/command prompt
- Or reload shell configuration: `source ~/.bashrc` (Linux/macOS)
- Check if installation directory is in PATH
#### Permission denied errors
- For system installation, ensure you have admin privileges
- Use `sudo` on Linux/macOS or run PowerShell as Administrator on Windows
- Try user installation instead: `--user` or `-User`
#### Download failures
- Check internet connection
- Try building from source: `--build-from-source` or `-BuildFromSource`
- Manual download: Save installer script and run locally
#### Plugin registration failures
- Verify nushell binary works: `nu --version`
- Try manual plugin registration: `nu -c "plugin add /path/to/plugin"`
- Check plugin binary permissions (should be executable)
### Build from Source Issues
#### Missing dependencies
Install required tools:
**Linux (Ubuntu/Debian)**:
```bash
sudo apt update
sudo apt install git curl build-essential
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
**Linux (RHEL/CentOS/Fedora)**:
```bash
sudo dnf install git curl gcc
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
**macOS**:
```bash
# Install Xcode command line tools
xcode-select --install
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
**Windows**:
1. Install Git from https://git-scm.com/
2. Install Rust from https://rustup.rs/
3. Install Visual Studio Build Tools
### Verification Steps
Test your installation:
```bash
# Check nushell version
nu --version
# Test basic functionality
nu -c "echo 'Hello from Nushell'"
# List installed plugins
nu -c "plugin list"
# Check PATH
which nu # Linux/macOS
where nu # Windows
```
## Uninstallation
### Complete Removal
```bash
# Linux/macOS
curl -L install-url/install.sh | sh -s -- --uninstall
# Windows
iwr install-url/install.ps1 | iex -ArgumentList "-Uninstall"
```
### Manual Removal
1. Remove binaries from installation directory
2. Remove configuration directory (optional)
3. Remove PATH entries from shell configuration files
4. Remove environment variable modifications (Windows)
## Security Considerations
### Script Verification
Before running any installer, you should:
1. Review the script source code
2. Verify the download URL and SSL certificate
3. Check script signatures if available
4. Consider downloading and running locally instead of piping
### Permissions
- User installations don't require admin privileges
- System installations require elevated privileges
- Scripts only modify necessary files and directories
- No automatic execution of arbitrary code from external sources
### Network Security
- Scripts use HTTPS for all downloads
- Verify SSL certificates before downloading
- Consider using corporate proxies or mirrors
- Firewall may need to allow downloads from GitHub/your hosting
## Distribution Details
### Binary Packages
Pre-built binaries are available for:
- Linux x86_64 (GNU libc)
- Linux aarch64 (GNU libc)
- macOS x86_64 (Intel)
- macOS arm64 (Apple Silicon)
- Windows x86_64
- Windows aarch64
### Package Structure
```
nushell-plugins-{platform}-{version}.tar.gz/
├── nu(.exe) # Main nushell binary
├── nu_plugin_*(.exe) # Plugin binaries
├── config/ # Default configuration
│ ├── config.nu
│ └── env.nu
├── scripts/ # Installation scripts
├── docs/ # Documentation
├── LICENSE # License file
└── manifest.json # Package manifest
```
## Contributing
### Testing Installers
Test the installers on various platforms:
1. Fresh VM or container
2. Different operating system versions
3. Various shell configurations
4. With and without prerequisites
### Reporting Issues
When reporting problems, include:
- Operating system and version
- Shell type and version
- Installation command used
- Complete error messages
- Network/firewall configuration
### Improvements
Areas for enhancement:
- Additional platform support
- Mirror/CDN support for faster downloads
- Package manager integration
- Corporate deployment features
- Automated testing workflows
## License
These bootstrap installers are part of the nushell-plugins project and are licensed under the same terms. See the main project LICENSE file for details.
## Support
- **Documentation**: https://nushell.sh
- **Community**: https://discord.gg/nushell
- **Issues**: https://github.com/your-org/nushell-plugins/issues
- **Discussions**: https://github.com/your-org/nushell-plugins/discussions
---
**Happy shell scripting with Nushell! 🚀**

View File

@ -0,0 +1,841 @@
# Universal Nushell + Plugins Bootstrap Installer for Windows
# PowerShell script that installs Nushell and plugins without any prerequisites
#
# This script:
# - Detects Windows platform (x86_64/arm64)
# - Downloads or builds Nushell + plugins
# - Installs to user location (%USERPROFILE%\.local\bin) or system (C:\Program Files\Nushell)
# - Updates PATH in system/user environment
# - Creates initial Nushell configuration
# - Registers all plugins automatically
# - Verifies installation
param(
[switch]$System,
[switch]$User = $true,
[switch]$NoPath,
[switch]$NoConfig,
[switch]$NoPlugins,
[switch]$BuildFromSource,
[switch]$Verify,
[switch]$Uninstall,
[string]$Version = "",
[switch]$Help
)
# Configuration
$RepoUrl = "https://github.com/your-org/nushell-plugins"
$BinaryRepoUrl = "$RepoUrl/releases/download"
$InstallDirUser = "$env:USERPROFILE\.local\bin"
$InstallDirSystem = "${env:ProgramFiles}\Nushell\bin"
$ConfigDir = "$env:USERPROFILE\.config\nushell"
$TempDir = "$env:TEMP\nushell-install-$(Get-Random)"
# Colors for output
$Colors = @{
Red = "Red"
Green = "Green"
Yellow = "Yellow"
Blue = "Blue"
Magenta = "Magenta"
Cyan = "Cyan"
}
# Logging functions
function Write-LogInfo {
param([string]$Message)
Write-Host " $Message" -ForegroundColor $Colors.Blue
}
function Write-LogSuccess {
param([string]$Message)
Write-Host "$Message" -ForegroundColor $Colors.Green
}
function Write-LogWarn {
param([string]$Message)
Write-Host "⚠️ $Message" -ForegroundColor $Colors.Yellow
}
function Write-LogError {
param([string]$Message)
Write-Host "$Message" -ForegroundColor $Colors.Red
}
function Write-LogHeader {
param([string]$Message)
Write-Host ""
Write-Host "🚀 $Message" -ForegroundColor $Colors.Magenta
Write-Host ("=" * $Message.Length) -ForegroundColor $Colors.Magenta
}
# Usage information
function Show-Usage {
@"
Nushell + Plugins Bootstrap Installer for Windows
USAGE:
# Download and run:
Invoke-WebRequest -Uri "install-url/install.ps1" | Invoke-Expression
# Or download and run with parameters:
.\install.ps1 [-System] [-User] [-NoPath] [-NoConfig] [-NoPlugins] [-BuildFromSource] [-Verify] [-Uninstall] [-Version <version>] [-Help]
PARAMETERS:
-System Install to system directory (C:\Program Files\Nushell, requires admin)
-User Install to user directory (~\.local\bin) [default]
-NoPath Don't modify PATH environment variable
-NoConfig Don't create initial nushell configuration
-NoPlugins Install only nushell, skip plugins
-BuildFromSource Build from source instead of downloading binaries
-Verify Verify installation after completion
-Uninstall Remove nushell and plugins
-Version <version> Install specific version (default: latest)
-Help Show this help message
EXAMPLES:
# Default installation (user directory, with plugins)
.\install.ps1
# System installation (requires admin)
.\install.ps1 -System
# Install without plugins
.\install.ps1 -NoPlugins
# Build from source
.\install.ps1 -BuildFromSource
# Install specific version
.\install.ps1 -Version "v0.107.1"
"@
}
# Platform detection
function Get-Platform {
$arch = $env:PROCESSOR_ARCHITECTURE
switch ($arch) {
"AMD64" { return "windows-x86_64" }
"ARM64" { return "windows-aarch64" }
default {
Write-LogError "Unsupported architecture: $arch"
exit 1
}
}
}
# Check if running as administrator
function Test-Admin {
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
# Check if command exists
function Test-Command {
param([string]$Command)
try {
Get-Command $Command -ErrorAction Stop | Out-Null
return $true
} catch {
return $false
}
}
# Check dependencies for building from source
function Test-BuildDependencies {
$missing = @()
if (-not (Test-Command "git")) {
$missing += "git"
}
if (-not (Test-Command "cargo")) {
$missing += "cargo"
}
if (-not (Test-Command "rustc")) {
$missing += "rust"
}
if ($missing.Count -gt 0) {
Write-LogError "Missing build dependencies: $($missing -join ', ')"
Write-LogInfo "Please install these tools or use binary installation instead"
return $false
}
return $true
}
# Download file with progress
function Get-FileWithProgress {
param(
[string]$Url,
[string]$OutputPath,
[string]$Description = "file"
)
Write-LogInfo "Downloading $Description..."
try {
$webClient = New-Object System.Net.WebClient
$webClient.DownloadFile($Url, $OutputPath)
Write-LogSuccess "Downloaded $Description"
return $true
} catch {
Write-LogError "Failed to download $Description from $Url"
Write-LogError $_.Exception.Message
return $false
}
}
# Extract archive
function Expand-Archive {
param(
[string]$ArchivePath,
[string]$DestinationPath
)
Write-LogInfo "Extracting archive..."
try {
if ($ArchivePath -like "*.zip") {
Expand-Archive -Path $ArchivePath -DestinationPath $DestinationPath -Force
} elseif ($ArchivePath -like "*.tar.gz") {
# Use tar if available (Windows 10 build 17063 and later)
if (Test-Command "tar") {
& tar -xzf $ArchivePath -C $DestinationPath
} else {
throw "tar command not found for .tar.gz extraction"
}
} else {
throw "Unsupported archive format: $ArchivePath"
}
Write-LogSuccess "Extracted archive"
return $true
} catch {
Write-LogError "Failed to extract $ArchivePath"
Write-LogError $_.Exception.Message
return $false
}
}
# Get latest release version
function Get-LatestVersion {
try {
$response = Invoke-RestMethod -Uri "https://api.github.com/repos/your-org/nushell-plugins/releases/latest"
return $response.tag_name
} catch {
Write-LogWarn "Could not detect latest version, using v0.107.1"
return "v0.107.1"
}
}
# Download and install binaries
function Install-FromBinaries {
param(
[string]$Platform,
[string]$Version,
[string]$InstallDir,
[bool]$IncludePlugins
)
Write-LogHeader "Installing from Pre-built Binaries"
# Create temporary directory
New-Item -ItemType Directory -Path $TempDir -Force | Out-Null
Set-Location $TempDir
# Determine archive name and URL
$archiveFormat = if ($Platform -like "*windows*") { "zip" } else { "tar.gz" }
$archiveName = "nushell-plugins-$Platform-$Version.$archiveFormat"
$downloadUrl = "$BinaryRepoUrl/$Version/$archiveName"
# Download archive
if (-not (Get-FileWithProgress -Url $downloadUrl -OutputPath $archiveName -Description "Nushell distribution")) {
Write-LogWarn "Binary download failed, trying alternative..."
# Try without version prefix
$archiveName = "nushell-plugins-$Platform.$archiveFormat"
$downloadUrl = "$BinaryRepoUrl/latest/$archiveName"
if (-not (Get-FileWithProgress -Url $downloadUrl -OutputPath $archiveName -Description "Nushell distribution (latest)")) {
Write-LogError "Failed to download binaries"
return $false
}
}
# Extract archive
if (-not (Expand-Archive -ArchivePath $archiveName -DestinationPath ".")) {
return $false
}
# Find extracted directory
$extractDir = Get-ChildItem -Directory | Select-Object -First 1
if (-not $extractDir) {
Write-LogError "No extracted directory found"
return $false
}
Write-LogInfo "Installing binaries to $InstallDir..."
# Create install directory
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
# Install nushell binary
$nuBinary = "nu.exe"
$nuSourcePath = Join-Path $extractDir.FullName $nuBinary
if (Test-Path $nuSourcePath) {
Copy-Item $nuSourcePath -Destination $InstallDir
Write-LogSuccess "Installed nushell binary"
} else {
Write-LogError "Nushell binary not found in archive"
return $false
}
# Install plugins if requested
if ($IncludePlugins) {
$pluginFiles = Get-ChildItem -Path $extractDir.FullName -Name "nu_plugin_*.exe"
$pluginCount = 0
foreach ($pluginFile in $pluginFiles) {
$pluginSourcePath = Join-Path $extractDir.FullName $pluginFile
Copy-Item $pluginSourcePath -Destination $InstallDir
$pluginCount++
}
if ($pluginCount -gt 0) {
Write-LogSuccess "Installed $pluginCount plugins"
} else {
Write-LogWarn "No plugins found in archive"
}
}
# Copy configuration files if they exist
$configSourceDir = Join-Path $extractDir.FullName "config"
if (Test-Path $configSourceDir) {
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
Copy-Item -Path "$configSourceDir\*" -Destination $ConfigDir -Recurse -Force
Write-LogSuccess "Installed configuration files"
}
return $true
}
# Build and install from source
function Install-FromSource {
param(
[string]$InstallDir,
[bool]$IncludePlugins
)
Write-LogHeader "Building from Source"
# Check dependencies
if (-not (Test-BuildDependencies)) {
return $false
}
# Create temporary directory
New-Item -ItemType Directory -Path $TempDir -Force | Out-Null
Set-Location $TempDir
# Clone repository
Write-LogInfo "Cloning repository..."
try {
& git clone --recursive $RepoUrl nushell-plugins
Set-Location nushell-plugins
} catch {
Write-LogError "Failed to clone repository"
return $false
}
# Build nushell
Write-LogInfo "Building nushell..."
try {
if (Test-Command "just") {
& just build-nushell
} else {
# Fallback to manual build
Set-Location nushell
& cargo build --release --features "plugin,network,sqlite,trash-support,rustls-tls"
Set-Location ..
}
} catch {
Write-LogError "Failed to build nushell"
return $false
}
# Build plugins if requested
if ($IncludePlugins) {
Write-LogInfo "Building plugins..."
try {
if (Test-Command "just") {
& just build
} else {
# Build plugins manually
$pluginDirs = Get-ChildItem -Directory -Name "nu_plugin_*" | Where-Object { $_ -ne "nushell" }
foreach ($pluginDir in $pluginDirs) {
Write-LogInfo "Building $pluginDir..."
Set-Location $pluginDir
try {
& cargo build --release
Write-LogSuccess "Built $pluginDir"
} catch {
Write-LogWarn "Failed to build $pluginDir"
}
Set-Location ..
}
}
} catch {
Write-LogWarn "Failed to build some plugins"
}
}
# Install binaries
Write-LogInfo "Installing binaries to $InstallDir..."
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
# Install nushell
$nuBinary = "nushell\target\release\nu.exe"
if (Test-Path $nuBinary) {
Copy-Item $nuBinary -Destination "$InstallDir\nu.exe"
Write-LogSuccess "Installed nushell binary"
} else {
Write-LogError "Nushell binary not found"
return $false
}
# Install plugins
if ($IncludePlugins) {
$pluginCount = 0
$pluginDirs = Get-ChildItem -Directory -Name "nu_plugin_*" | Where-Object { $_ -ne "nushell" }
foreach ($pluginDir in $pluginDirs) {
$pluginBinary = "$pluginDir\target\release\$pluginDir.exe"
if (Test-Path $pluginBinary) {
Copy-Item $pluginBinary -Destination "$InstallDir\$pluginDir.exe"
$pluginCount++
}
}
if ($pluginCount -gt 0) {
Write-LogSuccess "Installed $pluginCount plugins"
} else {
Write-LogWarn "No plugins were built successfully"
}
}
return $true
}
# Register plugins with nushell
function Register-Plugins {
param(
[string]$InstallDir
)
$nuBinary = Join-Path $InstallDir "nu.exe"
if (-not (Test-Path $nuBinary)) {
Write-LogError "Nushell binary not found: $nuBinary"
return $false
}
Write-LogHeader "Registering Plugins"
# Find all plugin binaries
$pluginFiles = Get-ChildItem -Path $InstallDir -Name "nu_plugin_*.exe"
$pluginCount = 0
foreach ($pluginFile in $pluginFiles) {
$pluginPath = Join-Path $InstallDir $pluginFile
$pluginName = [System.IO.Path]::GetFileNameWithoutExtension($pluginFile)
Write-LogInfo "Registering $pluginName..."
try {
& $nuBinary -c "plugin add '$pluginPath'"
Write-LogSuccess "Registered $pluginName"
$pluginCount++
} catch {
Write-LogWarn "Failed to register $pluginName"
}
}
if ($pluginCount -gt 0) {
Write-LogSuccess "Successfully registered $pluginCount plugins"
} else {
Write-LogWarn "No plugins were registered"
}
return $true
}
# Update PATH environment variable
function Update-PathEnvironment {
param(
[string]$InstallDir,
[bool]$SystemInstall
)
Write-LogHeader "Updating PATH Environment"
# Determine scope
$scope = if ($SystemInstall) { "Machine" } else { "User" }
try {
# Get current PATH
$currentPath = [Environment]::GetEnvironmentVariable("PATH", $scope)
# Check if already in PATH
if ($currentPath -split ';' -contains $InstallDir) {
Write-LogInfo "PATH already contains $InstallDir"
return $true
}
# Add to PATH
$newPath = "$InstallDir;$currentPath"
[Environment]::SetEnvironmentVariable("PATH", $newPath, $scope)
# Update current session
$env:PATH = "$InstallDir;$env:PATH"
Write-LogSuccess "Updated PATH environment variable"
Write-LogInfo "Restart your terminal to apply changes globally"
return $true
} catch {
Write-LogError "Failed to update PATH environment variable"
Write-LogError $_.Exception.Message
return $false
}
}
# Create initial nushell configuration
function New-NushellConfig {
Write-LogHeader "Creating Nushell Configuration"
# Create config directory
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
# Create basic config.nu if it doesn't exist
$configFile = Join-Path $ConfigDir "config.nu"
if (-not (Test-Path $configFile)) {
@"
# Nushell Configuration
# Created by nushell-plugins installer
# Set up basic configuration
`$env.config = {
show_banner: false
edit_mode: emacs
shell_integration: true
table: {
mode: rounded
index_mode: always
show_empty: true
padding: { left: 1, right: 1 }
}
completions: {
case_sensitive: false
quick: true
partial: true
algorithm: "prefix"
}
history: {
max_size: 10000
sync_on_enter: true
file_format: "plaintext"
}
filesize: {
metric: false
format: "auto"
}
}
# Load custom commands and aliases
# Add your custom configuration below
"@ | Out-File -FilePath $configFile -Encoding utf8
Write-LogSuccess "Created config.nu"
} else {
Write-LogInfo "config.nu already exists, skipping"
}
# Create basic env.nu if it doesn't exist
$envFile = Join-Path $ConfigDir "env.nu"
if (-not (Test-Path $envFile)) {
@"
# Nushell Environment Configuration
# Created by nushell-plugins installer
# Environment variables
`$env.EDITOR = "notepad"
`$env.BROWSER = "msedge"
# Nushell specific environment
`$env.NU_LIB_DIRS = [
(`$nu.config-path | path dirname | path join "scripts")
]
`$env.NU_PLUGIN_DIRS = [
(`$nu.config-path | path dirname | path join "plugins")
]
# Add your custom environment variables below
"@ | Out-File -FilePath $envFile -Encoding utf8
Write-LogSuccess "Created env.nu"
} else {
Write-LogInfo "env.nu already exists, skipping"
}
# Create directories
$scriptsDir = Join-Path $ConfigDir "scripts"
$pluginsDir = Join-Path $ConfigDir "plugins"
New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null
New-Item -ItemType Directory -Path $pluginsDir -Force | Out-Null
return $true
}
# Verify installation
function Test-Installation {
param(
[string]$InstallDir
)
$nuBinary = Join-Path $InstallDir "nu.exe"
Write-LogHeader "Verifying Installation"
# Check if nushell binary exists
if (-not (Test-Path $nuBinary)) {
Write-LogError "Nushell binary not found: $nuBinary"
return $false
}
# Test nushell version
Write-LogInfo "Testing nushell binary..."
try {
$versionOutput = & $nuBinary --version
Write-LogSuccess "Nushell version: $versionOutput"
} catch {
Write-LogError "Failed to run nushell binary"
Write-LogError $_.Exception.Message
return $false
}
# Test basic nushell command
Write-LogInfo "Testing basic nushell functionality..."
try {
$testOutput = & $nuBinary -c "echo 'Hello from Nushell'"
if ($testOutput -eq "Hello from Nushell") {
Write-LogSuccess "Basic nushell functionality works"
} else {
Write-LogWarn "Unexpected output from basic test"
}
} catch {
Write-LogError "Basic nushell functionality failed"
return $false
}
# List registered plugins
Write-LogInfo "Checking registered plugins..."
try {
$pluginOutput = & $nuBinary -c "plugin list"
$pluginCount = ($pluginOutput | Select-String "nu_plugin_").Count
if ($pluginCount -gt 0) {
Write-LogSuccess "Found $pluginCount registered plugins"
} else {
Write-LogWarn "No plugins are registered"
}
} catch {
Write-LogWarn "Could not check plugin status"
}
# Check PATH
Write-LogInfo "Checking PATH configuration..."
try {
$nuInPath = Get-Command nu -ErrorAction SilentlyContinue
if ($nuInPath) {
Write-LogSuccess "Nushell is available in PATH"
} else {
Write-LogWarn "Nushell is not in PATH. You may need to restart your terminal."
}
} catch {
Write-LogWarn "Could not verify PATH configuration"
}
Write-LogSuccess "Installation verification complete!"
return $true
}
# Uninstall function
function Uninstall-Nushell {
Write-LogHeader "Uninstalling Nushell"
$removedFiles = 0
# Remove from user directory
if (Test-Path $InstallDirUser) {
$userBinaries = Get-ChildItem -Path $InstallDirUser -Name "nu.exe", "nu_plugin_*.exe" -ErrorAction SilentlyContinue
foreach ($binary in $userBinaries) {
$filePath = Join-Path $InstallDirUser $binary
Remove-Item $filePath -Force -ErrorAction SilentlyContinue
Write-LogSuccess "Removed $binary from $InstallDirUser"
$removedFiles++
}
}
# Remove from system directory (if accessible)
if ((Test-Admin) -and (Test-Path $InstallDirSystem)) {
$systemBinaries = Get-ChildItem -Path $InstallDirSystem -Name "nu.exe", "nu_plugin_*.exe" -ErrorAction SilentlyContinue
foreach ($binary in $systemBinaries) {
$filePath = Join-Path $InstallDirSystem $binary
Remove-Item $filePath -Force -ErrorAction SilentlyContinue
Write-LogSuccess "Removed $binary from $InstallDirSystem"
$removedFiles++
}
}
# Option to remove configuration
$response = Read-Host "Remove nushell configuration directory ($ConfigDir)? [y/N]"
if ($response -match "^[yY]") {
if (Test-Path $ConfigDir) {
Remove-Item $ConfigDir -Recurse -Force -ErrorAction SilentlyContinue
Write-LogSuccess "Removed configuration directory"
}
} else {
Write-LogInfo "Configuration directory preserved"
}
if ($removedFiles -gt 0) {
Write-LogSuccess "Uninstallation complete ($removedFiles files removed)"
Write-LogWarn "You may need to manually remove PATH entries from your environment variables"
} else {
Write-LogWarn "No nushell files found to remove"
}
}
# Main installation function
function Main {
# Handle help
if ($Help) {
Show-Usage
exit 0
}
# Handle uninstall
if ($Uninstall) {
Uninstall-Nushell
exit 0
}
# Show header
Write-LogHeader "Nushell + Plugins Installer for Windows"
Write-LogInfo "Universal bootstrap installer for Nushell and plugins"
Write-LogInfo ""
# Detect platform
$platform = Get-Platform
Write-LogInfo "Detected platform: $platform"
# Determine installation directory and check privileges
if ($System) {
$installDir = $InstallDirSystem
if (-not (Test-Admin)) {
Write-LogError "System installation requires administrator privileges"
Write-LogInfo "Run PowerShell as Administrator or use -User for user installation"
exit 1
}
} else {
$installDir = $InstallDirUser
}
Write-LogInfo "Installing to: $installDir"
# Get version if not specified
if (-not $Version) {
$Version = Get-LatestVersion
}
Write-LogInfo "Version: $Version"
# Cleanup function
$cleanup = {
if (Test-Path $TempDir) {
Remove-Item $TempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
try {
# Install based on method
if ($BuildFromSource) {
if (-not (Install-FromSource -InstallDir $installDir -IncludePlugins (-not $NoPlugins))) {
Write-LogError "Source installation failed"
exit 1
}
} else {
if (-not (Install-FromBinaries -Platform $platform -Version $Version -InstallDir $installDir -IncludePlugins (-not $NoPlugins))) {
Write-LogError "Binary installation failed"
exit 1
}
}
# Register plugins
if (-not $NoPlugins) {
Register-Plugins -InstallDir $installDir
}
# Update PATH
if (-not $NoPath) {
Update-PathEnvironment -InstallDir $installDir -SystemInstall $System
}
# Create configuration
if (-not $NoConfig) {
New-NushellConfig
}
# Verify installation
if ($Verify) {
if (-not (Test-Installation -InstallDir $installDir)) {
Write-LogError "Installation verification failed"
exit 1
}
}
# Final success message
Write-LogHeader "Installation Complete!"
Write-LogSuccess "Nushell has been successfully installed to $installDir"
if (-not $NoPlugins) {
Write-LogSuccess "Plugins have been registered with Nushell"
}
if (-not $NoPath) {
Write-LogInfo "To use Nushell, restart your terminal or start a new PowerShell session"
}
Write-LogInfo ""
Write-LogInfo "Try running: nu --version"
Write-LogInfo "Or start Nushell with: nu"
if (-not $NoPlugins) {
Write-LogInfo "Check plugins with: nu -c 'plugin list'"
}
Write-LogInfo ""
Write-LogInfo "For more information, visit: https://nushell.sh"
Write-LogInfo ""
Write-LogSuccess "Happy shell scripting! 🚀"
} finally {
& $cleanup
}
}
# Run main function
Main

923
installers/bootstrap/install.sh Executable file
View File

@ -0,0 +1,923 @@
#!/bin/bash
# Universal Nushell + Plugins Bootstrap Installer
# POSIX compliant shell script that installs Nushell and plugins without any prerequisites
#
# This script:
# - Detects platform (Linux/macOS, x86_64/arm64)
# - Downloads or builds Nushell + plugins
# - Installs to user location (~/.local/bin) or system (/usr/local/bin)
# - Updates PATH in shell configuration files
# - Creates initial Nushell configuration
# - Registers all plugins automatically
# - Verifies installation
set -e # Exit on error
# Configuration
REPO_URL="https://github.com/your-org/nushell-plugins"
BINARY_REPO_URL="$REPO_URL/releases/download"
INSTALL_DIR_USER="$HOME/.local/bin"
INSTALL_DIR_SYSTEM="/usr/local/bin"
CONFIG_DIR="$HOME/.config/nushell"
TEMP_DIR="/tmp/nushell-install-$$"
# 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_header() {
printf "\n${PURPLE}🚀 %s${NC}\n" "$1"
printf "${PURPLE}%s${NC}\n" "$(printf '=%.0s' $(seq 1 ${#1}))"
}
# Usage information
usage() {
cat << 'EOF'
Nushell + Plugins Bootstrap Installer
USAGE:
curl -L install-url/install.sh | sh
# or
./install.sh [OPTIONS]
OPTIONS:
--system Install to system directory (/usr/local/bin, requires sudo)
--user Install to user directory (~/.local/bin) [default]
--no-path Don't modify shell PATH configuration
--no-config Don't create initial nushell configuration
--no-plugins Install only nushell, skip plugins
--build-from-source Build from source instead of downloading binaries
--verify Verify installation after completion
--uninstall Remove nushell and plugins
--version VERSION Install specific version (default: latest)
--help Show this help message
EXAMPLES:
# Default installation (user directory, with plugins)
curl -L install-url/install.sh | sh
# System installation
curl -L install-url/install.sh | sh -s -- --system
# Install without plugins
curl -L install-url/install.sh | sh -s -- --no-plugins
# Build from source
curl -L install-url/install.sh | sh -s -- --build-from-source
# Install specific version
curl -L install-url/install.sh | sh -s -- --version v0.107.1
EOF
}
# Platform detection
detect_platform() {
local os arch
# Detect OS
case "$(uname -s)" in
Linux) os="linux" ;;
Darwin) os="darwin" ;;
CYGWIN*|MINGW*|MSYS*) os="windows" ;;
*) log_error "Unsupported OS: $(uname -s)"; exit 1 ;;
esac
# Detect architecture
case "$(uname -m)" in
x86_64|amd64) arch="x86_64" ;;
aarch64|arm64) arch="arm64" ;;
armv7l) arch="armv7" ;;
*) log_error "Unsupported architecture: $(uname -m)"; exit 1 ;;
esac
# Special handling for Darwin arm64
if [ "$os" = "darwin" ] && [ "$arch" = "arm64" ]; then
arch="arm64"
fi
echo "${os}-${arch}"
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check dependencies for building from source
check_build_dependencies() {
local missing=""
if ! command_exists "git"; then
missing="$missing git"
fi
if ! command_exists "cargo"; then
missing="$missing cargo"
fi
if ! command_exists "rustc"; then
missing="$missing rust"
fi
if [ -n "$missing" ]; then
log_error "Missing build dependencies:$missing"
log_info "Please install these tools or use binary installation instead"
return 1
fi
return 0
}
# Download file with progress
download_file() {
local url="$1"
local output="$2"
local desc="${3:-file}"
log_info "Downloading $desc..."
if command_exists "curl"; then
if ! curl -L --fail --progress-bar "$url" -o "$output"; then
log_error "Failed to download $desc from $url"
return 1
fi
elif command_exists "wget"; then
if ! wget --progress=bar:force "$url" -O "$output"; then
log_error "Failed to download $desc from $url"
return 1
fi
else
log_error "Neither curl nor wget is available"
return 1
fi
log_success "Downloaded $desc"
return 0
}
# Extract archive
extract_archive() {
local archive="$1"
local destination="$2"
log_info "Extracting archive..."
case "$archive" in
*.tar.gz)
if ! tar -xzf "$archive" -C "$destination"; then
log_error "Failed to extract $archive"
return 1
fi
;;
*.zip)
if command_exists "unzip"; then
if ! unzip -q "$archive" -d "$destination"; then
log_error "Failed to extract $archive"
return 1
fi
else
log_error "unzip command not found"
return 1
fi
;;
*)
log_error "Unsupported archive format: $archive"
return 1
;;
esac
log_success "Extracted archive"
return 0
}
# Get latest release version
get_latest_version() {
local version=""
if command_exists "curl"; then
version=$(curl -s "https://api.github.com/repos/your-org/nushell-plugins/releases/latest" | \
grep '"tag_name":' | \
sed -E 's/.*"([^"]+)".*/\1/')
elif command_exists "wget"; then
version=$(wget -qO- "https://api.github.com/repos/your-org/nushell-plugins/releases/latest" | \
grep '"tag_name":' | \
sed -E 's/.*"([^"]+)".*/\1/')
fi
if [ -z "$version" ]; then
# Fallback to a reasonable default
version="v0.107.1"
log_warn "Could not detect latest version, using $version"
fi
echo "$version"
}
# Download and install binaries
install_from_binaries() {
local platform="$1"
local version="$2"
local install_dir="$3"
local include_plugins="$4"
log_header "Installing from Pre-built Binaries"
# Create temporary directory
mkdir -p "$TEMP_DIR"
cd "$TEMP_DIR"
# Determine archive name and URL
local archive_name="nushell-plugins-${platform}-${version}.tar.gz"
local download_url="${BINARY_REPO_URL}/${version}/${archive_name}"
# Download archive
if ! download_file "$download_url" "$archive_name" "Nushell distribution"; then
log_warn "Binary download failed, trying alternative..."
# Try without version prefix
archive_name="nushell-plugins-${platform}.tar.gz"
download_url="${BINARY_REPO_URL}/latest/${archive_name}"
if ! download_file "$download_url" "$archive_name" "Nushell distribution (latest)"; then
log_error "Failed to download binaries"
return 1
fi
fi
# Extract archive
if ! extract_archive "$archive_name" "."; then
return 1
fi
# Find extracted directory
local extract_dir=""
for dir in */; do
if [ -d "$dir" ]; then
extract_dir="$dir"
break
fi
done
if [ -z "$extract_dir" ]; then
log_error "No extracted directory found"
return 1
fi
log_info "Installing binaries to $install_dir..."
# Create install directory
mkdir -p "$install_dir"
# Install nushell binary
local nu_binary="nu"
if [ "$platform" = "windows-x86_64" ]; then
nu_binary="nu.exe"
fi
if [ -f "${extract_dir}${nu_binary}" ]; then
cp "${extract_dir}${nu_binary}" "$install_dir/"
chmod +x "${install_dir}/${nu_binary}"
log_success "Installed nushell binary"
else
log_error "Nushell binary not found in archive"
return 1
fi
# Install plugins if requested
if [ "$include_plugins" = "true" ]; then
local plugin_count=0
for plugin_file in "${extract_dir}"nu_plugin_*; do
if [ -f "$plugin_file" ]; then
local plugin_name=$(basename "$plugin_file")
cp "$plugin_file" "$install_dir/"
chmod +x "${install_dir}/${plugin_name}"
plugin_count=$((plugin_count + 1))
fi
done
if [ $plugin_count -gt 0 ]; then
log_success "Installed $plugin_count plugins"
else
log_warn "No plugins found in archive"
fi
fi
# Copy configuration files if they exist
if [ -d "${extract_dir}config/" ]; then
mkdir -p "$CONFIG_DIR"
cp -r "${extract_dir}config/"* "$CONFIG_DIR/"
log_success "Installed configuration files"
fi
return 0
}
# Build and install from source
install_from_source() {
local install_dir="$1"
local include_plugins="$2"
log_header "Building from Source"
# Check dependencies
if ! check_build_dependencies; then
return 1
fi
# Create temporary directory
mkdir -p "$TEMP_DIR"
cd "$TEMP_DIR"
# Clone repository
log_info "Cloning repository..."
if ! git clone --recursive "$REPO_URL" nushell-plugins; then
log_error "Failed to clone repository"
return 1
fi
cd nushell-plugins
# Build nushell
log_info "Building nushell..."
if command_exists "just"; then
if ! just build-nushell; then
log_error "Failed to build nushell with just"
return 1
fi
else
# Fallback to manual build
cd nushell
if ! cargo build --release --features "plugin,network,sqlite,trash-support,rustls-tls"; then
log_error "Failed to build nushell"
return 1
fi
cd ..
fi
# Build plugins if requested
if [ "$include_plugins" = "true" ]; then
log_info "Building plugins..."
if command_exists "just"; then
if ! just build; then
log_warn "Failed to build some plugins"
fi
else
# Build plugins manually
for plugin_dir in nu_plugin_*; do
if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then
log_info "Building $plugin_dir..."
cd "$plugin_dir"
if cargo build --release; then
log_success "Built $plugin_dir"
else
log_warn "Failed to build $plugin_dir"
fi
cd ..
fi
done
fi
fi
# Install binaries
log_info "Installing binaries to $install_dir..."
mkdir -p "$install_dir"
# Install nushell
local nu_binary="nushell/target/release/nu"
if [ -f "$nu_binary" ]; then
cp "$nu_binary" "$install_dir/"
chmod +x "${install_dir}/nu"
log_success "Installed nushell binary"
else
log_error "Nushell binary not found"
return 1
fi
# Install plugins
if [ "$include_plugins" = "true" ]; then
local plugin_count=0
for plugin_dir in nu_plugin_*; do
if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then
local plugin_binary="${plugin_dir}/target/release/${plugin_dir}"
if [ -f "$plugin_binary" ]; then
cp "$plugin_binary" "$install_dir/"
chmod +x "${install_dir}/${plugin_dir}"
plugin_count=$((plugin_count + 1))
fi
done
done
if [ $plugin_count -gt 0 ]; then
log_success "Installed $plugin_count plugins"
else
log_warn "No plugins were built successfully"
fi
fi
return 0
}
# Register plugins with nushell
register_plugins() {
local install_dir="$1"
local nu_binary="${install_dir}/nu"
if [ ! -f "$nu_binary" ]; then
log_error "Nushell binary not found: $nu_binary"
return 1
fi
log_header "Registering Plugins"
# Find all plugin binaries
local plugin_count=0
for plugin_file in "${install_dir}"/nu_plugin_*; do
if [ -f "$plugin_file" ] && [ -x "$plugin_file" ]; then
local plugin_name=$(basename "$plugin_file")
log_info "Registering $plugin_name..."
if "$nu_binary" -c "plugin add '$plugin_file'"; then
log_success "Registered $plugin_name"
plugin_count=$((plugin_count + 1))
else
log_warn "Failed to register $plugin_name"
fi
fi
done
if [ $plugin_count -gt 0 ]; then
log_success "Successfully registered $plugin_count plugins"
else
log_warn "No plugins were registered"
fi
return 0
}
# Update PATH in shell configuration
update_shell_path() {
local install_dir="$1"
log_header "Updating Shell Configuration"
# List of shell configuration files to update
local shell_configs=""
# Detect current shell and add its config file first
case "$SHELL" in
*/bash) shell_configs="$HOME/.bashrc $HOME/.bash_profile" ;;
*/zsh) shell_configs="$HOME/.zshrc" ;;
*/fish) shell_configs="$HOME/.config/fish/config.fish" ;;
*/nu) shell_configs="$HOME/.config/nushell/env.nu" ;;
esac
# Add common configuration files
shell_configs="$shell_configs $HOME/.profile"
local updated=false
for config_file in $shell_configs; do
if [ -f "$config_file" ] || [ "$config_file" = "$HOME/.bashrc" ] || [ "$config_file" = "$HOME/.profile" ]; then
# Check if already in PATH
if grep -q "$install_dir" "$config_file" 2>/dev/null; then
log_info "PATH already updated in $(basename "$config_file")"
continue
fi
# Create config file if it doesn't exist
if [ ! -f "$config_file" ]; then
touch "$config_file"
fi
# Add PATH update to config file
case "$config_file" in
*.fish)
echo "fish_add_path $install_dir" >> "$config_file"
;;
*/env.nu)
echo "\$env.PATH = (\$env.PATH | split row (char esep) | append \"$install_dir\" | uniq)" >> "$config_file"
;;
*)
echo "" >> "$config_file"
echo "# Added by nushell installer" >> "$config_file"
echo "export PATH=\"$install_dir:\$PATH\"" >> "$config_file"
;;
esac
log_success "Updated $(basename "$config_file")"
updated=true
fi
done
if [ "$updated" = "true" ]; then
log_info "Please restart your terminal or run 'source ~/.bashrc' to update PATH"
else
log_warn "No shell configuration files were updated"
log_info "Please manually add $install_dir to your PATH"
fi
# Update current session PATH
export PATH="$install_dir:$PATH"
log_success "Updated PATH for current session"
}
# Create initial nushell configuration
create_nushell_config() {
log_header "Creating Nushell Configuration"
# Create config directory
mkdir -p "$CONFIG_DIR"
# Create basic config.nu if it doesn't exist
local config_file="$CONFIG_DIR/config.nu"
if [ ! -f "$config_file" ]; then
cat > "$config_file" << 'EOF'
# Nushell Configuration
# Created by nushell-plugins installer
# Set up basic configuration
$env.config = {
show_banner: false
edit_mode: emacs
shell_integration: true
table: {
mode: rounded
index_mode: always
show_empty: true
padding: { left: 1, right: 1 }
}
completions: {
case_sensitive: false
quick: true
partial: true
algorithm: "prefix"
}
history: {
max_size: 10000
sync_on_enter: true
file_format: "plaintext"
}
filesize: {
metric: false
format: "auto"
}
}
# Load custom commands and aliases
# Add your custom configuration below
EOF
log_success "Created config.nu"
else
log_info "config.nu already exists, skipping"
fi
# Create basic env.nu if it doesn't exist
local env_file="$CONFIG_DIR/env.nu"
if [ ! -f "$env_file" ]; then
cat > "$env_file" << 'EOF'
# Nushell Environment Configuration
# Created by nushell-plugins installer
# Environment variables
$env.EDITOR = "nano"
$env.BROWSER = "firefox"
# Nushell specific environment
$env.NU_LIB_DIRS = [
($nu.config-path | path dirname | path join "scripts")
]
$env.NU_PLUGIN_DIRS = [
($nu.config-path | path dirname | path join "plugins")
]
# Add your custom environment variables below
EOF
log_success "Created env.nu"
else
log_info "env.nu already exists, skipping"
fi
# Create scripts directory
local scripts_dir="$CONFIG_DIR/scripts"
mkdir -p "$scripts_dir"
# Create plugins directory
local plugins_dir="$CONFIG_DIR/plugins"
mkdir -p "$plugins_dir"
}
# Verify installation
verify_installation() {
local install_dir="$1"
local nu_binary="${install_dir}/nu"
log_header "Verifying Installation"
# Check if nushell binary exists and is executable
if [ ! -f "$nu_binary" ]; then
log_error "Nushell binary not found: $nu_binary"
return 1
fi
if [ ! -x "$nu_binary" ]; then
log_error "Nushell binary is not executable: $nu_binary"
return 1
fi
# Test nushell version
log_info "Testing nushell binary..."
local version_output
if version_output=$("$nu_binary" --version 2>&1); then
log_success "Nushell version: $version_output"
else
log_error "Failed to run nushell binary"
log_error "Output: $version_output"
return 1
fi
# Test basic nushell command
log_info "Testing basic nushell functionality..."
if "$nu_binary" -c "echo 'Hello from Nushell'" >/dev/null 2>&1; then
log_success "Basic nushell functionality works"
else
log_error "Basic nushell functionality failed"
return 1
fi
# List registered plugins
log_info "Checking registered plugins..."
local plugin_output
if plugin_output=$("$nu_binary" -c "plugin list" 2>&1); then
local plugin_count=$(echo "$plugin_output" | grep -c "nu_plugin_" || true)
if [ "$plugin_count" -gt 0 ]; then
log_success "Found $plugin_count registered plugins"
else
log_warn "No plugins are registered"
fi
else
log_warn "Could not check plugin status"
fi
# Check PATH
log_info "Checking PATH configuration..."
if command -v nu >/dev/null 2>&1; then
log_success "Nushell is available in PATH"
else
log_warn "Nushell is not in PATH. You may need to restart your terminal."
fi
log_success "Installation verification complete!"
return 0
}
# Uninstall function
uninstall_nushell() {
log_header "Uninstalling Nushell"
local removed_files=0
# Remove from user directory
if [ -d "$INSTALL_DIR_USER" ]; then
for binary in nu nu_plugin_*; do
local file_path="$INSTALL_DIR_USER/$binary"
if [ -f "$file_path" ]; then
rm -f "$file_path"
log_success "Removed $binary from $INSTALL_DIR_USER"
removed_files=$((removed_files + 1))
fi
done
fi
# Remove from system directory (if accessible)
if [ -w "$INSTALL_DIR_SYSTEM" ] 2>/dev/null; then
for binary in nu nu_plugin_*; do
local file_path="$INSTALL_DIR_SYSTEM/$binary"
if [ -f "$file_path" ]; then
rm -f "$file_path"
log_success "Removed $binary from $INSTALL_DIR_SYSTEM"
removed_files=$((removed_files + 1))
fi
done
fi
# Option to remove configuration
printf "Remove nushell configuration directory ($CONFIG_DIR)? [y/N]: "
read -r response
case "$response" in
[yY]|[yY][eE][sS])
if [ -d "$CONFIG_DIR" ]; then
rm -rf "$CONFIG_DIR"
log_success "Removed configuration directory"
fi
;;
*)
log_info "Configuration directory preserved"
;;
esac
if [ $removed_files -gt 0 ]; then
log_success "Uninstallation complete ($removed_files files removed)"
log_warn "You may need to manually remove PATH entries from your shell configuration"
else
log_warn "No nushell files found to remove"
fi
}
# Main installation function
main() {
local install_mode="user"
local modify_path="true"
local create_config="true"
local include_plugins="true"
local build_from_source="false"
local verify_install="false"
local do_uninstall="false"
local version=""
# Parse command line arguments
while [ $# -gt 0 ]; do
case "$1" in
--system)
install_mode="system"
shift
;;
--user)
install_mode="user"
shift
;;
--no-path)
modify_path="false"
shift
;;
--no-config)
create_config="false"
shift
;;
--no-plugins)
include_plugins="false"
shift
;;
--build-from-source)
build_from_source="true"
shift
;;
--verify)
verify_install="true"
shift
;;
--uninstall)
do_uninstall="true"
shift
;;
--version)
version="$2"
shift 2
;;
--help)
usage
exit 0
;;
*)
log_error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Handle uninstall
if [ "$do_uninstall" = "true" ]; then
uninstall_nushell
exit 0
fi
# Show header
log_header "Nushell + Plugins Installer"
log_info "Universal bootstrap installer for Nushell and plugins"
log_info ""
# Detect platform
local platform
platform=$(detect_platform)
log_info "Detected platform: $platform"
# Determine installation directory
local install_dir
if [ "$install_mode" = "system" ]; then
install_dir="$INSTALL_DIR_SYSTEM"
if [ "$(id -u)" != "0" ] && [ ! -w "$(dirname "$install_dir")" ]; then
log_error "System installation requires root privileges"
log_info "Run with sudo or use --user for user installation"
exit 1
fi
else
install_dir="$INSTALL_DIR_USER"
fi
log_info "Installing to: $install_dir"
# Get version if not specified
if [ -z "$version" ]; then
version=$(get_latest_version)
fi
log_info "Version: $version"
# Cleanup function
cleanup() {
if [ -d "$TEMP_DIR" ]; then
rm -rf "$TEMP_DIR"
fi
}
trap cleanup EXIT
# Install based on method
if [ "$build_from_source" = "true" ]; then
if ! install_from_source "$install_dir" "$include_plugins"; then
log_error "Source installation failed"
exit 1
fi
else
if ! install_from_binaries "$platform" "$version" "$install_dir" "$include_plugins"; then
log_error "Binary installation failed"
exit 1
fi
fi
# Register plugins
if [ "$include_plugins" = "true" ]; then
register_plugins "$install_dir"
fi
# Update PATH
if [ "$modify_path" = "true" ]; then
update_shell_path "$install_dir"
fi
# Create configuration
if [ "$create_config" = "true" ]; then
create_nushell_config
fi
# Verify installation
if [ "$verify_install" = "true" ]; then
if ! verify_installation "$install_dir"; then
log_error "Installation verification failed"
exit 1
fi
fi
# Final success message
log_header "Installation Complete!"
log_success "Nushell has been successfully installed to $install_dir"
if [ "$include_plugins" = "true" ]; then
log_success "Plugins have been registered with Nushell"
fi
if [ "$modify_path" = "true" ]; then
log_info "To use Nushell, restart your terminal or run:"
log_info " source ~/.bashrc # or your shell's config file"
fi
log_info ""
log_info "Try running: nu --version"
log_info "Or start Nushell with: nu"
if [ "$include_plugins" = "true" ]; then
log_info "Check plugins with: nu -c 'plugin list'"
fi
log_info ""
log_info "For more information, visit: https://nushell.sh"
log_info ""
log_success "Happy shell scripting! 🚀"
}
# Run main function with all arguments
main "$@"

View File

@ -9,6 +9,7 @@ import 'justfiles/alias.just'
import 'justfiles/help.just'
import 'justfiles/build.just'
import 'justfiles/distro.just'
import 'justfiles/full_distro.just'
import 'justfiles/upstream.just'
import 'justfiles/qa.just'
import 'justfiles/tools.just'

326
justfiles/full_distro.just Normal file
View File

@ -0,0 +1,326 @@
# Full Distribution Module - Complete Nushell Distribution
# Commands for building, collecting, and packaging complete Nushell distributions
# 🚀 NUSHELL BUILD COMMANDS
# Build nushell with all workspace plugins
[no-cd]
build-nushell:
@echo "🔨 Building Nushell with all workspace plugins..."
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu --release
# Build nushell in debug mode
[no-cd]
build-nushell-debug:
@echo "🔨 Building Nushell (debug mode)..."
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu
# Build nushell for specific target
[no-cd]
build-nushell-target TARGET:
@echo "🔨 Building Nushell for {{TARGET}}..."
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu --release --target {{TARGET}}
# Build nushell with custom features
[no-cd]
build-nushell-features FEATURES:
@echo "🔨 Building Nushell with features: {{FEATURES}}..."
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu --release --features {{FEATURES}}
# Build nushell with custom profile
[no-cd]
build-nushell-profile PROFILE:
@echo "🔨 Building Nushell with profile: {{PROFILE}}..."
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu --profile {{PROFILE}}
# Check nushell build dependencies
[no-cd]
build-nushell-deps:
@echo "🔍 Checking Nushell build dependencies..."
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu deps
# Show nushell build information
[no-cd]
build-nushell-info:
@echo " Nushell build information..."
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu info
# 📦 FULL BUILD WORKFLOWS
# Build everything: nushell + all plugins
[no-cd]
build-full:
@echo "🚀 Building complete distribution (nushell + all plugins)..."
@just validate-nushell
@just build-nushell
@just build
# Build full release (optimized)
[no-cd]
build-full-release:
@echo "🚀 Building full release distribution..."
@just validate-nushell
@just build-nushell
@just build-release
# Build full distribution for specific target
[no-cd]
build-full-target TARGET:
@echo "🚀 Building full distribution for {{TARGET}}..."
@just validate-nushell
@just build-nushell-target {{TARGET}}
@just build-cross {{TARGET}}
# Build full distribution for all targets
[no-cd]
build-full-cross:
@echo "🌍 Building full distribution for all targets..."
@just validate-nushell
@just build-nushell
@just build-cross-all
# 📋 FULL COLLECTION COMMANDS
# Collect all binaries (nushell + plugins)
[no-cd]
collect-full:
@echo "📦 Collecting full binaries (nushell + plugins)..."
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --release --force
# Collect full binaries in debug mode
[no-cd]
collect-full-debug:
@echo "📦 Collecting full binaries (debug mode)..."
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --force
# List available full binaries
[no-cd]
collect-full-list:
@echo "📋 Available full binaries:"
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --list
# List available platforms for full collection
[no-cd]
collect-full-platforms:
@echo "🌍 Available platforms for full collection:"
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --list-platforms
# Collect full binaries for specific platform
[no-cd]
collect-full-platform PLATFORM:
@echo "📦 Collecting full binaries for {{PLATFORM}}..."
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --platform {{PLATFORM}} --release --force
# Collect full binaries for all platforms
[no-cd]
collect-full-all:
@echo "🌍 Collecting full binaries for all platforms..."
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --all-platforms --release --force
# Collect only plugins (exclude nushell)
[no-cd]
collect-plugins-only:
@echo "📦 Collecting plugins only..."
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --release --force --include-nushell=false
# Collect full binaries with custom profile
[no-cd]
collect-full-profile PROFILE:
@echo "📦 Collecting full binaries with profile {{PROFILE}}..."
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --profile {{PROFILE}} --force
# 📦 FULL PACKAGING COMMANDS
# Create full distribution package
[no-cd]
pack-full:
@echo "📦 Creating full distribution package..."
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --force
# Package full distribution for specific platform
[no-cd]
pack-full-platform PLATFORM:
@echo "📦 Packaging full distribution for {{PLATFORM}}..."
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --platform {{PLATFORM}} --force
# Package full distribution for all platforms
[no-cd]
pack-full-all:
@echo "🌍 Packaging full distribution for all platforms..."
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --all-platforms --force
# Package full distribution with checksums
[no-cd]
pack-full-checksums:
@echo "🔐 Packaging full distribution with checksums..."
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --all-platforms --checksums --force
# Package full distribution with bootstrap installers
[no-cd]
pack-full-bootstrap:
@echo "🚀 Packaging full distribution with bootstrap installers..."
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --all-platforms --checksums --bootstrap --force
# Show what would be packaged
[no-cd]
pack-full-list:
@echo "📋 Files that would be packaged in full distribution:"
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --list
# 🚀 FULL RELEASE WORKFLOWS
# Complete full release workflow
[no-cd]
release-full:
@echo "🚀 Running complete full release workflow..."
@just validate-nushell
@just build-full-release
@just collect-full
@just pack-full-checksums
# Cross-platform full release
[no-cd]
release-full-cross:
@echo "🌍 Running cross-platform full release workflow..."
@just validate-nushell
@just build-full-cross
@just collect-full-all
@just pack-full-bootstrap
# Quick full release for current platform
[no-cd]
release-full-quick:
@echo "⚡ Quick full release for current platform..."
@just validate-nushell
@just build-full-release
@just collect-full
@just pack-full
# Full release for specific target
[no-cd]
release-full-target TARGET:
@echo "🎯 Full release for {{TARGET}}..."
@just validate-nushell
@just build-full-target {{TARGET}}
@just collect-full-platform {{TARGET}}
@just pack-full-platform {{TARGET}}
# Development full release (debug mode)
[no-cd]
release-full-dev:
@echo "🛠️ Development full release..."
@just validate-nushell
@just build-full
@just collect-full-debug
@just pack-full
# 🔍 VERIFICATION COMMANDS
# Verify full installation
[no-cd]
verify-full:
@echo "🔍 Verifying full installation..."
@{{justfile_directory()}}/scripts/run.sh verify_installation.nu
# Test full installation locally
[no-cd]
test-install-full:
@echo "🧪 Testing full installation locally..."
@{{justfile_directory()}}/scripts/run.sh install_full_nushell.nu --test --verify
# Verify specific platform installation
[no-cd]
verify-full-platform PLATFORM:
@echo "🔍 Verifying {{PLATFORM}} installation..."
@{{justfile_directory()}}/scripts/run.sh verify_installation.nu --platform {{PLATFORM}}
# 📊 FULL DISTRIBUTION INFORMATION
# Show full distribution status
[no-cd]
full-status:
@echo "📊 Full Distribution Status"
@echo "=========================="
@echo ""
@echo "🔨 Build Status:"
@if [ -f "nushell/target/release/nu" ] || [ -f "nushell/target/release/nu.exe" ]; then \
echo " ✅ Nushell binary built"; \
else \
echo " ❌ Nushell binary not built"; \
fi
@echo ""
@echo "📦 Collection Status:"
@if [ -d "distribution" ]; then \
echo " ✅ Distribution directory exists"; \
echo " 📁 Platforms: $$(find distribution -maxdepth 1 -type d ! -name distribution | wc -l)"; \
else \
echo " ❌ Distribution directory not found"; \
fi
@echo ""
@echo "📦 Packaging Status:"
@if [ -d "bin_archives" ]; then \
echo " ✅ Package archives exist"; \
echo " 📦 Archives: $$(ls bin_archives/*.tar.gz bin_archives/*.zip 2>/dev/null | wc -l)"; \
else \
echo " ❌ No package archives found"; \
fi
# Show full distribution help
[no-cd]
full-help:
@echo "🚀 Full Distribution Module Help"
@echo "==============================="
@echo ""
@echo "NUSHELL BUILD:"
@echo " build-nushell - Build nushell with workspace plugins"
@echo " build-nushell-debug - Build nushell (debug mode)"
@echo " build-nushell-target TARGET - Build nushell for specific target"
@echo " build-nushell-features FEAT - Build nushell with custom features"
@echo " build-nushell-deps - Check nushell build dependencies"
@echo " build-nushell-info - Show nushell build information"
@echo ""
@echo "FULL BUILD WORKFLOWS:"
@echo " build-full - Build nushell + all plugins"
@echo " build-full-release - Build full release distribution"
@echo " build-full-target TARGET - Build full distribution for target"
@echo " build-full-cross - Build full distribution for all targets"
@echo ""
@echo "FULL COLLECTION:"
@echo " collect-full - Collect all binaries (nushell + plugins)"
@echo " collect-full-debug - Collect full binaries (debug mode)"
@echo " collect-full-list - List available full binaries"
@echo " collect-full-platforms - List available platforms"
@echo " collect-full-platform PLAT - Collect for specific platform"
@echo " collect-full-all - Collect for all platforms"
@echo " collect-plugins-only - Collect only plugins (no nushell)"
@echo ""
@echo "FULL PACKAGING:"
@echo " pack-full - Create full distribution package"
@echo " pack-full-platform PLATFORM - Package for specific platform"
@echo " pack-full-all - Package for all platforms"
@echo " pack-full-checksums - Package with checksums"
@echo " pack-full-bootstrap - Package with bootstrap installers"
@echo " pack-full-list - Show what would be packaged"
@echo ""
@echo "FULL RELEASE WORKFLOWS:"
@echo " release-full - Complete full release workflow"
@echo " release-full-cross - Cross-platform full release"
@echo " release-full-quick - Quick full release (current platform)"
@echo " release-full-target TARGET - Full release for specific target"
@echo " release-full-dev - Development full release (debug)"
@echo ""
@echo "VERIFICATION:"
@echo " verify-full - Verify full installation"
@echo " test-install-full - Test full installation locally"
@echo " verify-full-platform PLAT - Verify platform installation"
@echo ""
@echo "INFORMATION:"
@echo " full-status - Show full distribution status"
@echo " full-help - Show this help"
@echo ""
@echo "EXAMPLES:"
@echo " just build-full-release"
@echo " just collect-full-all"
@echo " just pack-full-bootstrap"
@echo " just release-full-cross"
@echo " just verify-full"

274
scripts/build_nushell.nu Executable file
View File

@ -0,0 +1,274 @@
#!/usr/bin/env nu
# Build Nushell with all workspace plugins
#
# This script builds the complete nushell binary with all built-in plugins
# from the nushell workspace, optimized for distribution.
use lib/common_lib.nu [log_info, log_error, log_success, log_warn, validate_nushell_version]
def main [
--release (-r) # Build in release mode (default: debug)
--features (-f): string = "" # Additional cargo features to enable
--target (-t): string = "" # Build target (cross-compilation)
--verbose (-v) # Enable verbose output
--profile (-p): string = "" # Build profile (release, dev, etc.)
] {
log_info "🔨 Building Nushell with workspace plugins..."
# Validate nushell version consistency first
validate_nushell_version
# Get nushell directory
let nushell_dir = $"($env.PWD)/nushell"
# Validate nushell directory exists
if not ($nushell_dir | path exists) {
log_error $"Nushell directory not found: ($nushell_dir)"
exit 1
}
# Build cargo command
mut cargo_cmd = ["cargo", "build"]
# Add build mode
if $release != null {
$cargo_cmd = ($cargo_cmd | append "--release")
log_info "🚀 Building in release mode"
} else {
log_info "🛠️ Building in debug mode"
}
# Add profile if specified
if ($profile | is-not-empty) {
$cargo_cmd = ($cargo_cmd | append ["--profile", $profile])
log_info $"📋 Using profile: ($profile)"
}
# Add target if specified
if ($target | is-not-empty) {
$cargo_cmd = ($cargo_cmd | append ["--target", $target])
log_info $"🎯 Building for target: ($target)"
}
# Determine features to enable
mut all_features = [
"plugin" # Enable plugin system
"network" # Network commands
"sqlite" # SQLite support
"trash-support" # Trash commands
"rustls-tls" # TLS support
"system-clipboard" # System clipboard support
]
# Add additional features if provided
if ($features | is-not-empty) {
let custom_features = ($features | split row ",")
$all_features = ($all_features | append $custom_features)
}
# Add features to cargo command
let features_str = ($all_features | str join ",")
$cargo_cmd = ($cargo_cmd | append ["--features", $features_str])
log_info $"✨ Enabled features: ($features_str)"
# Add verbose flag if requested
if $verbose != null {
$cargo_cmd = ($cargo_cmd | append "--verbose")
log_info "📢 Verbose output enabled"
}
# Change to nushell directory
log_info $"📁 Working in: ($nushell_dir)"
try {
# Execute cargo build
let cargo_cmd_str = ($cargo_cmd | str join " ")
log_info $"🔨 Running: ($cargo_cmd_str)"
cd $nushell_dir
let result = bash -c $cargo_cmd_str
# Determine output directory based on build mode and target
mut target_dir = $"($nushell_dir)/target"
if ($target | is-not-empty) {
$target_dir = $"($target_dir)/($target)"
}
mut profile_dir = if $release != null {
"release"
} else if ($profile | is-not-empty) {
$profile
} else {
"debug"
}
let binary_path = $"($target_dir)/($profile_dir)/nu"
let binary_path = if $nu.os-info.name == "windows" {
$"($binary_path).exe"
} else {
$binary_path
}
# Check if binary was created
if ($binary_path | path exists) {
let binary_size = (ls $binary_path | get size.0)
log_success $"✅ Nushell binary built successfully!"
log_info $"📄 Binary location: ($binary_path)"
log_info $"📊 Binary size: ($binary_size)"
# Show binary info
try {
let version_info = run-external $binary_path "--version" | complete
if $version_info.exit_code == 0 {
log_info $"🔢 Version: ($version_info.stdout | str trim)"
}
} catch {
log_warn "⚠️ Could not get version information"
}
# List workspace plugins that are built-in
log_info "🧩 Built-in workspace plugins:"
let workspace_plugins = [
"nu_plugin_custom_values"
"nu_plugin_example"
"nu_plugin_formats"
"nu_plugin_gstat"
"nu_plugin_inc"
"nu_plugin_polars"
"nu_plugin_query"
"nu_plugin_stress_internals"
]
for plugin in $workspace_plugins {
let plugin_binary = $"($target_dir)/($profile_dir)/($plugin)"
let plugin_binary = if $nu.os-info.name == "windows" {
$"($plugin_binary).exe"
} else {
$plugin_binary
}
if ($plugin_binary | path exists) {
let plugin_size = (ls $plugin_binary | get size.0)
log_info $" ✅ ($plugin): ($plugin_size)"
} else {
log_warn $" ❌ ($plugin): not built"
}
}
return {
success: true
binary_path: $binary_path
target_dir: $target_dir
profile_dir: $profile_dir
features: $features_str
}
} else {
log_error $"❌ Binary not found at expected location: ($binary_path)"
exit 1
}
} catch { |err|
log_error $"❌ Build failed: ($err.msg)"
exit 1
}
}
# Show build information
def "build info" [] {
log_info "🔨 Nushell Build Information"
log_info "==========================="
log_info ""
log_info "Available features:"
log_info " plugin - Enable plugin system"
log_info " network - Network commands (http, etc.)"
log_info " sqlite - SQLite database support"
log_info " trash-support - Trash/recycle bin commands"
log_info " rustls-tls - TLS support with rustls"
log_info " system-clipboard - System clipboard integration"
log_info " native-tls - TLS support with native libs"
log_info " static-link-openssl - Statically link OpenSSL"
log_info ""
log_info "Build profiles:"
log_info " dev - Debug build (default)"
log_info " release - Optimized release build"
log_info " profiling - Release with debug info"
log_info " ci - CI optimized build"
log_info ""
log_info "Workspace plugins (built-in):"
log_info " nu_plugin_custom_values - Custom value handling"
log_info " nu_plugin_example - Example plugin"
log_info " nu_plugin_formats - Additional file formats"
log_info " nu_plugin_gstat - Git status plugin"
log_info " nu_plugin_inc - Increment plugin"
log_info " nu_plugin_polars - DataFrame support"
log_info " nu_plugin_query - Query plugin"
log_info " nu_plugin_stress_internals - Stress testing"
}
# Check build dependencies
def "build deps" [] {
log_info "🔍 Checking build dependencies..."
# Check Rust toolchain
try {
let rust_version = run-external "rustc" "--version" | complete
if $rust_version.exit_code == 0 {
log_success $"✅ Rust: ($rust_version.stdout | str trim)"
} else {
log_error "❌ Rust compiler not found"
return false
}
} catch {
log_error "❌ Rust compiler not found"
return false
}
# Check Cargo
try {
let cargo_version = run-external "cargo" "--version" | complete
if $cargo_version.exit_code == 0 {
log_success $"✅ Cargo: ($cargo_version.stdout | str trim)"
} else {
log_error "❌ Cargo not found"
return false
}
} catch {
log_error "❌ Cargo not found"
return false
}
# Check nushell directory
let nushell_dir = $"($env.PWD)/nushell"
if ($nushell_dir | path exists) {
log_success $"✅ Nushell source: ($nushell_dir)"
} else {
log_error $"❌ Nushell source not found: ($nushell_dir)"
return false
}
# Check Cargo.toml
let cargo_toml = $"($nushell_dir)/Cargo.toml"
if ($cargo_toml | path exists) {
log_success $"✅ Nushell Cargo.toml found"
# Show workspace members
try {
let cargo_content = open $cargo_toml
if "workspace" in $cargo_content {
let members = $cargo_content.workspace.members | length
log_info $"📦 Workspace members: ($members)"
}
} catch {
log_warn "⚠️ Could not parse Cargo.toml"
}
} else {
log_error $"❌ Nushell Cargo.toml not found: ($cargo_toml)"
return false
}
log_success "✅ All build dependencies satisfied"
return true
}

656
scripts/collect_full_binaries.nu Executable file
View File

@ -0,0 +1,656 @@
#!/usr/bin/env nu
# Collect Full Binaries Script
# Collects nushell binary, workspace plugins, and custom plugins for full distribution
use lib/common_lib.nu [log_info, log_error, log_success, log_warn, validate_nushell_version]
def main [
--platform (-p): string = "" # Target platform (e.g., linux-x86_64, darwin-arm64)
--output (-o): string = "distribution" # Output directory
--force (-f): bool = false # Force overwrite existing files
--list (-l): bool = false # List available binaries
--list-platforms: bool = false # List available platforms
--all-platforms: bool = false # Collect for all available platforms
--include-nushell (-n): bool = true # Include nushell binary (default: true)
--release (-r): bool = false # Use release builds (default: debug)
--profile: string = "" # Build profile to use
] {
# Validate nushell version consistency first
validate_nushell_version
if $list_platforms {
list_available_platforms
return
}
if $list {
list_available_binaries $platform $release $profile
return
}
if $all_platforms {
collect_all_platforms $output $force $include_nushell $release $profile
return
}
# Collect binaries for specific or current platform
collect_binaries $platform $output $force $include_nushell $release $profile
}
# List available platforms for collection
def list_available_platforms [] {
log_info "🌍 Available platforms for collection:"
log_info "===================================="
let platforms = detect_available_platforms
if ($platforms | is-empty) {
log_warn "⚠️ No built binaries found. Build plugins first with 'just build' or 'just build-cross-all'"
return
}
for platform in $platforms {
log_info $" ✅ ($platform.name)"
log_info $" Target: ($platform.target)"
log_info $" Nushell: ($platform.has_nushell)"
log_info $" Plugins: ($platform.plugin_count)"
log_info ""
}
}
# List available binaries for a platform
def list_available_binaries [
platform: string
use_release: bool
profile: string
] {
log_info "📋 Available binaries:"
log_info "====================="
let current_platform = if ($platform | is-empty) {
detect_current_platform
} else {
$platform
}
log_info $"Platform: ($current_platform)"
log_info ""
# Check nushell binary
let nushell_info = get_nushell_binary_info $current_platform $use_release $profile
if ($nushell_info | is-not-empty) {
log_info "🚀 Nushell Binary:"
log_info $" Path: ($nushell_info.path)"
log_info $" Size: ($nushell_info.size)"
log_info $" Modified: ($nushell_info.modified)"
log_info ""
} else {
log_warn "⚠️ Nushell binary not found for this platform"
log_info ""
}
# Check workspace plugins
let workspace_plugins = get_workspace_plugins_info $current_platform $use_release $profile
if not ($workspace_plugins | is-empty) {
log_info "🧩 Workspace Plugins:"
for plugin in $workspace_plugins {
log_info $" ✅ ($plugin.name) - ($plugin.size)"
}
log_info ""
}
# Check custom plugins
let custom_plugins = get_custom_plugins_info $current_platform $use_release $profile
if not ($custom_plugins | is-empty) {
log_info "🔧 Custom Plugins:"
for plugin in $custom_plugins {
log_info $" ✅ ($plugin.name) - ($plugin.size)"
}
log_info ""
}
let total_count = (
($workspace_plugins | length) +
($custom_plugins | length) +
(if ($nushell_info | is-not-empty) { 1 } else { 0 })
)
log_info $"📊 Total binaries: ($total_count)"
}
# Collect binaries for all platforms
def collect_all_platforms [
output: string
force: bool
include_nushell: bool
use_release: bool
profile: string
] {
log_info "🌍 Collecting binaries for all platforms..."
let platforms = detect_available_platforms
if ($platforms | is-empty) {
log_error "❌ No built binaries found. Build first with 'just build-cross-all'"
exit 1
}
for platform in $platforms {
log_info $"📦 Collecting for ($platform.name)..."
try {
collect_binaries $platform.target $output $force $include_nushell $use_release $profile
} catch { |err|
log_error $"❌ Failed to collect ($platform.name): ($err.msg)"
}
}
log_success "✅ Collection complete for all platforms"
}
# Main collection function
def collect_binaries [
platform: string
output: string
force: bool
include_nushell: bool
use_release: bool
profile: string
] {
let target_platform = if ($platform | is-empty) {
detect_current_platform
} else {
$platform
}
log_info $"📦 Collecting binaries for platform: ($target_platform)"
# Create output structure
let platform_dir = $"($output)/($target_platform)"
create_target_structure $platform_dir $force
mut collected_files = []
# Collect nushell binary if requested
if $include_nushell {
let nushell_info = get_nushell_binary_info $target_platform $use_release $profile
if ($nushell_info | is-not-empty) {
let dest_path = $"($platform_dir)/nu"
let dest_path = if $nu.os-info.name == "windows" { $"($dest_path).exe" } else { $dest_path }
copy_binary $nushell_info.path $dest_path $force
$collected_files = ($collected_files | append {
name: "nu"
type: "nushell"
path: $dest_path
size: (get_file_size $dest_path)
})
log_success $"✅ Collected nushell binary"
} else {
log_warn "⚠️ Nushell binary not found for platform ($target_platform)"
}
}
# Collect workspace plugins
let workspace_plugins = get_workspace_plugins_info $target_platform $use_release $profile
for plugin in $workspace_plugins {
let dest_path = $"($platform_dir)/($plugin.name)"
copy_binary $plugin.path $dest_path $force
$collected_files = ($collected_files | append {
name: $plugin.name
type: "workspace_plugin"
path: $dest_path
size: (get_file_size $dest_path)
})
}
# Collect custom plugins
let custom_plugins = get_custom_plugins_info $target_platform $use_release $profile
for plugin in $custom_plugins {
let dest_path = $"($platform_dir)/($plugin.name)"
copy_binary $plugin.path $dest_path $force
$collected_files = ($collected_files | append {
name: $plugin.name
type: "custom_plugin"
path: $dest_path
size: (get_file_size $dest_path)
})
}
# Copy additional files
copy_additional_files $platform_dir $force
# Create manifest
create_manifest $platform_dir $target_platform $collected_files $include_nushell
# Create installation script
create_installation_scripts $platform_dir
# Summary
let total_size = ($collected_files | get size | math sum)
log_success $"✅ Collection complete for ($target_platform)"
log_info $"📊 Collected ($collected_files | length) binaries"
log_info $"📁 Output directory: ($platform_dir)"
log_info $"💾 Total size: ($total_size)"
return {
platform: $target_platform
output_dir: $platform_dir
files: $collected_files
total_size: $total_size
}
}
# Get nushell binary information
def get_nushell_binary_info [
platform: string
use_release: bool
profile: string
]: nothing -> record {
let nushell_dir = $"($env.PWD)/nushell"
let mut target_dir = $"($nushell_dir)/target"
# Handle cross-compilation targets
let target_triple = convert_platform_to_target $platform
if ($target_triple | is-not-empty) and $target_triple != (detect_current_target) {
$target_dir = $"($target_dir)/($target_triple)"
}
# Determine profile directory
let mut profile_dir = if ($profile | is-not-empty) {
$profile
} else if $use_release {
"release"
} else {
"debug"
}
let binary_name = if ($platform | str contains "windows") or $nu.os-info.name == "windows" {
"nu.exe"
} else {
"nu"
}
let binary_path = $"($target_dir)/($profile_dir)/($binary_name)"
if ($binary_path | path exists) {
let file_info = ls $binary_path | get 0
return {
name: "nu"
path: $binary_path
size: $file_info.size
modified: $file_info.modified
}
}
return {}
}
# Get workspace plugins information
def get_workspace_plugins_info [
platform: string
use_release: bool
profile: string
]: nothing -> list<record> {
let nushell_dir = $"($env.PWD)/nushell"
let mut target_dir = $"($nushell_dir)/target"
# Handle cross-compilation targets
let target_triple = convert_platform_to_target $platform
if ($target_triple | is-not-empty) and $target_triple != (detect_current_target) {
$target_dir = $"($target_dir)/($target_triple)"
}
# Determine profile directory
let mut profile_dir = if ($profile | is-not-empty) {
$profile
} else if $use_release {
"release"
} else {
"debug"
}
let workspace_plugins = [
"nu_plugin_custom_values"
"nu_plugin_example"
"nu_plugin_formats"
"nu_plugin_gstat"
"nu_plugin_inc"
"nu_plugin_polars"
"nu_plugin_query"
"nu_plugin_stress_internals"
]
mut found_plugins = []
for plugin in $workspace_plugins {
let binary_name = if ($platform | str contains "windows") or $nu.os-info.name == "windows" {
$"($plugin).exe"
} else {
$plugin
}
let binary_path = $"($target_dir)/($profile_dir)/($binary_name)"
if ($binary_path | path exists) {
let file_info = ls $binary_path | get 0
$found_plugins = ($found_plugins | append {
name: $plugin
path: $binary_path
size: $file_info.size
modified: $file_info.modified
})
}
}
return $found_plugins
}
# Get custom plugins information
def get_custom_plugins_info [
platform: string
use_release: bool
profile: string
]: nothing -> list<record> {
# Get list of plugin directories (nu_plugin_*)
let plugin_dirs = ls nu_plugin_*
| where type == dir
| where name != "nushell" # Exclude nushell submodule
| get name
mut found_plugins = []
let target_triple = convert_platform_to_target $platform
for plugin_dir in $plugin_dirs {
let mut target_dir = $"($plugin_dir)/target"
# Handle cross-compilation targets
if ($target_triple | is-not-empty) and $target_triple != (detect_current_target) {
$target_dir = $"($target_dir)/($target_triple)"
}
# Determine profile directory
let mut profile_dir = if ($profile | is-not-empty) {
$profile
} else if $use_release {
"release"
} else {
"debug"
}
let binary_name = if ($platform | str contains "windows") or $nu.os-info.name == "windows" {
$"($plugin_dir).exe"
} else {
$plugin_dir
}
let binary_path = $"($target_dir)/($profile_dir)/($binary_name)"
if ($binary_path | path exists) {
let file_info = ls $binary_path | get 0
$found_plugins = ($found_plugins | append {
name: $plugin_dir
path: $binary_path
size: $file_info.size
modified: $file_info.modified
})
}
}
return $found_plugins
}
# Utility functions
def detect_current_platform []: nothing -> string {
let os = $nu.os-info.name
let arch = $nu.os-info.arch
match [$os, $arch] {
["linux", "x86_64"] => "linux-x86_64"
["linux", "aarch64"] => "linux-aarch64"
["macos", "x86_64"] => "darwin-x86_64"
["macos", "aarch64"] => "darwin-arm64"
["windows", "x86_64"] => "windows-x86_64"
["windows", "aarch64"] => "windows-aarch64"
_ => $"($os)-($arch)"
}
}
def detect_current_target []: nothing -> string {
let os = $nu.os-info.name
let arch = $nu.os-info.arch
match [$os, $arch] {
["linux", "x86_64"] => "x86_64-unknown-linux-gnu"
["linux", "aarch64"] => "aarch64-unknown-linux-gnu"
["macos", "x86_64"] => "x86_64-apple-darwin"
["macos", "aarch64"] => "aarch64-apple-darwin"
["windows", "x86_64"] => "x86_64-pc-windows-msvc"
["windows", "aarch64"] => "aarch64-pc-windows-msvc"
_ => $"($arch)-unknown-($os)"
}
}
def convert_platform_to_target [platform: string]: nothing -> string {
match $platform {
"linux-x86_64" => "x86_64-unknown-linux-gnu"
"linux-aarch64" => "aarch64-unknown-linux-gnu"
"darwin-x86_64" => "x86_64-apple-darwin"
"darwin-arm64" => "aarch64-apple-darwin"
"windows-x86_64" => "x86_64-pc-windows-msvc"
"windows-aarch64" => "aarch64-pc-windows-msvc"
_ => $platform
}
}
def detect_available_platforms []: nothing -> list<record> {
mut platforms = []
# Check nushell target directory for built platforms
let nushell_target = $"($env.PWD)/nushell/target"
if ($nushell_target | path exists) {
let target_dirs = try { ls $nushell_target | where type == dir | get name } catch { [] }
for target_dir in $target_dirs {
if ($target_dir | str ends-with "target") { continue } # Skip base target dir
let target_name = ($target_dir | path basename)
let platform_name = convert_target_to_platform $target_name
# Check if this target has binaries
let release_dir = $"($target_dir)/release"
let debug_dir = $"($target_dir)/debug"
let has_release = ($release_dir | path exists) and (try { ls $release_dir | where name =~ "nu" | length } catch { 0 }) > 0
let has_debug = ($debug_dir | path exists) and (try { ls $debug_dir | where name =~ "nu" | length } catch { 0 }) > 0
if $has_release or $has_debug {
$platforms = ($platforms | append {
name: $platform_name
target: $target_name
has_nushell: true
plugin_count: 0 # Will be calculated
})
}
}
}
# If no cross-compiled targets found, add current platform
if ($platforms | is-empty) {
let current_platform = detect_current_platform
let current_target = detect_current_target
$platforms = ($platforms | append {
name: $current_platform
target: $current_target
has_nushell: true
plugin_count: 0
})
}
return $platforms
}
def convert_target_to_platform [target: string]: nothing -> string {
match $target {
"x86_64-unknown-linux-gnu" => "linux-x86_64"
"aarch64-unknown-linux-gnu" => "linux-aarch64"
"x86_64-apple-darwin" => "darwin-x86_64"
"aarch64-apple-darwin" => "darwin-arm64"
"x86_64-pc-windows-msvc" => "windows-x86_64"
"aarch64-pc-windows-msvc" => "windows-aarch64"
_ => $target
}
}
def create_target_structure [target_path: string, force: bool] {
if ($target_path | path exists) and not $force {
log_error $"Target directory exists: ($target_path). Use --force to overwrite."
exit 1
}
if ($target_path | path exists) and $force {
rm -rf $target_path
log_warn $"🗑️ Removed existing directory: ($target_path)"
}
mkdir $target_path
log_info $"📁 Created target directory: ($target_path)"
# Create subdirectories
let subdirs = ["scripts", "config", "docs"]
for subdir in $subdirs {
mkdir $"($target_path)/($subdir)"
}
}
def copy_binary [src: string, dest: string, force: bool] {
if ($dest | path exists) and not $force {
log_warn $"⚠️ File exists, skipping: ($dest)"
return
}
cp $src $dest
chmod +x $dest
}
def copy_additional_files [target_path: string, force: bool] {
let files_to_copy = [
{src: "LICENSE", dest: "LICENSE", required: false}
{src: "README.md", dest: "README.md", required: false}
{src: "CHANGELOG.md", dest: "CHANGELOG.md", required: false}
]
for file in $files_to_copy {
if ($file.src | path exists) {
let dest_path = $"($target_path)/($file.dest)"
if not ($dest_path | path exists) or $force {
cp $file.src $dest_path
log_info $" 📄 Copied ($file.src) → ($file.dest)"
}
}
}
}
def create_manifest [
target_path: string
platform: string
files: list<record>
includes_nushell: bool
] {
let manifest = {
platform: $platform
created: (date now | format date %Y-%m-%d_%H-%M-%S)
includes_nushell: $includes_nushell
total_files: ($files | length)
files: $files
nushell_version: (try {
if $includes_nushell {
let nu_binary = ($files | where type == "nushell" | get 0)
if ($nu_binary | is-not-empty) {
run-external $nu_binary.path "--version" | complete | get stdout | str trim
} else {
"unknown"
}
} else {
"not_included"
}
} catch { "unknown" })
}
$manifest | to json | save --force $"($target_path)/manifest.json"
log_info $"📋 Created manifest: ($target_path)/manifest.json"
}
def create_installation_scripts [target_path: string] {
# Create a basic installation script that registers plugins
let install_script = $"#!/usr/bin/env nu
# Installation script for nushell and plugins
# This script registers all plugins with the nushell binary
def main [
--verify: bool = false # Verify installation after completion
] {
print \"🚀 Installing Nushell and plugins...\"
# Get current directory (should be the distribution directory)
let dist_dir = $env.PWD
# Find nushell binary
let nu_binary = if (\"nu\" | path exists) {
\"./nu\"
} else if (\"nu.exe\" | path exists) {
\"./nu.exe\"
} else {
print \"❌ Nushell binary not found in distribution\"
exit 1
}
# Find plugin binaries
let plugins = ls . | where type == file | where name =~ \"nu_plugin_\" | get name
print $\"📦 Found ($plugins | length) plugins to register\"
# Register each plugin
for plugin in $plugins {
print $\" Registering ($plugin)...\"
try {
run-external $nu_binary \"plugin\" \"add\" (\"./\" + $plugin)
} catch { |err|
print $\" ⚠️ Failed to register ($plugin): ($err.msg)\"
}
}
if $verify {
print \"🔍 Verifying installation...\"
try {
let plugin_list = run-external $nu_binary \"-c\" \"plugin list\" | complete
if $plugin_list.exit_code == 0 {
print \"✅ Plugin verification successful\"
print $plugin_list.stdout
} else {
print \"❌ Plugin verification failed\"
print $plugin_list.stderr
}
} catch { |err|
print $\"❌ Verification failed: ($err.msg)\"
}
}
print \"✅ Installation complete!\"
}
"
$install_script | save --force $"($target_path)/install.nu"
chmod +x $"($target_path)/install.nu"
log_info $"📜 Created installation script: ($target_path)/install.nu"
}
def get_file_size [path: string]: nothing -> int {
try {
ls $path | get size.0
} catch {
0
}
}

View File

@ -0,0 +1,599 @@
#!/usr/bin/env nu
# Create Distribution Packages Script
# Creates platform-specific distribution packages with nushell binary, plugins, and installers
use lib/common_lib.nu [
log_info, log_error, log_success, log_warn, log_debug,
validate_nushell_version, get_current_platform, get_supported_platforms,
get_binary_extension, get_archive_extension, ensure_dir, remove_dir,
copy_file, create_checksums, get_workspace_version, create_manifest
]
def main [
--platform (-p): string = "" # Target platform (e.g., linux-x86_64, darwin-arm64)
--output (-o): string = "bin_archives" # Output directory for packages
--force (-f) # Force overwrite existing packages
--list (-l) # List what would be packaged
--all-platforms # Create packages for all platforms
--checksums # Generate checksums for packages
--bootstrap # Include bootstrap installers
--include-docs # Include documentation
--version (-v): string = "" # Version override
] {
# Validate nushell version consistency first
validate_nushell_version
# Get version
let version = if ($version | str length) > 0 {
$version
} else {
get_workspace_version
}
if $list {
list_packageable_components $platform $version
return
}
if $all_platforms {
create_all_platform_packages $output $force $checksums $bootstrap $include_docs $version
return
}
# Create package for specific or current platform
let target_platform = if ($platform | str length) > 0 {
$platform
} else {
get_current_platform
}
create_platform_package $target_platform $output $force $checksums $bootstrap $include_docs $version
}
# List components that would be packaged
def list_packageable_components [platform: string, version: string] {
let target_platform = if ($platform | str length) > 0 {
$platform
} else {
get_current_platform
}
log_info $"📋 Components for platform: ($target_platform)"
log_info "================================================"
# Check nushell binary
let nushell_components = get_nushell_components $target_platform $version
log_info "🚀 Nushell Components:"
$nushell_components.binaries | each {|binary|
let status = if ($binary.path | path exists) { "✅" } else { "❌" }
log_info $" ($status) ($binary.name) - ($binary.path)"
}
# Check plugins
let plugin_components = get_plugin_components $target_platform $version
log_info ""
log_info "🔌 Plugin Components:"
$plugin_components.binaries | each {|binary|
let status = if ($binary.path | path exists) { "✅" } else { "❌" }
log_info $" ($status) ($binary.name) - ($binary.path)"
}
# Check configuration templates
let config_components = get_config_components $version
log_info ""
log_info "⚙️ Configuration Components:"
$config_components | each {|config|
let status = if ($config.path | path exists) { "✅" } else { "❌" }
log_info $" ($status) ($config.name) - ($config.path)"
}
# Check installers
let installer_components = get_installer_components $target_platform
log_info ""
log_info "📥 Installer Components:"
$installer_components | each {|installer|
let status = if ($installer.path | path exists) { "✅" } else { "❌" }
log_info $" ($status) ($installer.name) - ($installer.path)"
}
}
# Create packages for all supported platforms
def create_all_platform_packages [
output: string,
force: bool,
checksums: bool,
bootstrap: bool,
include_docs: bool,
version: string
] {
log_info "🌍 Creating packages for all supported platforms..."
let platforms = get_supported_platforms
mut successful_packages = []
mut failed_packages = []
for platform in $platforms {
log_info $""
log_info $"📦 Processing platform: ($platform)"
log_info "================================"
let result = try {
create_platform_package $platform $output $force $checksums $bootstrap $include_docs $version
"success"
} catch {|err|
log_error $"Failed to create package for ($platform): ($err.msg)"
"failed"
}
if $result == "success" {
$successful_packages = ($successful_packages | append $platform)
} else {
$failed_packages = ($failed_packages | append $platform)
}
}
# Summary
log_info ""
log_info "📊 Package Creation Summary"
log_info "========================="
log_success $"✅ Successful: ($successful_packages | length) platforms"
if ($successful_packages | length) > 0 {
$successful_packages | each {|platform| log_success $" - ($platform)"}
}
if ($failed_packages | length) > 0 {
log_error $"❌ Failed: ($failed_packages | length) platforms"
$failed_packages | each {|platform| log_error $" - ($platform)"}
}
# Create cross-platform checksums if requested
if $checksums != null {
create_cross_platform_checksums $output
}
}
# Create package for a specific platform
def create_platform_package [
platform: string,
output: string,
force: bool,
checksums: bool,
bootstrap: bool,
include_docs: bool,
version: string
] {
log_info $"📦 Creating package for platform: ($platform)"
# Validate platform
if not ($platform in (get_supported_platforms)) {
log_error $"Unsupported platform: ($platform). Supported: (get_supported_platforms | str join ', ')"
return
}
# Setup package directory structure
let package_name = $"nushell-full-($version)-($platform)"
let package_dir = $"($output)/($package_name)"
let archive_extension = get_archive_extension $platform
# Check if package already exists
let archive_path = $"($output)/($package_name)($archive_extension)"
if ($archive_path | path exists) and ($force == null) {
log_warn $"Package already exists: ($archive_path). Use --force to overwrite."
return
}
# Clean and create package directory
ensure_dir $output
remove_dir $package_dir
ensure_dir $package_dir
# Create package structure
ensure_dir $"($package_dir)/bin"
ensure_dir $"($package_dir)/config"
ensure_dir $"($package_dir)/scripts"
if $include_docs != null {
ensure_dir $"($package_dir)/docs"
}
# Package components
mut components = {}
# Package nushell binary
let nushell_components = package_nushell_components $platform $package_dir $version
$components = ($components | merge {nushell: $nushell_components})
# Package plugins
let plugin_components = package_plugin_components $platform $package_dir $version
$components = ($components | merge {plugins: $plugin_components})
# Package configuration
let config_components = package_config_components $package_dir $version
$components = ($components | merge {configs: $config_components})
# Package installers
let installer_components = package_installer_components $platform $package_dir $bootstrap
$components = ($components | merge {installers: $installer_components})
# Package documentation if requested
if $include_docs != null {
let docs_components = package_docs_components $package_dir
$components = ($components | merge {docs: $docs_components})
}
# Create manifest
create_manifest $version $platform $components $"($package_dir)/manifest.json"
# Create README for package
create_package_readme $platform $package_dir $version
# Create archive
create_package_archive $package_dir $archive_path $platform
# Create checksums if requested
if $checksums != null {
create_checksums [$archive_path] $output
}
# Clean up temporary directory
remove_dir $package_dir
log_success $"✅ Package created: ($archive_path)"
}
# Package nushell components
def package_nushell_components [platform: string, package_dir: string, version: string] {
log_info "📦 Packaging nushell components..."
let components = get_nushell_components $platform $version
mut packaged = []
for binary in $components.binaries {
if ($binary.path | path exists) {
let dest_path = $"($package_dir)/bin/($binary.name)"
if (copy_file $binary.path $dest_path) {
$packaged = ($packaged | append $binary.name)
}
} else {
log_warn $"Nushell binary not found: ($binary.path)"
}
}
{
binaries: $packaged,
version: $version,
platform: $platform
}
}
# Package plugin components
def package_plugin_components [platform: string, package_dir: string, version: string] {
log_info "📦 Packaging plugin components..."
let components = get_plugin_components $platform $version
mut packaged = []
for binary in $components.binaries {
if ($binary.path | path exists) {
let dest_path = $"($package_dir)/bin/($binary.name)"
if (copy_file $binary.path $dest_path) {
$packaged = ($packaged | append $binary.name)
}
} else {
log_warn $"Plugin binary not found: ($binary.path)"
}
}
{
binaries: $packaged,
count: ($packaged | length)
}
}
# Package configuration components
def package_config_components [package_dir: string, version: string] {
log_info "📦 Packaging configuration components..."
let components = get_config_components $version
mut packaged = []
for config in $components {
if ($config.path | path exists) {
let dest_path = $"($package_dir)/config/($config.name)"
if (copy_file $config.path $dest_path) {
$packaged = ($packaged | append $config.name)
}
} else {
log_warn $"Configuration file not found: ($config.path)"
}
}
# Create distribution-specific config
create_distribution_config $package_dir $version
$packaged = ($packaged | append "distribution_config.toml")
{
files: $packaged,
count: ($packaged | length)
}
}
# Package installer components
def package_installer_components [platform: string, package_dir: string, bootstrap: bool] {
log_info "📦 Packaging installer components..."
let components = get_installer_components $platform
mut packaged = []
for installer in $components {
if ($installer.path | path exists) {
let dest_path = $"($package_dir)/($installer.name)"
if (copy_file $installer.path $dest_path) {
# Make installer executable on Unix-like systems
if not ($platform | str starts-with "windows") {
try { chmod +x $dest_path }
}
$packaged = ($packaged | append $installer.name)
}
} else {
log_warn $"Installer not found: ($installer.path)"
}
}
# Create uninstaller
create_uninstaller $platform $package_dir
$packaged = ($packaged | append "uninstall.sh")
{
files: $packaged,
bootstrap: $bootstrap
}
}
# Package documentation components
def package_docs_components [package_dir: string] {
log_info "📦 Packaging documentation components..."
let docs = [
{name: "README.md", path: "./README.md"},
{name: "CLAUDE.md", path: "./CLAUDE.md"},
{name: "LICENSE", path: "./LICENSE"}
]
mut packaged = []
for doc in $docs {
if ($doc.path | path exists) {
let dest_path = $"($package_dir)/docs/($doc.name)"
if (copy_file $doc.path $dest_path) {
$packaged = ($packaged | append $doc.name)
}
}
}
{
files: $packaged
}
}
# Get nushell components for platform
def get_nushell_components [platform: string, version: string] {
let extension = get_binary_extension $platform
let profile_dir = if ($platform | str contains "release") or true { "release" } else { "debug" }
# Check if platform-specific build exists in distribution
let dist_binary = $"./distribution/($platform)/nu($extension)"
let workspace_binary = $"./nushell/target/($profile_dir)/nu($extension)"
let nushell_path = if ($dist_binary | path exists) {
$dist_binary
} else if ($workspace_binary | path exists) {
$workspace_binary
} else {
$workspace_binary # Will be reported as missing in list
}
{
binaries: [
{
name: $"nu($extension)",
path: $nushell_path,
component: "nushell"
}
]
}
}
# Get plugin components for platform
def get_plugin_components [platform: string, version: string] {
let extension = get_binary_extension $platform
let dist_dir = $"./distribution/($platform)"
# Get plugins from distribution directory if it exists
let plugin_binaries = if ($dist_dir | path exists) {
glob $"($dist_dir)/nu_plugin_*($extension)"
| each {|path|
let name = ($path | path basename)
{
name: $name,
path: $path,
component: "plugin"
}
}
} else {
# Fallback to individual plugin target directories
glob "nu_plugin_*"
| where ($it | path type) == "dir"
| each {|plugin_dir|
let plugin_name = ($plugin_dir | path basename)
let binary_name = $"($plugin_name)($extension)"
mut binary_path = $"($plugin_dir)/target/release/($binary_name)"
if not ($binary_path | path exists) {
# Try debug build
$binary_path = $"($plugin_dir)/target/debug/($binary_name)"
}
{
name: $binary_name,
path: $binary_path,
component: "plugin"
}
}
}
{
binaries: $plugin_binaries
}
}
# Get configuration components
def get_config_components [version: string] {
[
{name: "default_config.nu", path: "./scripts/templates/default_config.nu"},
{name: "default_env.nu", path: "./scripts/templates/default_env.nu"}
]
}
# Get installer components
def get_installer_components [platform: string] {
if ($platform | str starts-with "windows") {
[
{name: "install.ps1", path: "./scripts/templates/install.ps1"}
]
} else {
[
{name: "install.sh", path: "./scripts/templates/install.sh"}
]
}
}
# Create distribution-specific configuration
def create_distribution_config [package_dir: string, version: string] {
let config = {
distribution: {
version: $version,
created_at: (date now | format date "%Y-%m-%d %H:%M:%S UTC"),
full_distribution: true
}
}
$config | to toml | save -f $"($package_dir)/config/distribution_config.toml"
}
# Create package README
def create_package_readme [platform: string, package_dir: string, version: string] {
let readme_content = [
$"# Nushell Full Distribution v($version)",
"",
"This package contains a complete Nushell distribution with all plugins and configurations.",
"",
"## Platform",
$"- Target: ($platform)",
$"- Created: (date now | format date '%Y-%m-%d %H:%M:%S UTC')",
"",
"## Contents",
"",
"### Binaries (bin/)",
"- nu - Nushell shell binary",
"- nu_plugin_* - Plugin binaries",
"",
"### Configuration (config/)",
"- default_config.nu - Default nushell configuration",
"- default_env.nu - Default environment setup",
"- distribution_config.toml - Distribution metadata",
"",
"### Installation",
"Run the installer for your platform:",
"- Unix/Linux/macOS: ./install.sh",
"- Windows: ./install.ps1",
"",
"### Manual Installation",
"1. Copy binaries from bin/ to a directory in your PATH",
"2. Copy configuration files from config/ to your nushell config directory",
"3. Register plugins: nu -c 'plugin add path/to/plugin'",
"",
"### Verification",
"After installation, verify with:",
"nu --version",
"",
"### Uninstallation",
"Run: ./uninstall.sh (or ./uninstall.ps1 on Windows)",
"",
"## Documentation",
"See docs/ directory for additional documentation.",
"",
"## Support",
"For issues and support, visit: https://github.com/nushell/nushell"
] | str join "\n"
$readme_content | save -f $"($package_dir)/README.md"
}
# Create uninstaller script
def create_uninstaller [platform: string, package_dir: string] {
let uninstaller_path = if ($platform | str starts-with "windows") {
"./scripts/templates/uninstall.ps1"
} else {
"./scripts/templates/uninstall.sh"
}
if ($uninstaller_path | path exists) {
let dest = if ($platform | str starts-with "windows") {
$"($package_dir)/uninstall.ps1"
} else {
$"($package_dir)/uninstall.sh"
}
copy_file $uninstaller_path $dest
}
}
# Create package archive
def create_package_archive [package_dir: string, archive_path: string, platform: string] {
log_info $"📦 Creating archive: ($archive_path)"
let package_name = ($package_dir | path basename)
let archive_name = ($archive_path | path basename)
let work_dir = ($package_dir | path dirname)
if ($platform | str starts-with "windows") {
# Create ZIP archive for Windows
cd $work_dir
try {
run-external "zip" "-r" $archive_name $package_name
} catch {
# Fallback to tar if zip not available
run-external "tar" "-czf" $archive_name $package_name
}
} else {
# Create tar.gz archive for Unix-like systems
cd $work_dir
run-external "tar" "-czf" $archive_name $package_name
}
# Move archive to final location if needed
let temp_archive = $"($work_dir)/($archive_name)"
if ($temp_archive | path exists) and ($temp_archive != $archive_path) {
mv $temp_archive $archive_path
}
if ($archive_path | path exists) {
let size = (ls $archive_path | get 0.size)
log_success $"✅ Archive created: ($archive_path) (($size))"
} else {
log_error $"❌ Failed to create archive: ($archive_path)"
}
}
# Create cross-platform checksums
def create_cross_platform_checksums [output: string] {
log_info "🔐 Creating cross-platform checksums..."
let archives = glob $"($output)/*.tar.gz" | append (glob $"($output)/*.zip")
if ($archives | length) > 0 {
create_checksums $archives $output
log_success $"✅ Cross-platform checksums created in ($output)/checksums.txt"
} else {
log_warn "No archives found for checksum generation"
}
}

503
scripts/install_full_nushell.nu Executable file
View File

@ -0,0 +1,503 @@
#!/usr/bin/env nu
# Install Full Nushell Distribution Script
# Advanced nu-based installer called after bootstrap gets nushell running
# Provides plugin selection, configuration, and integration with existing nu environment
use lib/common_lib.nu [
log_info, log_error, log_success, log_warn, log_debug,
get_current_platform, ensure_dir, copy_file
]
def main [
--install-dir (-i): string = "" # Installation directory override
--config-dir (-c): string = "" # Configuration directory override
--plugins (-p): list<string> = [] # Specific plugins to install
--all-plugins (-a): bool = false # Install all available plugins
--no-register: bool = false # Don't register plugins with nushell
--register-only: bool = false # Only register plugins, don't copy binaries
--verify: bool = false # Verify installation after completion
--test: bool = false # Run in test mode (dry run)
--config-backup: bool = true # Backup existing configuration
--interactive (-I): bool = false # Interactive mode for plugin selection
--update (-u): bool = false # Update existing installation
] {
log_info "🚀 Nushell Full Distribution Advanced Installer"
log_info "=============================================="
if $test {
log_warn "🧪 Running in test mode (dry run)"
}
# Validate environment
validate_installation_environment $test
# Get installation paths
let paths = get_installation_paths $install_dir $config_dir
log_info ""
log_info "📍 Installation Configuration:"
log_info $" Binary directory: ($paths.bin_dir)"
log_info $" Config directory: ($paths.config_dir)"
log_info $" Plugins directory: ($paths.plugins_dir)"
if $register_only {
log_info " Mode: Register existing binaries only"
} else if $no_register {
log_info " Mode: Copy binaries without registration"
} else {
log_info " Mode: Copy binaries and register plugins"
}
# Check if this is an update
let is_update = $update or (check_existing_installation $paths)
if $is_update {
log_info " Type: Update existing installation"
} else {
log_info " Type: New installation"
}
# Plugin selection
let selected_plugins = if $interactive {
select_plugins_interactive
} else if ($plugins | length) > 0 {
$plugins
} else if $all_plugins {
get_available_plugins | get name
} else {
get_recommended_plugins
}
log_info ""
log_info $"🔌 Selected plugins: (($selected_plugins | length))"
$selected_plugins | each {|plugin| log_info $" - ($plugin)"}
if not $test {
# Backup existing configuration if requested
if $config_backup and $is_update {
backup_existing_config $paths.config_dir
}
# Create installation directories
setup_installation_directories $paths
# Install binaries
if not $register_only {
install_nushell_binary $paths $test
install_plugin_binaries $selected_plugins $paths $test
}
# Install configuration
install_configuration $paths $is_update $test
# Register plugins
if not $no_register {
register_plugins_with_nushell $selected_plugins $paths $test
}
# Verify installation if requested
if $verify {
verify_full_installation $paths $selected_plugins
}
log_success "✅ Installation completed successfully!"
print_installation_summary $paths $selected_plugins $is_update
} else {
log_info "🧪 Test mode completed - no changes made"
}
}
# Validate installation environment
def validate_installation_environment [test: bool] {
log_debug "Validating installation environment..."
# Check if we're running in nushell
if ($env.NU_VERSION? | is-empty) {
log_error "This installer must be run with nushell"
exit 1
}
# Check if we have write permissions to likely installation directories
let test_dirs = [
"~/.local/bin",
"~/.config/nushell",
"/usr/local/bin"
]
for dir in $test_dirs {
let expanded_dir = ($dir | path expand)
if ($expanded_dir | path exists) and not $test {
try {
touch $"($expanded_dir)/.write_test"
rm $"($expanded_dir)/.write_test"
log_debug $"✅ Write permission confirmed: ($expanded_dir)"
break
} catch {
log_debug $"❌ No write permission: ($expanded_dir)"
}
}
}
log_success "Environment validation passed"
}
# Get installation paths
def get_installation_paths [install_dir_override: string, config_dir_override: string] {
let home_dir = ($env.HOME | path expand)
# Determine binary installation directory
let bin_dir = if ($install_dir_override | str length) > 0 {
($install_dir_override | path expand)
} else if (which nu | length) > 0 {
# Use directory where current nu is installed
(which nu | get 0.path | path dirname)
} else {
# Default to ~/.local/bin
$"($home_dir)/.local/bin"
}
# Determine configuration directory
let config_dir = if ($config_dir_override | str length) > 0 {
($config_dir_override | path expand)
} else if ($env.XDG_CONFIG_HOME? | is-not-empty) {
$"($env.XDG_CONFIG_HOME)/nushell"
} else {
$"($home_dir)/.config/nushell"
}
# Plugin directory (for registration)
let plugins_dir = $"($config_dir)/plugins"
{
bin_dir: $bin_dir,
config_dir: $config_dir,
plugins_dir: $plugins_dir,
home_dir: $home_dir
}
}
# Check if installation already exists
def check_existing_installation [paths: record] -> bool {
let nu_exists = ($"($paths.bin_dir)/nu" | path exists)
let config_exists = ($"($paths.config_dir)/config.nu" | path exists)
$nu_exists or $config_exists
}
# Get available plugins from current directory
def get_available_plugins [] {
let bin_files = if ("./bin" | path exists) {
ls ./bin | where name =~ "nu_plugin_" | get name | each {|path| ($path | path basename | str replace ".exe" "")}
} else {
[]
}
$bin_files | each {|name| {name: $name, path: $"./bin/($name)"}}
}
# Get recommended plugins for default installation
def get_recommended_plugins [] {
let available = (get_available_plugins | get name)
let recommended = [
"nu_plugin_clipboard",
"nu_plugin_hashes",
"nu_plugin_desktop_notifications",
"nu_plugin_highlight"
]
# Return intersection of recommended and available
$recommended | where $it in $available
}
# Interactive plugin selection
def select_plugins_interactive [] {
log_info "🔌 Plugin Selection"
log_info "=================="
let available_plugins = get_available_plugins
if ($available_plugins | length) == 0 {
log_warn "No plugins found in package"
return []
}
log_info "Available plugins:"
$available_plugins | enumerate | each {|item|
log_info $" (($item.index + 1)). ($item.item.name)"
}
log_info ""
log_info "Selection options:"
log_info " - Enter numbers separated by spaces (e.g., '1 3 4')"
log_info " - Enter 'all' for all plugins"
log_info " - Enter 'recommended' for recommended plugins"
log_info " - Press Enter for recommended plugins"
let selection = (input "Your selection: ")
match $selection {
"all" => {$available_plugins | get name},
"recommended" | "" => {get_recommended_plugins},
_ => {
let indices = ($selection | split row " " | each {|x| ($x | into int) - 1})
$indices | each {|i| $available_plugins | get $i | get name}
}
}
}
# Setup installation directories
def setup_installation_directories [paths: record] {
log_info "📁 Setting up installation directories..."
ensure_dir $paths.bin_dir
ensure_dir $paths.config_dir
ensure_dir $paths.plugins_dir
log_success "✅ Installation directories ready"
}
# Install nushell binary
def install_nushell_binary [paths: record, test: bool] {
log_info "🚀 Installing nushell binary..."
let current_platform = get_current_platform
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
let nu_binary = $"./bin/nu($binary_extension)"
if not ($nu_binary | path exists) {
log_error $"Nushell binary not found: ($nu_binary)"
exit 1
}
let dest_path = $"($paths.bin_dir)/nu($binary_extension)"
if not $test {
copy_file $nu_binary $dest_path
# Make executable on Unix-like systems
if not ($current_platform | str starts-with "windows") {
chmod +x $dest_path
}
}
log_success $"✅ Nushell binary installed: ($dest_path)"
}
# Install plugin binaries
def install_plugin_binaries [plugins: list<string>, paths: record, test: bool] {
log_info $"🔌 Installing plugin binaries... (($plugins | length))"
let current_platform = get_current_platform
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
for plugin in $plugins {
let plugin_binary = $"./bin/($plugin)($binary_extension)"
let dest_path = $"($paths.bin_dir)/($plugin)($binary_extension)"
if ($plugin_binary | path exists) {
if not $test {
copy_file $plugin_binary $dest_path
# Make executable on Unix-like systems
if not ($current_platform | str starts-with "windows") {
chmod +x $dest_path
}
}
log_success $"✅ Installed plugin: ($plugin)"
} else {
log_warn $"⚠️ Plugin binary not found: ($plugin_binary)"
}
}
}
# Install configuration files
def install_configuration [paths: record, is_update: bool, test: bool] {
log_info "⚙️ Installing configuration files..."
let config_files = [
{src: "./config/default_config.nu", dest: "config.nu", required: true},
{src: "./config/default_env.nu", dest: "env.nu", required: true},
{src: "./config/distribution_config.toml", dest: "distribution_config.toml", required: false}
]
for file in $config_files {
let src_path = $file.src
let dest_path = $"($paths.config_dir)/($file.dest)"
if ($src_path | path exists) {
if ($dest_path | path exists) and not $is_update {
log_info $"⚠️ Configuration file exists, creating backup: ($file.dest)"
if not $test {
cp $dest_path $"($dest_path).backup.(date now | format date '%Y%m%d_%H%M%S')"
}
}
if not $test {
copy_file $src_path $dest_path
}
log_success $"✅ Installed config: ($file.dest)"
} else if $file.required {
log_error $"Required configuration file not found: ($src_path)"
exit 1
}
}
}
# Register plugins with nushell
def register_plugins_with_nushell [plugins: list<string>, paths: record, test: bool] {
log_info $"📝 Registering plugins with nushell... (($plugins | length))"
let current_platform = get_current_platform
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
let registration_commands = []
for plugin in $plugins {
let plugin_path = $"($paths.bin_dir)/($plugin)($binary_extension)"
if ($plugin_path | path exists) or $test {
let register_cmd = $"plugin add ($plugin_path)"
$registration_commands = ($registration_commands | append $register_cmd)
log_success $"✅ Will register: ($plugin)"
} else {
log_warn $"⚠️ Cannot register plugin (binary not found): ($plugin)"
}
}
if not $test and ($registration_commands | length) > 0 {
# Create a temporary script to register plugins
let register_script = $"($paths.config_dir)/register_plugins_temp.nu"
let script_content = ([
"# Temporary plugin registration script",
"# Generated by install_full_nushell.nu",
""
] | append $registration_commands | append [
"",
"print \"✅ All plugins registered successfully\"",
$"rm ($register_script)"
] | str join "\n")
$script_content | save $register_script
log_info "Running plugin registration..."
try {
nu $register_script
} catch {|err|
log_error $"Plugin registration failed: ($err.msg)"
log_info "You can register plugins manually later with:"
$registration_commands | each {|cmd| log_info $" nu -c '($cmd)'"}
}
}
}
# Backup existing configuration
def backup_existing_config [config_dir: string] {
log_info "💾 Backing up existing configuration..."
let backup_dir = $"($config_dir)_backup_(date now | format date '%Y%m%d_%H%M%S')"
if ($config_dir | path exists) {
cp -r $config_dir $backup_dir
log_success $"✅ Configuration backed up to: ($backup_dir)"
} else {
log_info "No existing configuration to backup"
}
}
# Verify installation
def verify_full_installation [paths: record, plugins: list<string>] {
log_info "🔍 Verifying installation..."
let verification_results = {}
# Verify nushell binary
let current_platform = get_current_platform
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
let nu_path = $"($paths.bin_dir)/nu($binary_extension)"
let nu_check = if ($nu_path | path exists) {
try {
let version = (run-external $nu_path "--version")
{status: "ok", message: $"Nushell installed: ($version)"}
} catch {|err|
{status: "error", message: $"Nushell binary exists but not executable: ($err.msg)"}
}
} else {
{status: "error", message: "Nushell binary not found"}
}
$verification_results = ($verification_results | insert "nushell" $nu_check)
# Verify plugins
for plugin in $plugins {
let plugin_path = $"($paths.bin_dir)/($plugin)($binary_extension)"
let plugin_check = if ($plugin_path | path exists) {
{status: "ok", message: $"Plugin binary installed: ($plugin)"}
} else {
{status: "error", message: $"Plugin binary not found: ($plugin)"}
}
$verification_results = ($verification_results | insert $plugin $plugin_check)
}
# Verify configuration
let config_files = ["config.nu", "env.nu"]
for config_file in $config_files {
let config_path = $"($paths.config_dir)/($config_file)"
let config_check = if ($config_path | path exists) {
{status: "ok", message: $"Configuration installed: ($config_file)"}
} else {
{status: "error", message: $"Configuration not found: ($config_file)"}
}
$verification_results = ($verification_results | insert $config_file $config_check)
}
# Display verification results
log_info "📊 Verification Results:"
log_info "======================"
let all_ok = true
for item in ($verification_results | items) {
let status_icon = if $item.value.status == "ok" { "✅" } else { "❌"; $all_ok = false }
log_info $" ($status_icon) ($item.key): ($item.value.message)"
}
if $all_ok {
log_success "🎉 All verification checks passed!"
} else {
log_warn "⚠️ Some verification checks failed"
}
}
# Print installation summary
def print_installation_summary [paths: record, plugins: list<string>, is_update: bool] {
let action = if $is_update { "Updated" } else { "Installed" }
log_info ""
log_info $"📊 Installation Summary"
log_info "======================"
log_info $"Action: ($action) Nushell Full Distribution"
log_info $"Binary directory: ($paths.bin_dir)"
log_info $"Config directory: ($paths.config_dir)"
log_info $"Plugins installed: (($plugins | length))"
if ($plugins | length) > 0 {
$plugins | each {|plugin| log_info $" - ($plugin)"}
}
log_info ""
log_info "🚀 Next Steps:"
log_info "============="
log_info $"1. Add ($paths.bin_dir) to your PATH if not already present"
log_info "2. Restart your terminal or run: source ~/.bashrc (or equivalent)"
log_info "3. Test installation: nu --version"
log_info "4. Start using nushell: nu"
# Check if PATH update is needed
let path_entries = ($env.PATH | split row ":")
if not ($paths.bin_dir in $path_entries) {
log_info ""
log_warn "⚠️ PATH Update Required"
log_info $"Add this line to your shell profile (~/.bashrc, ~/.zshrc, etc.):"
log_info $"export PATH=\"($paths.bin_dir):$PATH\""
}
}

285
scripts/lib/common_lib.nu Normal file
View File

@ -0,0 +1,285 @@
#!/usr/bin/env nu
# Common Library for Nushell Plugins Distribution Scripts
# Provides shared utilities, logging, and validation functions
# Logging functions with consistent formatting
export def log_info [message: string] {
print $"(ansi blue) ($message)(ansi reset)"
}
export def log_success [message: string] {
print $"(ansi green)✅ ($message)(ansi reset)"
}
export def log_warn [message: string] {
print $"(ansi yellow)⚠️ ($message)(ansi reset)"
}
export def log_error [message: string] {
print $"(ansi red)❌ ($message)(ansi reset)"
}
export def log_debug [message: string] {
if ($env.DEBUG? | default false) {
print $"(ansi dim)🐛 DEBUG: ($message)(ansi reset)"
}
}
# Validate nushell version consistency between system and workspace
export def validate_nushell_version [] {
log_debug "Validating nushell version consistency..."
# Check if nushell submodule exists
let nushell_dir = "./nushell"
if not ($nushell_dir | path exists) {
log_error $"Nushell submodule not found at ($nushell_dir)"
exit 1
}
# Get system nu version
let system_version = try {
(nu --version | str replace "nushell " "" | str replace " .*" "")
} catch {
log_error "Could not determine system nushell version"
exit 1
}
# Get workspace nu version from Cargo.toml
let cargo_toml_path = $"($nushell_dir)/Cargo.toml"
let workspace_version = try {
(open $cargo_toml_path | get package.version)
} catch {
log_error $"Could not read package version from ($cargo_toml_path)"
exit 1
}
if $system_version != $workspace_version {
log_error $"Version mismatch: system nu ($system_version) != workspace nu ($workspace_version)"
log_info "Run 'just fix-nushell' to resolve version inconsistencies"
exit 1
}
log_success $"Nushell version validation passed: ($system_version)"
}
# Get current platform identifier
export def get_current_platform [] {
let os = (sys host | get name)
let arch = (run-external "uname" "-m" | str trim)
let platform_name = match $os {
"Linux" => "linux",
"Darwin" => "darwin",
"Windows" => "windows",
_ => ($os | str downcase)
}
let arch_name = match $arch {
"x86_64" => "x86_64",
"aarch64" => "arm64",
"arm64" => "arm64",
_ => $arch
}
$"($platform_name)-($arch_name)"
}
# Get all supported platforms
export def get_supported_platforms [] {
[
"linux-x86_64",
"linux-arm64",
"darwin-x86_64",
"darwin-arm64",
"windows-x86_64"
]
}
# Get binary extension for platform
export def get_binary_extension [platform: string] {
if ($platform | str starts-with "windows") {
".exe"
} else {
""
}
}
# Get archive extension for platform
export def get_archive_extension [platform: string] {
if ($platform | str starts-with "windows") {
".zip"
} else {
".tar.gz"
}
}
# Ensure directory exists
export def ensure_dir [path: string] {
if not ($path | path exists) {
mkdir $path
log_info $"Created directory: ($path)"
}
}
# Remove directory if it exists
export def remove_dir [path: string] {
if ($path | path exists) {
rm -rf $path
log_info $"Removed directory: ($path)"
}
}
# Copy file with logging
export def copy_file [src: string, dest: string] {
if not ($src | path exists) {
log_error $"Source file does not exist: ($src)"
return false
}
let dest_dir = ($dest | path dirname)
ensure_dir $dest_dir
cp $src $dest
log_debug $"Copied ($src) -> ($dest)"
true
}
# Create checksums for files
export def create_checksums [files: list<string>, output_dir: string] {
let checksum_file = $"($output_dir)/checksums.txt"
let checksums = $files | each {|file|
if ($file | path exists) {
let hash = (open $file --raw | hash sha256)
let filename = ($file | path basename)
$"($hash) ($filename)"
}
} | compact
$checksums | save -f $checksum_file
log_success $"Created checksums file: ($checksum_file)"
}
# Detect available plugin directories
export def get_plugin_directories [] {
glob "nu_plugin_*" | where {|it| ($it | path exists) and (($it | path type) == "dir")}
}
# Get plugin name from directory
export def get_plugin_name [dir: string] {
$dir | path basename
}
# Check if path is a plugin directory
export def is_plugin_directory [path: string] {
($path | path basename | str starts-with "nu_plugin_") and ($path | path type) == "dir"
}
# Get version from current workspace
export def get_workspace_version [] {
let version_from_git = try {
(git describe --tags --abbrev=0 2>/dev/null | str replace "^v" "")
} catch {
""
}
if ($version_from_git | str length) > 0 {
$version_from_git
} else {
# Fallback to version from nushell Cargo.toml
try {
(open "./nushell/Cargo.toml" | get package.version)
} catch {
"0.1.0"
}
}
}
# Create manifest for distribution
export def create_manifest [
version: string,
platform: string,
components: record,
output_file: string
] {
let manifest = {
version: $version,
platform: $platform,
created_at: (date now | format date "%Y-%m-%d %H:%M:%S UTC"),
components: $components,
nushell_version: (try { (nu --version) } catch { "unknown" })
}
$manifest | to json | save -f $output_file
log_success $"Created manifest: ($output_file)"
}
# Parse command line flags into structured data
export def parse_flags [args: list] {
mut flags = {}
mut i = 0
while $i < ($args | length) {
let arg = ($args | get $i)
if ($arg | str starts-with "--") {
let flag_name = ($arg | str replace "--" "")
# Check if next arg is a value or another flag
let next_i = ($i + 1)
if $next_i < ($args | length) {
let next_arg = ($args | get $next_i)
if not ($next_arg | str starts-with "--") {
$flags = ($flags | insert $flag_name $next_arg)
$i = ($i + 2)
} else {
$flags = ($flags | insert $flag_name true)
$i = ($i + 1)
}
} else {
$flags = ($flags | insert $flag_name true)
$i = ($i + 1)
}
} else {
$i = ($i + 1)
}
}
$flags
}
# Execute command with error handling
export def exec_with_error [command: string] {
log_debug $"Executing: ($command)"
let result = try {
(bash -c $command)
} catch {|err|
log_error $"Command failed: ($command)"
log_error $"Error: ($err.msg)"
exit 1
}
$result
}
# Check if binary exists and is executable
export def check_binary [path: string] {
if not ($path | path exists) {
false
} else if ($path | path type) != "file" {
false
} else {
# On Unix-like systems, check if executable
if (sys host | get name) != "Windows" {
try {
(ls -l $path | get 0.mode | str contains "x")
} catch {
false
}
} else {
true
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,317 @@
# Nushell Environment Configuration Template
# Default environment setup for Nushell + Plugins distribution
# Created by nushell-plugins full distribution system
# =============================================================================
# CORE ENVIRONMENT VARIABLES
# =============================================================================
# Default editor and browser
$env.EDITOR = (
if $nu.os-info.name == "windows" {
"notepad.exe"
} else if (which code | is-not-empty) {
"code"
} else if (which nano | is-not-empty) {
"nano"
} else if (which vim | is-not-empty) {
"vim"
} else {
"vi"
}
)
$env.BROWSER = (
if $nu.os-info.name == "windows" {
"msedge.exe"
} else if $nu.os-info.name == "macos" {
"open"
} else if (which firefox | is-not-empty) {
"firefox"
} else if (which chromium | is-not-empty) {
"chromium"
} else {
"xdg-open"
}
)
# Pager configuration
$env.PAGER = (
if (which bat | is-not-empty) {
"bat"
} else if (which less | is-not-empty) {
"less"
} else {
"more"
}
)
# =============================================================================
# NUSHELL SPECIFIC CONFIGURATION
# =============================================================================
# Nushell library directories
$env.NU_LIB_DIRS = [
($nu.config-path | path dirname | path join "scripts")
($nu.config-path | path dirname | path join "lib")
($nu.config-path | path dirname | path join "modules")
]
# Plugin directories
$env.NU_PLUGIN_DIRS = [
($nu.config-path | path dirname | path join "plugins")
]
# Custom prompt indicators
$env.PROMPT_INDICATOR = "〉"
$env.PROMPT_INDICATOR_VI_INSERT = ": "
$env.PROMPT_INDICATOR_VI_NORMAL = "〉"
$env.PROMPT_MULTILINE_INDICATOR = "::: "
# =============================================================================
# DEVELOPMENT ENVIRONMENT
# =============================================================================
# Rust environment
if (which cargo | is-not-empty) {
# Add cargo bin to PATH if not already there
let cargo_bin = ($env.HOME | path join ".cargo" "bin")
if ($cargo_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $cargo_bin | is-empty) {
$env.PATH = ($env.PATH | split row (char esep) | append $cargo_bin | str join (char esep))
}
}
# Go environment
if (which go | is-not-empty) {
$env.GOPATH = ($env.HOME | path join "go")
let go_bin = ($env.GOPATH | path join "bin")
if ($go_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $go_bin | is-empty) {
$env.PATH = ($env.PATH | split row (char esep) | append $go_bin | str join (char esep))
}
}
# Node.js environment
if (which npm | is-not-empty) {
# Add global npm bin to PATH
try {
let npm_bin = (npm config get prefix | str trim | path join "bin")
if ($npm_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $npm_bin | is-empty) {
$env.PATH = ($env.PATH | split row (char esep) | append $npm_bin | str join (char esep))
}
}
}
# Python environment
if (which python3 | is-not-empty) or (which python | is-not-empty) {
# Add user Python bin to PATH
let python_cmd = if (which python3 | is-not-empty) { "python3" } else { "python" }
try {
let python_user_bin = (run-external $python_cmd "-m" "site" "--user-base" | str trim | path join "bin")
if ($python_user_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $python_user_bin | is-empty) {
$env.PATH = ($env.PATH | split row (char esep) | append $python_user_bin | str join (char esep))
}
}
}
# =============================================================================
# SYSTEM SPECIFIC CONFIGURATION
# =============================================================================
# Linux specific
if $nu.os-info.name == "linux" {
# Add local bin directories to PATH
let local_bins = [
($env.HOME | path join ".local" "bin")
"/usr/local/bin"
]
for local_bin in $local_bins {
if ($local_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $local_bin | is-empty) {
$env.PATH = ($env.PATH | split row (char esep) | append $local_bin | str join (char esep))
}
}
# XDG directories
$env.XDG_CONFIG_HOME = ($env.HOME | path join ".config")
$env.XDG_DATA_HOME = ($env.HOME | path join ".local" "share")
$env.XDG_CACHE_HOME = ($env.HOME | path join ".cache")
}
# macOS specific
if $nu.os-info.name == "macos" {
# Add Homebrew to PATH if it exists
let homebrew_paths = [
"/opt/homebrew/bin" # Apple Silicon
"/usr/local/bin" # Intel
]
for homebrew_path in $homebrew_paths {
if ($homebrew_path | path exists) and ($env.PATH | split row (char esep) | where $it == $homebrew_path | is-empty) {
$env.PATH = ($env.PATH | split row (char esep) | prepend $homebrew_path | str join (char esep))
}
}
# macOS specific environment variables
if (which brew | is-not-empty) {
$env.HOMEBREW_NO_ANALYTICS = "1"
$env.HOMEBREW_NO_AUTO_UPDATE = "1"
}
}
# Windows specific
if $nu.os-info.name == "windows" {
# Windows specific environment (most variables should already be set by system)
# Add user local bin if it exists
let local_bin = ($env.USERPROFILE | path join ".local" "bin")
if ($local_bin | path exists) and ($env.PATH | split row ";" | where $it == $local_bin | is-empty) {
$env.PATH = ($env.PATH + ";" + $local_bin)
}
}
# =============================================================================
# PERFORMANCE AND BEHAVIOR
# =============================================================================
# History configuration
$env.NU_HISTORY_SIZE = 10000
# Completions
$env.NU_COMPLETION_ALGORITHM = "prefix" # or "fuzzy"
# Plugin timeout (in seconds)
$env.NU_PLUGIN_TIMEOUT = 60
# =============================================================================
# CUSTOM ALIASES AND SHORTCUTS
# =============================================================================
# Common directory shortcuts
$env.REPOS = ($env.HOME | path join "repos")
$env.PROJECTS = ($env.HOME | path join "projects")
$env.DOWNLOADS = (
if $nu.os-info.name == "windows" {
($env.USERPROFILE | path join "Downloads")
} else {
($env.HOME | path join "Downloads")
}
)
# =============================================================================
# THEME AND APPEARANCE
# =============================================================================
# Terminal colors (if supported)
$env.COLORTERM = "truecolor"
# LS_COLORS for better directory listings
if $nu.os-info.name != "windows" {
$env.LS_COLORS = "di=1;34:ln=1;36:so=1;35:pi=1;33:ex=1;32:bd=1;33:cd=1;33:su=1;31:sg=1;31:tw=1;34:ow=1;34"
}
# =============================================================================
# CONDITIONAL FEATURES
# =============================================================================
# Enable starship prompt if available
if (which starship | is-not-empty) {
$env.STARSHIP_CONFIG = ($nu.config-path | path dirname | path join "starship.toml")
}
# Enable zoxide if available
if (which zoxide | is-not-empty) {
$env._ZO_DATA_DIR = ($nu.config-path | path dirname | path join "zoxide")
}
# Enable direnv if available
if (which direnv | is-not-empty) {
$env.DIRENV_LOG_FORMAT = "" # Quiet direnv output
}
# =============================================================================
# PLUGIN SPECIFIC CONFIGURATION
# =============================================================================
# Clipboard plugin settings
$env.NU_CLIPBOARD_PROVIDER = (
if $nu.os-info.name == "linux" and (which xclip | is-not-empty) {
"xclip"
} else if $nu.os-info.name == "linux" and (which wl-copy | is-not-empty) {
"wl-clipboard"
} else if $nu.os-info.name == "macos" {
"pbcopy"
} else if $nu.os-info.name == "windows" {
"windows"
} else {
"none"
}
)
# Image plugin settings
$env.NU_IMAGE_VIEWER = (
if $nu.os-info.name == "macos" {
"open"
} else if $nu.os-info.name == "windows" {
"mspaint"
} else if (which feh | is-not-empty) {
"feh"
} else if (which eog | is-not-empty) {
"eog"
} else {
"xdg-open"
}
)
# =============================================================================
# USER CUSTOMIZATION SECTION
# =============================================================================
# Load user-specific environment file if it exists
let user_env_file = ($nu.config-path | path dirname | path join "user_env.nu")
if ($user_env_file | path exists) {
source $user_env_file
}
# Load local environment file if it exists (for project-specific settings)
let local_env_file = (pwd | path join ".nu_env")
if ($local_env_file | path exists) {
source $local_env_file
}
# =============================================================================
# FINAL PATH CLEANUP
# =============================================================================
# Remove duplicate PATH entries while preserving order
$env.PATH = (
$env.PATH
| split row (char esep)
| uniq
| where ($it | path exists)
| str join (char esep)
)
# =============================================================================
# STARTUP INFORMATION (Optional)
# =============================================================================
# Uncomment to show environment info on startup
# print $"🚀 Nushell environment loaded"
# print $"📁 Config: ($nu.config-path)"
# print $"🧩 Plugins: (plugin list | length) loaded"
# print $"💻 Platform: ($nu.os-info.name) ($nu.os-info.arch)"
# =============================================================================
# NOTES FOR CUSTOMIZATION
# =============================================================================
# This file contains sensible defaults for a Nushell + Plugins environment.
# To customize:
#
# 1. Create user_env.nu in the same directory for user-specific settings
# 2. Create .nu_env in project directories for project-specific settings
# 3. Modify this file directly (but changes may be lost on updates)
#
# For more information:
# - Nushell Book: https://nushell.sh/book/
# - Environment Variables: https://nushell.sh/book/environment.html
# - Custom Commands: https://nushell.sh/book/custom_commands.html

View File

@ -0,0 +1,841 @@
# Universal Nushell + Plugins Bootstrap Installer for Windows
# PowerShell script that installs Nushell and plugins without any prerequisites
#
# This script:
# - Detects Windows platform (x86_64/arm64)
# - Downloads or builds Nushell + plugins
# - Installs to user location (%USERPROFILE%\.local\bin) or system (C:\Program Files\Nushell)
# - Updates PATH in system/user environment
# - Creates initial Nushell configuration
# - Registers all plugins automatically
# - Verifies installation
param(
[switch]$System,
[switch]$User = $true,
[switch]$NoPath,
[switch]$NoConfig,
[switch]$NoPlugins,
[switch]$BuildFromSource,
[switch]$Verify,
[switch]$Uninstall,
[string]$Version = "",
[switch]$Help
)
# Configuration
$RepoUrl = "https://github.com/your-org/nushell-plugins"
$BinaryRepoUrl = "$RepoUrl/releases/download"
$InstallDirUser = "$env:USERPROFILE\.local\bin"
$InstallDirSystem = "${env:ProgramFiles}\Nushell\bin"
$ConfigDir = "$env:USERPROFILE\.config\nushell"
$TempDir = "$env:TEMP\nushell-install-$(Get-Random)"
# Colors for output
$Colors = @{
Red = "Red"
Green = "Green"
Yellow = "Yellow"
Blue = "Blue"
Magenta = "Magenta"
Cyan = "Cyan"
}
# Logging functions
function Write-LogInfo {
param([string]$Message)
Write-Host " $Message" -ForegroundColor $Colors.Blue
}
function Write-LogSuccess {
param([string]$Message)
Write-Host "$Message" -ForegroundColor $Colors.Green
}
function Write-LogWarn {
param([string]$Message)
Write-Host "⚠️ $Message" -ForegroundColor $Colors.Yellow
}
function Write-LogError {
param([string]$Message)
Write-Host "$Message" -ForegroundColor $Colors.Red
}
function Write-LogHeader {
param([string]$Message)
Write-Host ""
Write-Host "🚀 $Message" -ForegroundColor $Colors.Magenta
Write-Host ("=" * $Message.Length) -ForegroundColor $Colors.Magenta
}
# Usage information
function Show-Usage {
@"
Nushell + Plugins Bootstrap Installer for Windows
USAGE:
# Download and run:
Invoke-WebRequest -Uri "install-url/install.ps1" | Invoke-Expression
# Or download and run with parameters:
.\install.ps1 [-System] [-User] [-NoPath] [-NoConfig] [-NoPlugins] [-BuildFromSource] [-Verify] [-Uninstall] [-Version <version>] [-Help]
PARAMETERS:
-System Install to system directory (C:\Program Files\Nushell, requires admin)
-User Install to user directory (~\.local\bin) [default]
-NoPath Don't modify PATH environment variable
-NoConfig Don't create initial nushell configuration
-NoPlugins Install only nushell, skip plugins
-BuildFromSource Build from source instead of downloading binaries
-Verify Verify installation after completion
-Uninstall Remove nushell and plugins
-Version <version> Install specific version (default: latest)
-Help Show this help message
EXAMPLES:
# Default installation (user directory, with plugins)
.\install.ps1
# System installation (requires admin)
.\install.ps1 -System
# Install without plugins
.\install.ps1 -NoPlugins
# Build from source
.\install.ps1 -BuildFromSource
# Install specific version
.\install.ps1 -Version "v0.107.1"
"@
}
# Platform detection
function Get-Platform {
$arch = $env:PROCESSOR_ARCHITECTURE
switch ($arch) {
"AMD64" { return "windows-x86_64" }
"ARM64" { return "windows-aarch64" }
default {
Write-LogError "Unsupported architecture: $arch"
exit 1
}
}
}
# Check if running as administrator
function Test-Admin {
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
# Check if command exists
function Test-Command {
param([string]$Command)
try {
Get-Command $Command -ErrorAction Stop | Out-Null
return $true
} catch {
return $false
}
}
# Check dependencies for building from source
function Test-BuildDependencies {
$missing = @()
if (-not (Test-Command "git")) {
$missing += "git"
}
if (-not (Test-Command "cargo")) {
$missing += "cargo"
}
if (-not (Test-Command "rustc")) {
$missing += "rust"
}
if ($missing.Count -gt 0) {
Write-LogError "Missing build dependencies: $($missing -join ', ')"
Write-LogInfo "Please install these tools or use binary installation instead"
return $false
}
return $true
}
# Download file with progress
function Get-FileWithProgress {
param(
[string]$Url,
[string]$OutputPath,
[string]$Description = "file"
)
Write-LogInfo "Downloading $Description..."
try {
$webClient = New-Object System.Net.WebClient
$webClient.DownloadFile($Url, $OutputPath)
Write-LogSuccess "Downloaded $Description"
return $true
} catch {
Write-LogError "Failed to download $Description from $Url"
Write-LogError $_.Exception.Message
return $false
}
}
# Extract archive
function Expand-Archive {
param(
[string]$ArchivePath,
[string]$DestinationPath
)
Write-LogInfo "Extracting archive..."
try {
if ($ArchivePath -like "*.zip") {
Expand-Archive -Path $ArchivePath -DestinationPath $DestinationPath -Force
} elseif ($ArchivePath -like "*.tar.gz") {
# Use tar if available (Windows 10 build 17063 and later)
if (Test-Command "tar") {
& tar -xzf $ArchivePath -C $DestinationPath
} else {
throw "tar command not found for .tar.gz extraction"
}
} else {
throw "Unsupported archive format: $ArchivePath"
}
Write-LogSuccess "Extracted archive"
return $true
} catch {
Write-LogError "Failed to extract $ArchivePath"
Write-LogError $_.Exception.Message
return $false
}
}
# Get latest release version
function Get-LatestVersion {
try {
$response = Invoke-RestMethod -Uri "https://api.github.com/repos/your-org/nushell-plugins/releases/latest"
return $response.tag_name
} catch {
Write-LogWarn "Could not detect latest version, using v0.107.1"
return "v0.107.1"
}
}
# Download and install binaries
function Install-FromBinaries {
param(
[string]$Platform,
[string]$Version,
[string]$InstallDir,
[bool]$IncludePlugins
)
Write-LogHeader "Installing from Pre-built Binaries"
# Create temporary directory
New-Item -ItemType Directory -Path $TempDir -Force | Out-Null
Set-Location $TempDir
# Determine archive name and URL
$archiveFormat = if ($Platform -like "*windows*") { "zip" } else { "tar.gz" }
$archiveName = "nushell-plugins-$Platform-$Version.$archiveFormat"
$downloadUrl = "$BinaryRepoUrl/$Version/$archiveName"
# Download archive
if (-not (Get-FileWithProgress -Url $downloadUrl -OutputPath $archiveName -Description "Nushell distribution")) {
Write-LogWarn "Binary download failed, trying alternative..."
# Try without version prefix
$archiveName = "nushell-plugins-$Platform.$archiveFormat"
$downloadUrl = "$BinaryRepoUrl/latest/$archiveName"
if (-not (Get-FileWithProgress -Url $downloadUrl -OutputPath $archiveName -Description "Nushell distribution (latest)")) {
Write-LogError "Failed to download binaries"
return $false
}
}
# Extract archive
if (-not (Expand-Archive -ArchivePath $archiveName -DestinationPath ".")) {
return $false
}
# Find extracted directory
$extractDir = Get-ChildItem -Directory | Select-Object -First 1
if (-not $extractDir) {
Write-LogError "No extracted directory found"
return $false
}
Write-LogInfo "Installing binaries to $InstallDir..."
# Create install directory
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
# Install nushell binary
$nuBinary = "nu.exe"
$nuSourcePath = Join-Path $extractDir.FullName $nuBinary
if (Test-Path $nuSourcePath) {
Copy-Item $nuSourcePath -Destination $InstallDir
Write-LogSuccess "Installed nushell binary"
} else {
Write-LogError "Nushell binary not found in archive"
return $false
}
# Install plugins if requested
if ($IncludePlugins) {
$pluginFiles = Get-ChildItem -Path $extractDir.FullName -Name "nu_plugin_*.exe"
$pluginCount = 0
foreach ($pluginFile in $pluginFiles) {
$pluginSourcePath = Join-Path $extractDir.FullName $pluginFile
Copy-Item $pluginSourcePath -Destination $InstallDir
$pluginCount++
}
if ($pluginCount -gt 0) {
Write-LogSuccess "Installed $pluginCount plugins"
} else {
Write-LogWarn "No plugins found in archive"
}
}
# Copy configuration files if they exist
$configSourceDir = Join-Path $extractDir.FullName "config"
if (Test-Path $configSourceDir) {
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
Copy-Item -Path "$configSourceDir\*" -Destination $ConfigDir -Recurse -Force
Write-LogSuccess "Installed configuration files"
}
return $true
}
# Build and install from source
function Install-FromSource {
param(
[string]$InstallDir,
[bool]$IncludePlugins
)
Write-LogHeader "Building from Source"
# Check dependencies
if (-not (Test-BuildDependencies)) {
return $false
}
# Create temporary directory
New-Item -ItemType Directory -Path $TempDir -Force | Out-Null
Set-Location $TempDir
# Clone repository
Write-LogInfo "Cloning repository..."
try {
& git clone --recursive $RepoUrl nushell-plugins
Set-Location nushell-plugins
} catch {
Write-LogError "Failed to clone repository"
return $false
}
# Build nushell
Write-LogInfo "Building nushell..."
try {
if (Test-Command "just") {
& just build-nushell
} else {
# Fallback to manual build
Set-Location nushell
& cargo build --release --features "plugin,network,sqlite,trash-support,rustls-tls"
Set-Location ..
}
} catch {
Write-LogError "Failed to build nushell"
return $false
}
# Build plugins if requested
if ($IncludePlugins) {
Write-LogInfo "Building plugins..."
try {
if (Test-Command "just") {
& just build
} else {
# Build plugins manually
$pluginDirs = Get-ChildItem -Directory -Name "nu_plugin_*" | Where-Object { $_ -ne "nushell" }
foreach ($pluginDir in $pluginDirs) {
Write-LogInfo "Building $pluginDir..."
Set-Location $pluginDir
try {
& cargo build --release
Write-LogSuccess "Built $pluginDir"
} catch {
Write-LogWarn "Failed to build $pluginDir"
}
Set-Location ..
}
}
} catch {
Write-LogWarn "Failed to build some plugins"
}
}
# Install binaries
Write-LogInfo "Installing binaries to $InstallDir..."
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
# Install nushell
$nuBinary = "nushell\target\release\nu.exe"
if (Test-Path $nuBinary) {
Copy-Item $nuBinary -Destination "$InstallDir\nu.exe"
Write-LogSuccess "Installed nushell binary"
} else {
Write-LogError "Nushell binary not found"
return $false
}
# Install plugins
if ($IncludePlugins) {
$pluginCount = 0
$pluginDirs = Get-ChildItem -Directory -Name "nu_plugin_*" | Where-Object { $_ -ne "nushell" }
foreach ($pluginDir in $pluginDirs) {
$pluginBinary = "$pluginDir\target\release\$pluginDir.exe"
if (Test-Path $pluginBinary) {
Copy-Item $pluginBinary -Destination "$InstallDir\$pluginDir.exe"
$pluginCount++
}
}
if ($pluginCount -gt 0) {
Write-LogSuccess "Installed $pluginCount plugins"
} else {
Write-LogWarn "No plugins were built successfully"
}
}
return $true
}
# Register plugins with nushell
function Register-Plugins {
param(
[string]$InstallDir
)
$nuBinary = Join-Path $InstallDir "nu.exe"
if (-not (Test-Path $nuBinary)) {
Write-LogError "Nushell binary not found: $nuBinary"
return $false
}
Write-LogHeader "Registering Plugins"
# Find all plugin binaries
$pluginFiles = Get-ChildItem -Path $InstallDir -Name "nu_plugin_*.exe"
$pluginCount = 0
foreach ($pluginFile in $pluginFiles) {
$pluginPath = Join-Path $InstallDir $pluginFile
$pluginName = [System.IO.Path]::GetFileNameWithoutExtension($pluginFile)
Write-LogInfo "Registering $pluginName..."
try {
& $nuBinary -c "plugin add '$pluginPath'"
Write-LogSuccess "Registered $pluginName"
$pluginCount++
} catch {
Write-LogWarn "Failed to register $pluginName"
}
}
if ($pluginCount -gt 0) {
Write-LogSuccess "Successfully registered $pluginCount plugins"
} else {
Write-LogWarn "No plugins were registered"
}
return $true
}
# Update PATH environment variable
function Update-PathEnvironment {
param(
[string]$InstallDir,
[bool]$SystemInstall
)
Write-LogHeader "Updating PATH Environment"
# Determine scope
$scope = if ($SystemInstall) { "Machine" } else { "User" }
try {
# Get current PATH
$currentPath = [Environment]::GetEnvironmentVariable("PATH", $scope)
# Check if already in PATH
if ($currentPath -split ';' -contains $InstallDir) {
Write-LogInfo "PATH already contains $InstallDir"
return $true
}
# Add to PATH
$newPath = "$InstallDir;$currentPath"
[Environment]::SetEnvironmentVariable("PATH", $newPath, $scope)
# Update current session
$env:PATH = "$InstallDir;$env:PATH"
Write-LogSuccess "Updated PATH environment variable"
Write-LogInfo "Restart your terminal to apply changes globally"
return $true
} catch {
Write-LogError "Failed to update PATH environment variable"
Write-LogError $_.Exception.Message
return $false
}
}
# Create initial nushell configuration
function New-NushellConfig {
Write-LogHeader "Creating Nushell Configuration"
# Create config directory
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
# Create basic config.nu if it doesn't exist
$configFile = Join-Path $ConfigDir "config.nu"
if (-not (Test-Path $configFile)) {
@"
# Nushell Configuration
# Created by nushell-plugins installer
# Set up basic configuration
`$env.config = {
show_banner: false
edit_mode: emacs
shell_integration: true
table: {
mode: rounded
index_mode: always
show_empty: true
padding: { left: 1, right: 1 }
}
completions: {
case_sensitive: false
quick: true
partial: true
algorithm: "prefix"
}
history: {
max_size: 10000
sync_on_enter: true
file_format: "plaintext"
}
filesize: {
metric: false
format: "auto"
}
}
# Load custom commands and aliases
# Add your custom configuration below
"@ | Out-File -FilePath $configFile -Encoding utf8
Write-LogSuccess "Created config.nu"
} else {
Write-LogInfo "config.nu already exists, skipping"
}
# Create basic env.nu if it doesn't exist
$envFile = Join-Path $ConfigDir "env.nu"
if (-not (Test-Path $envFile)) {
@"
# Nushell Environment Configuration
# Created by nushell-plugins installer
# Environment variables
`$env.EDITOR = "notepad"
`$env.BROWSER = "msedge"
# Nushell specific environment
`$env.NU_LIB_DIRS = [
(`$nu.config-path | path dirname | path join "scripts")
]
`$env.NU_PLUGIN_DIRS = [
(`$nu.config-path | path dirname | path join "plugins")
]
# Add your custom environment variables below
"@ | Out-File -FilePath $envFile -Encoding utf8
Write-LogSuccess "Created env.nu"
} else {
Write-LogInfo "env.nu already exists, skipping"
}
# Create directories
$scriptsDir = Join-Path $ConfigDir "scripts"
$pluginsDir = Join-Path $ConfigDir "plugins"
New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null
New-Item -ItemType Directory -Path $pluginsDir -Force | Out-Null
return $true
}
# Verify installation
function Test-Installation {
param(
[string]$InstallDir
)
$nuBinary = Join-Path $InstallDir "nu.exe"
Write-LogHeader "Verifying Installation"
# Check if nushell binary exists
if (-not (Test-Path $nuBinary)) {
Write-LogError "Nushell binary not found: $nuBinary"
return $false
}
# Test nushell version
Write-LogInfo "Testing nushell binary..."
try {
$versionOutput = & $nuBinary --version
Write-LogSuccess "Nushell version: $versionOutput"
} catch {
Write-LogError "Failed to run nushell binary"
Write-LogError $_.Exception.Message
return $false
}
# Test basic nushell command
Write-LogInfo "Testing basic nushell functionality..."
try {
$testOutput = & $nuBinary -c "echo 'Hello from Nushell'"
if ($testOutput -eq "Hello from Nushell") {
Write-LogSuccess "Basic nushell functionality works"
} else {
Write-LogWarn "Unexpected output from basic test"
}
} catch {
Write-LogError "Basic nushell functionality failed"
return $false
}
# List registered plugins
Write-LogInfo "Checking registered plugins..."
try {
$pluginOutput = & $nuBinary -c "plugin list"
$pluginCount = ($pluginOutput | Select-String "nu_plugin_").Count
if ($pluginCount -gt 0) {
Write-LogSuccess "Found $pluginCount registered plugins"
} else {
Write-LogWarn "No plugins are registered"
}
} catch {
Write-LogWarn "Could not check plugin status"
}
# Check PATH
Write-LogInfo "Checking PATH configuration..."
try {
$nuInPath = Get-Command nu -ErrorAction SilentlyContinue
if ($nuInPath) {
Write-LogSuccess "Nushell is available in PATH"
} else {
Write-LogWarn "Nushell is not in PATH. You may need to restart your terminal."
}
} catch {
Write-LogWarn "Could not verify PATH configuration"
}
Write-LogSuccess "Installation verification complete!"
return $true
}
# Uninstall function
function Uninstall-Nushell {
Write-LogHeader "Uninstalling Nushell"
$removedFiles = 0
# Remove from user directory
if (Test-Path $InstallDirUser) {
$userBinaries = Get-ChildItem -Path $InstallDirUser -Name "nu.exe", "nu_plugin_*.exe" -ErrorAction SilentlyContinue
foreach ($binary in $userBinaries) {
$filePath = Join-Path $InstallDirUser $binary
Remove-Item $filePath -Force -ErrorAction SilentlyContinue
Write-LogSuccess "Removed $binary from $InstallDirUser"
$removedFiles++
}
}
# Remove from system directory (if accessible)
if ((Test-Admin) -and (Test-Path $InstallDirSystem)) {
$systemBinaries = Get-ChildItem -Path $InstallDirSystem -Name "nu.exe", "nu_plugin_*.exe" -ErrorAction SilentlyContinue
foreach ($binary in $systemBinaries) {
$filePath = Join-Path $InstallDirSystem $binary
Remove-Item $filePath -Force -ErrorAction SilentlyContinue
Write-LogSuccess "Removed $binary from $InstallDirSystem"
$removedFiles++
}
}
# Option to remove configuration
$response = Read-Host "Remove nushell configuration directory ($ConfigDir)? [y/N]"
if ($response -match "^[yY]") {
if (Test-Path $ConfigDir) {
Remove-Item $ConfigDir -Recurse -Force -ErrorAction SilentlyContinue
Write-LogSuccess "Removed configuration directory"
}
} else {
Write-LogInfo "Configuration directory preserved"
}
if ($removedFiles -gt 0) {
Write-LogSuccess "Uninstallation complete ($removedFiles files removed)"
Write-LogWarn "You may need to manually remove PATH entries from your environment variables"
} else {
Write-LogWarn "No nushell files found to remove"
}
}
# Main installation function
function Main {
# Handle help
if ($Help) {
Show-Usage
exit 0
}
# Handle uninstall
if ($Uninstall) {
Uninstall-Nushell
exit 0
}
# Show header
Write-LogHeader "Nushell + Plugins Installer for Windows"
Write-LogInfo "Universal bootstrap installer for Nushell and plugins"
Write-LogInfo ""
# Detect platform
$platform = Get-Platform
Write-LogInfo "Detected platform: $platform"
# Determine installation directory and check privileges
if ($System) {
$installDir = $InstallDirSystem
if (-not (Test-Admin)) {
Write-LogError "System installation requires administrator privileges"
Write-LogInfo "Run PowerShell as Administrator or use -User for user installation"
exit 1
}
} else {
$installDir = $InstallDirUser
}
Write-LogInfo "Installing to: $installDir"
# Get version if not specified
if (-not $Version) {
$Version = Get-LatestVersion
}
Write-LogInfo "Version: $Version"
# Cleanup function
$cleanup = {
if (Test-Path $TempDir) {
Remove-Item $TempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
try {
# Install based on method
if ($BuildFromSource) {
if (-not (Install-FromSource -InstallDir $installDir -IncludePlugins (-not $NoPlugins))) {
Write-LogError "Source installation failed"
exit 1
}
} else {
if (-not (Install-FromBinaries -Platform $platform -Version $Version -InstallDir $installDir -IncludePlugins (-not $NoPlugins))) {
Write-LogError "Binary installation failed"
exit 1
}
}
# Register plugins
if (-not $NoPlugins) {
Register-Plugins -InstallDir $installDir
}
# Update PATH
if (-not $NoPath) {
Update-PathEnvironment -InstallDir $installDir -SystemInstall $System
}
# Create configuration
if (-not $NoConfig) {
New-NushellConfig
}
# Verify installation
if ($Verify) {
if (-not (Test-Installation -InstallDir $installDir)) {
Write-LogError "Installation verification failed"
exit 1
}
}
# Final success message
Write-LogHeader "Installation Complete!"
Write-LogSuccess "Nushell has been successfully installed to $installDir"
if (-not $NoPlugins) {
Write-LogSuccess "Plugins have been registered with Nushell"
}
if (-not $NoPath) {
Write-LogInfo "To use Nushell, restart your terminal or start a new PowerShell session"
}
Write-LogInfo ""
Write-LogInfo "Try running: nu --version"
Write-LogInfo "Or start Nushell with: nu"
if (-not $NoPlugins) {
Write-LogInfo "Check plugins with: nu -c 'plugin list'"
}
Write-LogInfo ""
Write-LogInfo "For more information, visit: https://nushell.sh"
Write-LogInfo ""
Write-LogSuccess "Happy shell scripting! 🚀"
} finally {
& $cleanup
}
}
# Run main function
Main

923
scripts/templates/install.sh Executable file
View File

@ -0,0 +1,923 @@
#!/bin/bash
# Universal Nushell + Plugins Bootstrap Installer
# POSIX compliant shell script that installs Nushell and plugins without any prerequisites
#
# This script:
# - Detects platform (Linux/macOS, x86_64/arm64)
# - Downloads or builds Nushell + plugins
# - Installs to user location (~/.local/bin) or system (/usr/local/bin)
# - Updates PATH in shell configuration files
# - Creates initial Nushell configuration
# - Registers all plugins automatically
# - Verifies installation
set -e # Exit on error
# Configuration
REPO_URL="https://github.com/your-org/nushell-plugins"
BINARY_REPO_URL="$REPO_URL/releases/download"
INSTALL_DIR_USER="$HOME/.local/bin"
INSTALL_DIR_SYSTEM="/usr/local/bin"
CONFIG_DIR="$HOME/.config/nushell"
TEMP_DIR="/tmp/nushell-install-$$"
# 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_header() {
printf "\n${PURPLE}🚀 %s${NC}\n" "$1"
printf "${PURPLE}%s${NC}\n" "$(printf '=%.0s' $(seq 1 ${#1}))"
}
# Usage information
usage() {
cat << 'EOF'
Nushell + Plugins Bootstrap Installer
USAGE:
curl -L install-url/install.sh | sh
# or
./install.sh [OPTIONS]
OPTIONS:
--system Install to system directory (/usr/local/bin, requires sudo)
--user Install to user directory (~/.local/bin) [default]
--no-path Don't modify shell PATH configuration
--no-config Don't create initial nushell configuration
--no-plugins Install only nushell, skip plugins
--build-from-source Build from source instead of downloading binaries
--verify Verify installation after completion
--uninstall Remove nushell and plugins
--version VERSION Install specific version (default: latest)
--help Show this help message
EXAMPLES:
# Default installation (user directory, with plugins)
curl -L install-url/install.sh | sh
# System installation
curl -L install-url/install.sh | sh -s -- --system
# Install without plugins
curl -L install-url/install.sh | sh -s -- --no-plugins
# Build from source
curl -L install-url/install.sh | sh -s -- --build-from-source
# Install specific version
curl -L install-url/install.sh | sh -s -- --version v0.107.1
EOF
}
# Platform detection
detect_platform() {
local os arch
# Detect OS
case "$(uname -s)" in
Linux) os="linux" ;;
Darwin) os="darwin" ;;
CYGWIN*|MINGW*|MSYS*) os="windows" ;;
*) log_error "Unsupported OS: $(uname -s)"; exit 1 ;;
esac
# Detect architecture
case "$(uname -m)" in
x86_64|amd64) arch="x86_64" ;;
aarch64|arm64) arch="arm64" ;;
armv7l) arch="armv7" ;;
*) log_error "Unsupported architecture: $(uname -m)"; exit 1 ;;
esac
# Special handling for Darwin arm64
if [ "$os" = "darwin" ] && [ "$arch" = "arm64" ]; then
arch="arm64"
fi
echo "${os}-${arch}"
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check dependencies for building from source
check_build_dependencies() {
local missing=""
if ! command_exists "git"; then
missing="$missing git"
fi
if ! command_exists "cargo"; then
missing="$missing cargo"
fi
if ! command_exists "rustc"; then
missing="$missing rust"
fi
if [ -n "$missing" ]; then
log_error "Missing build dependencies:$missing"
log_info "Please install these tools or use binary installation instead"
return 1
fi
return 0
}
# Download file with progress
download_file() {
local url="$1"
local output="$2"
local desc="${3:-file}"
log_info "Downloading $desc..."
if command_exists "curl"; then
if ! curl -L --fail --progress-bar "$url" -o "$output"; then
log_error "Failed to download $desc from $url"
return 1
fi
elif command_exists "wget"; then
if ! wget --progress=bar:force "$url" -O "$output"; then
log_error "Failed to download $desc from $url"
return 1
fi
else
log_error "Neither curl nor wget is available"
return 1
fi
log_success "Downloaded $desc"
return 0
}
# Extract archive
extract_archive() {
local archive="$1"
local destination="$2"
log_info "Extracting archive..."
case "$archive" in
*.tar.gz)
if ! tar -xzf "$archive" -C "$destination"; then
log_error "Failed to extract $archive"
return 1
fi
;;
*.zip)
if command_exists "unzip"; then
if ! unzip -q "$archive" -d "$destination"; then
log_error "Failed to extract $archive"
return 1
fi
else
log_error "unzip command not found"
return 1
fi
;;
*)
log_error "Unsupported archive format: $archive"
return 1
;;
esac
log_success "Extracted archive"
return 0
}
# Get latest release version
get_latest_version() {
local version=""
if command_exists "curl"; then
version=$(curl -s "https://api.github.com/repos/your-org/nushell-plugins/releases/latest" | \
grep '"tag_name":' | \
sed -E 's/.*"([^"]+)".*/\1/')
elif command_exists "wget"; then
version=$(wget -qO- "https://api.github.com/repos/your-org/nushell-plugins/releases/latest" | \
grep '"tag_name":' | \
sed -E 's/.*"([^"]+)".*/\1/')
fi
if [ -z "$version" ]; then
# Fallback to a reasonable default
version="v0.107.1"
log_warn "Could not detect latest version, using $version"
fi
echo "$version"
}
# Download and install binaries
install_from_binaries() {
local platform="$1"
local version="$2"
local install_dir="$3"
local include_plugins="$4"
log_header "Installing from Pre-built Binaries"
# Create temporary directory
mkdir -p "$TEMP_DIR"
cd "$TEMP_DIR"
# Determine archive name and URL
local archive_name="nushell-plugins-${platform}-${version}.tar.gz"
local download_url="${BINARY_REPO_URL}/${version}/${archive_name}"
# Download archive
if ! download_file "$download_url" "$archive_name" "Nushell distribution"; then
log_warn "Binary download failed, trying alternative..."
# Try without version prefix
archive_name="nushell-plugins-${platform}.tar.gz"
download_url="${BINARY_REPO_URL}/latest/${archive_name}"
if ! download_file "$download_url" "$archive_name" "Nushell distribution (latest)"; then
log_error "Failed to download binaries"
return 1
fi
fi
# Extract archive
if ! extract_archive "$archive_name" "."; then
return 1
fi
# Find extracted directory
local extract_dir=""
for dir in */; do
if [ -d "$dir" ]; then
extract_dir="$dir"
break
fi
done
if [ -z "$extract_dir" ]; then
log_error "No extracted directory found"
return 1
fi
log_info "Installing binaries to $install_dir..."
# Create install directory
mkdir -p "$install_dir"
# Install nushell binary
local nu_binary="nu"
if [ "$platform" = "windows-x86_64" ]; then
nu_binary="nu.exe"
fi
if [ -f "${extract_dir}${nu_binary}" ]; then
cp "${extract_dir}${nu_binary}" "$install_dir/"
chmod +x "${install_dir}/${nu_binary}"
log_success "Installed nushell binary"
else
log_error "Nushell binary not found in archive"
return 1
fi
# Install plugins if requested
if [ "$include_plugins" = "true" ]; then
local plugin_count=0
for plugin_file in "${extract_dir}"nu_plugin_*; do
if [ -f "$plugin_file" ]; then
local plugin_name=$(basename "$plugin_file")
cp "$plugin_file" "$install_dir/"
chmod +x "${install_dir}/${plugin_name}"
plugin_count=$((plugin_count + 1))
fi
done
if [ $plugin_count -gt 0 ]; then
log_success "Installed $plugin_count plugins"
else
log_warn "No plugins found in archive"
fi
fi
# Copy configuration files if they exist
if [ -d "${extract_dir}config/" ]; then
mkdir -p "$CONFIG_DIR"
cp -r "${extract_dir}config/"* "$CONFIG_DIR/"
log_success "Installed configuration files"
fi
return 0
}
# Build and install from source
install_from_source() {
local install_dir="$1"
local include_plugins="$2"
log_header "Building from Source"
# Check dependencies
if ! check_build_dependencies; then
return 1
fi
# Create temporary directory
mkdir -p "$TEMP_DIR"
cd "$TEMP_DIR"
# Clone repository
log_info "Cloning repository..."
if ! git clone --recursive "$REPO_URL" nushell-plugins; then
log_error "Failed to clone repository"
return 1
fi
cd nushell-plugins
# Build nushell
log_info "Building nushell..."
if command_exists "just"; then
if ! just build-nushell; then
log_error "Failed to build nushell with just"
return 1
fi
else
# Fallback to manual build
cd nushell
if ! cargo build --release --features "plugin,network,sqlite,trash-support,rustls-tls"; then
log_error "Failed to build nushell"
return 1
fi
cd ..
fi
# Build plugins if requested
if [ "$include_plugins" = "true" ]; then
log_info "Building plugins..."
if command_exists "just"; then
if ! just build; then
log_warn "Failed to build some plugins"
fi
else
# Build plugins manually
for plugin_dir in nu_plugin_*; do
if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then
log_info "Building $plugin_dir..."
cd "$plugin_dir"
if cargo build --release; then
log_success "Built $plugin_dir"
else
log_warn "Failed to build $plugin_dir"
fi
cd ..
fi
done
fi
fi
# Install binaries
log_info "Installing binaries to $install_dir..."
mkdir -p "$install_dir"
# Install nushell
local nu_binary="nushell/target/release/nu"
if [ -f "$nu_binary" ]; then
cp "$nu_binary" "$install_dir/"
chmod +x "${install_dir}/nu"
log_success "Installed nushell binary"
else
log_error "Nushell binary not found"
return 1
fi
# Install plugins
if [ "$include_plugins" = "true" ]; then
local plugin_count=0
for plugin_dir in nu_plugin_*; do
if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then
local plugin_binary="${plugin_dir}/target/release/${plugin_dir}"
if [ -f "$plugin_binary" ]; then
cp "$plugin_binary" "$install_dir/"
chmod +x "${install_dir}/${plugin_dir}"
plugin_count=$((plugin_count + 1))
fi
done
done
if [ $plugin_count -gt 0 ]; then
log_success "Installed $plugin_count plugins"
else
log_warn "No plugins were built successfully"
fi
fi
return 0
}
# Register plugins with nushell
register_plugins() {
local install_dir="$1"
local nu_binary="${install_dir}/nu"
if [ ! -f "$nu_binary" ]; then
log_error "Nushell binary not found: $nu_binary"
return 1
fi
log_header "Registering Plugins"
# Find all plugin binaries
local plugin_count=0
for plugin_file in "${install_dir}"/nu_plugin_*; do
if [ -f "$plugin_file" ] && [ -x "$plugin_file" ]; then
local plugin_name=$(basename "$plugin_file")
log_info "Registering $plugin_name..."
if "$nu_binary" -c "plugin add '$plugin_file'"; then
log_success "Registered $plugin_name"
plugin_count=$((plugin_count + 1))
else
log_warn "Failed to register $plugin_name"
fi
fi
done
if [ $plugin_count -gt 0 ]; then
log_success "Successfully registered $plugin_count plugins"
else
log_warn "No plugins were registered"
fi
return 0
}
# Update PATH in shell configuration
update_shell_path() {
local install_dir="$1"
log_header "Updating Shell Configuration"
# List of shell configuration files to update
local shell_configs=""
# Detect current shell and add its config file first
case "$SHELL" in
*/bash) shell_configs="$HOME/.bashrc $HOME/.bash_profile" ;;
*/zsh) shell_configs="$HOME/.zshrc" ;;
*/fish) shell_configs="$HOME/.config/fish/config.fish" ;;
*/nu) shell_configs="$HOME/.config/nushell/env.nu" ;;
esac
# Add common configuration files
shell_configs="$shell_configs $HOME/.profile"
local updated=false
for config_file in $shell_configs; do
if [ -f "$config_file" ] || [ "$config_file" = "$HOME/.bashrc" ] || [ "$config_file" = "$HOME/.profile" ]; then
# Check if already in PATH
if grep -q "$install_dir" "$config_file" 2>/dev/null; then
log_info "PATH already updated in $(basename "$config_file")"
continue
fi
# Create config file if it doesn't exist
if [ ! -f "$config_file" ]; then
touch "$config_file"
fi
# Add PATH update to config file
case "$config_file" in
*.fish)
echo "fish_add_path $install_dir" >> "$config_file"
;;
*/env.nu)
echo "\$env.PATH = (\$env.PATH | split row (char esep) | append \"$install_dir\" | uniq)" >> "$config_file"
;;
*)
echo "" >> "$config_file"
echo "# Added by nushell installer" >> "$config_file"
echo "export PATH=\"$install_dir:\$PATH\"" >> "$config_file"
;;
esac
log_success "Updated $(basename "$config_file")"
updated=true
fi
done
if [ "$updated" = "true" ]; then
log_info "Please restart your terminal or run 'source ~/.bashrc' to update PATH"
else
log_warn "No shell configuration files were updated"
log_info "Please manually add $install_dir to your PATH"
fi
# Update current session PATH
export PATH="$install_dir:$PATH"
log_success "Updated PATH for current session"
}
# Create initial nushell configuration
create_nushell_config() {
log_header "Creating Nushell Configuration"
# Create config directory
mkdir -p "$CONFIG_DIR"
# Create basic config.nu if it doesn't exist
local config_file="$CONFIG_DIR/config.nu"
if [ ! -f "$config_file" ]; then
cat > "$config_file" << 'EOF'
# Nushell Configuration
# Created by nushell-plugins installer
# Set up basic configuration
$env.config = {
show_banner: false
edit_mode: emacs
shell_integration: true
table: {
mode: rounded
index_mode: always
show_empty: true
padding: { left: 1, right: 1 }
}
completions: {
case_sensitive: false
quick: true
partial: true
algorithm: "prefix"
}
history: {
max_size: 10000
sync_on_enter: true
file_format: "plaintext"
}
filesize: {
metric: false
format: "auto"
}
}
# Load custom commands and aliases
# Add your custom configuration below
EOF
log_success "Created config.nu"
else
log_info "config.nu already exists, skipping"
fi
# Create basic env.nu if it doesn't exist
local env_file="$CONFIG_DIR/env.nu"
if [ ! -f "$env_file" ]; then
cat > "$env_file" << 'EOF'
# Nushell Environment Configuration
# Created by nushell-plugins installer
# Environment variables
$env.EDITOR = "nano"
$env.BROWSER = "firefox"
# Nushell specific environment
$env.NU_LIB_DIRS = [
($nu.config-path | path dirname | path join "scripts")
]
$env.NU_PLUGIN_DIRS = [
($nu.config-path | path dirname | path join "plugins")
]
# Add your custom environment variables below
EOF
log_success "Created env.nu"
else
log_info "env.nu already exists, skipping"
fi
# Create scripts directory
local scripts_dir="$CONFIG_DIR/scripts"
mkdir -p "$scripts_dir"
# Create plugins directory
local plugins_dir="$CONFIG_DIR/plugins"
mkdir -p "$plugins_dir"
}
# Verify installation
verify_installation() {
local install_dir="$1"
local nu_binary="${install_dir}/nu"
log_header "Verifying Installation"
# Check if nushell binary exists and is executable
if [ ! -f "$nu_binary" ]; then
log_error "Nushell binary not found: $nu_binary"
return 1
fi
if [ ! -x "$nu_binary" ]; then
log_error "Nushell binary is not executable: $nu_binary"
return 1
fi
# Test nushell version
log_info "Testing nushell binary..."
local version_output
if version_output=$("$nu_binary" --version 2>&1); then
log_success "Nushell version: $version_output"
else
log_error "Failed to run nushell binary"
log_error "Output: $version_output"
return 1
fi
# Test basic nushell command
log_info "Testing basic nushell functionality..."
if "$nu_binary" -c "echo 'Hello from Nushell'" >/dev/null 2>&1; then
log_success "Basic nushell functionality works"
else
log_error "Basic nushell functionality failed"
return 1
fi
# List registered plugins
log_info "Checking registered plugins..."
local plugin_output
if plugin_output=$("$nu_binary" -c "plugin list" 2>&1); then
local plugin_count=$(echo "$plugin_output" | grep -c "nu_plugin_" || true)
if [ "$plugin_count" -gt 0 ]; then
log_success "Found $plugin_count registered plugins"
else
log_warn "No plugins are registered"
fi
else
log_warn "Could not check plugin status"
fi
# Check PATH
log_info "Checking PATH configuration..."
if command -v nu >/dev/null 2>&1; then
log_success "Nushell is available in PATH"
else
log_warn "Nushell is not in PATH. You may need to restart your terminal."
fi
log_success "Installation verification complete!"
return 0
}
# Uninstall function
uninstall_nushell() {
log_header "Uninstalling Nushell"
local removed_files=0
# Remove from user directory
if [ -d "$INSTALL_DIR_USER" ]; then
for binary in nu nu_plugin_*; do
local file_path="$INSTALL_DIR_USER/$binary"
if [ -f "$file_path" ]; then
rm -f "$file_path"
log_success "Removed $binary from $INSTALL_DIR_USER"
removed_files=$((removed_files + 1))
fi
done
fi
# Remove from system directory (if accessible)
if [ -w "$INSTALL_DIR_SYSTEM" ] 2>/dev/null; then
for binary in nu nu_plugin_*; do
local file_path="$INSTALL_DIR_SYSTEM/$binary"
if [ -f "$file_path" ]; then
rm -f "$file_path"
log_success "Removed $binary from $INSTALL_DIR_SYSTEM"
removed_files=$((removed_files + 1))
fi
done
fi
# Option to remove configuration
printf "Remove nushell configuration directory ($CONFIG_DIR)? [y/N]: "
read -r response
case "$response" in
[yY]|[yY][eE][sS])
if [ -d "$CONFIG_DIR" ]; then
rm -rf "$CONFIG_DIR"
log_success "Removed configuration directory"
fi
;;
*)
log_info "Configuration directory preserved"
;;
esac
if [ $removed_files -gt 0 ]; then
log_success "Uninstallation complete ($removed_files files removed)"
log_warn "You may need to manually remove PATH entries from your shell configuration"
else
log_warn "No nushell files found to remove"
fi
}
# Main installation function
main() {
local install_mode="user"
local modify_path="true"
local create_config="true"
local include_plugins="true"
local build_from_source="false"
local verify_install="false"
local do_uninstall="false"
local version=""
# Parse command line arguments
while [ $# -gt 0 ]; do
case "$1" in
--system)
install_mode="system"
shift
;;
--user)
install_mode="user"
shift
;;
--no-path)
modify_path="false"
shift
;;
--no-config)
create_config="false"
shift
;;
--no-plugins)
include_plugins="false"
shift
;;
--build-from-source)
build_from_source="true"
shift
;;
--verify)
verify_install="true"
shift
;;
--uninstall)
do_uninstall="true"
shift
;;
--version)
version="$2"
shift 2
;;
--help)
usage
exit 0
;;
*)
log_error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Handle uninstall
if [ "$do_uninstall" = "true" ]; then
uninstall_nushell
exit 0
fi
# Show header
log_header "Nushell + Plugins Installer"
log_info "Universal bootstrap installer for Nushell and plugins"
log_info ""
# Detect platform
local platform
platform=$(detect_platform)
log_info "Detected platform: $platform"
# Determine installation directory
local install_dir
if [ "$install_mode" = "system" ]; then
install_dir="$INSTALL_DIR_SYSTEM"
if [ "$(id -u)" != "0" ] && [ ! -w "$(dirname "$install_dir")" ]; then
log_error "System installation requires root privileges"
log_info "Run with sudo or use --user for user installation"
exit 1
fi
else
install_dir="$INSTALL_DIR_USER"
fi
log_info "Installing to: $install_dir"
# Get version if not specified
if [ -z "$version" ]; then
version=$(get_latest_version)
fi
log_info "Version: $version"
# Cleanup function
cleanup() {
if [ -d "$TEMP_DIR" ]; then
rm -rf "$TEMP_DIR"
fi
}
trap cleanup EXIT
# Install based on method
if [ "$build_from_source" = "true" ]; then
if ! install_from_source "$install_dir" "$include_plugins"; then
log_error "Source installation failed"
exit 1
fi
else
if ! install_from_binaries "$platform" "$version" "$install_dir" "$include_plugins"; then
log_error "Binary installation failed"
exit 1
fi
fi
# Register plugins
if [ "$include_plugins" = "true" ]; then
register_plugins "$install_dir"
fi
# Update PATH
if [ "$modify_path" = "true" ]; then
update_shell_path "$install_dir"
fi
# Create configuration
if [ "$create_config" = "true" ]; then
create_nushell_config
fi
# Verify installation
if [ "$verify_install" = "true" ]; then
if ! verify_installation "$install_dir"; then
log_error "Installation verification failed"
exit 1
fi
fi
# Final success message
log_header "Installation Complete!"
log_success "Nushell has been successfully installed to $install_dir"
if [ "$include_plugins" = "true" ]; then
log_success "Plugins have been registered with Nushell"
fi
if [ "$modify_path" = "true" ]; then
log_info "To use Nushell, restart your terminal or run:"
log_info " source ~/.bashrc # or your shell's config file"
fi
log_info ""
log_info "Try running: nu --version"
log_info "Or start Nushell with: nu"
if [ "$include_plugins" = "true" ]; then
log_info "Check plugins with: nu -c 'plugin list'"
fi
log_info ""
log_info "For more information, visit: https://nushell.sh"
log_info ""
log_success "Happy shell scripting! 🚀"
}
# Run main function with all arguments
main "$@"

View File

@ -0,0 +1,528 @@
# 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

563
scripts/templates/uninstall.sh Executable file
View File

@ -0,0 +1,563 @@
#!/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
--user Remove from user location (~/.local/bin) - default
--dry-run Show what would be removed without actually removing
--debug Enable debug output
EXAMPLES:
$0 # Interactive removal from user location
$0 -y # Non-interactive removal
$0 --backup-config -y # Remove with config backup
$0 --system # Remove system installation (needs sudo)
$0 --dry-run # Show what would be removed
EOF
}
# Parse command line arguments
INTERACTIVE=1
KEEP_CONFIG=0
BACKUP_CONFIG=0
SYSTEM_INSTALL=0
DRY_RUN=0
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
;;
--dry-run)
DRY_RUN=1
shift
;;
--debug)
DEBUG=1
shift
;;
*)
log_error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Determine installation directory
if [ "$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 "$@"

843
scripts/verify_installation.nu Executable file
View File

@ -0,0 +1,843 @@
#!/usr/bin/env nu
# Verify Installation Script
# Comprehensive installation verification for Nushell full distribution
# Tests nu binary, all plugins, configuration, and generates detailed report
use lib/common_lib.nu [
log_info, log_error, log_success, log_warn, log_debug,
get_current_platform, check_binary
]
def main [
--platform (-p): string = "" # Target platform for verification
--install-dir (-i): string = "" # Installation directory to check
--config-dir (-c): string = "" # Configuration directory to check
--plugins (-P): list<string> = [] # Specific plugins to verify
--all-plugins (-a) # Verify all found plugins
--report (-r): string = "" # Generate report to file
--json # Output report in JSON format
--quick (-q) # Quick verification (basic checks only)
--verbose (-v) # Verbose output with detailed information
--fix-permissions # Attempt to fix permission issues
] {
log_info "🔍 Nushell Full Distribution Installation Verification"
log_info "================================================="
if $verbose != null {
log_debug "Verbose mode enabled"
}
# Get verification configuration
let config = get_verification_config $platform $install_dir $config_dir
log_info ""
log_info "📍 Verification Configuration:"
log_info $" Platform: ($config.platform)"
log_info $" Binary directory: ($config.bin_dir)"
log_info $" Config directory: ($config.config_dir)"
if $quick != null {
log_info " Mode: Quick verification"
} else {
log_info " Mode: Comprehensive verification"
}
# Run verification checks
let verification_results = run_verification_suite $config $plugins $all_plugins $quick $verbose $fix_permissions
# Generate and display report
let report = generate_verification_report $verification_results $config
display_verification_report $report $verbose $json
# Save report to file if requested
if ($report | str length) > 0 {
save_verification_report $report $json $report
}
# Exit with appropriate code
let overall_status = $report.summary.overall_status
if $overall_status == "passed" {
log_success "🎉 All verification checks passed!"
exit 0
} else if $overall_status == "warning" {
log_warn "⚠️ Verification completed with warnings"
exit 0
} else {
log_error "❌ Verification failed"
exit 1
}
}
# Get verification configuration
def get_verification_config [platform: string, install_dir: string, config_dir: string] -> record {
let current_platform = if ($platform | str length) > 0 {
$platform
} else {
get_current_platform
}
let home_dir = ($env.HOME | path expand)
# Determine binary directory
let bin_dir = if ($install_dir | str length) > 0 {
($install_dir | path expand)
} else if (which nu | length) > 0 {
(which nu | get 0.path | path dirname)
} else {
$"($home_dir)/.local/bin"
}
# Determine configuration directory
let config_dir = if ($config_dir | str length) > 0 {
($config_dir | path expand)
} else if ($env.XDG_CONFIG_HOME? | is-not-empty) {
$"($env.XDG_CONFIG_HOME)/nushell"
} else {
$"($home_dir)/.config/nushell"
}
{
platform: $current_platform,
bin_dir: $bin_dir,
config_dir: $config_dir,
binary_extension: (if ($current_platform | str starts-with "windows") { ".exe" } else { "" }),
home_dir: $home_dir
}
}
# Run complete verification suite
def run_verification_suite [
config: record,
plugins: list<string>,
all_plugins: bool,
quick: bool,
verbose: bool,
fix_permissions: bool
] -> record {
let results = {}
# Verify nushell binary
log_info "🚀 Verifying Nushell binary..."
let nushell_result = verify_nushell_binary $config $verbose $fix_permissions
$results = ($results | insert "nushell" $nushell_result)
# Verify plugins
log_info "🔌 Verifying plugins..."
let plugin_results = verify_plugins $config $plugins $all_plugins $verbose $fix_permissions
$results = ($results | insert "plugins" $plugin_results)
# Verify configuration
log_info "⚙️ Verifying configuration..."
let config_results = verify_configuration $config $verbose
$results = ($results | insert "configuration" $config_results)
# Verify PATH integration
log_info "🛣️ Verifying PATH integration..."
let path_results = verify_path_integration $config $verbose
$results = ($results | insert "path_integration" $path_results)
# Extended checks for comprehensive verification
if not $quick {
# Verify plugin registration
log_info "📝 Verifying plugin registration..."
let registration_results = verify_plugin_registration $config $verbose
$results = ($results | insert "plugin_registration" $registration_results)
# Test basic functionality
log_info "🧪 Testing basic functionality..."
let functionality_results = test_basic_functionality $config $verbose
$results = ($results | insert "functionality" $functionality_results)
# System integration checks
log_info "🔗 Verifying system integration..."
let system_results = verify_system_integration $config $verbose
$results = ($results | insert "system_integration" $system_results)
}
$results
}
# Verify nushell binary
def verify_nushell_binary [config: record, verbose: bool, fix_permissions: bool] -> record {
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
let result = {
component: "nushell_binary",
checks: []
}
# Check if binary exists
let exists_check = if ($nu_path | path exists) {
{name: "binary_exists", status: "passed", message: $"Binary found at ($nu_path)"}
} else {
{name: "binary_exists", status: "failed", message: $"Binary not found at ($nu_path)"}
}
$result.checks = ($result.checks | append $exists_check)
if $exists_check.status == "passed" {
# Check if binary is executable
let executable_check = if (check_binary $nu_path) {
{name: "binary_executable", status: "passed", message: "Binary is executable"}
} else {
let check_result = if $fix_permissions {
try {
chmod +x $nu_path
if (check_binary $nu_path) {
{name: "binary_executable", status: "passed", message: "Binary permissions fixed and is now executable"}
} else {
{name: "binary_executable", status: "failed", message: "Binary not executable (permission fix failed)"}
}
} catch {
{name: "binary_executable", status: "failed", message: "Binary not executable (cannot fix permissions)"}
}
} else {
{name: "binary_executable", status: "failed", message: "Binary not executable (use --fix-permissions to attempt fix)"}
}
$check_result
}
$result.checks = ($result.checks | append $executable_check)
# Test version command
if $executable_check.status == "passed" {
let version_check = try {
let version_output = (run-external $nu_path "--version")
{name: "version_command", status: "passed", message: $"Version: ($version_output)", details: $version_output}
} catch {|err|
{name: "version_command", status: "failed", message: $"Version command failed: ($err.msg)"}
}
$result.checks = ($result.checks | append $version_check)
# Test basic command
let basic_test = try {
let test_output = (run-external $nu_path "-c" "echo 'test'")
if $test_output == "test" {
{name: "basic_command", status: "passed", message: "Basic command execution works"}
} else {
{name: "basic_command", status: "warning", message: $"Unexpected output: ($test_output)"}
}
} catch {|err|
{name: "basic_command", status: "failed", message: $"Basic command failed: ($err.msg)"}
}
$result.checks = ($result.checks | append $basic_test)
}
}
$result
}
# Verify plugins
def verify_plugins [config: record, specific_plugins: list<string>, all_plugins: bool, verbose: bool, fix_permissions: bool] -> record {
let plugin_list = if $all_plugins {
# Find all plugin binaries in bin directory
if ($config.bin_dir | path exists) {
ls $config.bin_dir
| where name =~ $"nu_plugin_.*($config.binary_extension)$"
| get name
| each {|path| ($path | path basename | str replace $config.binary_extension "")}
} else {
[]
}
} else if ($specific_plugins | length) > 0 {
$specific_plugins
} else {
# Default plugins to check
[
"nu_plugin_clipboard",
"nu_plugin_hashes",
"nu_plugin_desktop_notifications",
"nu_plugin_highlight"
]
}
let results = {
component: "plugins",
plugin_count: ($plugin_list | length),
plugins: {}
}
if ($plugin_list | length) == 0 {
$results = ($results | insert "message" "No plugins to verify")
return $results
}
for plugin in $plugin_list {
let plugin_path = $"($config.bin_dir)/($plugin)($config.binary_extension)"
let plugin_result = {
name: $plugin,
path: $plugin_path,
checks: []
}
# Check if plugin binary exists
let exists_check = if ($plugin_path | path exists) {
{name: "binary_exists", status: "passed", message: $"Plugin binary found at ($plugin_path)"}
} else {
{name: "binary_exists", status: "failed", message: $"Plugin binary not found at ($plugin_path)"}
}
$plugin_result.checks = ($plugin_result.checks | append $exists_check)
if $exists_check.status == "passed" {
# Check if plugin is executable
let executable_check = if (check_binary $plugin_path) {
{name: "binary_executable", status: "passed", message: "Plugin binary is executable"}
} else {
let check_result = if $fix_permissions {
try {
chmod +x $plugin_path
if (check_binary $plugin_path) {
{name: "binary_executable", status: "passed", message: "Plugin permissions fixed"}
} else {
{name: "binary_executable", status: "failed", message: "Plugin not executable (fix failed)"}
}
} catch {
{name: "binary_executable", status: "failed", message: "Plugin not executable (cannot fix)"}
}
} else {
{name: "binary_executable", status: "failed", message: "Plugin not executable"}
}
$check_result
}
$plugin_result.checks = ($plugin_result.checks | append $executable_check)
# Test plugin help command (if executable)
if $executable_check.status == "passed" {
let help_check = try {
let help_output = (run-external $plugin_path "--help")
{name: "help_command", status: "passed", message: "Plugin help command works"}
} catch {|err|
{name: "help_command", status: "warning", message: $"Plugin help command issue: ($err.msg)"}
}
$plugin_result.checks = ($plugin_result.checks | append $help_check)
}
}
$results.plugins = ($results.plugins | insert $plugin $plugin_result)
}
$results
}
# Verify configuration files
def verify_configuration [config: record, verbose: bool] -> record {
let config_files = [
{name: "config.nu", required: true, description: "Main nushell configuration"},
{name: "env.nu", required: true, description: "Environment configuration"},
{name: "distribution_config.toml", required: false, description: "Distribution metadata"}
]
let results = {
component: "configuration",
config_dir: $config.config_dir,
files: {}
}
for file_info in $config_files {
let file_path = $"($config.config_dir)/($file_info.name)"
let file_result = {
name: $file_info.name,
path: $file_path,
required: $file_info.required,
description: $file_info.description,
checks: []
}
# Check if file exists
let exists_check = if ($file_path | path exists) {
{name: "file_exists", status: "passed", message: $"Configuration file found: ($file_info.name)"}
} else {
let status = if $file_info.required { "failed" } else { "warning" }
{name: "file_exists", status: $status, message: $"Configuration file not found: ($file_info.name)"}
}
$file_result.checks = ($file_result.checks | append $exists_check)
if $exists_check.status == "passed" {
# Check file readability
let readable_check = try {
let content = (open $file_path --raw)
let size = ($content | str length)
{name: "file_readable", status: "passed", message: $"File readable (($size) characters)"}
} catch {|err|
{name: "file_readable", status: "failed", message: $"File not readable: ($err.msg)"}
}
$file_result.checks = ($file_result.checks | append $readable_check)
# Syntax check for .nu files
if ($file_info.name | str ends-with ".nu") and $readable_check.status == "passed" {
let syntax_check = try {
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
if ($nu_path | path exists) {
# Test parsing the configuration file
run-external $nu_path "-c" $"source ($file_path); echo 'syntax ok'"
{name: "syntax_check", status: "passed", message: "Configuration syntax is valid"}
} else {
{name: "syntax_check", status: "warning", message: "Cannot verify syntax (nu binary not available)"}
}
} catch {|err|
{name: "syntax_check", status: "failed", message: $"Syntax error in configuration: ($err.msg)"}
}
$file_result.checks = ($file_result.checks | append $syntax_check)
}
}
$results.files = ($results.files | insert $file_info.name $file_result)
}
$results
}
# Verify PATH integration
def verify_path_integration [config: record, verbose: bool] -> record {
let results = {
component: "path_integration",
bin_dir: $config.bin_dir,
checks: []
}
# Check if bin directory is in PATH
let path_entries = ($env.PATH | split row (if (sys host | get name) == "Windows" { ";" } else { ":" }))
let in_path_check = if ($config.bin_dir in $path_entries) {
{name: "bin_dir_in_path", status: "passed", message: $"Binary directory is in PATH: ($config.bin_dir)"}
} else {
{name: "bin_dir_in_path", status: "warning", message: $"Binary directory not in PATH: ($config.bin_dir)"}
}
$results.checks = ($results.checks | append $in_path_check)
# Check if nu command is available globally
let global_nu_check = if (which nu | length) > 0 {
let nu_location = (which nu | get 0.path)
{name: "nu_globally_available", status: "passed", message: $"nu command available globally at: ($nu_location)"}
} else {
{name: "nu_globally_available", status: "failed", message: "nu command not available globally"}
}
$results.checks = ($results.checks | append $global_nu_check)
$results
}
# Verify plugin registration
def verify_plugin_registration [config: record, verbose: bool] -> record {
let results = {
component: "plugin_registration",
checks: []
}
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
if not ($nu_path | path exists) {
$results.checks = ($results.checks | append {
name: "nu_binary_available",
status: "failed",
message: "Cannot verify plugin registration - nu binary not available"
})
return $results
}
# Get list of registered plugins
let registered_plugins_check = try {
let plugin_list = (run-external $nu_path "-c" "plugin list | get name")
{
name: "list_registered_plugins",
status: "passed",
message: $"Found (($plugin_list | length)) registered plugins",
details: $plugin_list
}
} catch {|err|
{
name: "list_registered_plugins",
status: "warning",
message: $"Could not list registered plugins: ($err.msg)"
}
}
$results.checks = ($results.checks | append $registered_plugins_check)
# Test plugin functionality if registration check passed
if $registered_plugins_check.status == "passed" and ($registered_plugins_check.details? | is-not-empty) {
let plugin_test_check = try {
# Test a simple plugin command if clipboard plugin is available
if "nu_plugin_clipboard" in $registered_plugins_check.details {
run-external $nu_path "-c" "clipboard --help"
{name: "plugin_functionality", status: "passed", message: "Plugin commands are functional"}
} else {
{name: "plugin_functionality", status: "warning", message: "No testable plugins found"}
}
} catch {|err|
{name: "plugin_functionality", status: "warning", message: $"Plugin functionality test failed: ($err.msg)"}
}
$results.checks = ($results.checks | append $plugin_test_check)
}
$results
}
# Test basic functionality
def test_basic_functionality [config: record, verbose: bool] -> record {
let results = {
component: "basic_functionality",
checks: []
}
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
if not ($nu_path | path exists) {
$results.checks = ($results.checks | append {
name: "nu_binary_available",
status: "failed",
message: "Cannot test functionality - nu binary not available"
})
return $results
}
# Test basic arithmetic
let arithmetic_test = try {
let result = (run-external $nu_path "-c" "2 + 2")
if ($result | str trim) == "4" {
{name: "arithmetic", status: "passed", message: "Basic arithmetic works"}
} else {
{name: "arithmetic", status: "warning", message: $"Unexpected arithmetic result: ($result)"}
}
} catch {|err|
{name: "arithmetic", status: "failed", message: $"Arithmetic test failed: ($err.msg)"}
}
$results.checks = ($results.checks | append $arithmetic_test)
# Test piping
let pipe_test = try {
let result = (run-external $nu_path "-c" "echo 'hello world' | str upcase")
if ($result | str trim) == "HELLO WORLD" {
{name: "piping", status: "passed", message: "Command piping works"}
} else {
{name: "piping", status: "warning", message: $"Unexpected piping result: ($result)"}
}
} catch {|err|
{name: "piping", status: "failed", message: $"Piping test failed: ($err.msg)"}
}
$results.checks = ($results.checks | append $pipe_test)
# Test table operations
let table_test = try {
let result = (run-external $nu_path "-c" "[{name: 'test', value: 42}] | get 0.value")
if ($result | str trim) == "42" {
{name: "tables", status: "passed", message: "Table operations work"}
} else {
{name: "tables", status: "warning", message: $"Unexpected table result: ($result)"}
}
} catch {|err|
{name: "tables", status: "failed", message: $"Table test failed: ($err.msg)"}
}
$results.checks = ($results.checks | append $table_test)
$results
}
# Verify system integration
def verify_system_integration [config: record, verbose: bool] -> record {
let results = {
component: "system_integration",
checks: []
}
# Check shell profile integration
let profile_files = [
"~/.bashrc",
"~/.zshrc",
"~/.profile",
"~/.bash_profile"
]
let profile_check = {
name: "shell_profile_integration",
status: "warning",
message: "Shell profile integration not detected",
details: []
}
for profile_file in $profile_files {
let expanded_path = ($profile_file | path expand)
if ($expanded_path | path exists) {
try {
let content = (open $expanded_path --raw)
if ($content | str contains $config.bin_dir) {
$profile_check.status = "passed"
$profile_check.message = $"Shell profile integration found in ($profile_file)"
break
}
} catch {
# Ignore errors reading profile files
}
}
}
$results.checks = ($results.checks | append $profile_check)
# Check terminal integration (can nu be started as a shell)
let terminal_test = try {
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
if ($nu_path | path exists) {
# Test if nu can be started (quick test)
run-external $nu_path "-c" "exit"
{name: "terminal_integration", status: "passed", message: "Nu can be started as shell"}
} else {
{name: "terminal_integration", status: "failed", message: "Nu binary not available for terminal test"}
}
} catch {|err|
{name: "terminal_integration", status: "warning", message: $"Terminal integration test issue: ($err.msg)"}
}
$results.checks = ($results.checks | append $terminal_test)
$results
}
# Generate verification report
def generate_verification_report [results: record, config: record] -> record {
let summary = calculate_summary $results
{
metadata: {
verification_time: (date now | format date "%Y-%m-%d %H:%M:%S UTC"),
platform: $config.platform,
bin_directory: $config.bin_dir,
config_directory: $config.config_dir,
nushell_version: (try { (nu --version) } catch { "unknown" })
},
summary: $summary,
detailed_results: $results
}
}
# Calculate summary statistics
def calculate_summary [results: record] -> record {
mut total_checks = 0
mut passed_checks = 0
mut warning_checks = 0
mut failed_checks = 0
let components = []
for component in ($results | items) {
let component_name = $component.key
let component_data = $component.value
let component_summary = if "checks" in ($component_data | columns) {
# Direct checks in component
let checks = $component_data.checks
$total_checks = $total_checks + ($checks | length)
let component_passed = ($checks | where status == "passed" | length)
let component_warning = ($checks | where status == "warning" | length)
let component_failed = ($checks | where status == "failed" | length)
$passed_checks = $passed_checks + $component_passed
$warning_checks = $warning_checks + $component_warning
$failed_checks = $failed_checks + $component_failed
{
name: $component_name,
total: ($checks | length),
passed: $component_passed,
warning: $component_warning,
failed: $component_failed
}
} else if "plugins" in ($component_data | columns) {
# Plugin component with nested structure
mut plugin_total = 0
mut plugin_passed = 0
mut plugin_warning = 0
mut plugin_failed = 0
for plugin in ($component_data.plugins | items) {
let plugin_checks = $plugin.value.checks
$plugin_total = $plugin_total + ($plugin_checks | length)
$plugin_passed = $plugin_passed + ($plugin_checks | where status == "passed" | length)
$plugin_warning = $plugin_warning + ($plugin_checks | where status == "warning" | length)
$plugin_failed = $plugin_failed + ($plugin_checks | where status == "failed" | length)
}
$total_checks = $total_checks + $plugin_total
$passed_checks = $passed_checks + $plugin_passed
$warning_checks = $warning_checks + $plugin_warning
$failed_checks = $failed_checks + $plugin_failed
{
name: $component_name,
total: $plugin_total,
passed: $plugin_passed,
warning: $plugin_warning,
failed: $plugin_failed
}
} else if "files" in ($component_data | columns) {
# Configuration component with files
mut config_total = 0
mut config_passed = 0
mut config_warning = 0
mut config_failed = 0
for file in ($component_data.files | items) {
let file_checks = $file.value.checks
$config_total = $config_total + ($file_checks | length)
$config_passed = $config_passed + ($file_checks | where status == "passed" | length)
$config_warning = $config_warning + ($file_checks | where status == "warning" | length)
$config_failed = $config_failed + ($file_checks | where status == "failed" | length)
}
$total_checks = $total_checks + $config_total
$passed_checks = $passed_checks + $config_passed
$warning_checks = $warning_checks + $config_warning
$failed_checks = $failed_checks + $config_failed
{
name: $component_name,
total: $config_total,
passed: $config_passed,
warning: $config_warning,
failed: $config_failed
}
} else {
{
name: $component_name,
total: 0,
passed: 0,
warning: 0,
failed: 0
}
}
$components = ($components | append $component_summary)
}
let overall_status = if $failed_checks > 0 {
"failed"
} else if $warning_checks > 0 {
"warning"
} else {
"passed"
}
{
overall_status: $overall_status,
total_checks: $total_checks,
passed_checks: $passed_checks,
warning_checks: $warning_checks,
failed_checks: $failed_checks,
components: $components
}
}
# Display verification report
def display_verification_report [report: record, verbose: bool, json_format: bool] {
if $json_format {
$report | to json
} else {
log_info ""
log_info "📊 Verification Summary"
log_info "======================"
let summary = $report.summary
let status_icon = match $summary.overall_status {
"passed" => "✅",
"warning" => "⚠️",
"failed" => "❌"
}
log_info $"Overall Status: ($status_icon) ($summary.overall_status | str upcase)"
log_info $"Total Checks: ($summary.total_checks)"
log_info $"✅ Passed: ($summary.passed_checks)"
log_info $"⚠️ Warnings: ($summary.warning_checks)"
log_info $"❌ Failed: ($summary.failed_checks)"
log_info ""
log_info "📋 Component Summary:"
for component in $summary.components {
let comp_status = if $component.failed > 0 {
"❌"
} else if $component.warning > 0 {
"⚠️"
} else {
"✅"
}
log_info $" ($comp_status) ($component.name): ($component.passed)/($component.total) passed"
}
if $verbose {
log_info ""
log_info "🔍 Detailed Results:"
display_detailed_results $report.detailed_results
}
}
}
# Display detailed results
def display_detailed_results [results: record] {
for component in ($results | items) {
log_info $""
log_info $"📂 ($component.key | str upcase)"
log_info $" {'=' | str repeat (($component.key | str length) + 2)}"
let data = $component.value
if "checks" in ($data | columns) {
display_checks $data.checks " "
} else if "plugins" in ($data | columns) {
for plugin in ($data.plugins | items) {
log_info $" 🔌 ($plugin.key):"
display_checks $plugin.value.checks " "
}
} else if "files" in ($data | columns) {
for file in ($data.files | items) {
log_info $" 📄 ($file.key):"
display_checks $file.value.checks " "
}
}
}
}
# Display individual checks
def display_checks [checks: list, indent: string] {
for check in $checks {
let status_icon = match $check.status {
"passed" => "✅",
"warning" => "⚠️",
"failed" => "❌"
}
log_info $"($indent)($status_icon) ($check.name): ($check.message)"
}
}
# Save verification report to file
def save_verification_report [report: record, json_format: bool, output_path: string] {
let content = if $json_format {
($report | to json)
} else {
generate_text_report $report
}
$content | save -f $output_path
log_success $"✅ Verification report saved: ($output_path)"
}
# Generate text format report
def generate_text_report [report: record] -> string {
let lines = [
"Nushell Full Distribution - Installation Verification Report",
"=" | str repeat 58,
"",
$"Generated: ($report.metadata.verification_time)",
$"Platform: ($report.metadata.platform)",
$"Binary Directory: ($report.metadata.bin_directory)",
$"Config Directory: ($report.metadata.config_directory)",
$"Nushell Version: ($report.metadata.nushell_version)",
"",
"SUMMARY",
"-------",
$"Overall Status: ($report.summary.overall_status | str upcase)",
$"Total Checks: ($report.summary.total_checks)",
$"Passed: ($report.summary.passed_checks)",
$"Warnings: ($report.summary.warning_checks)",
$"Failed: ($report.summary.failed_checks)",
""
]
# Add component details
let component_lines = $report.summary.components | each {|comp|
[
$"($comp.name): ($comp.passed)/($comp.total) passed"
]
} | flatten
$lines | append $component_lines | str join "\n"
}