diff --git a/CHANGELOG.md b/CHANGELOG.md index f9a3f3a..017f852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/etc/distribution_config.toml b/etc/distribution_config.toml new file mode 100644 index 0000000..e0fff47 --- /dev/null +++ b/etc/distribution_config.toml @@ -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 \ No newline at end of file diff --git a/installers/bootstrap/README.md b/installers/bootstrap/README.md new file mode 100644 index 0000000..10977ac --- /dev/null +++ b/installers/bootstrap/README.md @@ -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 ` | 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! ๐Ÿš€** \ No newline at end of file diff --git a/installers/bootstrap/install.ps1 b/installers/bootstrap/install.ps1 new file mode 100644 index 0000000..8c22a0a --- /dev/null +++ b/installers/bootstrap/install.ps1 @@ -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 ] [-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 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 \ No newline at end of file diff --git a/installers/bootstrap/install.sh b/installers/bootstrap/install.sh new file mode 100755 index 0000000..36d185c --- /dev/null +++ b/installers/bootstrap/install.sh @@ -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 "$@" \ No newline at end of file diff --git a/justfile b/justfile index 52160e9..c0bb906 100644 --- a/justfile +++ b/justfile @@ -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' diff --git a/justfiles/full_distro.just b/justfiles/full_distro.just new file mode 100644 index 0000000..6ec9584 --- /dev/null +++ b/justfiles/full_distro.just @@ -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" \ No newline at end of file diff --git a/scripts/build_nushell.nu b/scripts/build_nushell.nu new file mode 100755 index 0000000..7b79873 --- /dev/null +++ b/scripts/build_nushell.nu @@ -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 +} \ No newline at end of file diff --git a/scripts/collect_full_binaries.nu b/scripts/collect_full_binaries.nu new file mode 100755 index 0000000..5c22963 --- /dev/null +++ b/scripts/collect_full_binaries.nu @@ -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 { + 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 { + # 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 { + 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 + 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 + } +} \ No newline at end of file diff --git a/scripts/create_distribution_packages.nu b/scripts/create_distribution_packages.nu new file mode 100755 index 0000000..b39b6a7 --- /dev/null +++ b/scripts/create_distribution_packages.nu @@ -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" + } +} \ No newline at end of file diff --git a/scripts/install_full_nushell.nu b/scripts/install_full_nushell.nu new file mode 100755 index 0000000..4529792 --- /dev/null +++ b/scripts/install_full_nushell.nu @@ -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 = [] # 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, 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, 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] { + 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, 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\"" + } +} \ No newline at end of file diff --git a/scripts/lib/common_lib.nu b/scripts/lib/common_lib.nu new file mode 100644 index 0000000..ad30660 --- /dev/null +++ b/scripts/lib/common_lib.nu @@ -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, 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 + } + } +} \ No newline at end of file diff --git a/scripts/templates/default_config.nu b/scripts/templates/default_config.nu new file mode 100644 index 0000000..0277349 --- /dev/null +++ b/scripts/templates/default_config.nu @@ -0,0 +1,1102 @@ +# Nushell Configuration Template +# Default configuration for Nushell + Plugins distribution +# Created by nushell-plugins full distribution system + +# ============================================================================= +# CORE CONFIGURATION +# ============================================================================= + +$env.config = { + # Show banner on startup + show_banner: false + + # Editing mode (emacs, vi) + edit_mode: emacs + + # Shell integration features + shell_integration: true + + # Use ansi coloring + use_ansi_coloring: true + + # Bracketed paste mode + bracketed_paste: true + + # Render right prompt on the last line of the prompt + render_right_prompt_on_last_line: false + + # ============================================================================= + # TABLE CONFIGURATION + # ============================================================================= + + table: { + # Table rendering mode + mode: rounded # basic, compact, compact_double, light, thin, with_love, rounded, reinforced, heavy, none, other + + # Index column mode + index_mode: always # always, never, auto + + # Show empty tables + show_empty: true + + # Table padding + padding: { left: 1, right: 1 } + + # Trim table content + trim: { + methodology: wrapping # wrapping, truncating + wrapping_try_keep_words: true + truncating_suffix: "..." + } + + # Header styling + header_on_separator: false + } + + # ============================================================================= + # ERROR HANDLING + # ============================================================================= + + error_style: "fancy" # fancy, plain + + # ============================================================================= + # DATETIME FORMATTING + # ============================================================================= + + datetime_format: { + # Normal datetime format + normal: '%a, %d %b %Y %H:%M:%S %z' # Tue, 1 Jan 2023 12:00:00 +0000 + + # Table datetime format (shorter for table display) + table: '%m/%d/%y %I:%M:%S%p' # 01/01/23 12:00:00AM + } + + # ============================================================================= + # COMPLETIONS CONFIGURATION + # ============================================================================= + + completions: { + # Case sensitivity for completions + case_sensitive: false + + # Quick completions (don't wait for all to be calculated) + quick: true + + # Partial completions (complete common prefix) + partial: true + + # Completion algorithm + algorithm: "prefix" # prefix, fuzzy + + # External completer command + external: { + enable: true + max_results: 100 + completer: null + } + } + + # ============================================================================= + # FILE SIZE FORMATTING + # ============================================================================= + + filesize: { + # Use metric units (KB, MB, GB) vs binary (KiB, MiB, GiB) + metric: false + + # Filesize format + format: "auto" # auto, b, kb, kib, mb, mib, gb, gib, tb, tib, pb, pib, eb, eib, zb, zib + } + + # ============================================================================= + # CURSOR SHAPE CONFIGURATION + # ============================================================================= + + cursor_shape: { + # Cursor shapes for different modes + emacs: line # line, block, underscore, blink_line, blink_block, blink_underscore + vi_insert: block + vi_normal: underscore + } + + # ============================================================================= + # COLOR CONFIGURATION + # ============================================================================= + + color_config: { + # Separator color + separator: white + + # Leading trailing space + leading_trailing_space_bg: { attr: n } + + # Header colors + header: green_bold + + # Empty cell + empty: blue + + # Boolean values + bool: { + true: light_cyan + false: light_gray + } + + # Integer types + int: white + float: white + + # String and date types + string: white + date: purple + + # Duration + duration: white + + # File sizes + filesize: { + b: white + kb: cyan + mb: blue + gb: green + tb: red + pb: purple + } + + # Range + range: white + + # Binary + binary: cyan_bold + + # Lists + list: white + record: white + + # Nothing + nothing: white + + # Block + block: white + + # Hints + hints: dark_gray + + # Search result + search_result: { + fg: white + bg: red + } + + # Shape colors + shape_garbage: { fg: white bg: red attr: b} + shape_binary: purple_bold + shape_bool: light_cyan + shape_int: purple_bold + shape_float: purple_bold + shape_range: yellow_bold + shape_internalcall: cyan_bold + shape_external: cyan + shape_externalarg: green_bold + shape_literal: blue + shape_operator: yellow + shape_signature: green_bold + shape_string: green + shape_string_interpolation: cyan_bold + shape_datetime: cyan_bold + shape_list: cyan_bold + shape_table: blue_bold + shape_record: cyan_bold + shape_block: blue_bold + shape_filepath: cyan + shape_directory: cyan + shape_globpattern: cyan_bold + shape_variable: purple + shape_flag: blue_bold + shape_custom: green + shape_nothing: light_cyan + shape_matching_brackets: { attr: u } + } + + # ============================================================================= + # HISTORY CONFIGURATION + # ============================================================================= + + history: { + # Maximum number of history entries + max_size: 100_000 + + # Sync history on enter + sync_on_enter: true + + # History file format + file_format: "plaintext" # sqlite, plaintext + + # Isolation mode + isolation: false + } + + # ============================================================================= + # KEYBINDINGS + # ============================================================================= + + keybindings: [ + { + name: completion_menu + modifier: none + keycode: tab + mode: [emacs vi_normal vi_insert] + event: { + until: [ + { send: menu name: completion_menu } + { send: menunext } + { edit: complete } + ] + } + } + { + name: history_menu + modifier: control + keycode: char_r + mode: [emacs, vi_insert, vi_normal] + event: { send: menu name: history_menu } + } + { + name: help_menu + modifier: none + keycode: f1 + mode: [emacs, vi_insert, vi_normal] + event: { send: menu name: help_menu } + } + { + name: completion_previous + modifier: shift + keycode: backtab + mode: [emacs, vi_normal, vi_insert] + event: { send: menuprevious } + } + { + name: next_page + modifier: control + keycode: char_x + mode: emacs + event: { send: menupagenext } + } + { + name: undo_or_previous_page + modifier: control + keycode: char_z + mode: emacs + event: { + until: [ + { send: menupageprevious } + { edit: undo } + ] + } + } + { + name: escape + modifier: none + keycode: escape + mode: [emacs, vi_insert] + event: { send: esc } + } + { + name: cancel_command + modifier: control + keycode: char_c + mode: [emacs, vi_insert, vi_normal] + event: { send: ctrlc } + } + { + name: quit_shell + modifier: control + keycode: char_d + mode: [emacs, vi_insert, vi_normal] + event: { send: ctrld } + } + { + name: clear_screen + modifier: control + keycode: char_l + mode: [emacs, vi_insert, vi_normal] + event: { send: clearscreen } + } + { + name: search_history + modifier: control + keycode: char_q + mode: [emacs, vi_insert, vi_normal] + event: { send: searchhistory } + } + { + name: open_command_editor + modifier: control + keycode: char_o + mode: [emacs, vi_insert, vi_normal] + event: { send: openeditor } + } + { + name: move_up + modifier: none + keycode: up + mode: [emacs, vi_insert, vi_normal] + event: { + until: [ + { send: menuup } + { send: up } + ] + } + } + { + name: move_down + modifier: none + keycode: down + mode: [emacs, vi_insert, vi_normal] + event: { + until: [ + { send: menudown } + { send: down } + ] + } + } + { + name: move_left + modifier: none + keycode: left + mode: [emacs, vi_insert, vi_normal] + event: { + until: [ + { send: menuleft } + { send: left } + ] + } + } + { + name: move_right_or_take_history_hint + modifier: none + keycode: right + mode: [emacs, vi_insert, vi_normal] + event: { + until: [ + { send: historyhintcomplete } + { send: menuright } + { send: right } + ] + } + } + { + name: move_one_word_left + modifier: control + keycode: left + mode: [emacs, vi_insert, vi_normal] + event: { edit: movewordleft } + } + { + name: move_one_word_right_or_take_history_hint + modifier: control + keycode: right + mode: [emacs, vi_insert, vi_normal] + event: { + until: [ + { send: historyhintwordcomplete } + { edit: movewordright } + ] + } + } + { + name: move_to_line_start + modifier: none + keycode: home + mode: [emacs, vi_insert, vi_normal] + event: { edit: movetolinestart } + } + { + name: move_to_line_start_alt + modifier: control + keycode: char_a + mode: [emacs, vi_insert] + event: { edit: movetolinestart } + } + { + name: move_to_line_end_or_take_history_hint + modifier: none + keycode: end + mode: [emacs, vi_insert, vi_normal] + event: { + until: [ + { send: historyhintcomplete } + { edit: movetolineend } + ] + } + } + { + name: move_to_line_end_alt + modifier: control + keycode: char_e + mode: [emacs, vi_insert] + event: { + until: [ + { send: historyhintcomplete } + { edit: movetolineend } + ] + } + } + { + name: move_to_line_start_vi + modifier: none + keycode: char_0 + mode: vi_normal + event: { edit: movetolinestart } + } + { + name: move_to_line_end_vi + modifier: none + keycode: char_4 + mode: vi_normal + event: { edit: movetolineend } + } + { + name: move_up_vi + modifier: none + keycode: char_k + mode: vi_normal + event: { send: up } + } + { + name: move_down_vi + modifier: none + keycode: char_j + mode: vi_normal + event: { send: down } + } + { + name: backspace + modifier: none + keycode: backspace + mode: [emacs, vi_insert] + event: { edit: backspace } + } + { + name: delete_char + modifier: none + keycode: delete + mode: [emacs, vi_insert] + event: { edit: delete } + } + { + name: delete_word + modifier: control + keycode: char_w + mode: [emacs, vi_insert] + event: { edit: backspaceword } + } + { + name: cut_word_to_right + modifier: control + keycode: char_k + mode: emacs + event: { edit: cuttoend } + } + { + name: cut_line_to_right + modifier: control + keycode: char_k + mode: vi_insert + event: { edit: cuttoend } + } + { + name: cut_line_from_start + modifier: control + keycode: char_u + mode: [emacs, vi_insert] + event: { edit: cutfromstart } + } + { + name: swap_graphemes + modifier: control + keycode: char_t + mode: emacs + event: { edit: swapgraphemes } + } + { + name: move_one_word_left_vi + modifier: none + keycode: char_b + mode: vi_normal + event: { edit: movewordleft } + } + { + name: move_one_word_right_vi + modifier: none + keycode: char_w + mode: vi_normal + event: { edit: movewordright } + } + { + name: move_one_word_right_end_vi + modifier: none + keycode: char_e + mode: vi_normal + event: { edit: movewordrightend } + } + { + name: move_one_big_word_left_vi + modifier: shift + keycode: char_b + mode: vi_normal + event: { edit: movebigwordleft } + } + { + name: move_one_big_word_right_vi + modifier: shift + keycode: char_w + mode: vi_normal + event: { edit: movebigwordright } + } + { + name: move_one_big_word_right_end_vi + modifier: shift + keycode: char_e + mode: vi_normal + event: { edit: movebigwordrightend } + } + { + name: delete_one_character_backward_vi + modifier: none + keycode: char_x + mode: vi_normal + event: { edit: backspace } + } + { + name: delete_one_word_backward_vi + modifier: none + keycode: char_X + mode: vi_normal + event: { edit: backspaceword } + } + { + name: delete_to_end_of_line_vi + modifier: shift + keycode: char_d + mode: vi_normal + event: { edit: cuttoend } + } + { + name: delete_line_vi + modifier: none + keycode: char_dd + mode: vi_normal + event: { edit: cutline } + } + { + name: change_to_end_of_line_vi + modifier: shift + keycode: char_c + mode: vi_normal + event: [ + { edit: cuttoend } + { edit: insertmode } + ] + } + { + name: change_line_vi + modifier: none + keycode: char_cc + mode: vi_normal + event: [ + { edit: cutline } + { edit: insertmode } + ] + } + { + name: open_line_above_vi + modifier: shift + keycode: char_o + mode: vi_normal + event: [ + { edit: insertcharafter } + { edit: newline } + { edit: moveup } + { edit: insertmode } + ] + } + { + name: open_line_below_vi + modifier: none + keycode: char_o + mode: vi_normal + event: [ + { edit: insertcharafter } + { edit: newline } + { edit: insertmode } + ] + } + { + name: enter_vi_insert_mode + modifier: none + keycode: char_i + mode: vi_normal + event: { edit: insertmode } + } + { + name: enter_vi_append_mode + modifier: none + keycode: char_a + mode: vi_normal + event: [ + { edit: moveright } + { edit: insertmode } + ] + } + { + name: enter_vi_append_mode_at_end_of_line + modifier: shift + keycode: char_a + mode: vi_normal + event: [ + { edit: movetolineend } + { edit: insertmode } + ] + } + { + name: enter_vi_insert_mode_at_start_of_line + modifier: shift + keycode: char_i + mode: vi_normal + event: [ + { edit: movetolinestart } + { edit: insertmode } + ] + } + { + name: yank_vi + modifier: none + keycode: char_y + mode: vi_normal + event: { edit: yank } + } + { + name: yank_to_end_of_line_vi + modifier: shift + keycode: char_y + mode: vi_normal + event: { edit: yanktoend } + } + { + name: yank_line_vi + modifier: none + keycode: char_yy + mode: vi_normal + event: { edit: yankline } + } + { + name: paste_after_vi + modifier: none + keycode: char_p + mode: vi_normal + event: { edit: pasteafter } + } + { + name: paste_before_vi + modifier: shift + keycode: char_p + mode: vi_normal + event: { edit: pastebefore } + } + { + name: undo_vi + modifier: none + keycode: char_u + mode: vi_normal + event: { edit: undo } + } + { + name: redo_vi + modifier: control + keycode: char_r + mode: vi_normal + event: { edit: redo } + } + { + name: enter_vi_normal_mode + modifier: none + keycode: escape + mode: vi_insert + event: { edit: normalmode } + } + ] + + # ============================================================================= + # MENU CONFIGURATION + # ============================================================================= + + menus: [ + # Completion menu + { + name: completion_menu + only_buffer_difference: false + marker: "| " + type: { + layout: columnar + columns: 4 + col_width: 20 + col_padding: 2 + } + style: { + text: green + selected_text: { attr: r } + description_text: yellow + match_text: { attr: u } + selected_match_text: { attr: ur } + } + } + + # History menu + { + name: history_menu + only_buffer_difference: true + marker: "? " + type: { + layout: list + page_size: 10 + } + style: { + text: green + selected_text: green_reverse + description_text: yellow + } + } + + # Help menu + { + name: help_menu + only_buffer_difference: true + marker: "? " + type: { + layout: description + columns: 4 + col_width: 20 + col_padding: 2 + selection_rows: 4 + description_rows: 10 + } + style: { + text: green + selected_text: green_reverse + description_text: yellow + } + } + ] + + # ============================================================================= + # HOOKS CONFIGURATION + # ============================================================================= + + hooks: { + pre_prompt: [ + # Update terminal title with current directory + {|| + print -n $"\e]0;($env.PWD | path basename) - Nushell\e\\" + } + ] + pre_execution: [ + # Add timestamp to long-running commands + {|| + let start_time = (date now | format date '%Y-%m-%d %H:%M:%S') + $env.COMMAND_START_TIME = $start_time + } + ] + env_change: { + PWD: [ + # Auto-activate direnv if available + {|before, after| + if (which direnv | is-not-empty) { + direnv export json | from json | default {} | load-env + } + } + ] + } + command_not_found: { + # Suggest alternatives for command not found + |cmd_name| ( + try { + if (which thefuck | is-not-empty) { + $"๐Ÿ’ก Did you mean: (thefuck --alias | str trim)" + } else { + $"โŒ Command not found: ($cmd_name)" + } + } catch { + $"โŒ Command not found: ($cmd_name)" + } + ) + } + display_output: { + # Custom output formatting + |output| ( + if ($output | describe) == "table" { + $output | table --expand + } else { + $output + } + ) + } + } + + # ============================================================================= + # PLUGIN CONFIGURATION + # ============================================================================= + + plugins: { + # Plugin loading timeout (in seconds) + timeout: 60 + + # Auto-register plugins on startup + auto_register: true + + # Plugin search paths + search_paths: [ + ($nu.config-path | path dirname | path join "plugins") + ] + } + + # ============================================================================= + # PERFORMANCE SETTINGS + # ============================================================================= + + # Buffer size for commands + buffer_editor: null + + # Use LSP for completions if available + use_lsp: true + + # Maximum recursion depth + recursion_limit: 50 + + # Plugin call timeout + plugin_timeout: 60_000 # milliseconds + + # ============================================================================= + # EXTERNAL INTEGRATIONS + # ============================================================================= + + # Carapace (external completer) + carapace: { + enable: false + cache_dir: ($env.HOME | path join ".cache" "carapace") + } + + # Starship prompt + starship: { + enable: (which starship | is-not-empty) + config: ($nu.config-path | path dirname | path join "starship.toml") + } + + # Zoxide integration + zoxide: { + enable: (which zoxide | is-not-empty) + hook: "pwd" # none, prompt, pwd + } + + # ============================================================================= + # DEVELOPMENT AND DEBUG + # ============================================================================= + + # Debug mode + debug_mode: false + + # Log level + log_level: "warn" # error, warn, info, debug, trace + + # Profiling + profile: false +} + +# ============================================================================= +# PLUGIN LOADING AND REGISTRATION +# ============================================================================= + +# Auto-load common plugins if they're available +let plugin_binaries = [ + "nu_plugin_clipboard" + "nu_plugin_desktop_notifications" + "nu_plugin_hashes" + "nu_plugin_highlight" + "nu_plugin_image" + "nu_plugin_kcl" + "nu_plugin_tera" + "nu_plugin_custom_values" + "nu_plugin_example" + "nu_plugin_formats" + "nu_plugin_gstat" + "nu_plugin_inc" + "nu_plugin_polars" + "nu_plugin_query" +] + +# Register plugins that are available +for plugin in $plugin_binaries { + try { + # Check if plugin is already registered + if (plugin list | where name == $plugin | is-empty) { + # Try to find and register the plugin + let plugin_path = (which $plugin | get path.0?) + if ($plugin_path | is-not-empty) { + plugin add $plugin_path + } + } + } catch { + # Silently ignore plugin loading errors + } +} + +# ============================================================================= +# CUSTOM FUNCTIONS AND ALIASES +# ============================================================================= + +# Enhanced ls with better colors and formatting +def --env la [...rest] { + ls -la $rest | sort-by type name +} + +# Quick directory navigation +def --env .. [] { + cd .. +} + +def --env ... [] { + cd ../.. +} + +def --env .... [] { + cd ../../.. +} + +# Git shortcuts (if git is available) +if (which git | is-not-empty) { + def gs [] { git status --short } + def ga [...files] { git add $files } + def gc [message: string] { git commit -m $message } + def gp [] { git push } + def gl [] { git log --oneline -10 } +} + +# System information +def sysinfo [] { + { + OS: $nu.os-info.name + Arch: $nu.os-info.arch + Version: $nu.os-info.version + Kernel: $nu.os-info.kernel_version + Hostname: (hostname) + Shell: $"Nushell (version)" + Plugins: (plugin list | length) + Config: $nu.config-path + } +} + +# Plugin management helpers +def plugin-info [] { + plugin list | select name version is_running pid +} + +def plugin-restart [name: string] { + plugin stop $name + plugin use $name +} + +# Quick file operations +def --env take [name: string] { + mkdir $name + cd $name +} + +def trash [path: string] { + if (which trash | is-not-empty) { + ^trash $path + } else { + print $"โš ๏ธ No trash command available, use 'rm ($path)' to delete permanently" + } +} + +# Enhanced which command +def which-all [command: string] { + which -a $command | select path +} + +# ============================================================================= +# PROMPT CONFIGURATION +# ============================================================================= + +# Custom prompt function (used if starship is not available) +def create_left_prompt [] { + let home = $nu.home-path + let dir = ([ + ($env.PWD | str substring 0..($home | str length) | str replace $home "~") + ($env.PWD | str substring ($home | str length)..) + ] | str join) + + let path_color = (if (is-admin) { ansi red_bold } else { ansi green_bold }) + let separator_color = (ansi light_gray) + let path_segment = $"($path_color)($dir)" + + # Git information if in a git repo + let git_info = if (ls -la | where name == .git | is-not-empty) { + try { + let branch = (git branch --show-current 2>/dev/null | str trim) + if not ($branch | is-empty) { + let git_color = ansi yellow + $" ($git_color)($branch)(ansi reset)" + } else { "" } + } catch { "" } + } else { "" } + + $"($path_segment)($git_info)($separator_color) โฏ (ansi reset)" +} + +def create_right_prompt [] { + # Show current time and execution duration + let time_segment = (date now | format date '%H:%M:%S') + + let duration = if "COMMAND_START_TIME" in $env { + let start = ($env.COMMAND_START_TIME | into datetime) + let duration = ((date now) - $start) + if ($duration > 1sec) { + $" took (ansi yellow)($duration)(ansi reset)" + } else { "" } + } else { "" } + + $"(ansi light_gray)($time_segment)($duration)(ansi reset)" +} + +# Set prompt functions if starship is not available +if not (which starship | is-not-empty) { + $env.PROMPT_COMMAND = { || create_left_prompt } + $env.PROMPT_COMMAND_RIGHT = { || create_right_prompt } +} + +# ============================================================================= +# STARTUP MESSAGE +# ============================================================================= + +# Uncomment to show welcome message +# print $"๐Ÿš€ Welcome to Nushell + Plugins!" +# print $"๐Ÿ“ฆ (plugin list | length) plugins loaded" +# print $"๐Ÿ’ก Type 'help' for commands or 'sysinfo' for system information" +# print "" + +# ============================================================================= +# USER CUSTOMIZATION +# ============================================================================= + +# Load user-specific configuration if it exists +let user_config_file = ($nu.config-path | path dirname | path join "user_config.nu") +if ($user_config_file | path exists) { + source $user_config_file +} + +# Load local project configuration if it exists +let local_config_file = (pwd | path join ".nu_config") +if ($local_config_file | path exists) { + source $local_config_file +} \ No newline at end of file diff --git a/scripts/templates/default_env.nu b/scripts/templates/default_env.nu new file mode 100644 index 0000000..eca154f --- /dev/null +++ b/scripts/templates/default_env.nu @@ -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 \ No newline at end of file diff --git a/scripts/templates/install.ps1 b/scripts/templates/install.ps1 new file mode 100644 index 0000000..8c22a0a --- /dev/null +++ b/scripts/templates/install.ps1 @@ -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 ] [-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 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 \ No newline at end of file diff --git a/scripts/templates/install.sh b/scripts/templates/install.sh new file mode 100755 index 0000000..36d185c --- /dev/null +++ b/scripts/templates/install.sh @@ -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 "$@" \ No newline at end of file diff --git a/scripts/templates/uninstall.ps1 b/scripts/templates/uninstall.ps1 new file mode 100644 index 0000000..3b1368d --- /dev/null +++ b/scripts/templates/uninstall.ps1 @@ -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 \ No newline at end of file diff --git a/scripts/templates/uninstall.sh b/scripts/templates/uninstall.sh new file mode 100755 index 0000000..fe54e7c --- /dev/null +++ b/scripts/templates/uninstall.sh @@ -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 "$@" \ No newline at end of file diff --git a/scripts/verify_installation.nu b/scripts/verify_installation.nu new file mode 100755 index 0000000..7e46d6f --- /dev/null +++ b/scripts/verify_installation.nu @@ -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 = [] # 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, + 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, 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" +} \ No newline at end of file