feat: Add complete Nushell full distribution system
Some checks failed
Build and Test / Validate Setup (push) Has been cancelled
Build and Test / Build (darwin-amd64) (push) Has been cancelled
Build and Test / Build (darwin-arm64) (push) Has been cancelled
Build and Test / Build (linux-amd64) (push) Has been cancelled
Build and Test / Build (windows-amd64) (push) Has been cancelled
Build and Test / Build (linux-arm64) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Package Results (push) Has been cancelled
Build and Test / Quality Gate (push) Has been cancelled
Some checks failed
Build and Test / Validate Setup (push) Has been cancelled
Build and Test / Build (darwin-amd64) (push) Has been cancelled
Build and Test / Build (darwin-arm64) (push) Has been cancelled
Build and Test / Build (linux-amd64) (push) Has been cancelled
Build and Test / Build (windows-amd64) (push) Has been cancelled
Build and Test / Build (linux-arm64) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Package Results (push) Has been cancelled
Build and Test / Quality Gate (push) Has been cancelled
## Major Features Added - **Complete distribution infrastructure**: Build, package, and distribute Nushell binary alongside plugins - **Zero-prerequisite installation**: Bootstrap installers work on fresh systems without Rust/Cargo/Nushell - **Cross-platform support**: Linux, macOS, Windows (x86_64, ARM64) - **Self-contained packages**: Everything needed for complete Nushell environment ## New Components ### Build System - `scripts/build_nushell.nu` - Build nushell with all workspace plugins - `scripts/collect_full_binaries.nu` - Collect nu binary + all plugins - `justfiles/full_distro.just` - 40+ new recipes for distribution workflows ### Bootstrap Installers (Zero Prerequisites) - `installers/bootstrap/install.sh` - Universal POSIX installer (900+ lines) - `installers/bootstrap/install.ps1` - Windows PowerShell installer (800+ lines) - Complete platform detection, PATH setup, plugin registration ### Distribution System - `scripts/create_distribution_packages.nu` - Multi-platform package creator - `scripts/install_full_nushell.nu` - Advanced nu-based installer - `scripts/verify_installation.nu` - Installation verification suite - `scripts/lib/common_lib.nu` - Shared utilities and logging ### Configuration Management - `scripts/templates/default_config.nu` - Complete nushell configuration (500+ lines) - `scripts/templates/default_env.nu` - Cross-platform environment setup - `etc/distribution_config.toml` - Central distribution settings - `scripts/templates/uninstall.sh` & `uninstall.ps1` - Clean removal ## Key Workflows ```bash just build-full # Build nushell + all plugins just pack-full-all # Create packages for all platforms just verify-full # Verify installation just release-full-cross # Complete cross-platform release ``` ## Installation Experience - One-liner: `curl -sSf https://your-url/install.sh | sh` - Multiple modes: user, system, portable installation - Automatic plugin registration with verification - Professional uninstall capability ## Benefits - ✅ Solves bootstrap problem - no prerequisites needed - ✅ Production-ready distribution system - ✅ Complete cross-platform support - ✅ Professional installation experience - ✅ Integrates seamlessly with existing plugin workflows - ✅ Enterprise-grade verification and logging
This commit is contained in:
parent
741efa1e70
commit
41455c5b3e
59
CHANGELOG.md
59
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
|
||||
|
352
etc/distribution_config.toml
Normal file
352
etc/distribution_config.toml
Normal file
@ -0,0 +1,352 @@
|
||||
# Nushell + Plugins Distribution Configuration
|
||||
# Configuration file for the full distribution system
|
||||
|
||||
[distribution]
|
||||
# Distribution metadata
|
||||
name = "Nushell + Plugins"
|
||||
description = "Complete Nushell distribution with built-in and custom plugins"
|
||||
version = "0.107.1"
|
||||
homepage = "https://nushell.sh"
|
||||
repository = "https://github.com/your-org/nushell-plugins"
|
||||
|
||||
# Distribution maintainer information
|
||||
[distribution.maintainer]
|
||||
name = "Nushell Plugins Team"
|
||||
email = "maintainer@your-domain.com"
|
||||
organization = "Your Organization"
|
||||
|
||||
# Release information
|
||||
[distribution.release]
|
||||
# Release channels: stable, beta, nightly
|
||||
channel = "stable"
|
||||
# Build types: release, debug, profiling
|
||||
build_type = "release"
|
||||
# Supported architectures
|
||||
architectures = [
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"x86_64-apple-darwin",
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"aarch64-pc-windows-msvc"
|
||||
]
|
||||
|
||||
# Platform-specific configurations
|
||||
[platforms.linux]
|
||||
package_formats = ["tar.gz"]
|
||||
install_prefix_user = "$HOME/.local"
|
||||
install_prefix_system = "/usr/local"
|
||||
shell_configs = [
|
||||
"$HOME/.bashrc",
|
||||
"$HOME/.zshrc",
|
||||
"$HOME/.config/fish/config.fish",
|
||||
"$HOME/.profile"
|
||||
]
|
||||
|
||||
[platforms.macos]
|
||||
package_formats = ["tar.gz"]
|
||||
install_prefix_user = "$HOME/.local"
|
||||
install_prefix_system = "/usr/local"
|
||||
shell_configs = [
|
||||
"$HOME/.bash_profile",
|
||||
"$HOME/.zshrc",
|
||||
"$HOME/.config/fish/config.fish",
|
||||
"$HOME/.profile"
|
||||
]
|
||||
|
||||
[platforms.windows]
|
||||
package_formats = ["zip"]
|
||||
install_prefix_user = "%USERPROFILE%\\.local"
|
||||
install_prefix_system = "%ProgramFiles%\\Nushell"
|
||||
registry_keys = [
|
||||
"HKCU\\Environment",
|
||||
"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"
|
||||
]
|
||||
|
||||
# Nushell configuration
|
||||
[nushell]
|
||||
# Nushell source directory (relative to project root)
|
||||
source_dir = "nushell"
|
||||
# Features to enable when building nushell
|
||||
features = [
|
||||
"plugin",
|
||||
"network",
|
||||
"sqlite",
|
||||
"trash-support",
|
||||
"rustls-tls",
|
||||
"system-clipboard"
|
||||
]
|
||||
# Build profile
|
||||
profile = "release"
|
||||
# Additional build flags
|
||||
build_flags = ["--locked"]
|
||||
|
||||
# Workspace plugins (built with nushell)
|
||||
[plugins.workspace]
|
||||
# Enable all workspace plugins by default
|
||||
enabled = true
|
||||
# Specific workspace plugins to include
|
||||
plugins = [
|
||||
"nu_plugin_custom_values",
|
||||
"nu_plugin_example",
|
||||
"nu_plugin_formats",
|
||||
"nu_plugin_gstat",
|
||||
"nu_plugin_inc",
|
||||
"nu_plugin_polars",
|
||||
"nu_plugin_query",
|
||||
"nu_plugin_stress_internals"
|
||||
]
|
||||
|
||||
# Custom plugins (external to nushell workspace)
|
||||
[plugins.custom]
|
||||
# Enable custom plugins by default
|
||||
enabled = true
|
||||
# Specific custom plugins to include
|
||||
plugins = [
|
||||
"nu_plugin_clipboard",
|
||||
"nu_plugin_desktop_notifications",
|
||||
"nu_plugin_fluent",
|
||||
"nu_plugin_hashes",
|
||||
"nu_plugin_highlight",
|
||||
"nu_plugin_image",
|
||||
"nu_plugin_kcl",
|
||||
"nu_plugin_port_extension",
|
||||
"nu_plugin_qr_maker",
|
||||
"nu_plugin_tera"
|
||||
]
|
||||
|
||||
# Plugin-specific configurations
|
||||
[plugins.custom.nu_plugin_clipboard]
|
||||
description = "Cross-platform clipboard access"
|
||||
required_system_deps = ["xclip", "wl-clipboard"] # Linux only
|
||||
|
||||
[plugins.custom.nu_plugin_desktop_notifications]
|
||||
description = "Desktop notification support"
|
||||
required_system_deps = ["libnotify"] # Linux only
|
||||
|
||||
[plugins.custom.nu_plugin_highlight]
|
||||
description = "Syntax highlighting for code files"
|
||||
optional_deps = ["bat", "pygments"]
|
||||
|
||||
[plugins.custom.nu_plugin_image]
|
||||
description = "Image processing and viewing"
|
||||
optional_deps = ["imagemagick", "ffmpeg"]
|
||||
|
||||
[plugins.custom.nu_plugin_tera]
|
||||
description = "Template processing with Tera"
|
||||
|
||||
# Build configuration
|
||||
[build]
|
||||
# Parallel jobs for building
|
||||
jobs = 0 # 0 = auto-detect
|
||||
# Target directory for builds
|
||||
target_dir = "target"
|
||||
# Whether to use cargo workspaces
|
||||
use_workspace = true
|
||||
# Additional cargo flags
|
||||
cargo_flags = ["--release", "--locked"]
|
||||
# Environment variables for build
|
||||
env_vars = {}
|
||||
|
||||
# Cross-compilation settings
|
||||
[build.cross]
|
||||
# Enable cross-compilation
|
||||
enabled = true
|
||||
# Cross-compilation tool (cross, cargo-zigbuild, etc.)
|
||||
tool = "cross"
|
||||
# Docker image for cross-compilation
|
||||
docker_image = "ghcr.io/cross-rs/cross"
|
||||
|
||||
# Collection settings
|
||||
[collection]
|
||||
# Output directory for collected binaries
|
||||
output_dir = "distribution"
|
||||
# Whether to include nushell binary
|
||||
include_nushell = true
|
||||
# Whether to include workspace plugins
|
||||
include_workspace_plugins = true
|
||||
# Whether to include custom plugins
|
||||
include_custom_plugins = true
|
||||
# Files to copy to distribution
|
||||
additional_files = [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
# Directories to copy
|
||||
additional_dirs = [
|
||||
"scripts/templates"
|
||||
]
|
||||
|
||||
# Packaging settings
|
||||
[packaging]
|
||||
# Output directory for packages
|
||||
output_dir = "bin_archives"
|
||||
# Package formats per platform
|
||||
formats = {
|
||||
linux = ["tar.gz"],
|
||||
macos = ["tar.gz"],
|
||||
windows = ["zip"]
|
||||
}
|
||||
# Compression level (0-9)
|
||||
compression_level = 6
|
||||
# Whether to generate checksums
|
||||
generate_checksums = true
|
||||
# Checksum algorithms
|
||||
checksum_algorithms = ["sha256", "sha512"]
|
||||
|
||||
# Manifest settings
|
||||
[packaging.manifest]
|
||||
# Include detailed manifest in packages
|
||||
enabled = true
|
||||
# Manifest format (json, toml, yaml)
|
||||
format = "json"
|
||||
# Include build information
|
||||
include_build_info = true
|
||||
# Include system information
|
||||
include_system_info = true
|
||||
# Include plugin information
|
||||
include_plugin_info = true
|
||||
|
||||
# Installation settings
|
||||
[installation]
|
||||
# Default configuration templates
|
||||
config_templates = {
|
||||
env = "scripts/templates/default_env.nu",
|
||||
config = "scripts/templates/default_config.nu"
|
||||
}
|
||||
# Whether to create config directory
|
||||
create_config_dir = true
|
||||
# Whether to update PATH
|
||||
update_path = true
|
||||
# Whether to auto-register plugins
|
||||
auto_register_plugins = true
|
||||
# Shell integration scripts
|
||||
shell_scripts = [
|
||||
"installers/bootstrap/install.sh",
|
||||
"installers/bootstrap/install.ps1"
|
||||
]
|
||||
|
||||
# Verification settings
|
||||
[verification]
|
||||
# Enable installation verification
|
||||
enabled = true
|
||||
# Tests to run during verification
|
||||
tests = [
|
||||
"version_check",
|
||||
"basic_functionality",
|
||||
"plugin_list",
|
||||
"path_check"
|
||||
]
|
||||
# Timeout for verification tests (seconds)
|
||||
timeout = 30
|
||||
|
||||
# Update and maintenance
|
||||
[maintenance]
|
||||
# Automatic dependency updates
|
||||
auto_update_deps = false
|
||||
# Security advisory checks
|
||||
security_checks = true
|
||||
# Cleanup old builds
|
||||
cleanup_builds = true
|
||||
# Maximum build artifacts to keep
|
||||
max_build_artifacts = 5
|
||||
|
||||
# CI/CD integration
|
||||
[ci]
|
||||
# Supported CI platforms
|
||||
platforms = ["github", "gitlab", "jenkins"]
|
||||
# Artifact retention (days)
|
||||
artifact_retention = 30
|
||||
# Test matrix
|
||||
test_matrix = [
|
||||
"ubuntu-latest",
|
||||
"macos-latest",
|
||||
"windows-latest"
|
||||
]
|
||||
|
||||
# Release automation
|
||||
[release]
|
||||
# Automatic releases on tag
|
||||
auto_release = true
|
||||
# Release notes generation
|
||||
generate_release_notes = true
|
||||
# Pre-release validation
|
||||
validate_before_release = true
|
||||
# Release channels
|
||||
channels = {
|
||||
stable = "main",
|
||||
beta = "develop",
|
||||
nightly = "nightly"
|
||||
}
|
||||
|
||||
# Documentation settings
|
||||
[documentation]
|
||||
# Generate documentation during build
|
||||
generate_docs = true
|
||||
# Documentation formats
|
||||
formats = ["html", "markdown"]
|
||||
# Include API documentation
|
||||
include_api_docs = true
|
||||
# Documentation output directory
|
||||
output_dir = "docs"
|
||||
|
||||
# Logging and monitoring
|
||||
[logging]
|
||||
# Log level (error, warn, info, debug, trace)
|
||||
level = "info"
|
||||
# Log format (compact, full, json)
|
||||
format = "compact"
|
||||
# Log to file
|
||||
file_logging = false
|
||||
# Log file path
|
||||
log_file = "build.log"
|
||||
|
||||
# Security settings
|
||||
[security]
|
||||
# Code signing (for releases)
|
||||
code_signing = false
|
||||
# GPG signing key
|
||||
gpg_key = ""
|
||||
# Vulnerability scanning
|
||||
vulnerability_scan = true
|
||||
# Supply chain verification
|
||||
supply_chain_check = true
|
||||
|
||||
# Analytics and telemetry
|
||||
[analytics]
|
||||
# Usage analytics (anonymized)
|
||||
enabled = false
|
||||
# Analytics endpoint
|
||||
endpoint = ""
|
||||
# Data retention period
|
||||
retention_days = 90
|
||||
|
||||
# Custom hooks and scripts
|
||||
[hooks]
|
||||
# Pre-build hooks
|
||||
pre_build = []
|
||||
# Post-build hooks
|
||||
post_build = []
|
||||
# Pre-package hooks
|
||||
pre_package = []
|
||||
# Post-package hooks
|
||||
post_package = []
|
||||
# Pre-release hooks
|
||||
pre_release = []
|
||||
# Post-release hooks
|
||||
post_release = []
|
||||
|
||||
# Feature flags
|
||||
[features]
|
||||
# Experimental features
|
||||
experimental_features = false
|
||||
# Beta features
|
||||
beta_features = false
|
||||
# Development features
|
||||
dev_features = false
|
||||
|
||||
# Custom settings (user-configurable)
|
||||
[custom]
|
||||
# User can add any additional settings here
|
||||
# These will be preserved during updates
|
377
installers/bootstrap/README.md
Normal file
377
installers/bootstrap/README.md
Normal file
@ -0,0 +1,377 @@
|
||||
# Nushell + Plugins Bootstrap Installers
|
||||
|
||||
Universal installers for Nushell and plugins that work without any prerequisites. These scripts solve the bootstrap problem by providing one-command installation that works on fresh systems.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Linux/macOS (POSIX Shell)
|
||||
```bash
|
||||
curl -L https://your-url/install.sh | sh
|
||||
```
|
||||
|
||||
### Windows (PowerShell)
|
||||
```powershell
|
||||
Invoke-WebRequest -Uri "https://your-url/install.ps1" | Invoke-Expression
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Zero Prerequisites**: Works on fresh systems without Rust, Git, or other dependencies
|
||||
- **Multi-Platform**: Supports Linux, macOS, and Windows (x86_64, ARM64)
|
||||
- **Complete Distribution**: Installs Nushell + all workspace and custom plugins
|
||||
- **Automatic Configuration**: Sets up PATH, creates default config, registers plugins
|
||||
- **Installation Modes**: User (~/.local/bin) or system (/usr/local/bin) installation
|
||||
- **Build Options**: Pre-built binaries or build from source
|
||||
- **Verification**: Optional installation verification and testing
|
||||
- **Uninstallation**: Clean removal of all installed components
|
||||
|
||||
## Installation Options
|
||||
|
||||
### Basic Installation
|
||||
Default installation to user directory with all plugins:
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
curl -L install-url/install.sh | sh
|
||||
|
||||
# Windows
|
||||
iwr install-url/install.ps1 | iex
|
||||
```
|
||||
|
||||
### System Installation
|
||||
Install to system directories (requires admin privileges):
|
||||
|
||||
```bash
|
||||
# Linux/macOS (requires sudo)
|
||||
curl -L install-url/install.sh | sudo sh -s -- --system
|
||||
|
||||
# Windows (requires admin PowerShell)
|
||||
iwr install-url/install.ps1 | iex -ArgumentList "-System"
|
||||
```
|
||||
|
||||
### Build from Source
|
||||
Build Nushell and plugins from source code:
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
curl -L install-url/install.sh | sh -s -- --build-from-source
|
||||
|
||||
# Windows
|
||||
iwr install-url/install.ps1 | iex -ArgumentList "-BuildFromSource"
|
||||
```
|
||||
|
||||
### Minimal Installation
|
||||
Install only Nushell without plugins:
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
curl -L install-url/install.sh | sh -s -- --no-plugins
|
||||
|
||||
# Windows
|
||||
iwr install-url/install.ps1 | iex -ArgumentList "-NoPlugins"
|
||||
```
|
||||
|
||||
## Command Line Options
|
||||
|
||||
### Linux/macOS (install.sh)
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--system` | Install to system directory (/usr/local/bin, requires sudo) |
|
||||
| `--user` | Install to user directory (~/.local/bin) [default] |
|
||||
| `--no-path` | Don't modify shell PATH configuration |
|
||||
| `--no-config` | Don't create initial nushell configuration |
|
||||
| `--no-plugins` | Install only nushell, skip plugins |
|
||||
| `--build-from-source` | Build from source instead of downloading binaries |
|
||||
| `--verify` | Verify installation after completion |
|
||||
| `--uninstall` | Remove nushell and plugins |
|
||||
| `--version VERSION` | Install specific version (default: latest) |
|
||||
| `--help` | Show help message |
|
||||
|
||||
### Windows (install.ps1)
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `-System` | Install to system directory (C:\Program Files\Nushell, requires admin) |
|
||||
| `-User` | Install to user directory (~\.local\bin) [default] |
|
||||
| `-NoPath` | Don't modify PATH environment variable |
|
||||
| `-NoConfig` | Don't create initial nushell configuration |
|
||||
| `-NoPlugins` | Install only nushell, skip plugins |
|
||||
| `-BuildFromSource` | Build from source instead of downloading binaries |
|
||||
| `-Verify` | Verify installation after completion |
|
||||
| `-Uninstall` | Remove nushell and plugins |
|
||||
| `-Version <version>` | Install specific version (default: latest) |
|
||||
| `-Help` | Show help message |
|
||||
|
||||
## Installation Locations
|
||||
|
||||
### User Installation (Default)
|
||||
- **Linux/macOS**: `~/.local/bin`
|
||||
- **Windows**: `%USERPROFILE%\.local\bin`
|
||||
- No admin privileges required
|
||||
- Affects only current user
|
||||
|
||||
### System Installation
|
||||
- **Linux/macOS**: `/usr/local/bin`
|
||||
- **Windows**: `C:\Program Files\Nushell\bin`
|
||||
- Requires admin privileges
|
||||
- Available to all users
|
||||
|
||||
### Configuration Directory
|
||||
- **Linux/macOS**: `~/.config/nushell`
|
||||
- **Windows**: `%USERPROFILE%\.config\nushell`
|
||||
|
||||
## What Gets Installed
|
||||
|
||||
### Core Components
|
||||
- **nushell binary**: Main shell executable
|
||||
- **Workspace plugins**: Built-in nushell plugins
|
||||
- `nu_plugin_custom_values`
|
||||
- `nu_plugin_example`
|
||||
- `nu_plugin_formats`
|
||||
- `nu_plugin_gstat`
|
||||
- `nu_plugin_inc`
|
||||
- `nu_plugin_polars`
|
||||
- `nu_plugin_query`
|
||||
- `nu_plugin_stress_internals`
|
||||
- **Custom plugins**: Additional community plugins
|
||||
- `nu_plugin_clipboard`
|
||||
- `nu_plugin_desktop_notifications`
|
||||
- `nu_plugin_hashes`
|
||||
- `nu_plugin_highlight`
|
||||
- `nu_plugin_image`
|
||||
- `nu_plugin_kcl`
|
||||
- `nu_plugin_tera`
|
||||
- And more...
|
||||
|
||||
### Configuration Files
|
||||
- `config.nu`: Main nushell configuration
|
||||
- `env.nu`: Environment configuration
|
||||
- Scripts directory for custom commands
|
||||
- Plugins directory for plugin configurations
|
||||
|
||||
### PATH Integration
|
||||
Automatically updates shell configuration files:
|
||||
- **Bash**: `~/.bashrc`, `~/.bash_profile`
|
||||
- **Zsh**: `~/.zshrc`
|
||||
- **Fish**: `~/.config/fish/config.fish`
|
||||
- **Nushell**: `~/.config/nushell/env.nu`
|
||||
- **Generic**: `~/.profile`
|
||||
- **Windows**: System/User PATH environment variable
|
||||
|
||||
## Examples
|
||||
|
||||
### Standard Installation with Verification
|
||||
```bash
|
||||
# Linux/macOS
|
||||
curl -L install-url/install.sh | sh -s -- --verify
|
||||
|
||||
# Windows
|
||||
iwr install-url/install.ps1 | iex -ArgumentList "-Verify"
|
||||
```
|
||||
|
||||
### Corporate/Enterprise Installation
|
||||
```bash
|
||||
# System installation without auto-config
|
||||
curl -L install-url/install.sh | sudo sh -s -- --system --no-config
|
||||
|
||||
# Windows system installation
|
||||
# Run as Administrator:
|
||||
iwr install-url/install.ps1 | iex -ArgumentList "-System", "-NoConfig"
|
||||
```
|
||||
|
||||
### Development Installation
|
||||
```bash
|
||||
# Build from source with verification
|
||||
curl -L install-url/install.sh | sh -s -- --build-from-source --verify
|
||||
|
||||
# Windows development
|
||||
iwr install-url/install.ps1 | iex -ArgumentList "-BuildFromSource", "-Verify"
|
||||
```
|
||||
|
||||
### Specific Version Installation
|
||||
```bash
|
||||
# Install specific version
|
||||
curl -L install-url/install.sh | sh -s -- --version v0.107.1
|
||||
|
||||
# Windows specific version
|
||||
iwr install-url/install.ps1 | iex -ArgumentList "-Version", "v0.107.1"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "Command not found" after installation
|
||||
- Restart your terminal/command prompt
|
||||
- Or reload shell configuration: `source ~/.bashrc` (Linux/macOS)
|
||||
- Check if installation directory is in PATH
|
||||
|
||||
#### Permission denied errors
|
||||
- For system installation, ensure you have admin privileges
|
||||
- Use `sudo` on Linux/macOS or run PowerShell as Administrator on Windows
|
||||
- Try user installation instead: `--user` or `-User`
|
||||
|
||||
#### Download failures
|
||||
- Check internet connection
|
||||
- Try building from source: `--build-from-source` or `-BuildFromSource`
|
||||
- Manual download: Save installer script and run locally
|
||||
|
||||
#### Plugin registration failures
|
||||
- Verify nushell binary works: `nu --version`
|
||||
- Try manual plugin registration: `nu -c "plugin add /path/to/plugin"`
|
||||
- Check plugin binary permissions (should be executable)
|
||||
|
||||
### Build from Source Issues
|
||||
|
||||
#### Missing dependencies
|
||||
Install required tools:
|
||||
|
||||
**Linux (Ubuntu/Debian)**:
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install git curl build-essential
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
|
||||
**Linux (RHEL/CentOS/Fedora)**:
|
||||
```bash
|
||||
sudo dnf install git curl gcc
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
|
||||
**macOS**:
|
||||
```bash
|
||||
# Install Xcode command line tools
|
||||
xcode-select --install
|
||||
|
||||
# Install Rust
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
|
||||
**Windows**:
|
||||
1. Install Git from https://git-scm.com/
|
||||
2. Install Rust from https://rustup.rs/
|
||||
3. Install Visual Studio Build Tools
|
||||
|
||||
### Verification Steps
|
||||
|
||||
Test your installation:
|
||||
|
||||
```bash
|
||||
# Check nushell version
|
||||
nu --version
|
||||
|
||||
# Test basic functionality
|
||||
nu -c "echo 'Hello from Nushell'"
|
||||
|
||||
# List installed plugins
|
||||
nu -c "plugin list"
|
||||
|
||||
# Check PATH
|
||||
which nu # Linux/macOS
|
||||
where nu # Windows
|
||||
```
|
||||
|
||||
## Uninstallation
|
||||
|
||||
### Complete Removal
|
||||
```bash
|
||||
# Linux/macOS
|
||||
curl -L install-url/install.sh | sh -s -- --uninstall
|
||||
|
||||
# Windows
|
||||
iwr install-url/install.ps1 | iex -ArgumentList "-Uninstall"
|
||||
```
|
||||
|
||||
### Manual Removal
|
||||
1. Remove binaries from installation directory
|
||||
2. Remove configuration directory (optional)
|
||||
3. Remove PATH entries from shell configuration files
|
||||
4. Remove environment variable modifications (Windows)
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Script Verification
|
||||
Before running any installer, you should:
|
||||
1. Review the script source code
|
||||
2. Verify the download URL and SSL certificate
|
||||
3. Check script signatures if available
|
||||
4. Consider downloading and running locally instead of piping
|
||||
|
||||
### Permissions
|
||||
- User installations don't require admin privileges
|
||||
- System installations require elevated privileges
|
||||
- Scripts only modify necessary files and directories
|
||||
- No automatic execution of arbitrary code from external sources
|
||||
|
||||
### Network Security
|
||||
- Scripts use HTTPS for all downloads
|
||||
- Verify SSL certificates before downloading
|
||||
- Consider using corporate proxies or mirrors
|
||||
- Firewall may need to allow downloads from GitHub/your hosting
|
||||
|
||||
## Distribution Details
|
||||
|
||||
### Binary Packages
|
||||
Pre-built binaries are available for:
|
||||
- Linux x86_64 (GNU libc)
|
||||
- Linux aarch64 (GNU libc)
|
||||
- macOS x86_64 (Intel)
|
||||
- macOS arm64 (Apple Silicon)
|
||||
- Windows x86_64
|
||||
- Windows aarch64
|
||||
|
||||
### Package Structure
|
||||
```
|
||||
nushell-plugins-{platform}-{version}.tar.gz/
|
||||
├── nu(.exe) # Main nushell binary
|
||||
├── nu_plugin_*(.exe) # Plugin binaries
|
||||
├── config/ # Default configuration
|
||||
│ ├── config.nu
|
||||
│ └── env.nu
|
||||
├── scripts/ # Installation scripts
|
||||
├── docs/ # Documentation
|
||||
├── LICENSE # License file
|
||||
└── manifest.json # Package manifest
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
### Testing Installers
|
||||
Test the installers on various platforms:
|
||||
1. Fresh VM or container
|
||||
2. Different operating system versions
|
||||
3. Various shell configurations
|
||||
4. With and without prerequisites
|
||||
|
||||
### Reporting Issues
|
||||
When reporting problems, include:
|
||||
- Operating system and version
|
||||
- Shell type and version
|
||||
- Installation command used
|
||||
- Complete error messages
|
||||
- Network/firewall configuration
|
||||
|
||||
### Improvements
|
||||
Areas for enhancement:
|
||||
- Additional platform support
|
||||
- Mirror/CDN support for faster downloads
|
||||
- Package manager integration
|
||||
- Corporate deployment features
|
||||
- Automated testing workflows
|
||||
|
||||
## License
|
||||
|
||||
These bootstrap installers are part of the nushell-plugins project and are licensed under the same terms. See the main project LICENSE file for details.
|
||||
|
||||
## Support
|
||||
|
||||
- **Documentation**: https://nushell.sh
|
||||
- **Community**: https://discord.gg/nushell
|
||||
- **Issues**: https://github.com/your-org/nushell-plugins/issues
|
||||
- **Discussions**: https://github.com/your-org/nushell-plugins/discussions
|
||||
|
||||
---
|
||||
|
||||
**Happy shell scripting with Nushell! 🚀**
|
841
installers/bootstrap/install.ps1
Normal file
841
installers/bootstrap/install.ps1
Normal file
@ -0,0 +1,841 @@
|
||||
# Universal Nushell + Plugins Bootstrap Installer for Windows
|
||||
# PowerShell script that installs Nushell and plugins without any prerequisites
|
||||
#
|
||||
# This script:
|
||||
# - Detects Windows platform (x86_64/arm64)
|
||||
# - Downloads or builds Nushell + plugins
|
||||
# - Installs to user location (%USERPROFILE%\.local\bin) or system (C:\Program Files\Nushell)
|
||||
# - Updates PATH in system/user environment
|
||||
# - Creates initial Nushell configuration
|
||||
# - Registers all plugins automatically
|
||||
# - Verifies installation
|
||||
|
||||
param(
|
||||
[switch]$System,
|
||||
[switch]$User = $true,
|
||||
[switch]$NoPath,
|
||||
[switch]$NoConfig,
|
||||
[switch]$NoPlugins,
|
||||
[switch]$BuildFromSource,
|
||||
[switch]$Verify,
|
||||
[switch]$Uninstall,
|
||||
[string]$Version = "",
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
# Configuration
|
||||
$RepoUrl = "https://github.com/your-org/nushell-plugins"
|
||||
$BinaryRepoUrl = "$RepoUrl/releases/download"
|
||||
$InstallDirUser = "$env:USERPROFILE\.local\bin"
|
||||
$InstallDirSystem = "${env:ProgramFiles}\Nushell\bin"
|
||||
$ConfigDir = "$env:USERPROFILE\.config\nushell"
|
||||
$TempDir = "$env:TEMP\nushell-install-$(Get-Random)"
|
||||
|
||||
# Colors for output
|
||||
$Colors = @{
|
||||
Red = "Red"
|
||||
Green = "Green"
|
||||
Yellow = "Yellow"
|
||||
Blue = "Blue"
|
||||
Magenta = "Magenta"
|
||||
Cyan = "Cyan"
|
||||
}
|
||||
|
||||
# Logging functions
|
||||
function Write-LogInfo {
|
||||
param([string]$Message)
|
||||
Write-Host "ℹ️ $Message" -ForegroundColor $Colors.Blue
|
||||
}
|
||||
|
||||
function Write-LogSuccess {
|
||||
param([string]$Message)
|
||||
Write-Host "✅ $Message" -ForegroundColor $Colors.Green
|
||||
}
|
||||
|
||||
function Write-LogWarn {
|
||||
param([string]$Message)
|
||||
Write-Host "⚠️ $Message" -ForegroundColor $Colors.Yellow
|
||||
}
|
||||
|
||||
function Write-LogError {
|
||||
param([string]$Message)
|
||||
Write-Host "❌ $Message" -ForegroundColor $Colors.Red
|
||||
}
|
||||
|
||||
function Write-LogHeader {
|
||||
param([string]$Message)
|
||||
Write-Host ""
|
||||
Write-Host "🚀 $Message" -ForegroundColor $Colors.Magenta
|
||||
Write-Host ("=" * $Message.Length) -ForegroundColor $Colors.Magenta
|
||||
}
|
||||
|
||||
# Usage information
|
||||
function Show-Usage {
|
||||
@"
|
||||
Nushell + Plugins Bootstrap Installer for Windows
|
||||
|
||||
USAGE:
|
||||
# Download and run:
|
||||
Invoke-WebRequest -Uri "install-url/install.ps1" | Invoke-Expression
|
||||
|
||||
# Or download and run with parameters:
|
||||
.\install.ps1 [-System] [-User] [-NoPath] [-NoConfig] [-NoPlugins] [-BuildFromSource] [-Verify] [-Uninstall] [-Version <version>] [-Help]
|
||||
|
||||
PARAMETERS:
|
||||
-System Install to system directory (C:\Program Files\Nushell, requires admin)
|
||||
-User Install to user directory (~\.local\bin) [default]
|
||||
-NoPath Don't modify PATH environment variable
|
||||
-NoConfig Don't create initial nushell configuration
|
||||
-NoPlugins Install only nushell, skip plugins
|
||||
-BuildFromSource Build from source instead of downloading binaries
|
||||
-Verify Verify installation after completion
|
||||
-Uninstall Remove nushell and plugins
|
||||
-Version <version> Install specific version (default: latest)
|
||||
-Help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
# Default installation (user directory, with plugins)
|
||||
.\install.ps1
|
||||
|
||||
# System installation (requires admin)
|
||||
.\install.ps1 -System
|
||||
|
||||
# Install without plugins
|
||||
.\install.ps1 -NoPlugins
|
||||
|
||||
# Build from source
|
||||
.\install.ps1 -BuildFromSource
|
||||
|
||||
# Install specific version
|
||||
.\install.ps1 -Version "v0.107.1"
|
||||
"@
|
||||
}
|
||||
|
||||
# Platform detection
|
||||
function Get-Platform {
|
||||
$arch = $env:PROCESSOR_ARCHITECTURE
|
||||
switch ($arch) {
|
||||
"AMD64" { return "windows-x86_64" }
|
||||
"ARM64" { return "windows-aarch64" }
|
||||
default {
|
||||
Write-LogError "Unsupported architecture: $arch"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check if running as administrator
|
||||
function Test-Admin {
|
||||
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
|
||||
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
# Check if command exists
|
||||
function Test-Command {
|
||||
param([string]$Command)
|
||||
try {
|
||||
Get-Command $Command -ErrorAction Stop | Out-Null
|
||||
return $true
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Check dependencies for building from source
|
||||
function Test-BuildDependencies {
|
||||
$missing = @()
|
||||
|
||||
if (-not (Test-Command "git")) {
|
||||
$missing += "git"
|
||||
}
|
||||
|
||||
if (-not (Test-Command "cargo")) {
|
||||
$missing += "cargo"
|
||||
}
|
||||
|
||||
if (-not (Test-Command "rustc")) {
|
||||
$missing += "rust"
|
||||
}
|
||||
|
||||
if ($missing.Count -gt 0) {
|
||||
Write-LogError "Missing build dependencies: $($missing -join ', ')"
|
||||
Write-LogInfo "Please install these tools or use binary installation instead"
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Download file with progress
|
||||
function Get-FileWithProgress {
|
||||
param(
|
||||
[string]$Url,
|
||||
[string]$OutputPath,
|
||||
[string]$Description = "file"
|
||||
)
|
||||
|
||||
Write-LogInfo "Downloading $Description..."
|
||||
|
||||
try {
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.DownloadFile($Url, $OutputPath)
|
||||
Write-LogSuccess "Downloaded $Description"
|
||||
return $true
|
||||
} catch {
|
||||
Write-LogError "Failed to download $Description from $Url"
|
||||
Write-LogError $_.Exception.Message
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Extract archive
|
||||
function Expand-Archive {
|
||||
param(
|
||||
[string]$ArchivePath,
|
||||
[string]$DestinationPath
|
||||
)
|
||||
|
||||
Write-LogInfo "Extracting archive..."
|
||||
|
||||
try {
|
||||
if ($ArchivePath -like "*.zip") {
|
||||
Expand-Archive -Path $ArchivePath -DestinationPath $DestinationPath -Force
|
||||
} elseif ($ArchivePath -like "*.tar.gz") {
|
||||
# Use tar if available (Windows 10 build 17063 and later)
|
||||
if (Test-Command "tar") {
|
||||
& tar -xzf $ArchivePath -C $DestinationPath
|
||||
} else {
|
||||
throw "tar command not found for .tar.gz extraction"
|
||||
}
|
||||
} else {
|
||||
throw "Unsupported archive format: $ArchivePath"
|
||||
}
|
||||
Write-LogSuccess "Extracted archive"
|
||||
return $true
|
||||
} catch {
|
||||
Write-LogError "Failed to extract $ArchivePath"
|
||||
Write-LogError $_.Exception.Message
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Get latest release version
|
||||
function Get-LatestVersion {
|
||||
try {
|
||||
$response = Invoke-RestMethod -Uri "https://api.github.com/repos/your-org/nushell-plugins/releases/latest"
|
||||
return $response.tag_name
|
||||
} catch {
|
||||
Write-LogWarn "Could not detect latest version, using v0.107.1"
|
||||
return "v0.107.1"
|
||||
}
|
||||
}
|
||||
|
||||
# Download and install binaries
|
||||
function Install-FromBinaries {
|
||||
param(
|
||||
[string]$Platform,
|
||||
[string]$Version,
|
||||
[string]$InstallDir,
|
||||
[bool]$IncludePlugins
|
||||
)
|
||||
|
||||
Write-LogHeader "Installing from Pre-built Binaries"
|
||||
|
||||
# Create temporary directory
|
||||
New-Item -ItemType Directory -Path $TempDir -Force | Out-Null
|
||||
Set-Location $TempDir
|
||||
|
||||
# Determine archive name and URL
|
||||
$archiveFormat = if ($Platform -like "*windows*") { "zip" } else { "tar.gz" }
|
||||
$archiveName = "nushell-plugins-$Platform-$Version.$archiveFormat"
|
||||
$downloadUrl = "$BinaryRepoUrl/$Version/$archiveName"
|
||||
|
||||
# Download archive
|
||||
if (-not (Get-FileWithProgress -Url $downloadUrl -OutputPath $archiveName -Description "Nushell distribution")) {
|
||||
Write-LogWarn "Binary download failed, trying alternative..."
|
||||
# Try without version prefix
|
||||
$archiveName = "nushell-plugins-$Platform.$archiveFormat"
|
||||
$downloadUrl = "$BinaryRepoUrl/latest/$archiveName"
|
||||
if (-not (Get-FileWithProgress -Url $downloadUrl -OutputPath $archiveName -Description "Nushell distribution (latest)")) {
|
||||
Write-LogError "Failed to download binaries"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Extract archive
|
||||
if (-not (Expand-Archive -ArchivePath $archiveName -DestinationPath ".")) {
|
||||
return $false
|
||||
}
|
||||
|
||||
# Find extracted directory
|
||||
$extractDir = Get-ChildItem -Directory | Select-Object -First 1
|
||||
if (-not $extractDir) {
|
||||
Write-LogError "No extracted directory found"
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-LogInfo "Installing binaries to $InstallDir..."
|
||||
|
||||
# Create install directory
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
|
||||
# Install nushell binary
|
||||
$nuBinary = "nu.exe"
|
||||
$nuSourcePath = Join-Path $extractDir.FullName $nuBinary
|
||||
|
||||
if (Test-Path $nuSourcePath) {
|
||||
Copy-Item $nuSourcePath -Destination $InstallDir
|
||||
Write-LogSuccess "Installed nushell binary"
|
||||
} else {
|
||||
Write-LogError "Nushell binary not found in archive"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Install plugins if requested
|
||||
if ($IncludePlugins) {
|
||||
$pluginFiles = Get-ChildItem -Path $extractDir.FullName -Name "nu_plugin_*.exe"
|
||||
$pluginCount = 0
|
||||
|
||||
foreach ($pluginFile in $pluginFiles) {
|
||||
$pluginSourcePath = Join-Path $extractDir.FullName $pluginFile
|
||||
Copy-Item $pluginSourcePath -Destination $InstallDir
|
||||
$pluginCount++
|
||||
}
|
||||
|
||||
if ($pluginCount -gt 0) {
|
||||
Write-LogSuccess "Installed $pluginCount plugins"
|
||||
} else {
|
||||
Write-LogWarn "No plugins found in archive"
|
||||
}
|
||||
}
|
||||
|
||||
# Copy configuration files if they exist
|
||||
$configSourceDir = Join-Path $extractDir.FullName "config"
|
||||
if (Test-Path $configSourceDir) {
|
||||
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
|
||||
Copy-Item -Path "$configSourceDir\*" -Destination $ConfigDir -Recurse -Force
|
||||
Write-LogSuccess "Installed configuration files"
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Build and install from source
|
||||
function Install-FromSource {
|
||||
param(
|
||||
[string]$InstallDir,
|
||||
[bool]$IncludePlugins
|
||||
)
|
||||
|
||||
Write-LogHeader "Building from Source"
|
||||
|
||||
# Check dependencies
|
||||
if (-not (Test-BuildDependencies)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
# Create temporary directory
|
||||
New-Item -ItemType Directory -Path $TempDir -Force | Out-Null
|
||||
Set-Location $TempDir
|
||||
|
||||
# Clone repository
|
||||
Write-LogInfo "Cloning repository..."
|
||||
try {
|
||||
& git clone --recursive $RepoUrl nushell-plugins
|
||||
Set-Location nushell-plugins
|
||||
} catch {
|
||||
Write-LogError "Failed to clone repository"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Build nushell
|
||||
Write-LogInfo "Building nushell..."
|
||||
try {
|
||||
if (Test-Command "just") {
|
||||
& just build-nushell
|
||||
} else {
|
||||
# Fallback to manual build
|
||||
Set-Location nushell
|
||||
& cargo build --release --features "plugin,network,sqlite,trash-support,rustls-tls"
|
||||
Set-Location ..
|
||||
}
|
||||
} catch {
|
||||
Write-LogError "Failed to build nushell"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Build plugins if requested
|
||||
if ($IncludePlugins) {
|
||||
Write-LogInfo "Building plugins..."
|
||||
try {
|
||||
if (Test-Command "just") {
|
||||
& just build
|
||||
} else {
|
||||
# Build plugins manually
|
||||
$pluginDirs = Get-ChildItem -Directory -Name "nu_plugin_*" | Where-Object { $_ -ne "nushell" }
|
||||
foreach ($pluginDir in $pluginDirs) {
|
||||
Write-LogInfo "Building $pluginDir..."
|
||||
Set-Location $pluginDir
|
||||
try {
|
||||
& cargo build --release
|
||||
Write-LogSuccess "Built $pluginDir"
|
||||
} catch {
|
||||
Write-LogWarn "Failed to build $pluginDir"
|
||||
}
|
||||
Set-Location ..
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-LogWarn "Failed to build some plugins"
|
||||
}
|
||||
}
|
||||
|
||||
# Install binaries
|
||||
Write-LogInfo "Installing binaries to $InstallDir..."
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
|
||||
# Install nushell
|
||||
$nuBinary = "nushell\target\release\nu.exe"
|
||||
if (Test-Path $nuBinary) {
|
||||
Copy-Item $nuBinary -Destination "$InstallDir\nu.exe"
|
||||
Write-LogSuccess "Installed nushell binary"
|
||||
} else {
|
||||
Write-LogError "Nushell binary not found"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Install plugins
|
||||
if ($IncludePlugins) {
|
||||
$pluginCount = 0
|
||||
$pluginDirs = Get-ChildItem -Directory -Name "nu_plugin_*" | Where-Object { $_ -ne "nushell" }
|
||||
|
||||
foreach ($pluginDir in $pluginDirs) {
|
||||
$pluginBinary = "$pluginDir\target\release\$pluginDir.exe"
|
||||
if (Test-Path $pluginBinary) {
|
||||
Copy-Item $pluginBinary -Destination "$InstallDir\$pluginDir.exe"
|
||||
$pluginCount++
|
||||
}
|
||||
}
|
||||
|
||||
if ($pluginCount -gt 0) {
|
||||
Write-LogSuccess "Installed $pluginCount plugins"
|
||||
} else {
|
||||
Write-LogWarn "No plugins were built successfully"
|
||||
}
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Register plugins with nushell
|
||||
function Register-Plugins {
|
||||
param(
|
||||
[string]$InstallDir
|
||||
)
|
||||
|
||||
$nuBinary = Join-Path $InstallDir "nu.exe"
|
||||
|
||||
if (-not (Test-Path $nuBinary)) {
|
||||
Write-LogError "Nushell binary not found: $nuBinary"
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-LogHeader "Registering Plugins"
|
||||
|
||||
# Find all plugin binaries
|
||||
$pluginFiles = Get-ChildItem -Path $InstallDir -Name "nu_plugin_*.exe"
|
||||
$pluginCount = 0
|
||||
|
||||
foreach ($pluginFile in $pluginFiles) {
|
||||
$pluginPath = Join-Path $InstallDir $pluginFile
|
||||
$pluginName = [System.IO.Path]::GetFileNameWithoutExtension($pluginFile)
|
||||
|
||||
Write-LogInfo "Registering $pluginName..."
|
||||
|
||||
try {
|
||||
& $nuBinary -c "plugin add '$pluginPath'"
|
||||
Write-LogSuccess "Registered $pluginName"
|
||||
$pluginCount++
|
||||
} catch {
|
||||
Write-LogWarn "Failed to register $pluginName"
|
||||
}
|
||||
}
|
||||
|
||||
if ($pluginCount -gt 0) {
|
||||
Write-LogSuccess "Successfully registered $pluginCount plugins"
|
||||
} else {
|
||||
Write-LogWarn "No plugins were registered"
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Update PATH environment variable
|
||||
function Update-PathEnvironment {
|
||||
param(
|
||||
[string]$InstallDir,
|
||||
[bool]$SystemInstall
|
||||
)
|
||||
|
||||
Write-LogHeader "Updating PATH Environment"
|
||||
|
||||
# Determine scope
|
||||
$scope = if ($SystemInstall) { "Machine" } else { "User" }
|
||||
|
||||
try {
|
||||
# Get current PATH
|
||||
$currentPath = [Environment]::GetEnvironmentVariable("PATH", $scope)
|
||||
|
||||
# Check if already in PATH
|
||||
if ($currentPath -split ';' -contains $InstallDir) {
|
||||
Write-LogInfo "PATH already contains $InstallDir"
|
||||
return $true
|
||||
}
|
||||
|
||||
# Add to PATH
|
||||
$newPath = "$InstallDir;$currentPath"
|
||||
[Environment]::SetEnvironmentVariable("PATH", $newPath, $scope)
|
||||
|
||||
# Update current session
|
||||
$env:PATH = "$InstallDir;$env:PATH"
|
||||
|
||||
Write-LogSuccess "Updated PATH environment variable"
|
||||
Write-LogInfo "Restart your terminal to apply changes globally"
|
||||
|
||||
return $true
|
||||
} catch {
|
||||
Write-LogError "Failed to update PATH environment variable"
|
||||
Write-LogError $_.Exception.Message
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Create initial nushell configuration
|
||||
function New-NushellConfig {
|
||||
Write-LogHeader "Creating Nushell Configuration"
|
||||
|
||||
# Create config directory
|
||||
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
|
||||
|
||||
# Create basic config.nu if it doesn't exist
|
||||
$configFile = Join-Path $ConfigDir "config.nu"
|
||||
if (-not (Test-Path $configFile)) {
|
||||
@"
|
||||
# Nushell Configuration
|
||||
# Created by nushell-plugins installer
|
||||
|
||||
# Set up basic configuration
|
||||
`$env.config = {
|
||||
show_banner: false
|
||||
edit_mode: emacs
|
||||
shell_integration: true
|
||||
|
||||
table: {
|
||||
mode: rounded
|
||||
index_mode: always
|
||||
show_empty: true
|
||||
padding: { left: 1, right: 1 }
|
||||
}
|
||||
|
||||
completions: {
|
||||
case_sensitive: false
|
||||
quick: true
|
||||
partial: true
|
||||
algorithm: "prefix"
|
||||
}
|
||||
|
||||
history: {
|
||||
max_size: 10000
|
||||
sync_on_enter: true
|
||||
file_format: "plaintext"
|
||||
}
|
||||
|
||||
filesize: {
|
||||
metric: false
|
||||
format: "auto"
|
||||
}
|
||||
}
|
||||
|
||||
# Load custom commands and aliases
|
||||
# Add your custom configuration below
|
||||
"@ | Out-File -FilePath $configFile -Encoding utf8
|
||||
Write-LogSuccess "Created config.nu"
|
||||
} else {
|
||||
Write-LogInfo "config.nu already exists, skipping"
|
||||
}
|
||||
|
||||
# Create basic env.nu if it doesn't exist
|
||||
$envFile = Join-Path $ConfigDir "env.nu"
|
||||
if (-not (Test-Path $envFile)) {
|
||||
@"
|
||||
# Nushell Environment Configuration
|
||||
# Created by nushell-plugins installer
|
||||
|
||||
# Environment variables
|
||||
`$env.EDITOR = "notepad"
|
||||
`$env.BROWSER = "msedge"
|
||||
|
||||
# Nushell specific environment
|
||||
`$env.NU_LIB_DIRS = [
|
||||
(`$nu.config-path | path dirname | path join "scripts")
|
||||
]
|
||||
|
||||
`$env.NU_PLUGIN_DIRS = [
|
||||
(`$nu.config-path | path dirname | path join "plugins")
|
||||
]
|
||||
|
||||
# Add your custom environment variables below
|
||||
"@ | Out-File -FilePath $envFile -Encoding utf8
|
||||
Write-LogSuccess "Created env.nu"
|
||||
} else {
|
||||
Write-LogInfo "env.nu already exists, skipping"
|
||||
}
|
||||
|
||||
# Create directories
|
||||
$scriptsDir = Join-Path $ConfigDir "scripts"
|
||||
$pluginsDir = Join-Path $ConfigDir "plugins"
|
||||
New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $pluginsDir -Force | Out-Null
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Verify installation
|
||||
function Test-Installation {
|
||||
param(
|
||||
[string]$InstallDir
|
||||
)
|
||||
|
||||
$nuBinary = Join-Path $InstallDir "nu.exe"
|
||||
|
||||
Write-LogHeader "Verifying Installation"
|
||||
|
||||
# Check if nushell binary exists
|
||||
if (-not (Test-Path $nuBinary)) {
|
||||
Write-LogError "Nushell binary not found: $nuBinary"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Test nushell version
|
||||
Write-LogInfo "Testing nushell binary..."
|
||||
try {
|
||||
$versionOutput = & $nuBinary --version
|
||||
Write-LogSuccess "Nushell version: $versionOutput"
|
||||
} catch {
|
||||
Write-LogError "Failed to run nushell binary"
|
||||
Write-LogError $_.Exception.Message
|
||||
return $false
|
||||
}
|
||||
|
||||
# Test basic nushell command
|
||||
Write-LogInfo "Testing basic nushell functionality..."
|
||||
try {
|
||||
$testOutput = & $nuBinary -c "echo 'Hello from Nushell'"
|
||||
if ($testOutput -eq "Hello from Nushell") {
|
||||
Write-LogSuccess "Basic nushell functionality works"
|
||||
} else {
|
||||
Write-LogWarn "Unexpected output from basic test"
|
||||
}
|
||||
} catch {
|
||||
Write-LogError "Basic nushell functionality failed"
|
||||
return $false
|
||||
}
|
||||
|
||||
# List registered plugins
|
||||
Write-LogInfo "Checking registered plugins..."
|
||||
try {
|
||||
$pluginOutput = & $nuBinary -c "plugin list"
|
||||
$pluginCount = ($pluginOutput | Select-String "nu_plugin_").Count
|
||||
if ($pluginCount -gt 0) {
|
||||
Write-LogSuccess "Found $pluginCount registered plugins"
|
||||
} else {
|
||||
Write-LogWarn "No plugins are registered"
|
||||
}
|
||||
} catch {
|
||||
Write-LogWarn "Could not check plugin status"
|
||||
}
|
||||
|
||||
# Check PATH
|
||||
Write-LogInfo "Checking PATH configuration..."
|
||||
try {
|
||||
$nuInPath = Get-Command nu -ErrorAction SilentlyContinue
|
||||
if ($nuInPath) {
|
||||
Write-LogSuccess "Nushell is available in PATH"
|
||||
} else {
|
||||
Write-LogWarn "Nushell is not in PATH. You may need to restart your terminal."
|
||||
}
|
||||
} catch {
|
||||
Write-LogWarn "Could not verify PATH configuration"
|
||||
}
|
||||
|
||||
Write-LogSuccess "Installation verification complete!"
|
||||
return $true
|
||||
}
|
||||
|
||||
# Uninstall function
|
||||
function Uninstall-Nushell {
|
||||
Write-LogHeader "Uninstalling Nushell"
|
||||
|
||||
$removedFiles = 0
|
||||
|
||||
# Remove from user directory
|
||||
if (Test-Path $InstallDirUser) {
|
||||
$userBinaries = Get-ChildItem -Path $InstallDirUser -Name "nu.exe", "nu_plugin_*.exe" -ErrorAction SilentlyContinue
|
||||
foreach ($binary in $userBinaries) {
|
||||
$filePath = Join-Path $InstallDirUser $binary
|
||||
Remove-Item $filePath -Force -ErrorAction SilentlyContinue
|
||||
Write-LogSuccess "Removed $binary from $InstallDirUser"
|
||||
$removedFiles++
|
||||
}
|
||||
}
|
||||
|
||||
# Remove from system directory (if accessible)
|
||||
if ((Test-Admin) -and (Test-Path $InstallDirSystem)) {
|
||||
$systemBinaries = Get-ChildItem -Path $InstallDirSystem -Name "nu.exe", "nu_plugin_*.exe" -ErrorAction SilentlyContinue
|
||||
foreach ($binary in $systemBinaries) {
|
||||
$filePath = Join-Path $InstallDirSystem $binary
|
||||
Remove-Item $filePath -Force -ErrorAction SilentlyContinue
|
||||
Write-LogSuccess "Removed $binary from $InstallDirSystem"
|
||||
$removedFiles++
|
||||
}
|
||||
}
|
||||
|
||||
# Option to remove configuration
|
||||
$response = Read-Host "Remove nushell configuration directory ($ConfigDir)? [y/N]"
|
||||
if ($response -match "^[yY]") {
|
||||
if (Test-Path $ConfigDir) {
|
||||
Remove-Item $ConfigDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-LogSuccess "Removed configuration directory"
|
||||
}
|
||||
} else {
|
||||
Write-LogInfo "Configuration directory preserved"
|
||||
}
|
||||
|
||||
if ($removedFiles -gt 0) {
|
||||
Write-LogSuccess "Uninstallation complete ($removedFiles files removed)"
|
||||
Write-LogWarn "You may need to manually remove PATH entries from your environment variables"
|
||||
} else {
|
||||
Write-LogWarn "No nushell files found to remove"
|
||||
}
|
||||
}
|
||||
|
||||
# Main installation function
|
||||
function Main {
|
||||
# Handle help
|
||||
if ($Help) {
|
||||
Show-Usage
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Handle uninstall
|
||||
if ($Uninstall) {
|
||||
Uninstall-Nushell
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Show header
|
||||
Write-LogHeader "Nushell + Plugins Installer for Windows"
|
||||
Write-LogInfo "Universal bootstrap installer for Nushell and plugins"
|
||||
Write-LogInfo ""
|
||||
|
||||
# Detect platform
|
||||
$platform = Get-Platform
|
||||
Write-LogInfo "Detected platform: $platform"
|
||||
|
||||
# Determine installation directory and check privileges
|
||||
if ($System) {
|
||||
$installDir = $InstallDirSystem
|
||||
if (-not (Test-Admin)) {
|
||||
Write-LogError "System installation requires administrator privileges"
|
||||
Write-LogInfo "Run PowerShell as Administrator or use -User for user installation"
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
$installDir = $InstallDirUser
|
||||
}
|
||||
|
||||
Write-LogInfo "Installing to: $installDir"
|
||||
|
||||
# Get version if not specified
|
||||
if (-not $Version) {
|
||||
$Version = Get-LatestVersion
|
||||
}
|
||||
Write-LogInfo "Version: $Version"
|
||||
|
||||
# Cleanup function
|
||||
$cleanup = {
|
||||
if (Test-Path $TempDir) {
|
||||
Remove-Item $TempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
# Install based on method
|
||||
if ($BuildFromSource) {
|
||||
if (-not (Install-FromSource -InstallDir $installDir -IncludePlugins (-not $NoPlugins))) {
|
||||
Write-LogError "Source installation failed"
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
if (-not (Install-FromBinaries -Platform $platform -Version $Version -InstallDir $installDir -IncludePlugins (-not $NoPlugins))) {
|
||||
Write-LogError "Binary installation failed"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Register plugins
|
||||
if (-not $NoPlugins) {
|
||||
Register-Plugins -InstallDir $installDir
|
||||
}
|
||||
|
||||
# Update PATH
|
||||
if (-not $NoPath) {
|
||||
Update-PathEnvironment -InstallDir $installDir -SystemInstall $System
|
||||
}
|
||||
|
||||
# Create configuration
|
||||
if (-not $NoConfig) {
|
||||
New-NushellConfig
|
||||
}
|
||||
|
||||
# Verify installation
|
||||
if ($Verify) {
|
||||
if (-not (Test-Installation -InstallDir $installDir)) {
|
||||
Write-LogError "Installation verification failed"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Final success message
|
||||
Write-LogHeader "Installation Complete!"
|
||||
Write-LogSuccess "Nushell has been successfully installed to $installDir"
|
||||
|
||||
if (-not $NoPlugins) {
|
||||
Write-LogSuccess "Plugins have been registered with Nushell"
|
||||
}
|
||||
|
||||
if (-not $NoPath) {
|
||||
Write-LogInfo "To use Nushell, restart your terminal or start a new PowerShell session"
|
||||
}
|
||||
|
||||
Write-LogInfo ""
|
||||
Write-LogInfo "Try running: nu --version"
|
||||
Write-LogInfo "Or start Nushell with: nu"
|
||||
|
||||
if (-not $NoPlugins) {
|
||||
Write-LogInfo "Check plugins with: nu -c 'plugin list'"
|
||||
}
|
||||
|
||||
Write-LogInfo ""
|
||||
Write-LogInfo "For more information, visit: https://nushell.sh"
|
||||
Write-LogInfo ""
|
||||
Write-LogSuccess "Happy shell scripting! 🚀"
|
||||
|
||||
} finally {
|
||||
& $cleanup
|
||||
}
|
||||
}
|
||||
|
||||
# Run main function
|
||||
Main
|
923
installers/bootstrap/install.sh
Executable file
923
installers/bootstrap/install.sh
Executable file
@ -0,0 +1,923 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Universal Nushell + Plugins Bootstrap Installer
|
||||
# POSIX compliant shell script that installs Nushell and plugins without any prerequisites
|
||||
#
|
||||
# This script:
|
||||
# - Detects platform (Linux/macOS, x86_64/arm64)
|
||||
# - Downloads or builds Nushell + plugins
|
||||
# - Installs to user location (~/.local/bin) or system (/usr/local/bin)
|
||||
# - Updates PATH in shell configuration files
|
||||
# - Creates initial Nushell configuration
|
||||
# - Registers all plugins automatically
|
||||
# - Verifies installation
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Configuration
|
||||
REPO_URL="https://github.com/your-org/nushell-plugins"
|
||||
BINARY_REPO_URL="$REPO_URL/releases/download"
|
||||
INSTALL_DIR_USER="$HOME/.local/bin"
|
||||
INSTALL_DIR_SYSTEM="/usr/local/bin"
|
||||
CONFIG_DIR="$HOME/.config/nushell"
|
||||
TEMP_DIR="/tmp/nushell-install-$$"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
PURPLE='\033[0;35m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
printf "${BLUE}ℹ️ %s${NC}\n" "$1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
printf "${GREEN}✅ %s${NC}\n" "$1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
printf "${YELLOW}⚠️ %s${NC}\n" "$1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
printf "${RED}❌ %s${NC}\n" "$1" >&2
|
||||
}
|
||||
|
||||
log_header() {
|
||||
printf "\n${PURPLE}🚀 %s${NC}\n" "$1"
|
||||
printf "${PURPLE}%s${NC}\n" "$(printf '=%.0s' $(seq 1 ${#1}))"
|
||||
}
|
||||
|
||||
# Usage information
|
||||
usage() {
|
||||
cat << 'EOF'
|
||||
Nushell + Plugins Bootstrap Installer
|
||||
|
||||
USAGE:
|
||||
curl -L install-url/install.sh | sh
|
||||
# or
|
||||
./install.sh [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--system Install to system directory (/usr/local/bin, requires sudo)
|
||||
--user Install to user directory (~/.local/bin) [default]
|
||||
--no-path Don't modify shell PATH configuration
|
||||
--no-config Don't create initial nushell configuration
|
||||
--no-plugins Install only nushell, skip plugins
|
||||
--build-from-source Build from source instead of downloading binaries
|
||||
--verify Verify installation after completion
|
||||
--uninstall Remove nushell and plugins
|
||||
--version VERSION Install specific version (default: latest)
|
||||
--help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
# Default installation (user directory, with plugins)
|
||||
curl -L install-url/install.sh | sh
|
||||
|
||||
# System installation
|
||||
curl -L install-url/install.sh | sh -s -- --system
|
||||
|
||||
# Install without plugins
|
||||
curl -L install-url/install.sh | sh -s -- --no-plugins
|
||||
|
||||
# Build from source
|
||||
curl -L install-url/install.sh | sh -s -- --build-from-source
|
||||
|
||||
# Install specific version
|
||||
curl -L install-url/install.sh | sh -s -- --version v0.107.1
|
||||
EOF
|
||||
}
|
||||
|
||||
# Platform detection
|
||||
detect_platform() {
|
||||
local os arch
|
||||
|
||||
# Detect OS
|
||||
case "$(uname -s)" in
|
||||
Linux) os="linux" ;;
|
||||
Darwin) os="darwin" ;;
|
||||
CYGWIN*|MINGW*|MSYS*) os="windows" ;;
|
||||
*) log_error "Unsupported OS: $(uname -s)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Detect architecture
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) arch="x86_64" ;;
|
||||
aarch64|arm64) arch="arm64" ;;
|
||||
armv7l) arch="armv7" ;;
|
||||
*) log_error "Unsupported architecture: $(uname -m)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Special handling for Darwin arm64
|
||||
if [ "$os" = "darwin" ] && [ "$arch" = "arm64" ]; then
|
||||
arch="arm64"
|
||||
fi
|
||||
|
||||
echo "${os}-${arch}"
|
||||
}
|
||||
|
||||
# Check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Check dependencies for building from source
|
||||
check_build_dependencies() {
|
||||
local missing=""
|
||||
|
||||
if ! command_exists "git"; then
|
||||
missing="$missing git"
|
||||
fi
|
||||
|
||||
if ! command_exists "cargo"; then
|
||||
missing="$missing cargo"
|
||||
fi
|
||||
|
||||
if ! command_exists "rustc"; then
|
||||
missing="$missing rust"
|
||||
fi
|
||||
|
||||
if [ -n "$missing" ]; then
|
||||
log_error "Missing build dependencies:$missing"
|
||||
log_info "Please install these tools or use binary installation instead"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Download file with progress
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local output="$2"
|
||||
local desc="${3:-file}"
|
||||
|
||||
log_info "Downloading $desc..."
|
||||
|
||||
if command_exists "curl"; then
|
||||
if ! curl -L --fail --progress-bar "$url" -o "$output"; then
|
||||
log_error "Failed to download $desc from $url"
|
||||
return 1
|
||||
fi
|
||||
elif command_exists "wget"; then
|
||||
if ! wget --progress=bar:force "$url" -O "$output"; then
|
||||
log_error "Failed to download $desc from $url"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "Neither curl nor wget is available"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Downloaded $desc"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Extract archive
|
||||
extract_archive() {
|
||||
local archive="$1"
|
||||
local destination="$2"
|
||||
|
||||
log_info "Extracting archive..."
|
||||
|
||||
case "$archive" in
|
||||
*.tar.gz)
|
||||
if ! tar -xzf "$archive" -C "$destination"; then
|
||||
log_error "Failed to extract $archive"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*.zip)
|
||||
if command_exists "unzip"; then
|
||||
if ! unzip -q "$archive" -d "$destination"; then
|
||||
log_error "Failed to extract $archive"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "unzip command not found"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
log_error "Unsupported archive format: $archive"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log_success "Extracted archive"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Get latest release version
|
||||
get_latest_version() {
|
||||
local version=""
|
||||
|
||||
if command_exists "curl"; then
|
||||
version=$(curl -s "https://api.github.com/repos/your-org/nushell-plugins/releases/latest" | \
|
||||
grep '"tag_name":' | \
|
||||
sed -E 's/.*"([^"]+)".*/\1/')
|
||||
elif command_exists "wget"; then
|
||||
version=$(wget -qO- "https://api.github.com/repos/your-org/nushell-plugins/releases/latest" | \
|
||||
grep '"tag_name":' | \
|
||||
sed -E 's/.*"([^"]+)".*/\1/')
|
||||
fi
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
# Fallback to a reasonable default
|
||||
version="v0.107.1"
|
||||
log_warn "Could not detect latest version, using $version"
|
||||
fi
|
||||
|
||||
echo "$version"
|
||||
}
|
||||
|
||||
# Download and install binaries
|
||||
install_from_binaries() {
|
||||
local platform="$1"
|
||||
local version="$2"
|
||||
local install_dir="$3"
|
||||
local include_plugins="$4"
|
||||
|
||||
log_header "Installing from Pre-built Binaries"
|
||||
|
||||
# Create temporary directory
|
||||
mkdir -p "$TEMP_DIR"
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
# Determine archive name and URL
|
||||
local archive_name="nushell-plugins-${platform}-${version}.tar.gz"
|
||||
local download_url="${BINARY_REPO_URL}/${version}/${archive_name}"
|
||||
|
||||
# Download archive
|
||||
if ! download_file "$download_url" "$archive_name" "Nushell distribution"; then
|
||||
log_warn "Binary download failed, trying alternative..."
|
||||
# Try without version prefix
|
||||
archive_name="nushell-plugins-${platform}.tar.gz"
|
||||
download_url="${BINARY_REPO_URL}/latest/${archive_name}"
|
||||
if ! download_file "$download_url" "$archive_name" "Nushell distribution (latest)"; then
|
||||
log_error "Failed to download binaries"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Extract archive
|
||||
if ! extract_archive "$archive_name" "."; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Find extracted directory
|
||||
local extract_dir=""
|
||||
for dir in */; do
|
||||
if [ -d "$dir" ]; then
|
||||
extract_dir="$dir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$extract_dir" ]; then
|
||||
log_error "No extracted directory found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Installing binaries to $install_dir..."
|
||||
|
||||
# Create install directory
|
||||
mkdir -p "$install_dir"
|
||||
|
||||
# Install nushell binary
|
||||
local nu_binary="nu"
|
||||
if [ "$platform" = "windows-x86_64" ]; then
|
||||
nu_binary="nu.exe"
|
||||
fi
|
||||
|
||||
if [ -f "${extract_dir}${nu_binary}" ]; then
|
||||
cp "${extract_dir}${nu_binary}" "$install_dir/"
|
||||
chmod +x "${install_dir}/${nu_binary}"
|
||||
log_success "Installed nushell binary"
|
||||
else
|
||||
log_error "Nushell binary not found in archive"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Install plugins if requested
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
local plugin_count=0
|
||||
for plugin_file in "${extract_dir}"nu_plugin_*; do
|
||||
if [ -f "$plugin_file" ]; then
|
||||
local plugin_name=$(basename "$plugin_file")
|
||||
cp "$plugin_file" "$install_dir/"
|
||||
chmod +x "${install_dir}/${plugin_name}"
|
||||
plugin_count=$((plugin_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $plugin_count -gt 0 ]; then
|
||||
log_success "Installed $plugin_count plugins"
|
||||
else
|
||||
log_warn "No plugins found in archive"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copy configuration files if they exist
|
||||
if [ -d "${extract_dir}config/" ]; then
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
cp -r "${extract_dir}config/"* "$CONFIG_DIR/"
|
||||
log_success "Installed configuration files"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Build and install from source
|
||||
install_from_source() {
|
||||
local install_dir="$1"
|
||||
local include_plugins="$2"
|
||||
|
||||
log_header "Building from Source"
|
||||
|
||||
# Check dependencies
|
||||
if ! check_build_dependencies; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Create temporary directory
|
||||
mkdir -p "$TEMP_DIR"
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
# Clone repository
|
||||
log_info "Cloning repository..."
|
||||
if ! git clone --recursive "$REPO_URL" nushell-plugins; then
|
||||
log_error "Failed to clone repository"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cd nushell-plugins
|
||||
|
||||
# Build nushell
|
||||
log_info "Building nushell..."
|
||||
if command_exists "just"; then
|
||||
if ! just build-nushell; then
|
||||
log_error "Failed to build nushell with just"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
# Fallback to manual build
|
||||
cd nushell
|
||||
if ! cargo build --release --features "plugin,network,sqlite,trash-support,rustls-tls"; then
|
||||
log_error "Failed to build nushell"
|
||||
return 1
|
||||
fi
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# Build plugins if requested
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
log_info "Building plugins..."
|
||||
if command_exists "just"; then
|
||||
if ! just build; then
|
||||
log_warn "Failed to build some plugins"
|
||||
fi
|
||||
else
|
||||
# Build plugins manually
|
||||
for plugin_dir in nu_plugin_*; do
|
||||
if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then
|
||||
log_info "Building $plugin_dir..."
|
||||
cd "$plugin_dir"
|
||||
if cargo build --release; then
|
||||
log_success "Built $plugin_dir"
|
||||
else
|
||||
log_warn "Failed to build $plugin_dir"
|
||||
fi
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install binaries
|
||||
log_info "Installing binaries to $install_dir..."
|
||||
mkdir -p "$install_dir"
|
||||
|
||||
# Install nushell
|
||||
local nu_binary="nushell/target/release/nu"
|
||||
if [ -f "$nu_binary" ]; then
|
||||
cp "$nu_binary" "$install_dir/"
|
||||
chmod +x "${install_dir}/nu"
|
||||
log_success "Installed nushell binary"
|
||||
else
|
||||
log_error "Nushell binary not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Install plugins
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
local plugin_count=0
|
||||
for plugin_dir in nu_plugin_*; do
|
||||
if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then
|
||||
local plugin_binary="${plugin_dir}/target/release/${plugin_dir}"
|
||||
if [ -f "$plugin_binary" ]; then
|
||||
cp "$plugin_binary" "$install_dir/"
|
||||
chmod +x "${install_dir}/${plugin_dir}"
|
||||
plugin_count=$((plugin_count + 1))
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [ $plugin_count -gt 0 ]; then
|
||||
log_success "Installed $plugin_count plugins"
|
||||
else
|
||||
log_warn "No plugins were built successfully"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Register plugins with nushell
|
||||
register_plugins() {
|
||||
local install_dir="$1"
|
||||
local nu_binary="${install_dir}/nu"
|
||||
|
||||
if [ ! -f "$nu_binary" ]; then
|
||||
log_error "Nushell binary not found: $nu_binary"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_header "Registering Plugins"
|
||||
|
||||
# Find all plugin binaries
|
||||
local plugin_count=0
|
||||
for plugin_file in "${install_dir}"/nu_plugin_*; do
|
||||
if [ -f "$plugin_file" ] && [ -x "$plugin_file" ]; then
|
||||
local plugin_name=$(basename "$plugin_file")
|
||||
log_info "Registering $plugin_name..."
|
||||
|
||||
if "$nu_binary" -c "plugin add '$plugin_file'"; then
|
||||
log_success "Registered $plugin_name"
|
||||
plugin_count=$((plugin_count + 1))
|
||||
else
|
||||
log_warn "Failed to register $plugin_name"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $plugin_count -gt 0 ]; then
|
||||
log_success "Successfully registered $plugin_count plugins"
|
||||
else
|
||||
log_warn "No plugins were registered"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Update PATH in shell configuration
|
||||
update_shell_path() {
|
||||
local install_dir="$1"
|
||||
|
||||
log_header "Updating Shell Configuration"
|
||||
|
||||
# List of shell configuration files to update
|
||||
local shell_configs=""
|
||||
|
||||
# Detect current shell and add its config file first
|
||||
case "$SHELL" in
|
||||
*/bash) shell_configs="$HOME/.bashrc $HOME/.bash_profile" ;;
|
||||
*/zsh) shell_configs="$HOME/.zshrc" ;;
|
||||
*/fish) shell_configs="$HOME/.config/fish/config.fish" ;;
|
||||
*/nu) shell_configs="$HOME/.config/nushell/env.nu" ;;
|
||||
esac
|
||||
|
||||
# Add common configuration files
|
||||
shell_configs="$shell_configs $HOME/.profile"
|
||||
|
||||
local updated=false
|
||||
|
||||
for config_file in $shell_configs; do
|
||||
if [ -f "$config_file" ] || [ "$config_file" = "$HOME/.bashrc" ] || [ "$config_file" = "$HOME/.profile" ]; then
|
||||
# Check if already in PATH
|
||||
if grep -q "$install_dir" "$config_file" 2>/dev/null; then
|
||||
log_info "PATH already updated in $(basename "$config_file")"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Create config file if it doesn't exist
|
||||
if [ ! -f "$config_file" ]; then
|
||||
touch "$config_file"
|
||||
fi
|
||||
|
||||
# Add PATH update to config file
|
||||
case "$config_file" in
|
||||
*.fish)
|
||||
echo "fish_add_path $install_dir" >> "$config_file"
|
||||
;;
|
||||
*/env.nu)
|
||||
echo "\$env.PATH = (\$env.PATH | split row (char esep) | append \"$install_dir\" | uniq)" >> "$config_file"
|
||||
;;
|
||||
*)
|
||||
echo "" >> "$config_file"
|
||||
echo "# Added by nushell installer" >> "$config_file"
|
||||
echo "export PATH=\"$install_dir:\$PATH\"" >> "$config_file"
|
||||
;;
|
||||
esac
|
||||
|
||||
log_success "Updated $(basename "$config_file")"
|
||||
updated=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$updated" = "true" ]; then
|
||||
log_info "Please restart your terminal or run 'source ~/.bashrc' to update PATH"
|
||||
else
|
||||
log_warn "No shell configuration files were updated"
|
||||
log_info "Please manually add $install_dir to your PATH"
|
||||
fi
|
||||
|
||||
# Update current session PATH
|
||||
export PATH="$install_dir:$PATH"
|
||||
log_success "Updated PATH for current session"
|
||||
}
|
||||
|
||||
# Create initial nushell configuration
|
||||
create_nushell_config() {
|
||||
log_header "Creating Nushell Configuration"
|
||||
|
||||
# Create config directory
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
# Create basic config.nu if it doesn't exist
|
||||
local config_file="$CONFIG_DIR/config.nu"
|
||||
if [ ! -f "$config_file" ]; then
|
||||
cat > "$config_file" << 'EOF'
|
||||
# Nushell Configuration
|
||||
# Created by nushell-plugins installer
|
||||
|
||||
# Set up basic configuration
|
||||
$env.config = {
|
||||
show_banner: false
|
||||
edit_mode: emacs
|
||||
shell_integration: true
|
||||
|
||||
table: {
|
||||
mode: rounded
|
||||
index_mode: always
|
||||
show_empty: true
|
||||
padding: { left: 1, right: 1 }
|
||||
}
|
||||
|
||||
completions: {
|
||||
case_sensitive: false
|
||||
quick: true
|
||||
partial: true
|
||||
algorithm: "prefix"
|
||||
}
|
||||
|
||||
history: {
|
||||
max_size: 10000
|
||||
sync_on_enter: true
|
||||
file_format: "plaintext"
|
||||
}
|
||||
|
||||
filesize: {
|
||||
metric: false
|
||||
format: "auto"
|
||||
}
|
||||
}
|
||||
|
||||
# Load custom commands and aliases
|
||||
# Add your custom configuration below
|
||||
EOF
|
||||
log_success "Created config.nu"
|
||||
else
|
||||
log_info "config.nu already exists, skipping"
|
||||
fi
|
||||
|
||||
# Create basic env.nu if it doesn't exist
|
||||
local env_file="$CONFIG_DIR/env.nu"
|
||||
if [ ! -f "$env_file" ]; then
|
||||
cat > "$env_file" << 'EOF'
|
||||
# Nushell Environment Configuration
|
||||
# Created by nushell-plugins installer
|
||||
|
||||
# Environment variables
|
||||
$env.EDITOR = "nano"
|
||||
$env.BROWSER = "firefox"
|
||||
|
||||
# Nushell specific environment
|
||||
$env.NU_LIB_DIRS = [
|
||||
($nu.config-path | path dirname | path join "scripts")
|
||||
]
|
||||
|
||||
$env.NU_PLUGIN_DIRS = [
|
||||
($nu.config-path | path dirname | path join "plugins")
|
||||
]
|
||||
|
||||
# Add your custom environment variables below
|
||||
EOF
|
||||
log_success "Created env.nu"
|
||||
else
|
||||
log_info "env.nu already exists, skipping"
|
||||
fi
|
||||
|
||||
# Create scripts directory
|
||||
local scripts_dir="$CONFIG_DIR/scripts"
|
||||
mkdir -p "$scripts_dir"
|
||||
|
||||
# Create plugins directory
|
||||
local plugins_dir="$CONFIG_DIR/plugins"
|
||||
mkdir -p "$plugins_dir"
|
||||
}
|
||||
|
||||
# Verify installation
|
||||
verify_installation() {
|
||||
local install_dir="$1"
|
||||
local nu_binary="${install_dir}/nu"
|
||||
|
||||
log_header "Verifying Installation"
|
||||
|
||||
# Check if nushell binary exists and is executable
|
||||
if [ ! -f "$nu_binary" ]; then
|
||||
log_error "Nushell binary not found: $nu_binary"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$nu_binary" ]; then
|
||||
log_error "Nushell binary is not executable: $nu_binary"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test nushell version
|
||||
log_info "Testing nushell binary..."
|
||||
local version_output
|
||||
if version_output=$("$nu_binary" --version 2>&1); then
|
||||
log_success "Nushell version: $version_output"
|
||||
else
|
||||
log_error "Failed to run nushell binary"
|
||||
log_error "Output: $version_output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test basic nushell command
|
||||
log_info "Testing basic nushell functionality..."
|
||||
if "$nu_binary" -c "echo 'Hello from Nushell'" >/dev/null 2>&1; then
|
||||
log_success "Basic nushell functionality works"
|
||||
else
|
||||
log_error "Basic nushell functionality failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# List registered plugins
|
||||
log_info "Checking registered plugins..."
|
||||
local plugin_output
|
||||
if plugin_output=$("$nu_binary" -c "plugin list" 2>&1); then
|
||||
local plugin_count=$(echo "$plugin_output" | grep -c "nu_plugin_" || true)
|
||||
if [ "$plugin_count" -gt 0 ]; then
|
||||
log_success "Found $plugin_count registered plugins"
|
||||
else
|
||||
log_warn "No plugins are registered"
|
||||
fi
|
||||
else
|
||||
log_warn "Could not check plugin status"
|
||||
fi
|
||||
|
||||
# Check PATH
|
||||
log_info "Checking PATH configuration..."
|
||||
if command -v nu >/dev/null 2>&1; then
|
||||
log_success "Nushell is available in PATH"
|
||||
else
|
||||
log_warn "Nushell is not in PATH. You may need to restart your terminal."
|
||||
fi
|
||||
|
||||
log_success "Installation verification complete!"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Uninstall function
|
||||
uninstall_nushell() {
|
||||
log_header "Uninstalling Nushell"
|
||||
|
||||
local removed_files=0
|
||||
|
||||
# Remove from user directory
|
||||
if [ -d "$INSTALL_DIR_USER" ]; then
|
||||
for binary in nu nu_plugin_*; do
|
||||
local file_path="$INSTALL_DIR_USER/$binary"
|
||||
if [ -f "$file_path" ]; then
|
||||
rm -f "$file_path"
|
||||
log_success "Removed $binary from $INSTALL_DIR_USER"
|
||||
removed_files=$((removed_files + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Remove from system directory (if accessible)
|
||||
if [ -w "$INSTALL_DIR_SYSTEM" ] 2>/dev/null; then
|
||||
for binary in nu nu_plugin_*; do
|
||||
local file_path="$INSTALL_DIR_SYSTEM/$binary"
|
||||
if [ -f "$file_path" ]; then
|
||||
rm -f "$file_path"
|
||||
log_success "Removed $binary from $INSTALL_DIR_SYSTEM"
|
||||
removed_files=$((removed_files + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Option to remove configuration
|
||||
printf "Remove nushell configuration directory ($CONFIG_DIR)? [y/N]: "
|
||||
read -r response
|
||||
case "$response" in
|
||||
[yY]|[yY][eE][sS])
|
||||
if [ -d "$CONFIG_DIR" ]; then
|
||||
rm -rf "$CONFIG_DIR"
|
||||
log_success "Removed configuration directory"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
log_info "Configuration directory preserved"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ $removed_files -gt 0 ]; then
|
||||
log_success "Uninstallation complete ($removed_files files removed)"
|
||||
log_warn "You may need to manually remove PATH entries from your shell configuration"
|
||||
else
|
||||
log_warn "No nushell files found to remove"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main installation function
|
||||
main() {
|
||||
local install_mode="user"
|
||||
local modify_path="true"
|
||||
local create_config="true"
|
||||
local include_plugins="true"
|
||||
local build_from_source="false"
|
||||
local verify_install="false"
|
||||
local do_uninstall="false"
|
||||
local version=""
|
||||
|
||||
# Parse command line arguments
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--system)
|
||||
install_mode="system"
|
||||
shift
|
||||
;;
|
||||
--user)
|
||||
install_mode="user"
|
||||
shift
|
||||
;;
|
||||
--no-path)
|
||||
modify_path="false"
|
||||
shift
|
||||
;;
|
||||
--no-config)
|
||||
create_config="false"
|
||||
shift
|
||||
;;
|
||||
--no-plugins)
|
||||
include_plugins="false"
|
||||
shift
|
||||
;;
|
||||
--build-from-source)
|
||||
build_from_source="true"
|
||||
shift
|
||||
;;
|
||||
--verify)
|
||||
verify_install="true"
|
||||
shift
|
||||
;;
|
||||
--uninstall)
|
||||
do_uninstall="true"
|
||||
shift
|
||||
;;
|
||||
--version)
|
||||
version="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Handle uninstall
|
||||
if [ "$do_uninstall" = "true" ]; then
|
||||
uninstall_nushell
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Show header
|
||||
log_header "Nushell + Plugins Installer"
|
||||
log_info "Universal bootstrap installer for Nushell and plugins"
|
||||
log_info ""
|
||||
|
||||
# Detect platform
|
||||
local platform
|
||||
platform=$(detect_platform)
|
||||
log_info "Detected platform: $platform"
|
||||
|
||||
# Determine installation directory
|
||||
local install_dir
|
||||
if [ "$install_mode" = "system" ]; then
|
||||
install_dir="$INSTALL_DIR_SYSTEM"
|
||||
if [ "$(id -u)" != "0" ] && [ ! -w "$(dirname "$install_dir")" ]; then
|
||||
log_error "System installation requires root privileges"
|
||||
log_info "Run with sudo or use --user for user installation"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
install_dir="$INSTALL_DIR_USER"
|
||||
fi
|
||||
|
||||
log_info "Installing to: $install_dir"
|
||||
|
||||
# Get version if not specified
|
||||
if [ -z "$version" ]; then
|
||||
version=$(get_latest_version)
|
||||
fi
|
||||
log_info "Version: $version"
|
||||
|
||||
# Cleanup function
|
||||
cleanup() {
|
||||
if [ -d "$TEMP_DIR" ]; then
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Install based on method
|
||||
if [ "$build_from_source" = "true" ]; then
|
||||
if ! install_from_source "$install_dir" "$include_plugins"; then
|
||||
log_error "Source installation failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! install_from_binaries "$platform" "$version" "$install_dir" "$include_plugins"; then
|
||||
log_error "Binary installation failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Register plugins
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
register_plugins "$install_dir"
|
||||
fi
|
||||
|
||||
# Update PATH
|
||||
if [ "$modify_path" = "true" ]; then
|
||||
update_shell_path "$install_dir"
|
||||
fi
|
||||
|
||||
# Create configuration
|
||||
if [ "$create_config" = "true" ]; then
|
||||
create_nushell_config
|
||||
fi
|
||||
|
||||
# Verify installation
|
||||
if [ "$verify_install" = "true" ]; then
|
||||
if ! verify_installation "$install_dir"; then
|
||||
log_error "Installation verification failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Final success message
|
||||
log_header "Installation Complete!"
|
||||
log_success "Nushell has been successfully installed to $install_dir"
|
||||
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
log_success "Plugins have been registered with Nushell"
|
||||
fi
|
||||
|
||||
if [ "$modify_path" = "true" ]; then
|
||||
log_info "To use Nushell, restart your terminal or run:"
|
||||
log_info " source ~/.bashrc # or your shell's config file"
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
log_info "Try running: nu --version"
|
||||
log_info "Or start Nushell with: nu"
|
||||
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
log_info "Check plugins with: nu -c 'plugin list'"
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
log_info "For more information, visit: https://nushell.sh"
|
||||
log_info ""
|
||||
log_success "Happy shell scripting! 🚀"
|
||||
}
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
1
justfile
1
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'
|
||||
|
326
justfiles/full_distro.just
Normal file
326
justfiles/full_distro.just
Normal file
@ -0,0 +1,326 @@
|
||||
# Full Distribution Module - Complete Nushell Distribution
|
||||
# Commands for building, collecting, and packaging complete Nushell distributions
|
||||
|
||||
# 🚀 NUSHELL BUILD COMMANDS
|
||||
|
||||
# Build nushell with all workspace plugins
|
||||
[no-cd]
|
||||
build-nushell:
|
||||
@echo "🔨 Building Nushell with all workspace plugins..."
|
||||
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu --release
|
||||
|
||||
# Build nushell in debug mode
|
||||
[no-cd]
|
||||
build-nushell-debug:
|
||||
@echo "🔨 Building Nushell (debug mode)..."
|
||||
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu
|
||||
|
||||
# Build nushell for specific target
|
||||
[no-cd]
|
||||
build-nushell-target TARGET:
|
||||
@echo "🔨 Building Nushell for {{TARGET}}..."
|
||||
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu --release --target {{TARGET}}
|
||||
|
||||
# Build nushell with custom features
|
||||
[no-cd]
|
||||
build-nushell-features FEATURES:
|
||||
@echo "🔨 Building Nushell with features: {{FEATURES}}..."
|
||||
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu --release --features {{FEATURES}}
|
||||
|
||||
# Build nushell with custom profile
|
||||
[no-cd]
|
||||
build-nushell-profile PROFILE:
|
||||
@echo "🔨 Building Nushell with profile: {{PROFILE}}..."
|
||||
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu --profile {{PROFILE}}
|
||||
|
||||
# Check nushell build dependencies
|
||||
[no-cd]
|
||||
build-nushell-deps:
|
||||
@echo "🔍 Checking Nushell build dependencies..."
|
||||
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu deps
|
||||
|
||||
# Show nushell build information
|
||||
[no-cd]
|
||||
build-nushell-info:
|
||||
@echo "ℹ️ Nushell build information..."
|
||||
@{{justfile_directory()}}/scripts/run.sh build_nushell.nu info
|
||||
|
||||
# 📦 FULL BUILD WORKFLOWS
|
||||
|
||||
# Build everything: nushell + all plugins
|
||||
[no-cd]
|
||||
build-full:
|
||||
@echo "🚀 Building complete distribution (nushell + all plugins)..."
|
||||
@just validate-nushell
|
||||
@just build-nushell
|
||||
@just build
|
||||
|
||||
# Build full release (optimized)
|
||||
[no-cd]
|
||||
build-full-release:
|
||||
@echo "🚀 Building full release distribution..."
|
||||
@just validate-nushell
|
||||
@just build-nushell
|
||||
@just build-release
|
||||
|
||||
# Build full distribution for specific target
|
||||
[no-cd]
|
||||
build-full-target TARGET:
|
||||
@echo "🚀 Building full distribution for {{TARGET}}..."
|
||||
@just validate-nushell
|
||||
@just build-nushell-target {{TARGET}}
|
||||
@just build-cross {{TARGET}}
|
||||
|
||||
# Build full distribution for all targets
|
||||
[no-cd]
|
||||
build-full-cross:
|
||||
@echo "🌍 Building full distribution for all targets..."
|
||||
@just validate-nushell
|
||||
@just build-nushell
|
||||
@just build-cross-all
|
||||
|
||||
# 📋 FULL COLLECTION COMMANDS
|
||||
|
||||
# Collect all binaries (nushell + plugins)
|
||||
[no-cd]
|
||||
collect-full:
|
||||
@echo "📦 Collecting full binaries (nushell + plugins)..."
|
||||
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --release --force
|
||||
|
||||
# Collect full binaries in debug mode
|
||||
[no-cd]
|
||||
collect-full-debug:
|
||||
@echo "📦 Collecting full binaries (debug mode)..."
|
||||
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --force
|
||||
|
||||
# List available full binaries
|
||||
[no-cd]
|
||||
collect-full-list:
|
||||
@echo "📋 Available full binaries:"
|
||||
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --list
|
||||
|
||||
# List available platforms for full collection
|
||||
[no-cd]
|
||||
collect-full-platforms:
|
||||
@echo "🌍 Available platforms for full collection:"
|
||||
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --list-platforms
|
||||
|
||||
# Collect full binaries for specific platform
|
||||
[no-cd]
|
||||
collect-full-platform PLATFORM:
|
||||
@echo "📦 Collecting full binaries for {{PLATFORM}}..."
|
||||
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --platform {{PLATFORM}} --release --force
|
||||
|
||||
# Collect full binaries for all platforms
|
||||
[no-cd]
|
||||
collect-full-all:
|
||||
@echo "🌍 Collecting full binaries for all platforms..."
|
||||
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --all-platforms --release --force
|
||||
|
||||
# Collect only plugins (exclude nushell)
|
||||
[no-cd]
|
||||
collect-plugins-only:
|
||||
@echo "📦 Collecting plugins only..."
|
||||
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --release --force --include-nushell=false
|
||||
|
||||
# Collect full binaries with custom profile
|
||||
[no-cd]
|
||||
collect-full-profile PROFILE:
|
||||
@echo "📦 Collecting full binaries with profile {{PROFILE}}..."
|
||||
@{{justfile_directory()}}/scripts/run.sh collect_full_binaries.nu --profile {{PROFILE}} --force
|
||||
|
||||
# 📦 FULL PACKAGING COMMANDS
|
||||
|
||||
# Create full distribution package
|
||||
[no-cd]
|
||||
pack-full:
|
||||
@echo "📦 Creating full distribution package..."
|
||||
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --force
|
||||
|
||||
# Package full distribution for specific platform
|
||||
[no-cd]
|
||||
pack-full-platform PLATFORM:
|
||||
@echo "📦 Packaging full distribution for {{PLATFORM}}..."
|
||||
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --platform {{PLATFORM}} --force
|
||||
|
||||
# Package full distribution for all platforms
|
||||
[no-cd]
|
||||
pack-full-all:
|
||||
@echo "🌍 Packaging full distribution for all platforms..."
|
||||
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --all-platforms --force
|
||||
|
||||
# Package full distribution with checksums
|
||||
[no-cd]
|
||||
pack-full-checksums:
|
||||
@echo "🔐 Packaging full distribution with checksums..."
|
||||
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --all-platforms --checksums --force
|
||||
|
||||
# Package full distribution with bootstrap installers
|
||||
[no-cd]
|
||||
pack-full-bootstrap:
|
||||
@echo "🚀 Packaging full distribution with bootstrap installers..."
|
||||
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --all-platforms --checksums --bootstrap --force
|
||||
|
||||
# Show what would be packaged
|
||||
[no-cd]
|
||||
pack-full-list:
|
||||
@echo "📋 Files that would be packaged in full distribution:"
|
||||
@{{justfile_directory()}}/scripts/run.sh create_distribution_packages.nu --list
|
||||
|
||||
# 🚀 FULL RELEASE WORKFLOWS
|
||||
|
||||
# Complete full release workflow
|
||||
[no-cd]
|
||||
release-full:
|
||||
@echo "🚀 Running complete full release workflow..."
|
||||
@just validate-nushell
|
||||
@just build-full-release
|
||||
@just collect-full
|
||||
@just pack-full-checksums
|
||||
|
||||
# Cross-platform full release
|
||||
[no-cd]
|
||||
release-full-cross:
|
||||
@echo "🌍 Running cross-platform full release workflow..."
|
||||
@just validate-nushell
|
||||
@just build-full-cross
|
||||
@just collect-full-all
|
||||
@just pack-full-bootstrap
|
||||
|
||||
# Quick full release for current platform
|
||||
[no-cd]
|
||||
release-full-quick:
|
||||
@echo "⚡ Quick full release for current platform..."
|
||||
@just validate-nushell
|
||||
@just build-full-release
|
||||
@just collect-full
|
||||
@just pack-full
|
||||
|
||||
# Full release for specific target
|
||||
[no-cd]
|
||||
release-full-target TARGET:
|
||||
@echo "🎯 Full release for {{TARGET}}..."
|
||||
@just validate-nushell
|
||||
@just build-full-target {{TARGET}}
|
||||
@just collect-full-platform {{TARGET}}
|
||||
@just pack-full-platform {{TARGET}}
|
||||
|
||||
# Development full release (debug mode)
|
||||
[no-cd]
|
||||
release-full-dev:
|
||||
@echo "🛠️ Development full release..."
|
||||
@just validate-nushell
|
||||
@just build-full
|
||||
@just collect-full-debug
|
||||
@just pack-full
|
||||
|
||||
# 🔍 VERIFICATION COMMANDS
|
||||
|
||||
# Verify full installation
|
||||
[no-cd]
|
||||
verify-full:
|
||||
@echo "🔍 Verifying full installation..."
|
||||
@{{justfile_directory()}}/scripts/run.sh verify_installation.nu
|
||||
|
||||
# Test full installation locally
|
||||
[no-cd]
|
||||
test-install-full:
|
||||
@echo "🧪 Testing full installation locally..."
|
||||
@{{justfile_directory()}}/scripts/run.sh install_full_nushell.nu --test --verify
|
||||
|
||||
# Verify specific platform installation
|
||||
[no-cd]
|
||||
verify-full-platform PLATFORM:
|
||||
@echo "🔍 Verifying {{PLATFORM}} installation..."
|
||||
@{{justfile_directory()}}/scripts/run.sh verify_installation.nu --platform {{PLATFORM}}
|
||||
|
||||
# 📊 FULL DISTRIBUTION INFORMATION
|
||||
|
||||
# Show full distribution status
|
||||
[no-cd]
|
||||
full-status:
|
||||
@echo "📊 Full Distribution Status"
|
||||
@echo "=========================="
|
||||
@echo ""
|
||||
@echo "🔨 Build Status:"
|
||||
@if [ -f "nushell/target/release/nu" ] || [ -f "nushell/target/release/nu.exe" ]; then \
|
||||
echo " ✅ Nushell binary built"; \
|
||||
else \
|
||||
echo " ❌ Nushell binary not built"; \
|
||||
fi
|
||||
@echo ""
|
||||
@echo "📦 Collection Status:"
|
||||
@if [ -d "distribution" ]; then \
|
||||
echo " ✅ Distribution directory exists"; \
|
||||
echo " 📁 Platforms: $$(find distribution -maxdepth 1 -type d ! -name distribution | wc -l)"; \
|
||||
else \
|
||||
echo " ❌ Distribution directory not found"; \
|
||||
fi
|
||||
@echo ""
|
||||
@echo "📦 Packaging Status:"
|
||||
@if [ -d "bin_archives" ]; then \
|
||||
echo " ✅ Package archives exist"; \
|
||||
echo " 📦 Archives: $$(ls bin_archives/*.tar.gz bin_archives/*.zip 2>/dev/null | wc -l)"; \
|
||||
else \
|
||||
echo " ❌ No package archives found"; \
|
||||
fi
|
||||
|
||||
# Show full distribution help
|
||||
[no-cd]
|
||||
full-help:
|
||||
@echo "🚀 Full Distribution Module Help"
|
||||
@echo "==============================="
|
||||
@echo ""
|
||||
@echo "NUSHELL BUILD:"
|
||||
@echo " build-nushell - Build nushell with workspace plugins"
|
||||
@echo " build-nushell-debug - Build nushell (debug mode)"
|
||||
@echo " build-nushell-target TARGET - Build nushell for specific target"
|
||||
@echo " build-nushell-features FEAT - Build nushell with custom features"
|
||||
@echo " build-nushell-deps - Check nushell build dependencies"
|
||||
@echo " build-nushell-info - Show nushell build information"
|
||||
@echo ""
|
||||
@echo "FULL BUILD WORKFLOWS:"
|
||||
@echo " build-full - Build nushell + all plugins"
|
||||
@echo " build-full-release - Build full release distribution"
|
||||
@echo " build-full-target TARGET - Build full distribution for target"
|
||||
@echo " build-full-cross - Build full distribution for all targets"
|
||||
@echo ""
|
||||
@echo "FULL COLLECTION:"
|
||||
@echo " collect-full - Collect all binaries (nushell + plugins)"
|
||||
@echo " collect-full-debug - Collect full binaries (debug mode)"
|
||||
@echo " collect-full-list - List available full binaries"
|
||||
@echo " collect-full-platforms - List available platforms"
|
||||
@echo " collect-full-platform PLAT - Collect for specific platform"
|
||||
@echo " collect-full-all - Collect for all platforms"
|
||||
@echo " collect-plugins-only - Collect only plugins (no nushell)"
|
||||
@echo ""
|
||||
@echo "FULL PACKAGING:"
|
||||
@echo " pack-full - Create full distribution package"
|
||||
@echo " pack-full-platform PLATFORM - Package for specific platform"
|
||||
@echo " pack-full-all - Package for all platforms"
|
||||
@echo " pack-full-checksums - Package with checksums"
|
||||
@echo " pack-full-bootstrap - Package with bootstrap installers"
|
||||
@echo " pack-full-list - Show what would be packaged"
|
||||
@echo ""
|
||||
@echo "FULL RELEASE WORKFLOWS:"
|
||||
@echo " release-full - Complete full release workflow"
|
||||
@echo " release-full-cross - Cross-platform full release"
|
||||
@echo " release-full-quick - Quick full release (current platform)"
|
||||
@echo " release-full-target TARGET - Full release for specific target"
|
||||
@echo " release-full-dev - Development full release (debug)"
|
||||
@echo ""
|
||||
@echo "VERIFICATION:"
|
||||
@echo " verify-full - Verify full installation"
|
||||
@echo " test-install-full - Test full installation locally"
|
||||
@echo " verify-full-platform PLAT - Verify platform installation"
|
||||
@echo ""
|
||||
@echo "INFORMATION:"
|
||||
@echo " full-status - Show full distribution status"
|
||||
@echo " full-help - Show this help"
|
||||
@echo ""
|
||||
@echo "EXAMPLES:"
|
||||
@echo " just build-full-release"
|
||||
@echo " just collect-full-all"
|
||||
@echo " just pack-full-bootstrap"
|
||||
@echo " just release-full-cross"
|
||||
@echo " just verify-full"
|
274
scripts/build_nushell.nu
Executable file
274
scripts/build_nushell.nu
Executable file
@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
# Build Nushell with all workspace plugins
|
||||
#
|
||||
# This script builds the complete nushell binary with all built-in plugins
|
||||
# from the nushell workspace, optimized for distribution.
|
||||
|
||||
use lib/common_lib.nu [log_info, log_error, log_success, log_warn, validate_nushell_version]
|
||||
|
||||
def main [
|
||||
--release (-r) # Build in release mode (default: debug)
|
||||
--features (-f): string = "" # Additional cargo features to enable
|
||||
--target (-t): string = "" # Build target (cross-compilation)
|
||||
--verbose (-v) # Enable verbose output
|
||||
--profile (-p): string = "" # Build profile (release, dev, etc.)
|
||||
] {
|
||||
log_info "🔨 Building Nushell with workspace plugins..."
|
||||
|
||||
# Validate nushell version consistency first
|
||||
validate_nushell_version
|
||||
|
||||
# Get nushell directory
|
||||
let nushell_dir = $"($env.PWD)/nushell"
|
||||
|
||||
# Validate nushell directory exists
|
||||
if not ($nushell_dir | path exists) {
|
||||
log_error $"Nushell directory not found: ($nushell_dir)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build cargo command
|
||||
mut cargo_cmd = ["cargo", "build"]
|
||||
|
||||
# Add build mode
|
||||
if $release != null {
|
||||
$cargo_cmd = ($cargo_cmd | append "--release")
|
||||
log_info "🚀 Building in release mode"
|
||||
} else {
|
||||
log_info "🛠️ Building in debug mode"
|
||||
}
|
||||
|
||||
# Add profile if specified
|
||||
if ($profile | is-not-empty) {
|
||||
$cargo_cmd = ($cargo_cmd | append ["--profile", $profile])
|
||||
log_info $"📋 Using profile: ($profile)"
|
||||
}
|
||||
|
||||
# Add target if specified
|
||||
if ($target | is-not-empty) {
|
||||
$cargo_cmd = ($cargo_cmd | append ["--target", $target])
|
||||
log_info $"🎯 Building for target: ($target)"
|
||||
}
|
||||
|
||||
# Determine features to enable
|
||||
mut all_features = [
|
||||
"plugin" # Enable plugin system
|
||||
"network" # Network commands
|
||||
"sqlite" # SQLite support
|
||||
"trash-support" # Trash commands
|
||||
"rustls-tls" # TLS support
|
||||
"system-clipboard" # System clipboard support
|
||||
]
|
||||
|
||||
# Add additional features if provided
|
||||
if ($features | is-not-empty) {
|
||||
let custom_features = ($features | split row ",")
|
||||
$all_features = ($all_features | append $custom_features)
|
||||
}
|
||||
|
||||
# Add features to cargo command
|
||||
let features_str = ($all_features | str join ",")
|
||||
$cargo_cmd = ($cargo_cmd | append ["--features", $features_str])
|
||||
log_info $"✨ Enabled features: ($features_str)"
|
||||
|
||||
# Add verbose flag if requested
|
||||
if $verbose != null {
|
||||
$cargo_cmd = ($cargo_cmd | append "--verbose")
|
||||
log_info "📢 Verbose output enabled"
|
||||
}
|
||||
|
||||
# Change to nushell directory
|
||||
log_info $"📁 Working in: ($nushell_dir)"
|
||||
|
||||
try {
|
||||
# Execute cargo build
|
||||
let cargo_cmd_str = ($cargo_cmd | str join " ")
|
||||
log_info $"🔨 Running: ($cargo_cmd_str)"
|
||||
|
||||
cd $nushell_dir
|
||||
let result = bash -c $cargo_cmd_str
|
||||
|
||||
# Determine output directory based on build mode and target
|
||||
mut target_dir = $"($nushell_dir)/target"
|
||||
|
||||
if ($target | is-not-empty) {
|
||||
$target_dir = $"($target_dir)/($target)"
|
||||
}
|
||||
|
||||
mut profile_dir = if $release != null {
|
||||
"release"
|
||||
} else if ($profile | is-not-empty) {
|
||||
$profile
|
||||
} else {
|
||||
"debug"
|
||||
}
|
||||
|
||||
let binary_path = $"($target_dir)/($profile_dir)/nu"
|
||||
let binary_path = if $nu.os-info.name == "windows" {
|
||||
$"($binary_path).exe"
|
||||
} else {
|
||||
$binary_path
|
||||
}
|
||||
|
||||
# Check if binary was created
|
||||
if ($binary_path | path exists) {
|
||||
let binary_size = (ls $binary_path | get size.0)
|
||||
log_success $"✅ Nushell binary built successfully!"
|
||||
log_info $"📄 Binary location: ($binary_path)"
|
||||
log_info $"📊 Binary size: ($binary_size)"
|
||||
|
||||
# Show binary info
|
||||
try {
|
||||
let version_info = run-external $binary_path "--version" | complete
|
||||
if $version_info.exit_code == 0 {
|
||||
log_info $"🔢 Version: ($version_info.stdout | str trim)"
|
||||
}
|
||||
} catch {
|
||||
log_warn "⚠️ Could not get version information"
|
||||
}
|
||||
|
||||
# List workspace plugins that are built-in
|
||||
log_info "🧩 Built-in workspace plugins:"
|
||||
let workspace_plugins = [
|
||||
"nu_plugin_custom_values"
|
||||
"nu_plugin_example"
|
||||
"nu_plugin_formats"
|
||||
"nu_plugin_gstat"
|
||||
"nu_plugin_inc"
|
||||
"nu_plugin_polars"
|
||||
"nu_plugin_query"
|
||||
"nu_plugin_stress_internals"
|
||||
]
|
||||
|
||||
for plugin in $workspace_plugins {
|
||||
let plugin_binary = $"($target_dir)/($profile_dir)/($plugin)"
|
||||
let plugin_binary = if $nu.os-info.name == "windows" {
|
||||
$"($plugin_binary).exe"
|
||||
} else {
|
||||
$plugin_binary
|
||||
}
|
||||
|
||||
if ($plugin_binary | path exists) {
|
||||
let plugin_size = (ls $plugin_binary | get size.0)
|
||||
log_info $" ✅ ($plugin): ($plugin_size)"
|
||||
} else {
|
||||
log_warn $" ❌ ($plugin): not built"
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true
|
||||
binary_path: $binary_path
|
||||
target_dir: $target_dir
|
||||
profile_dir: $profile_dir
|
||||
features: $features_str
|
||||
}
|
||||
|
||||
} else {
|
||||
log_error $"❌ Binary not found at expected location: ($binary_path)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
} catch { |err|
|
||||
log_error $"❌ Build failed: ($err.msg)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Show build information
|
||||
def "build info" [] {
|
||||
log_info "🔨 Nushell Build Information"
|
||||
log_info "==========================="
|
||||
log_info ""
|
||||
log_info "Available features:"
|
||||
log_info " plugin - Enable plugin system"
|
||||
log_info " network - Network commands (http, etc.)"
|
||||
log_info " sqlite - SQLite database support"
|
||||
log_info " trash-support - Trash/recycle bin commands"
|
||||
log_info " rustls-tls - TLS support with rustls"
|
||||
log_info " system-clipboard - System clipboard integration"
|
||||
log_info " native-tls - TLS support with native libs"
|
||||
log_info " static-link-openssl - Statically link OpenSSL"
|
||||
log_info ""
|
||||
log_info "Build profiles:"
|
||||
log_info " dev - Debug build (default)"
|
||||
log_info " release - Optimized release build"
|
||||
log_info " profiling - Release with debug info"
|
||||
log_info " ci - CI optimized build"
|
||||
log_info ""
|
||||
log_info "Workspace plugins (built-in):"
|
||||
log_info " nu_plugin_custom_values - Custom value handling"
|
||||
log_info " nu_plugin_example - Example plugin"
|
||||
log_info " nu_plugin_formats - Additional file formats"
|
||||
log_info " nu_plugin_gstat - Git status plugin"
|
||||
log_info " nu_plugin_inc - Increment plugin"
|
||||
log_info " nu_plugin_polars - DataFrame support"
|
||||
log_info " nu_plugin_query - Query plugin"
|
||||
log_info " nu_plugin_stress_internals - Stress testing"
|
||||
}
|
||||
|
||||
# Check build dependencies
|
||||
def "build deps" [] {
|
||||
log_info "🔍 Checking build dependencies..."
|
||||
|
||||
# Check Rust toolchain
|
||||
try {
|
||||
let rust_version = run-external "rustc" "--version" | complete
|
||||
if $rust_version.exit_code == 0 {
|
||||
log_success $"✅ Rust: ($rust_version.stdout | str trim)"
|
||||
} else {
|
||||
log_error "❌ Rust compiler not found"
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
log_error "❌ Rust compiler not found"
|
||||
return false
|
||||
}
|
||||
|
||||
# Check Cargo
|
||||
try {
|
||||
let cargo_version = run-external "cargo" "--version" | complete
|
||||
if $cargo_version.exit_code == 0 {
|
||||
log_success $"✅ Cargo: ($cargo_version.stdout | str trim)"
|
||||
} else {
|
||||
log_error "❌ Cargo not found"
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
log_error "❌ Cargo not found"
|
||||
return false
|
||||
}
|
||||
|
||||
# Check nushell directory
|
||||
let nushell_dir = $"($env.PWD)/nushell"
|
||||
if ($nushell_dir | path exists) {
|
||||
log_success $"✅ Nushell source: ($nushell_dir)"
|
||||
} else {
|
||||
log_error $"❌ Nushell source not found: ($nushell_dir)"
|
||||
return false
|
||||
}
|
||||
|
||||
# Check Cargo.toml
|
||||
let cargo_toml = $"($nushell_dir)/Cargo.toml"
|
||||
if ($cargo_toml | path exists) {
|
||||
log_success $"✅ Nushell Cargo.toml found"
|
||||
|
||||
# Show workspace members
|
||||
try {
|
||||
let cargo_content = open $cargo_toml
|
||||
if "workspace" in $cargo_content {
|
||||
let members = $cargo_content.workspace.members | length
|
||||
log_info $"📦 Workspace members: ($members)"
|
||||
}
|
||||
} catch {
|
||||
log_warn "⚠️ Could not parse Cargo.toml"
|
||||
}
|
||||
} else {
|
||||
log_error $"❌ Nushell Cargo.toml not found: ($cargo_toml)"
|
||||
return false
|
||||
}
|
||||
|
||||
log_success "✅ All build dependencies satisfied"
|
||||
return true
|
||||
}
|
656
scripts/collect_full_binaries.nu
Executable file
656
scripts/collect_full_binaries.nu
Executable file
@ -0,0 +1,656 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
# Collect Full Binaries Script
|
||||
# Collects nushell binary, workspace plugins, and custom plugins for full distribution
|
||||
|
||||
use lib/common_lib.nu [log_info, log_error, log_success, log_warn, validate_nushell_version]
|
||||
|
||||
def main [
|
||||
--platform (-p): string = "" # Target platform (e.g., linux-x86_64, darwin-arm64)
|
||||
--output (-o): string = "distribution" # Output directory
|
||||
--force (-f): bool = false # Force overwrite existing files
|
||||
--list (-l): bool = false # List available binaries
|
||||
--list-platforms: bool = false # List available platforms
|
||||
--all-platforms: bool = false # Collect for all available platforms
|
||||
--include-nushell (-n): bool = true # Include nushell binary (default: true)
|
||||
--release (-r): bool = false # Use release builds (default: debug)
|
||||
--profile: string = "" # Build profile to use
|
||||
] {
|
||||
# Validate nushell version consistency first
|
||||
validate_nushell_version
|
||||
|
||||
if $list_platforms {
|
||||
list_available_platforms
|
||||
return
|
||||
}
|
||||
|
||||
if $list {
|
||||
list_available_binaries $platform $release $profile
|
||||
return
|
||||
}
|
||||
|
||||
if $all_platforms {
|
||||
collect_all_platforms $output $force $include_nushell $release $profile
|
||||
return
|
||||
}
|
||||
|
||||
# Collect binaries for specific or current platform
|
||||
collect_binaries $platform $output $force $include_nushell $release $profile
|
||||
}
|
||||
|
||||
# List available platforms for collection
|
||||
def list_available_platforms [] {
|
||||
log_info "🌍 Available platforms for collection:"
|
||||
log_info "===================================="
|
||||
|
||||
let platforms = detect_available_platforms
|
||||
|
||||
if ($platforms | is-empty) {
|
||||
log_warn "⚠️ No built binaries found. Build plugins first with 'just build' or 'just build-cross-all'"
|
||||
return
|
||||
}
|
||||
|
||||
for platform in $platforms {
|
||||
log_info $" ✅ ($platform.name)"
|
||||
log_info $" Target: ($platform.target)"
|
||||
log_info $" Nushell: ($platform.has_nushell)"
|
||||
log_info $" Plugins: ($platform.plugin_count)"
|
||||
log_info ""
|
||||
}
|
||||
}
|
||||
|
||||
# List available binaries for a platform
|
||||
def list_available_binaries [
|
||||
platform: string
|
||||
use_release: bool
|
||||
profile: string
|
||||
] {
|
||||
log_info "📋 Available binaries:"
|
||||
log_info "====================="
|
||||
|
||||
let current_platform = if ($platform | is-empty) {
|
||||
detect_current_platform
|
||||
} else {
|
||||
$platform
|
||||
}
|
||||
|
||||
log_info $"Platform: ($current_platform)"
|
||||
log_info ""
|
||||
|
||||
# Check nushell binary
|
||||
let nushell_info = get_nushell_binary_info $current_platform $use_release $profile
|
||||
if ($nushell_info | is-not-empty) {
|
||||
log_info "🚀 Nushell Binary:"
|
||||
log_info $" Path: ($nushell_info.path)"
|
||||
log_info $" Size: ($nushell_info.size)"
|
||||
log_info $" Modified: ($nushell_info.modified)"
|
||||
log_info ""
|
||||
} else {
|
||||
log_warn "⚠️ Nushell binary not found for this platform"
|
||||
log_info ""
|
||||
}
|
||||
|
||||
# Check workspace plugins
|
||||
let workspace_plugins = get_workspace_plugins_info $current_platform $use_release $profile
|
||||
if not ($workspace_plugins | is-empty) {
|
||||
log_info "🧩 Workspace Plugins:"
|
||||
for plugin in $workspace_plugins {
|
||||
log_info $" ✅ ($plugin.name) - ($plugin.size)"
|
||||
}
|
||||
log_info ""
|
||||
}
|
||||
|
||||
# Check custom plugins
|
||||
let custom_plugins = get_custom_plugins_info $current_platform $use_release $profile
|
||||
if not ($custom_plugins | is-empty) {
|
||||
log_info "🔧 Custom Plugins:"
|
||||
for plugin in $custom_plugins {
|
||||
log_info $" ✅ ($plugin.name) - ($plugin.size)"
|
||||
}
|
||||
log_info ""
|
||||
}
|
||||
|
||||
let total_count = (
|
||||
($workspace_plugins | length) +
|
||||
($custom_plugins | length) +
|
||||
(if ($nushell_info | is-not-empty) { 1 } else { 0 })
|
||||
)
|
||||
|
||||
log_info $"📊 Total binaries: ($total_count)"
|
||||
}
|
||||
|
||||
# Collect binaries for all platforms
|
||||
def collect_all_platforms [
|
||||
output: string
|
||||
force: bool
|
||||
include_nushell: bool
|
||||
use_release: bool
|
||||
profile: string
|
||||
] {
|
||||
log_info "🌍 Collecting binaries for all platforms..."
|
||||
|
||||
let platforms = detect_available_platforms
|
||||
|
||||
if ($platforms | is-empty) {
|
||||
log_error "❌ No built binaries found. Build first with 'just build-cross-all'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
for platform in $platforms {
|
||||
log_info $"📦 Collecting for ($platform.name)..."
|
||||
try {
|
||||
collect_binaries $platform.target $output $force $include_nushell $use_release $profile
|
||||
} catch { |err|
|
||||
log_error $"❌ Failed to collect ($platform.name): ($err.msg)"
|
||||
}
|
||||
}
|
||||
|
||||
log_success "✅ Collection complete for all platforms"
|
||||
}
|
||||
|
||||
# Main collection function
|
||||
def collect_binaries [
|
||||
platform: string
|
||||
output: string
|
||||
force: bool
|
||||
include_nushell: bool
|
||||
use_release: bool
|
||||
profile: string
|
||||
] {
|
||||
let target_platform = if ($platform | is-empty) {
|
||||
detect_current_platform
|
||||
} else {
|
||||
$platform
|
||||
}
|
||||
|
||||
log_info $"📦 Collecting binaries for platform: ($target_platform)"
|
||||
|
||||
# Create output structure
|
||||
let platform_dir = $"($output)/($target_platform)"
|
||||
create_target_structure $platform_dir $force
|
||||
|
||||
mut collected_files = []
|
||||
|
||||
# Collect nushell binary if requested
|
||||
if $include_nushell {
|
||||
let nushell_info = get_nushell_binary_info $target_platform $use_release $profile
|
||||
if ($nushell_info | is-not-empty) {
|
||||
let dest_path = $"($platform_dir)/nu"
|
||||
let dest_path = if $nu.os-info.name == "windows" { $"($dest_path).exe" } else { $dest_path }
|
||||
|
||||
copy_binary $nushell_info.path $dest_path $force
|
||||
$collected_files = ($collected_files | append {
|
||||
name: "nu"
|
||||
type: "nushell"
|
||||
path: $dest_path
|
||||
size: (get_file_size $dest_path)
|
||||
})
|
||||
log_success $"✅ Collected nushell binary"
|
||||
} else {
|
||||
log_warn "⚠️ Nushell binary not found for platform ($target_platform)"
|
||||
}
|
||||
}
|
||||
|
||||
# Collect workspace plugins
|
||||
let workspace_plugins = get_workspace_plugins_info $target_platform $use_release $profile
|
||||
for plugin in $workspace_plugins {
|
||||
let dest_path = $"($platform_dir)/($plugin.name)"
|
||||
copy_binary $plugin.path $dest_path $force
|
||||
$collected_files = ($collected_files | append {
|
||||
name: $plugin.name
|
||||
type: "workspace_plugin"
|
||||
path: $dest_path
|
||||
size: (get_file_size $dest_path)
|
||||
})
|
||||
}
|
||||
|
||||
# Collect custom plugins
|
||||
let custom_plugins = get_custom_plugins_info $target_platform $use_release $profile
|
||||
for plugin in $custom_plugins {
|
||||
let dest_path = $"($platform_dir)/($plugin.name)"
|
||||
copy_binary $plugin.path $dest_path $force
|
||||
$collected_files = ($collected_files | append {
|
||||
name: $plugin.name
|
||||
type: "custom_plugin"
|
||||
path: $dest_path
|
||||
size: (get_file_size $dest_path)
|
||||
})
|
||||
}
|
||||
|
||||
# Copy additional files
|
||||
copy_additional_files $platform_dir $force
|
||||
|
||||
# Create manifest
|
||||
create_manifest $platform_dir $target_platform $collected_files $include_nushell
|
||||
|
||||
# Create installation script
|
||||
create_installation_scripts $platform_dir
|
||||
|
||||
# Summary
|
||||
let total_size = ($collected_files | get size | math sum)
|
||||
log_success $"✅ Collection complete for ($target_platform)"
|
||||
log_info $"📊 Collected ($collected_files | length) binaries"
|
||||
log_info $"📁 Output directory: ($platform_dir)"
|
||||
log_info $"💾 Total size: ($total_size)"
|
||||
|
||||
return {
|
||||
platform: $target_platform
|
||||
output_dir: $platform_dir
|
||||
files: $collected_files
|
||||
total_size: $total_size
|
||||
}
|
||||
}
|
||||
|
||||
# Get nushell binary information
|
||||
def get_nushell_binary_info [
|
||||
platform: string
|
||||
use_release: bool
|
||||
profile: string
|
||||
]: nothing -> record {
|
||||
let nushell_dir = $"($env.PWD)/nushell"
|
||||
let mut target_dir = $"($nushell_dir)/target"
|
||||
|
||||
# Handle cross-compilation targets
|
||||
let target_triple = convert_platform_to_target $platform
|
||||
if ($target_triple | is-not-empty) and $target_triple != (detect_current_target) {
|
||||
$target_dir = $"($target_dir)/($target_triple)"
|
||||
}
|
||||
|
||||
# Determine profile directory
|
||||
let mut profile_dir = if ($profile | is-not-empty) {
|
||||
$profile
|
||||
} else if $use_release {
|
||||
"release"
|
||||
} else {
|
||||
"debug"
|
||||
}
|
||||
|
||||
let binary_name = if ($platform | str contains "windows") or $nu.os-info.name == "windows" {
|
||||
"nu.exe"
|
||||
} else {
|
||||
"nu"
|
||||
}
|
||||
|
||||
let binary_path = $"($target_dir)/($profile_dir)/($binary_name)"
|
||||
|
||||
if ($binary_path | path exists) {
|
||||
let file_info = ls $binary_path | get 0
|
||||
return {
|
||||
name: "nu"
|
||||
path: $binary_path
|
||||
size: $file_info.size
|
||||
modified: $file_info.modified
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
# Get workspace plugins information
|
||||
def get_workspace_plugins_info [
|
||||
platform: string
|
||||
use_release: bool
|
||||
profile: string
|
||||
]: nothing -> list<record> {
|
||||
let nushell_dir = $"($env.PWD)/nushell"
|
||||
let mut target_dir = $"($nushell_dir)/target"
|
||||
|
||||
# Handle cross-compilation targets
|
||||
let target_triple = convert_platform_to_target $platform
|
||||
if ($target_triple | is-not-empty) and $target_triple != (detect_current_target) {
|
||||
$target_dir = $"($target_dir)/($target_triple)"
|
||||
}
|
||||
|
||||
# Determine profile directory
|
||||
let mut profile_dir = if ($profile | is-not-empty) {
|
||||
$profile
|
||||
} else if $use_release {
|
||||
"release"
|
||||
} else {
|
||||
"debug"
|
||||
}
|
||||
|
||||
let workspace_plugins = [
|
||||
"nu_plugin_custom_values"
|
||||
"nu_plugin_example"
|
||||
"nu_plugin_formats"
|
||||
"nu_plugin_gstat"
|
||||
"nu_plugin_inc"
|
||||
"nu_plugin_polars"
|
||||
"nu_plugin_query"
|
||||
"nu_plugin_stress_internals"
|
||||
]
|
||||
|
||||
mut found_plugins = []
|
||||
|
||||
for plugin in $workspace_plugins {
|
||||
let binary_name = if ($platform | str contains "windows") or $nu.os-info.name == "windows" {
|
||||
$"($plugin).exe"
|
||||
} else {
|
||||
$plugin
|
||||
}
|
||||
|
||||
let binary_path = $"($target_dir)/($profile_dir)/($binary_name)"
|
||||
|
||||
if ($binary_path | path exists) {
|
||||
let file_info = ls $binary_path | get 0
|
||||
$found_plugins = ($found_plugins | append {
|
||||
name: $plugin
|
||||
path: $binary_path
|
||||
size: $file_info.size
|
||||
modified: $file_info.modified
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return $found_plugins
|
||||
}
|
||||
|
||||
# Get custom plugins information
|
||||
def get_custom_plugins_info [
|
||||
platform: string
|
||||
use_release: bool
|
||||
profile: string
|
||||
]: nothing -> list<record> {
|
||||
# Get list of plugin directories (nu_plugin_*)
|
||||
let plugin_dirs = ls nu_plugin_*
|
||||
| where type == dir
|
||||
| where name != "nushell" # Exclude nushell submodule
|
||||
| get name
|
||||
|
||||
mut found_plugins = []
|
||||
let target_triple = convert_platform_to_target $platform
|
||||
|
||||
for plugin_dir in $plugin_dirs {
|
||||
let mut target_dir = $"($plugin_dir)/target"
|
||||
|
||||
# Handle cross-compilation targets
|
||||
if ($target_triple | is-not-empty) and $target_triple != (detect_current_target) {
|
||||
$target_dir = $"($target_dir)/($target_triple)"
|
||||
}
|
||||
|
||||
# Determine profile directory
|
||||
let mut profile_dir = if ($profile | is-not-empty) {
|
||||
$profile
|
||||
} else if $use_release {
|
||||
"release"
|
||||
} else {
|
||||
"debug"
|
||||
}
|
||||
|
||||
let binary_name = if ($platform | str contains "windows") or $nu.os-info.name == "windows" {
|
||||
$"($plugin_dir).exe"
|
||||
} else {
|
||||
$plugin_dir
|
||||
}
|
||||
|
||||
let binary_path = $"($target_dir)/($profile_dir)/($binary_name)"
|
||||
|
||||
if ($binary_path | path exists) {
|
||||
let file_info = ls $binary_path | get 0
|
||||
$found_plugins = ($found_plugins | append {
|
||||
name: $plugin_dir
|
||||
path: $binary_path
|
||||
size: $file_info.size
|
||||
modified: $file_info.modified
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return $found_plugins
|
||||
}
|
||||
|
||||
# Utility functions
|
||||
def detect_current_platform []: nothing -> string {
|
||||
let os = $nu.os-info.name
|
||||
let arch = $nu.os-info.arch
|
||||
|
||||
match [$os, $arch] {
|
||||
["linux", "x86_64"] => "linux-x86_64"
|
||||
["linux", "aarch64"] => "linux-aarch64"
|
||||
["macos", "x86_64"] => "darwin-x86_64"
|
||||
["macos", "aarch64"] => "darwin-arm64"
|
||||
["windows", "x86_64"] => "windows-x86_64"
|
||||
["windows", "aarch64"] => "windows-aarch64"
|
||||
_ => $"($os)-($arch)"
|
||||
}
|
||||
}
|
||||
|
||||
def detect_current_target []: nothing -> string {
|
||||
let os = $nu.os-info.name
|
||||
let arch = $nu.os-info.arch
|
||||
|
||||
match [$os, $arch] {
|
||||
["linux", "x86_64"] => "x86_64-unknown-linux-gnu"
|
||||
["linux", "aarch64"] => "aarch64-unknown-linux-gnu"
|
||||
["macos", "x86_64"] => "x86_64-apple-darwin"
|
||||
["macos", "aarch64"] => "aarch64-apple-darwin"
|
||||
["windows", "x86_64"] => "x86_64-pc-windows-msvc"
|
||||
["windows", "aarch64"] => "aarch64-pc-windows-msvc"
|
||||
_ => $"($arch)-unknown-($os)"
|
||||
}
|
||||
}
|
||||
|
||||
def convert_platform_to_target [platform: string]: nothing -> string {
|
||||
match $platform {
|
||||
"linux-x86_64" => "x86_64-unknown-linux-gnu"
|
||||
"linux-aarch64" => "aarch64-unknown-linux-gnu"
|
||||
"darwin-x86_64" => "x86_64-apple-darwin"
|
||||
"darwin-arm64" => "aarch64-apple-darwin"
|
||||
"windows-x86_64" => "x86_64-pc-windows-msvc"
|
||||
"windows-aarch64" => "aarch64-pc-windows-msvc"
|
||||
_ => $platform
|
||||
}
|
||||
}
|
||||
|
||||
def detect_available_platforms []: nothing -> list<record> {
|
||||
mut platforms = []
|
||||
|
||||
# Check nushell target directory for built platforms
|
||||
let nushell_target = $"($env.PWD)/nushell/target"
|
||||
if ($nushell_target | path exists) {
|
||||
let target_dirs = try { ls $nushell_target | where type == dir | get name } catch { [] }
|
||||
|
||||
for target_dir in $target_dirs {
|
||||
if ($target_dir | str ends-with "target") { continue } # Skip base target dir
|
||||
|
||||
let target_name = ($target_dir | path basename)
|
||||
let platform_name = convert_target_to_platform $target_name
|
||||
|
||||
# Check if this target has binaries
|
||||
let release_dir = $"($target_dir)/release"
|
||||
let debug_dir = $"($target_dir)/debug"
|
||||
|
||||
let has_release = ($release_dir | path exists) and (try { ls $release_dir | where name =~ "nu" | length } catch { 0 }) > 0
|
||||
let has_debug = ($debug_dir | path exists) and (try { ls $debug_dir | where name =~ "nu" | length } catch { 0 }) > 0
|
||||
|
||||
if $has_release or $has_debug {
|
||||
$platforms = ($platforms | append {
|
||||
name: $platform_name
|
||||
target: $target_name
|
||||
has_nushell: true
|
||||
plugin_count: 0 # Will be calculated
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# If no cross-compiled targets found, add current platform
|
||||
if ($platforms | is-empty) {
|
||||
let current_platform = detect_current_platform
|
||||
let current_target = detect_current_target
|
||||
|
||||
$platforms = ($platforms | append {
|
||||
name: $current_platform
|
||||
target: $current_target
|
||||
has_nushell: true
|
||||
plugin_count: 0
|
||||
})
|
||||
}
|
||||
|
||||
return $platforms
|
||||
}
|
||||
|
||||
def convert_target_to_platform [target: string]: nothing -> string {
|
||||
match $target {
|
||||
"x86_64-unknown-linux-gnu" => "linux-x86_64"
|
||||
"aarch64-unknown-linux-gnu" => "linux-aarch64"
|
||||
"x86_64-apple-darwin" => "darwin-x86_64"
|
||||
"aarch64-apple-darwin" => "darwin-arm64"
|
||||
"x86_64-pc-windows-msvc" => "windows-x86_64"
|
||||
"aarch64-pc-windows-msvc" => "windows-aarch64"
|
||||
_ => $target
|
||||
}
|
||||
}
|
||||
|
||||
def create_target_structure [target_path: string, force: bool] {
|
||||
if ($target_path | path exists) and not $force {
|
||||
log_error $"Target directory exists: ($target_path). Use --force to overwrite."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($target_path | path exists) and $force {
|
||||
rm -rf $target_path
|
||||
log_warn $"🗑️ Removed existing directory: ($target_path)"
|
||||
}
|
||||
|
||||
mkdir $target_path
|
||||
log_info $"📁 Created target directory: ($target_path)"
|
||||
|
||||
# Create subdirectories
|
||||
let subdirs = ["scripts", "config", "docs"]
|
||||
for subdir in $subdirs {
|
||||
mkdir $"($target_path)/($subdir)"
|
||||
}
|
||||
}
|
||||
|
||||
def copy_binary [src: string, dest: string, force: bool] {
|
||||
if ($dest | path exists) and not $force {
|
||||
log_warn $"⚠️ File exists, skipping: ($dest)"
|
||||
return
|
||||
}
|
||||
|
||||
cp $src $dest
|
||||
chmod +x $dest
|
||||
}
|
||||
|
||||
def copy_additional_files [target_path: string, force: bool] {
|
||||
let files_to_copy = [
|
||||
{src: "LICENSE", dest: "LICENSE", required: false}
|
||||
{src: "README.md", dest: "README.md", required: false}
|
||||
{src: "CHANGELOG.md", dest: "CHANGELOG.md", required: false}
|
||||
]
|
||||
|
||||
for file in $files_to_copy {
|
||||
if ($file.src | path exists) {
|
||||
let dest_path = $"($target_path)/($file.dest)"
|
||||
if not ($dest_path | path exists) or $force {
|
||||
cp $file.src $dest_path
|
||||
log_info $" 📄 Copied ($file.src) → ($file.dest)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def create_manifest [
|
||||
target_path: string
|
||||
platform: string
|
||||
files: list<record>
|
||||
includes_nushell: bool
|
||||
] {
|
||||
let manifest = {
|
||||
platform: $platform
|
||||
created: (date now | format date %Y-%m-%d_%H-%M-%S)
|
||||
includes_nushell: $includes_nushell
|
||||
total_files: ($files | length)
|
||||
files: $files
|
||||
nushell_version: (try {
|
||||
if $includes_nushell {
|
||||
let nu_binary = ($files | where type == "nushell" | get 0)
|
||||
if ($nu_binary | is-not-empty) {
|
||||
run-external $nu_binary.path "--version" | complete | get stdout | str trim
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
} else {
|
||||
"not_included"
|
||||
}
|
||||
} catch { "unknown" })
|
||||
}
|
||||
|
||||
$manifest | to json | save --force $"($target_path)/manifest.json"
|
||||
log_info $"📋 Created manifest: ($target_path)/manifest.json"
|
||||
}
|
||||
|
||||
def create_installation_scripts [target_path: string] {
|
||||
# Create a basic installation script that registers plugins
|
||||
let install_script = $"#!/usr/bin/env nu
|
||||
|
||||
# Installation script for nushell and plugins
|
||||
# This script registers all plugins with the nushell binary
|
||||
|
||||
def main [
|
||||
--verify: bool = false # Verify installation after completion
|
||||
] {
|
||||
print \"🚀 Installing Nushell and plugins...\"
|
||||
|
||||
# Get current directory (should be the distribution directory)
|
||||
let dist_dir = $env.PWD
|
||||
|
||||
# Find nushell binary
|
||||
let nu_binary = if (\"nu\" | path exists) {
|
||||
\"./nu\"
|
||||
} else if (\"nu.exe\" | path exists) {
|
||||
\"./nu.exe\"
|
||||
} else {
|
||||
print \"❌ Nushell binary not found in distribution\"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Find plugin binaries
|
||||
let plugins = ls . | where type == file | where name =~ \"nu_plugin_\" | get name
|
||||
|
||||
print $\"📦 Found ($plugins | length) plugins to register\"
|
||||
|
||||
# Register each plugin
|
||||
for plugin in $plugins {
|
||||
print $\" Registering ($plugin)...\"
|
||||
try {
|
||||
run-external $nu_binary \"plugin\" \"add\" (\"./\" + $plugin)
|
||||
} catch { |err|
|
||||
print $\" ⚠️ Failed to register ($plugin): ($err.msg)\"
|
||||
}
|
||||
}
|
||||
|
||||
if $verify {
|
||||
print \"🔍 Verifying installation...\"
|
||||
try {
|
||||
let plugin_list = run-external $nu_binary \"-c\" \"plugin list\" | complete
|
||||
if $plugin_list.exit_code == 0 {
|
||||
print \"✅ Plugin verification successful\"
|
||||
print $plugin_list.stdout
|
||||
} else {
|
||||
print \"❌ Plugin verification failed\"
|
||||
print $plugin_list.stderr
|
||||
}
|
||||
} catch { |err|
|
||||
print $\"❌ Verification failed: ($err.msg)\"
|
||||
}
|
||||
}
|
||||
|
||||
print \"✅ Installation complete!\"
|
||||
}
|
||||
"
|
||||
|
||||
$install_script | save --force $"($target_path)/install.nu"
|
||||
chmod +x $"($target_path)/install.nu"
|
||||
log_info $"📜 Created installation script: ($target_path)/install.nu"
|
||||
}
|
||||
|
||||
def get_file_size [path: string]: nothing -> int {
|
||||
try {
|
||||
ls $path | get size.0
|
||||
} catch {
|
||||
0
|
||||
}
|
||||
}
|
599
scripts/create_distribution_packages.nu
Executable file
599
scripts/create_distribution_packages.nu
Executable file
@ -0,0 +1,599 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
# Create Distribution Packages Script
|
||||
# Creates platform-specific distribution packages with nushell binary, plugins, and installers
|
||||
|
||||
use lib/common_lib.nu [
|
||||
log_info, log_error, log_success, log_warn, log_debug,
|
||||
validate_nushell_version, get_current_platform, get_supported_platforms,
|
||||
get_binary_extension, get_archive_extension, ensure_dir, remove_dir,
|
||||
copy_file, create_checksums, get_workspace_version, create_manifest
|
||||
]
|
||||
|
||||
def main [
|
||||
--platform (-p): string = "" # Target platform (e.g., linux-x86_64, darwin-arm64)
|
||||
--output (-o): string = "bin_archives" # Output directory for packages
|
||||
--force (-f) # Force overwrite existing packages
|
||||
--list (-l) # List what would be packaged
|
||||
--all-platforms # Create packages for all platforms
|
||||
--checksums # Generate checksums for packages
|
||||
--bootstrap # Include bootstrap installers
|
||||
--include-docs # Include documentation
|
||||
--version (-v): string = "" # Version override
|
||||
] {
|
||||
# Validate nushell version consistency first
|
||||
validate_nushell_version
|
||||
|
||||
# Get version
|
||||
let version = if ($version | str length) > 0 {
|
||||
$version
|
||||
} else {
|
||||
get_workspace_version
|
||||
}
|
||||
|
||||
if $list {
|
||||
list_packageable_components $platform $version
|
||||
return
|
||||
}
|
||||
|
||||
if $all_platforms {
|
||||
create_all_platform_packages $output $force $checksums $bootstrap $include_docs $version
|
||||
return
|
||||
}
|
||||
|
||||
# Create package for specific or current platform
|
||||
let target_platform = if ($platform | str length) > 0 {
|
||||
$platform
|
||||
} else {
|
||||
get_current_platform
|
||||
}
|
||||
|
||||
create_platform_package $target_platform $output $force $checksums $bootstrap $include_docs $version
|
||||
}
|
||||
|
||||
# List components that would be packaged
|
||||
def list_packageable_components [platform: string, version: string] {
|
||||
let target_platform = if ($platform | str length) > 0 {
|
||||
$platform
|
||||
} else {
|
||||
get_current_platform
|
||||
}
|
||||
|
||||
log_info $"📋 Components for platform: ($target_platform)"
|
||||
log_info "================================================"
|
||||
|
||||
# Check nushell binary
|
||||
let nushell_components = get_nushell_components $target_platform $version
|
||||
log_info "🚀 Nushell Components:"
|
||||
$nushell_components.binaries | each {|binary|
|
||||
let status = if ($binary.path | path exists) { "✅" } else { "❌" }
|
||||
log_info $" ($status) ($binary.name) - ($binary.path)"
|
||||
}
|
||||
|
||||
# Check plugins
|
||||
let plugin_components = get_plugin_components $target_platform $version
|
||||
log_info ""
|
||||
log_info "🔌 Plugin Components:"
|
||||
$plugin_components.binaries | each {|binary|
|
||||
let status = if ($binary.path | path exists) { "✅" } else { "❌" }
|
||||
log_info $" ($status) ($binary.name) - ($binary.path)"
|
||||
}
|
||||
|
||||
# Check configuration templates
|
||||
let config_components = get_config_components $version
|
||||
log_info ""
|
||||
log_info "⚙️ Configuration Components:"
|
||||
$config_components | each {|config|
|
||||
let status = if ($config.path | path exists) { "✅" } else { "❌" }
|
||||
log_info $" ($status) ($config.name) - ($config.path)"
|
||||
}
|
||||
|
||||
# Check installers
|
||||
let installer_components = get_installer_components $target_platform
|
||||
log_info ""
|
||||
log_info "📥 Installer Components:"
|
||||
$installer_components | each {|installer|
|
||||
let status = if ($installer.path | path exists) { "✅" } else { "❌" }
|
||||
log_info $" ($status) ($installer.name) - ($installer.path)"
|
||||
}
|
||||
}
|
||||
|
||||
# Create packages for all supported platforms
|
||||
def create_all_platform_packages [
|
||||
output: string,
|
||||
force: bool,
|
||||
checksums: bool,
|
||||
bootstrap: bool,
|
||||
include_docs: bool,
|
||||
version: string
|
||||
] {
|
||||
log_info "🌍 Creating packages for all supported platforms..."
|
||||
|
||||
let platforms = get_supported_platforms
|
||||
mut successful_packages = []
|
||||
mut failed_packages = []
|
||||
|
||||
for platform in $platforms {
|
||||
log_info $""
|
||||
log_info $"📦 Processing platform: ($platform)"
|
||||
log_info "================================"
|
||||
|
||||
let result = try {
|
||||
create_platform_package $platform $output $force $checksums $bootstrap $include_docs $version
|
||||
"success"
|
||||
} catch {|err|
|
||||
log_error $"Failed to create package for ($platform): ($err.msg)"
|
||||
"failed"
|
||||
}
|
||||
|
||||
if $result == "success" {
|
||||
$successful_packages = ($successful_packages | append $platform)
|
||||
} else {
|
||||
$failed_packages = ($failed_packages | append $platform)
|
||||
}
|
||||
}
|
||||
|
||||
# Summary
|
||||
log_info ""
|
||||
log_info "📊 Package Creation Summary"
|
||||
log_info "========================="
|
||||
log_success $"✅ Successful: ($successful_packages | length) platforms"
|
||||
if ($successful_packages | length) > 0 {
|
||||
$successful_packages | each {|platform| log_success $" - ($platform)"}
|
||||
}
|
||||
|
||||
if ($failed_packages | length) > 0 {
|
||||
log_error $"❌ Failed: ($failed_packages | length) platforms"
|
||||
$failed_packages | each {|platform| log_error $" - ($platform)"}
|
||||
}
|
||||
|
||||
# Create cross-platform checksums if requested
|
||||
if $checksums != null {
|
||||
create_cross_platform_checksums $output
|
||||
}
|
||||
}
|
||||
|
||||
# Create package for a specific platform
|
||||
def create_platform_package [
|
||||
platform: string,
|
||||
output: string,
|
||||
force: bool,
|
||||
checksums: bool,
|
||||
bootstrap: bool,
|
||||
include_docs: bool,
|
||||
version: string
|
||||
] {
|
||||
log_info $"📦 Creating package for platform: ($platform)"
|
||||
|
||||
# Validate platform
|
||||
if not ($platform in (get_supported_platforms)) {
|
||||
log_error $"Unsupported platform: ($platform). Supported: (get_supported_platforms | str join ', ')"
|
||||
return
|
||||
}
|
||||
|
||||
# Setup package directory structure
|
||||
let package_name = $"nushell-full-($version)-($platform)"
|
||||
let package_dir = $"($output)/($package_name)"
|
||||
let archive_extension = get_archive_extension $platform
|
||||
|
||||
# Check if package already exists
|
||||
let archive_path = $"($output)/($package_name)($archive_extension)"
|
||||
if ($archive_path | path exists) and ($force == null) {
|
||||
log_warn $"Package already exists: ($archive_path). Use --force to overwrite."
|
||||
return
|
||||
}
|
||||
|
||||
# Clean and create package directory
|
||||
ensure_dir $output
|
||||
remove_dir $package_dir
|
||||
ensure_dir $package_dir
|
||||
|
||||
# Create package structure
|
||||
ensure_dir $"($package_dir)/bin"
|
||||
ensure_dir $"($package_dir)/config"
|
||||
ensure_dir $"($package_dir)/scripts"
|
||||
if $include_docs != null {
|
||||
ensure_dir $"($package_dir)/docs"
|
||||
}
|
||||
|
||||
# Package components
|
||||
mut components = {}
|
||||
|
||||
# Package nushell binary
|
||||
let nushell_components = package_nushell_components $platform $package_dir $version
|
||||
$components = ($components | merge {nushell: $nushell_components})
|
||||
|
||||
# Package plugins
|
||||
let plugin_components = package_plugin_components $platform $package_dir $version
|
||||
$components = ($components | merge {plugins: $plugin_components})
|
||||
|
||||
# Package configuration
|
||||
let config_components = package_config_components $package_dir $version
|
||||
$components = ($components | merge {configs: $config_components})
|
||||
|
||||
# Package installers
|
||||
let installer_components = package_installer_components $platform $package_dir $bootstrap
|
||||
$components = ($components | merge {installers: $installer_components})
|
||||
|
||||
# Package documentation if requested
|
||||
if $include_docs != null {
|
||||
let docs_components = package_docs_components $package_dir
|
||||
$components = ($components | merge {docs: $docs_components})
|
||||
}
|
||||
|
||||
# Create manifest
|
||||
create_manifest $version $platform $components $"($package_dir)/manifest.json"
|
||||
|
||||
# Create README for package
|
||||
create_package_readme $platform $package_dir $version
|
||||
|
||||
# Create archive
|
||||
create_package_archive $package_dir $archive_path $platform
|
||||
|
||||
# Create checksums if requested
|
||||
if $checksums != null {
|
||||
create_checksums [$archive_path] $output
|
||||
}
|
||||
|
||||
# Clean up temporary directory
|
||||
remove_dir $package_dir
|
||||
|
||||
log_success $"✅ Package created: ($archive_path)"
|
||||
}
|
||||
|
||||
# Package nushell components
|
||||
def package_nushell_components [platform: string, package_dir: string, version: string] {
|
||||
log_info "📦 Packaging nushell components..."
|
||||
|
||||
let components = get_nushell_components $platform $version
|
||||
mut packaged = []
|
||||
|
||||
for binary in $components.binaries {
|
||||
if ($binary.path | path exists) {
|
||||
let dest_path = $"($package_dir)/bin/($binary.name)"
|
||||
if (copy_file $binary.path $dest_path) {
|
||||
$packaged = ($packaged | append $binary.name)
|
||||
}
|
||||
} else {
|
||||
log_warn $"Nushell binary not found: ($binary.path)"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
binaries: $packaged,
|
||||
version: $version,
|
||||
platform: $platform
|
||||
}
|
||||
}
|
||||
|
||||
# Package plugin components
|
||||
def package_plugin_components [platform: string, package_dir: string, version: string] {
|
||||
log_info "📦 Packaging plugin components..."
|
||||
|
||||
let components = get_plugin_components $platform $version
|
||||
mut packaged = []
|
||||
|
||||
for binary in $components.binaries {
|
||||
if ($binary.path | path exists) {
|
||||
let dest_path = $"($package_dir)/bin/($binary.name)"
|
||||
if (copy_file $binary.path $dest_path) {
|
||||
$packaged = ($packaged | append $binary.name)
|
||||
}
|
||||
} else {
|
||||
log_warn $"Plugin binary not found: ($binary.path)"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
binaries: $packaged,
|
||||
count: ($packaged | length)
|
||||
}
|
||||
}
|
||||
|
||||
# Package configuration components
|
||||
def package_config_components [package_dir: string, version: string] {
|
||||
log_info "📦 Packaging configuration components..."
|
||||
|
||||
let components = get_config_components $version
|
||||
mut packaged = []
|
||||
|
||||
for config in $components {
|
||||
if ($config.path | path exists) {
|
||||
let dest_path = $"($package_dir)/config/($config.name)"
|
||||
if (copy_file $config.path $dest_path) {
|
||||
$packaged = ($packaged | append $config.name)
|
||||
}
|
||||
} else {
|
||||
log_warn $"Configuration file not found: ($config.path)"
|
||||
}
|
||||
}
|
||||
|
||||
# Create distribution-specific config
|
||||
create_distribution_config $package_dir $version
|
||||
$packaged = ($packaged | append "distribution_config.toml")
|
||||
|
||||
{
|
||||
files: $packaged,
|
||||
count: ($packaged | length)
|
||||
}
|
||||
}
|
||||
|
||||
# Package installer components
|
||||
def package_installer_components [platform: string, package_dir: string, bootstrap: bool] {
|
||||
log_info "📦 Packaging installer components..."
|
||||
|
||||
let components = get_installer_components $platform
|
||||
mut packaged = []
|
||||
|
||||
for installer in $components {
|
||||
if ($installer.path | path exists) {
|
||||
let dest_path = $"($package_dir)/($installer.name)"
|
||||
if (copy_file $installer.path $dest_path) {
|
||||
# Make installer executable on Unix-like systems
|
||||
if not ($platform | str starts-with "windows") {
|
||||
try { chmod +x $dest_path }
|
||||
}
|
||||
$packaged = ($packaged | append $installer.name)
|
||||
}
|
||||
} else {
|
||||
log_warn $"Installer not found: ($installer.path)"
|
||||
}
|
||||
}
|
||||
|
||||
# Create uninstaller
|
||||
create_uninstaller $platform $package_dir
|
||||
$packaged = ($packaged | append "uninstall.sh")
|
||||
|
||||
{
|
||||
files: $packaged,
|
||||
bootstrap: $bootstrap
|
||||
}
|
||||
}
|
||||
|
||||
# Package documentation components
|
||||
def package_docs_components [package_dir: string] {
|
||||
log_info "📦 Packaging documentation components..."
|
||||
|
||||
let docs = [
|
||||
{name: "README.md", path: "./README.md"},
|
||||
{name: "CLAUDE.md", path: "./CLAUDE.md"},
|
||||
{name: "LICENSE", path: "./LICENSE"}
|
||||
]
|
||||
|
||||
mut packaged = []
|
||||
|
||||
for doc in $docs {
|
||||
if ($doc.path | path exists) {
|
||||
let dest_path = $"($package_dir)/docs/($doc.name)"
|
||||
if (copy_file $doc.path $dest_path) {
|
||||
$packaged = ($packaged | append $doc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
files: $packaged
|
||||
}
|
||||
}
|
||||
|
||||
# Get nushell components for platform
|
||||
def get_nushell_components [platform: string, version: string] {
|
||||
let extension = get_binary_extension $platform
|
||||
let profile_dir = if ($platform | str contains "release") or true { "release" } else { "debug" }
|
||||
|
||||
# Check if platform-specific build exists in distribution
|
||||
let dist_binary = $"./distribution/($platform)/nu($extension)"
|
||||
let workspace_binary = $"./nushell/target/($profile_dir)/nu($extension)"
|
||||
|
||||
let nushell_path = if ($dist_binary | path exists) {
|
||||
$dist_binary
|
||||
} else if ($workspace_binary | path exists) {
|
||||
$workspace_binary
|
||||
} else {
|
||||
$workspace_binary # Will be reported as missing in list
|
||||
}
|
||||
|
||||
{
|
||||
binaries: [
|
||||
{
|
||||
name: $"nu($extension)",
|
||||
path: $nushell_path,
|
||||
component: "nushell"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# Get plugin components for platform
|
||||
def get_plugin_components [platform: string, version: string] {
|
||||
let extension = get_binary_extension $platform
|
||||
let dist_dir = $"./distribution/($platform)"
|
||||
|
||||
# Get plugins from distribution directory if it exists
|
||||
let plugin_binaries = if ($dist_dir | path exists) {
|
||||
glob $"($dist_dir)/nu_plugin_*($extension)"
|
||||
| each {|path|
|
||||
let name = ($path | path basename)
|
||||
{
|
||||
name: $name,
|
||||
path: $path,
|
||||
component: "plugin"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# Fallback to individual plugin target directories
|
||||
glob "nu_plugin_*"
|
||||
| where ($it | path type) == "dir"
|
||||
| each {|plugin_dir|
|
||||
let plugin_name = ($plugin_dir | path basename)
|
||||
let binary_name = $"($plugin_name)($extension)"
|
||||
mut binary_path = $"($plugin_dir)/target/release/($binary_name)"
|
||||
|
||||
if not ($binary_path | path exists) {
|
||||
# Try debug build
|
||||
$binary_path = $"($plugin_dir)/target/debug/($binary_name)"
|
||||
}
|
||||
|
||||
{
|
||||
name: $binary_name,
|
||||
path: $binary_path,
|
||||
component: "plugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
binaries: $plugin_binaries
|
||||
}
|
||||
}
|
||||
|
||||
# Get configuration components
|
||||
def get_config_components [version: string] {
|
||||
[
|
||||
{name: "default_config.nu", path: "./scripts/templates/default_config.nu"},
|
||||
{name: "default_env.nu", path: "./scripts/templates/default_env.nu"}
|
||||
]
|
||||
}
|
||||
|
||||
# Get installer components
|
||||
def get_installer_components [platform: string] {
|
||||
if ($platform | str starts-with "windows") {
|
||||
[
|
||||
{name: "install.ps1", path: "./scripts/templates/install.ps1"}
|
||||
]
|
||||
} else {
|
||||
[
|
||||
{name: "install.sh", path: "./scripts/templates/install.sh"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# Create distribution-specific configuration
|
||||
def create_distribution_config [package_dir: string, version: string] {
|
||||
let config = {
|
||||
distribution: {
|
||||
version: $version,
|
||||
created_at: (date now | format date "%Y-%m-%d %H:%M:%S UTC"),
|
||||
full_distribution: true
|
||||
}
|
||||
}
|
||||
|
||||
$config | to toml | save -f $"($package_dir)/config/distribution_config.toml"
|
||||
}
|
||||
|
||||
# Create package README
|
||||
def create_package_readme [platform: string, package_dir: string, version: string] {
|
||||
let readme_content = [
|
||||
$"# Nushell Full Distribution v($version)",
|
||||
"",
|
||||
"This package contains a complete Nushell distribution with all plugins and configurations.",
|
||||
"",
|
||||
"## Platform",
|
||||
$"- Target: ($platform)",
|
||||
$"- Created: (date now | format date '%Y-%m-%d %H:%M:%S UTC')",
|
||||
"",
|
||||
"## Contents",
|
||||
"",
|
||||
"### Binaries (bin/)",
|
||||
"- nu - Nushell shell binary",
|
||||
"- nu_plugin_* - Plugin binaries",
|
||||
"",
|
||||
"### Configuration (config/)",
|
||||
"- default_config.nu - Default nushell configuration",
|
||||
"- default_env.nu - Default environment setup",
|
||||
"- distribution_config.toml - Distribution metadata",
|
||||
"",
|
||||
"### Installation",
|
||||
"Run the installer for your platform:",
|
||||
"- Unix/Linux/macOS: ./install.sh",
|
||||
"- Windows: ./install.ps1",
|
||||
"",
|
||||
"### Manual Installation",
|
||||
"1. Copy binaries from bin/ to a directory in your PATH",
|
||||
"2. Copy configuration files from config/ to your nushell config directory",
|
||||
"3. Register plugins: nu -c 'plugin add path/to/plugin'",
|
||||
"",
|
||||
"### Verification",
|
||||
"After installation, verify with:",
|
||||
"nu --version",
|
||||
"",
|
||||
"### Uninstallation",
|
||||
"Run: ./uninstall.sh (or ./uninstall.ps1 on Windows)",
|
||||
"",
|
||||
"## Documentation",
|
||||
"See docs/ directory for additional documentation.",
|
||||
"",
|
||||
"## Support",
|
||||
"For issues and support, visit: https://github.com/nushell/nushell"
|
||||
] | str join "\n"
|
||||
|
||||
$readme_content | save -f $"($package_dir)/README.md"
|
||||
}
|
||||
|
||||
# Create uninstaller script
|
||||
def create_uninstaller [platform: string, package_dir: string] {
|
||||
let uninstaller_path = if ($platform | str starts-with "windows") {
|
||||
"./scripts/templates/uninstall.ps1"
|
||||
} else {
|
||||
"./scripts/templates/uninstall.sh"
|
||||
}
|
||||
|
||||
if ($uninstaller_path | path exists) {
|
||||
let dest = if ($platform | str starts-with "windows") {
|
||||
$"($package_dir)/uninstall.ps1"
|
||||
} else {
|
||||
$"($package_dir)/uninstall.sh"
|
||||
}
|
||||
copy_file $uninstaller_path $dest
|
||||
}
|
||||
}
|
||||
|
||||
# Create package archive
|
||||
def create_package_archive [package_dir: string, archive_path: string, platform: string] {
|
||||
log_info $"📦 Creating archive: ($archive_path)"
|
||||
|
||||
let package_name = ($package_dir | path basename)
|
||||
let archive_name = ($archive_path | path basename)
|
||||
let work_dir = ($package_dir | path dirname)
|
||||
|
||||
if ($platform | str starts-with "windows") {
|
||||
# Create ZIP archive for Windows
|
||||
cd $work_dir
|
||||
try {
|
||||
run-external "zip" "-r" $archive_name $package_name
|
||||
} catch {
|
||||
# Fallback to tar if zip not available
|
||||
run-external "tar" "-czf" $archive_name $package_name
|
||||
}
|
||||
} else {
|
||||
# Create tar.gz archive for Unix-like systems
|
||||
cd $work_dir
|
||||
run-external "tar" "-czf" $archive_name $package_name
|
||||
}
|
||||
|
||||
# Move archive to final location if needed
|
||||
let temp_archive = $"($work_dir)/($archive_name)"
|
||||
if ($temp_archive | path exists) and ($temp_archive != $archive_path) {
|
||||
mv $temp_archive $archive_path
|
||||
}
|
||||
|
||||
if ($archive_path | path exists) {
|
||||
let size = (ls $archive_path | get 0.size)
|
||||
log_success $"✅ Archive created: ($archive_path) (($size))"
|
||||
} else {
|
||||
log_error $"❌ Failed to create archive: ($archive_path)"
|
||||
}
|
||||
}
|
||||
|
||||
# Create cross-platform checksums
|
||||
def create_cross_platform_checksums [output: string] {
|
||||
log_info "🔐 Creating cross-platform checksums..."
|
||||
|
||||
let archives = glob $"($output)/*.tar.gz" | append (glob $"($output)/*.zip")
|
||||
if ($archives | length) > 0 {
|
||||
create_checksums $archives $output
|
||||
log_success $"✅ Cross-platform checksums created in ($output)/checksums.txt"
|
||||
} else {
|
||||
log_warn "No archives found for checksum generation"
|
||||
}
|
||||
}
|
503
scripts/install_full_nushell.nu
Executable file
503
scripts/install_full_nushell.nu
Executable file
@ -0,0 +1,503 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
# Install Full Nushell Distribution Script
|
||||
# Advanced nu-based installer called after bootstrap gets nushell running
|
||||
# Provides plugin selection, configuration, and integration with existing nu environment
|
||||
|
||||
use lib/common_lib.nu [
|
||||
log_info, log_error, log_success, log_warn, log_debug,
|
||||
get_current_platform, ensure_dir, copy_file
|
||||
]
|
||||
|
||||
def main [
|
||||
--install-dir (-i): string = "" # Installation directory override
|
||||
--config-dir (-c): string = "" # Configuration directory override
|
||||
--plugins (-p): list<string> = [] # Specific plugins to install
|
||||
--all-plugins (-a): bool = false # Install all available plugins
|
||||
--no-register: bool = false # Don't register plugins with nushell
|
||||
--register-only: bool = false # Only register plugins, don't copy binaries
|
||||
--verify: bool = false # Verify installation after completion
|
||||
--test: bool = false # Run in test mode (dry run)
|
||||
--config-backup: bool = true # Backup existing configuration
|
||||
--interactive (-I): bool = false # Interactive mode for plugin selection
|
||||
--update (-u): bool = false # Update existing installation
|
||||
] {
|
||||
log_info "🚀 Nushell Full Distribution Advanced Installer"
|
||||
log_info "=============================================="
|
||||
|
||||
if $test {
|
||||
log_warn "🧪 Running in test mode (dry run)"
|
||||
}
|
||||
|
||||
# Validate environment
|
||||
validate_installation_environment $test
|
||||
|
||||
# Get installation paths
|
||||
let paths = get_installation_paths $install_dir $config_dir
|
||||
|
||||
log_info ""
|
||||
log_info "📍 Installation Configuration:"
|
||||
log_info $" Binary directory: ($paths.bin_dir)"
|
||||
log_info $" Config directory: ($paths.config_dir)"
|
||||
log_info $" Plugins directory: ($paths.plugins_dir)"
|
||||
|
||||
if $register_only {
|
||||
log_info " Mode: Register existing binaries only"
|
||||
} else if $no_register {
|
||||
log_info " Mode: Copy binaries without registration"
|
||||
} else {
|
||||
log_info " Mode: Copy binaries and register plugins"
|
||||
}
|
||||
|
||||
# Check if this is an update
|
||||
let is_update = $update or (check_existing_installation $paths)
|
||||
if $is_update {
|
||||
log_info " Type: Update existing installation"
|
||||
} else {
|
||||
log_info " Type: New installation"
|
||||
}
|
||||
|
||||
# Plugin selection
|
||||
let selected_plugins = if $interactive {
|
||||
select_plugins_interactive
|
||||
} else if ($plugins | length) > 0 {
|
||||
$plugins
|
||||
} else if $all_plugins {
|
||||
get_available_plugins | get name
|
||||
} else {
|
||||
get_recommended_plugins
|
||||
}
|
||||
|
||||
log_info ""
|
||||
log_info $"🔌 Selected plugins: (($selected_plugins | length))"
|
||||
$selected_plugins | each {|plugin| log_info $" - ($plugin)"}
|
||||
|
||||
if not $test {
|
||||
# Backup existing configuration if requested
|
||||
if $config_backup and $is_update {
|
||||
backup_existing_config $paths.config_dir
|
||||
}
|
||||
|
||||
# Create installation directories
|
||||
setup_installation_directories $paths
|
||||
|
||||
# Install binaries
|
||||
if not $register_only {
|
||||
install_nushell_binary $paths $test
|
||||
install_plugin_binaries $selected_plugins $paths $test
|
||||
}
|
||||
|
||||
# Install configuration
|
||||
install_configuration $paths $is_update $test
|
||||
|
||||
# Register plugins
|
||||
if not $no_register {
|
||||
register_plugins_with_nushell $selected_plugins $paths $test
|
||||
}
|
||||
|
||||
# Verify installation if requested
|
||||
if $verify {
|
||||
verify_full_installation $paths $selected_plugins
|
||||
}
|
||||
|
||||
log_success "✅ Installation completed successfully!"
|
||||
print_installation_summary $paths $selected_plugins $is_update
|
||||
} else {
|
||||
log_info "🧪 Test mode completed - no changes made"
|
||||
}
|
||||
}
|
||||
|
||||
# Validate installation environment
|
||||
def validate_installation_environment [test: bool] {
|
||||
log_debug "Validating installation environment..."
|
||||
|
||||
# Check if we're running in nushell
|
||||
if ($env.NU_VERSION? | is-empty) {
|
||||
log_error "This installer must be run with nushell"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if we have write permissions to likely installation directories
|
||||
let test_dirs = [
|
||||
"~/.local/bin",
|
||||
"~/.config/nushell",
|
||||
"/usr/local/bin"
|
||||
]
|
||||
|
||||
for dir in $test_dirs {
|
||||
let expanded_dir = ($dir | path expand)
|
||||
if ($expanded_dir | path exists) and not $test {
|
||||
try {
|
||||
touch $"($expanded_dir)/.write_test"
|
||||
rm $"($expanded_dir)/.write_test"
|
||||
log_debug $"✅ Write permission confirmed: ($expanded_dir)"
|
||||
break
|
||||
} catch {
|
||||
log_debug $"❌ No write permission: ($expanded_dir)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_success "Environment validation passed"
|
||||
}
|
||||
|
||||
# Get installation paths
|
||||
def get_installation_paths [install_dir_override: string, config_dir_override: string] {
|
||||
let home_dir = ($env.HOME | path expand)
|
||||
|
||||
# Determine binary installation directory
|
||||
let bin_dir = if ($install_dir_override | str length) > 0 {
|
||||
($install_dir_override | path expand)
|
||||
} else if (which nu | length) > 0 {
|
||||
# Use directory where current nu is installed
|
||||
(which nu | get 0.path | path dirname)
|
||||
} else {
|
||||
# Default to ~/.local/bin
|
||||
$"($home_dir)/.local/bin"
|
||||
}
|
||||
|
||||
# Determine configuration directory
|
||||
let config_dir = if ($config_dir_override | str length) > 0 {
|
||||
($config_dir_override | path expand)
|
||||
} else if ($env.XDG_CONFIG_HOME? | is-not-empty) {
|
||||
$"($env.XDG_CONFIG_HOME)/nushell"
|
||||
} else {
|
||||
$"($home_dir)/.config/nushell"
|
||||
}
|
||||
|
||||
# Plugin directory (for registration)
|
||||
let plugins_dir = $"($config_dir)/plugins"
|
||||
|
||||
{
|
||||
bin_dir: $bin_dir,
|
||||
config_dir: $config_dir,
|
||||
plugins_dir: $plugins_dir,
|
||||
home_dir: $home_dir
|
||||
}
|
||||
}
|
||||
|
||||
# Check if installation already exists
|
||||
def check_existing_installation [paths: record] -> bool {
|
||||
let nu_exists = ($"($paths.bin_dir)/nu" | path exists)
|
||||
let config_exists = ($"($paths.config_dir)/config.nu" | path exists)
|
||||
|
||||
$nu_exists or $config_exists
|
||||
}
|
||||
|
||||
# Get available plugins from current directory
|
||||
def get_available_plugins [] {
|
||||
let bin_files = if ("./bin" | path exists) {
|
||||
ls ./bin | where name =~ "nu_plugin_" | get name | each {|path| ($path | path basename | str replace ".exe" "")}
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
|
||||
$bin_files | each {|name| {name: $name, path: $"./bin/($name)"}}
|
||||
}
|
||||
|
||||
# Get recommended plugins for default installation
|
||||
def get_recommended_plugins [] {
|
||||
let available = (get_available_plugins | get name)
|
||||
let recommended = [
|
||||
"nu_plugin_clipboard",
|
||||
"nu_plugin_hashes",
|
||||
"nu_plugin_desktop_notifications",
|
||||
"nu_plugin_highlight"
|
||||
]
|
||||
|
||||
# Return intersection of recommended and available
|
||||
$recommended | where $it in $available
|
||||
}
|
||||
|
||||
# Interactive plugin selection
|
||||
def select_plugins_interactive [] {
|
||||
log_info "🔌 Plugin Selection"
|
||||
log_info "=================="
|
||||
|
||||
let available_plugins = get_available_plugins
|
||||
if ($available_plugins | length) == 0 {
|
||||
log_warn "No plugins found in package"
|
||||
return []
|
||||
}
|
||||
|
||||
log_info "Available plugins:"
|
||||
$available_plugins | enumerate | each {|item|
|
||||
log_info $" (($item.index + 1)). ($item.item.name)"
|
||||
}
|
||||
|
||||
log_info ""
|
||||
log_info "Selection options:"
|
||||
log_info " - Enter numbers separated by spaces (e.g., '1 3 4')"
|
||||
log_info " - Enter 'all' for all plugins"
|
||||
log_info " - Enter 'recommended' for recommended plugins"
|
||||
log_info " - Press Enter for recommended plugins"
|
||||
|
||||
let selection = (input "Your selection: ")
|
||||
|
||||
match $selection {
|
||||
"all" => {$available_plugins | get name},
|
||||
"recommended" | "" => {get_recommended_plugins},
|
||||
_ => {
|
||||
let indices = ($selection | split row " " | each {|x| ($x | into int) - 1})
|
||||
$indices | each {|i| $available_plugins | get $i | get name}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Setup installation directories
|
||||
def setup_installation_directories [paths: record] {
|
||||
log_info "📁 Setting up installation directories..."
|
||||
|
||||
ensure_dir $paths.bin_dir
|
||||
ensure_dir $paths.config_dir
|
||||
ensure_dir $paths.plugins_dir
|
||||
|
||||
log_success "✅ Installation directories ready"
|
||||
}
|
||||
|
||||
# Install nushell binary
|
||||
def install_nushell_binary [paths: record, test: bool] {
|
||||
log_info "🚀 Installing nushell binary..."
|
||||
|
||||
let current_platform = get_current_platform
|
||||
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
|
||||
let nu_binary = $"./bin/nu($binary_extension)"
|
||||
|
||||
if not ($nu_binary | path exists) {
|
||||
log_error $"Nushell binary not found: ($nu_binary)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
let dest_path = $"($paths.bin_dir)/nu($binary_extension)"
|
||||
|
||||
if not $test {
|
||||
copy_file $nu_binary $dest_path
|
||||
|
||||
# Make executable on Unix-like systems
|
||||
if not ($current_platform | str starts-with "windows") {
|
||||
chmod +x $dest_path
|
||||
}
|
||||
}
|
||||
|
||||
log_success $"✅ Nushell binary installed: ($dest_path)"
|
||||
}
|
||||
|
||||
# Install plugin binaries
|
||||
def install_plugin_binaries [plugins: list<string>, paths: record, test: bool] {
|
||||
log_info $"🔌 Installing plugin binaries... (($plugins | length))"
|
||||
|
||||
let current_platform = get_current_platform
|
||||
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
|
||||
|
||||
for plugin in $plugins {
|
||||
let plugin_binary = $"./bin/($plugin)($binary_extension)"
|
||||
let dest_path = $"($paths.bin_dir)/($plugin)($binary_extension)"
|
||||
|
||||
if ($plugin_binary | path exists) {
|
||||
if not $test {
|
||||
copy_file $plugin_binary $dest_path
|
||||
|
||||
# Make executable on Unix-like systems
|
||||
if not ($current_platform | str starts-with "windows") {
|
||||
chmod +x $dest_path
|
||||
}
|
||||
}
|
||||
log_success $"✅ Installed plugin: ($plugin)"
|
||||
} else {
|
||||
log_warn $"⚠️ Plugin binary not found: ($plugin_binary)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Install configuration files
|
||||
def install_configuration [paths: record, is_update: bool, test: bool] {
|
||||
log_info "⚙️ Installing configuration files..."
|
||||
|
||||
let config_files = [
|
||||
{src: "./config/default_config.nu", dest: "config.nu", required: true},
|
||||
{src: "./config/default_env.nu", dest: "env.nu", required: true},
|
||||
{src: "./config/distribution_config.toml", dest: "distribution_config.toml", required: false}
|
||||
]
|
||||
|
||||
for file in $config_files {
|
||||
let src_path = $file.src
|
||||
let dest_path = $"($paths.config_dir)/($file.dest)"
|
||||
|
||||
if ($src_path | path exists) {
|
||||
if ($dest_path | path exists) and not $is_update {
|
||||
log_info $"⚠️ Configuration file exists, creating backup: ($file.dest)"
|
||||
if not $test {
|
||||
cp $dest_path $"($dest_path).backup.(date now | format date '%Y%m%d_%H%M%S')"
|
||||
}
|
||||
}
|
||||
|
||||
if not $test {
|
||||
copy_file $src_path $dest_path
|
||||
}
|
||||
log_success $"✅ Installed config: ($file.dest)"
|
||||
} else if $file.required {
|
||||
log_error $"Required configuration file not found: ($src_path)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Register plugins with nushell
|
||||
def register_plugins_with_nushell [plugins: list<string>, paths: record, test: bool] {
|
||||
log_info $"📝 Registering plugins with nushell... (($plugins | length))"
|
||||
|
||||
let current_platform = get_current_platform
|
||||
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
|
||||
|
||||
let registration_commands = []
|
||||
|
||||
for plugin in $plugins {
|
||||
let plugin_path = $"($paths.bin_dir)/($plugin)($binary_extension)"
|
||||
|
||||
if ($plugin_path | path exists) or $test {
|
||||
let register_cmd = $"plugin add ($plugin_path)"
|
||||
$registration_commands = ($registration_commands | append $register_cmd)
|
||||
log_success $"✅ Will register: ($plugin)"
|
||||
} else {
|
||||
log_warn $"⚠️ Cannot register plugin (binary not found): ($plugin)"
|
||||
}
|
||||
}
|
||||
|
||||
if not $test and ($registration_commands | length) > 0 {
|
||||
# Create a temporary script to register plugins
|
||||
let register_script = $"($paths.config_dir)/register_plugins_temp.nu"
|
||||
|
||||
let script_content = ([
|
||||
"# Temporary plugin registration script",
|
||||
"# Generated by install_full_nushell.nu",
|
||||
""
|
||||
] | append $registration_commands | append [
|
||||
"",
|
||||
"print \"✅ All plugins registered successfully\"",
|
||||
$"rm ($register_script)"
|
||||
] | str join "\n")
|
||||
|
||||
$script_content | save $register_script
|
||||
|
||||
log_info "Running plugin registration..."
|
||||
try {
|
||||
nu $register_script
|
||||
} catch {|err|
|
||||
log_error $"Plugin registration failed: ($err.msg)"
|
||||
log_info "You can register plugins manually later with:"
|
||||
$registration_commands | each {|cmd| log_info $" nu -c '($cmd)'"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Backup existing configuration
|
||||
def backup_existing_config [config_dir: string] {
|
||||
log_info "💾 Backing up existing configuration..."
|
||||
|
||||
let backup_dir = $"($config_dir)_backup_(date now | format date '%Y%m%d_%H%M%S')"
|
||||
|
||||
if ($config_dir | path exists) {
|
||||
cp -r $config_dir $backup_dir
|
||||
log_success $"✅ Configuration backed up to: ($backup_dir)"
|
||||
} else {
|
||||
log_info "No existing configuration to backup"
|
||||
}
|
||||
}
|
||||
|
||||
# Verify installation
|
||||
def verify_full_installation [paths: record, plugins: list<string>] {
|
||||
log_info "🔍 Verifying installation..."
|
||||
|
||||
let verification_results = {}
|
||||
|
||||
# Verify nushell binary
|
||||
let current_platform = get_current_platform
|
||||
let binary_extension = if ($current_platform | str starts-with "windows") { ".exe" } else { "" }
|
||||
let nu_path = $"($paths.bin_dir)/nu($binary_extension)"
|
||||
|
||||
let nu_check = if ($nu_path | path exists) {
|
||||
try {
|
||||
let version = (run-external $nu_path "--version")
|
||||
{status: "ok", message: $"Nushell installed: ($version)"}
|
||||
} catch {|err|
|
||||
{status: "error", message: $"Nushell binary exists but not executable: ($err.msg)"}
|
||||
}
|
||||
} else {
|
||||
{status: "error", message: "Nushell binary not found"}
|
||||
}
|
||||
|
||||
$verification_results = ($verification_results | insert "nushell" $nu_check)
|
||||
|
||||
# Verify plugins
|
||||
for plugin in $plugins {
|
||||
let plugin_path = $"($paths.bin_dir)/($plugin)($binary_extension)"
|
||||
let plugin_check = if ($plugin_path | path exists) {
|
||||
{status: "ok", message: $"Plugin binary installed: ($plugin)"}
|
||||
} else {
|
||||
{status: "error", message: $"Plugin binary not found: ($plugin)"}
|
||||
}
|
||||
$verification_results = ($verification_results | insert $plugin $plugin_check)
|
||||
}
|
||||
|
||||
# Verify configuration
|
||||
let config_files = ["config.nu", "env.nu"]
|
||||
for config_file in $config_files {
|
||||
let config_path = $"($paths.config_dir)/($config_file)"
|
||||
let config_check = if ($config_path | path exists) {
|
||||
{status: "ok", message: $"Configuration installed: ($config_file)"}
|
||||
} else {
|
||||
{status: "error", message: $"Configuration not found: ($config_file)"}
|
||||
}
|
||||
$verification_results = ($verification_results | insert $config_file $config_check)
|
||||
}
|
||||
|
||||
# Display verification results
|
||||
log_info "📊 Verification Results:"
|
||||
log_info "======================"
|
||||
|
||||
let all_ok = true
|
||||
for item in ($verification_results | items) {
|
||||
let status_icon = if $item.value.status == "ok" { "✅" } else { "❌"; $all_ok = false }
|
||||
log_info $" ($status_icon) ($item.key): ($item.value.message)"
|
||||
}
|
||||
|
||||
if $all_ok {
|
||||
log_success "🎉 All verification checks passed!"
|
||||
} else {
|
||||
log_warn "⚠️ Some verification checks failed"
|
||||
}
|
||||
}
|
||||
|
||||
# Print installation summary
|
||||
def print_installation_summary [paths: record, plugins: list<string>, is_update: bool] {
|
||||
let action = if $is_update { "Updated" } else { "Installed" }
|
||||
|
||||
log_info ""
|
||||
log_info $"📊 Installation Summary"
|
||||
log_info "======================"
|
||||
log_info $"Action: ($action) Nushell Full Distribution"
|
||||
log_info $"Binary directory: ($paths.bin_dir)"
|
||||
log_info $"Config directory: ($paths.config_dir)"
|
||||
log_info $"Plugins installed: (($plugins | length))"
|
||||
|
||||
if ($plugins | length) > 0 {
|
||||
$plugins | each {|plugin| log_info $" - ($plugin)"}
|
||||
}
|
||||
|
||||
log_info ""
|
||||
log_info "🚀 Next Steps:"
|
||||
log_info "============="
|
||||
log_info $"1. Add ($paths.bin_dir) to your PATH if not already present"
|
||||
log_info "2. Restart your terminal or run: source ~/.bashrc (or equivalent)"
|
||||
log_info "3. Test installation: nu --version"
|
||||
log_info "4. Start using nushell: nu"
|
||||
|
||||
# Check if PATH update is needed
|
||||
let path_entries = ($env.PATH | split row ":")
|
||||
if not ($paths.bin_dir in $path_entries) {
|
||||
log_info ""
|
||||
log_warn "⚠️ PATH Update Required"
|
||||
log_info $"Add this line to your shell profile (~/.bashrc, ~/.zshrc, etc.):"
|
||||
log_info $"export PATH=\"($paths.bin_dir):$PATH\""
|
||||
}
|
||||
}
|
285
scripts/lib/common_lib.nu
Normal file
285
scripts/lib/common_lib.nu
Normal file
@ -0,0 +1,285 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
# Common Library for Nushell Plugins Distribution Scripts
|
||||
# Provides shared utilities, logging, and validation functions
|
||||
|
||||
# Logging functions with consistent formatting
|
||||
export def log_info [message: string] {
|
||||
print $"(ansi blue)ℹ️ ($message)(ansi reset)"
|
||||
}
|
||||
|
||||
export def log_success [message: string] {
|
||||
print $"(ansi green)✅ ($message)(ansi reset)"
|
||||
}
|
||||
|
||||
export def log_warn [message: string] {
|
||||
print $"(ansi yellow)⚠️ ($message)(ansi reset)"
|
||||
}
|
||||
|
||||
export def log_error [message: string] {
|
||||
print $"(ansi red)❌ ($message)(ansi reset)"
|
||||
}
|
||||
|
||||
export def log_debug [message: string] {
|
||||
if ($env.DEBUG? | default false) {
|
||||
print $"(ansi dim)🐛 DEBUG: ($message)(ansi reset)"
|
||||
}
|
||||
}
|
||||
|
||||
# Validate nushell version consistency between system and workspace
|
||||
export def validate_nushell_version [] {
|
||||
log_debug "Validating nushell version consistency..."
|
||||
|
||||
# Check if nushell submodule exists
|
||||
let nushell_dir = "./nushell"
|
||||
if not ($nushell_dir | path exists) {
|
||||
log_error $"Nushell submodule not found at ($nushell_dir)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Get system nu version
|
||||
let system_version = try {
|
||||
(nu --version | str replace "nushell " "" | str replace " .*" "")
|
||||
} catch {
|
||||
log_error "Could not determine system nushell version"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Get workspace nu version from Cargo.toml
|
||||
let cargo_toml_path = $"($nushell_dir)/Cargo.toml"
|
||||
let workspace_version = try {
|
||||
(open $cargo_toml_path | get package.version)
|
||||
} catch {
|
||||
log_error $"Could not read package version from ($cargo_toml_path)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if $system_version != $workspace_version {
|
||||
log_error $"Version mismatch: system nu ($system_version) != workspace nu ($workspace_version)"
|
||||
log_info "Run 'just fix-nushell' to resolve version inconsistencies"
|
||||
exit 1
|
||||
}
|
||||
|
||||
log_success $"Nushell version validation passed: ($system_version)"
|
||||
}
|
||||
|
||||
# Get current platform identifier
|
||||
export def get_current_platform [] {
|
||||
let os = (sys host | get name)
|
||||
let arch = (run-external "uname" "-m" | str trim)
|
||||
|
||||
let platform_name = match $os {
|
||||
"Linux" => "linux",
|
||||
"Darwin" => "darwin",
|
||||
"Windows" => "windows",
|
||||
_ => ($os | str downcase)
|
||||
}
|
||||
|
||||
let arch_name = match $arch {
|
||||
"x86_64" => "x86_64",
|
||||
"aarch64" => "arm64",
|
||||
"arm64" => "arm64",
|
||||
_ => $arch
|
||||
}
|
||||
|
||||
$"($platform_name)-($arch_name)"
|
||||
}
|
||||
|
||||
# Get all supported platforms
|
||||
export def get_supported_platforms [] {
|
||||
[
|
||||
"linux-x86_64",
|
||||
"linux-arm64",
|
||||
"darwin-x86_64",
|
||||
"darwin-arm64",
|
||||
"windows-x86_64"
|
||||
]
|
||||
}
|
||||
|
||||
# Get binary extension for platform
|
||||
export def get_binary_extension [platform: string] {
|
||||
if ($platform | str starts-with "windows") {
|
||||
".exe"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
# Get archive extension for platform
|
||||
export def get_archive_extension [platform: string] {
|
||||
if ($platform | str starts-with "windows") {
|
||||
".zip"
|
||||
} else {
|
||||
".tar.gz"
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure directory exists
|
||||
export def ensure_dir [path: string] {
|
||||
if not ($path | path exists) {
|
||||
mkdir $path
|
||||
log_info $"Created directory: ($path)"
|
||||
}
|
||||
}
|
||||
|
||||
# Remove directory if it exists
|
||||
export def remove_dir [path: string] {
|
||||
if ($path | path exists) {
|
||||
rm -rf $path
|
||||
log_info $"Removed directory: ($path)"
|
||||
}
|
||||
}
|
||||
|
||||
# Copy file with logging
|
||||
export def copy_file [src: string, dest: string] {
|
||||
if not ($src | path exists) {
|
||||
log_error $"Source file does not exist: ($src)"
|
||||
return false
|
||||
}
|
||||
|
||||
let dest_dir = ($dest | path dirname)
|
||||
ensure_dir $dest_dir
|
||||
|
||||
cp $src $dest
|
||||
log_debug $"Copied ($src) -> ($dest)"
|
||||
true
|
||||
}
|
||||
|
||||
# Create checksums for files
|
||||
export def create_checksums [files: list<string>, output_dir: string] {
|
||||
let checksum_file = $"($output_dir)/checksums.txt"
|
||||
|
||||
let checksums = $files | each {|file|
|
||||
if ($file | path exists) {
|
||||
let hash = (open $file --raw | hash sha256)
|
||||
let filename = ($file | path basename)
|
||||
$"($hash) ($filename)"
|
||||
}
|
||||
} | compact
|
||||
|
||||
$checksums | save -f $checksum_file
|
||||
log_success $"Created checksums file: ($checksum_file)"
|
||||
}
|
||||
|
||||
# Detect available plugin directories
|
||||
export def get_plugin_directories [] {
|
||||
glob "nu_plugin_*" | where {|it| ($it | path exists) and (($it | path type) == "dir")}
|
||||
}
|
||||
|
||||
# Get plugin name from directory
|
||||
export def get_plugin_name [dir: string] {
|
||||
$dir | path basename
|
||||
}
|
||||
|
||||
# Check if path is a plugin directory
|
||||
export def is_plugin_directory [path: string] {
|
||||
($path | path basename | str starts-with "nu_plugin_") and ($path | path type) == "dir"
|
||||
}
|
||||
|
||||
# Get version from current workspace
|
||||
export def get_workspace_version [] {
|
||||
let version_from_git = try {
|
||||
(git describe --tags --abbrev=0 2>/dev/null | str replace "^v" "")
|
||||
} catch {
|
||||
""
|
||||
}
|
||||
|
||||
if ($version_from_git | str length) > 0 {
|
||||
$version_from_git
|
||||
} else {
|
||||
# Fallback to version from nushell Cargo.toml
|
||||
try {
|
||||
(open "./nushell/Cargo.toml" | get package.version)
|
||||
} catch {
|
||||
"0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create manifest for distribution
|
||||
export def create_manifest [
|
||||
version: string,
|
||||
platform: string,
|
||||
components: record,
|
||||
output_file: string
|
||||
] {
|
||||
let manifest = {
|
||||
version: $version,
|
||||
platform: $platform,
|
||||
created_at: (date now | format date "%Y-%m-%d %H:%M:%S UTC"),
|
||||
components: $components,
|
||||
nushell_version: (try { (nu --version) } catch { "unknown" })
|
||||
}
|
||||
|
||||
$manifest | to json | save -f $output_file
|
||||
log_success $"Created manifest: ($output_file)"
|
||||
}
|
||||
|
||||
# Parse command line flags into structured data
|
||||
export def parse_flags [args: list] {
|
||||
mut flags = {}
|
||||
|
||||
mut i = 0
|
||||
while $i < ($args | length) {
|
||||
let arg = ($args | get $i)
|
||||
|
||||
if ($arg | str starts-with "--") {
|
||||
let flag_name = ($arg | str replace "--" "")
|
||||
|
||||
# Check if next arg is a value or another flag
|
||||
let next_i = ($i + 1)
|
||||
if $next_i < ($args | length) {
|
||||
let next_arg = ($args | get $next_i)
|
||||
if not ($next_arg | str starts-with "--") {
|
||||
$flags = ($flags | insert $flag_name $next_arg)
|
||||
$i = ($i + 2)
|
||||
} else {
|
||||
$flags = ($flags | insert $flag_name true)
|
||||
$i = ($i + 1)
|
||||
}
|
||||
} else {
|
||||
$flags = ($flags | insert $flag_name true)
|
||||
$i = ($i + 1)
|
||||
}
|
||||
} else {
|
||||
$i = ($i + 1)
|
||||
}
|
||||
}
|
||||
|
||||
$flags
|
||||
}
|
||||
|
||||
# Execute command with error handling
|
||||
export def exec_with_error [command: string] {
|
||||
log_debug $"Executing: ($command)"
|
||||
|
||||
let result = try {
|
||||
(bash -c $command)
|
||||
} catch {|err|
|
||||
log_error $"Command failed: ($command)"
|
||||
log_error $"Error: ($err.msg)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$result
|
||||
}
|
||||
|
||||
# Check if binary exists and is executable
|
||||
export def check_binary [path: string] {
|
||||
if not ($path | path exists) {
|
||||
false
|
||||
} else if ($path | path type) != "file" {
|
||||
false
|
||||
} else {
|
||||
# On Unix-like systems, check if executable
|
||||
if (sys host | get name) != "Windows" {
|
||||
try {
|
||||
(ls -l $path | get 0.mode | str contains "x")
|
||||
} catch {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
1102
scripts/templates/default_config.nu
Normal file
1102
scripts/templates/default_config.nu
Normal file
File diff suppressed because it is too large
Load Diff
317
scripts/templates/default_env.nu
Normal file
317
scripts/templates/default_env.nu
Normal file
@ -0,0 +1,317 @@
|
||||
# Nushell Environment Configuration Template
|
||||
# Default environment setup for Nushell + Plugins distribution
|
||||
# Created by nushell-plugins full distribution system
|
||||
|
||||
# =============================================================================
|
||||
# CORE ENVIRONMENT VARIABLES
|
||||
# =============================================================================
|
||||
|
||||
# Default editor and browser
|
||||
$env.EDITOR = (
|
||||
if $nu.os-info.name == "windows" {
|
||||
"notepad.exe"
|
||||
} else if (which code | is-not-empty) {
|
||||
"code"
|
||||
} else if (which nano | is-not-empty) {
|
||||
"nano"
|
||||
} else if (which vim | is-not-empty) {
|
||||
"vim"
|
||||
} else {
|
||||
"vi"
|
||||
}
|
||||
)
|
||||
|
||||
$env.BROWSER = (
|
||||
if $nu.os-info.name == "windows" {
|
||||
"msedge.exe"
|
||||
} else if $nu.os-info.name == "macos" {
|
||||
"open"
|
||||
} else if (which firefox | is-not-empty) {
|
||||
"firefox"
|
||||
} else if (which chromium | is-not-empty) {
|
||||
"chromium"
|
||||
} else {
|
||||
"xdg-open"
|
||||
}
|
||||
)
|
||||
|
||||
# Pager configuration
|
||||
$env.PAGER = (
|
||||
if (which bat | is-not-empty) {
|
||||
"bat"
|
||||
} else if (which less | is-not-empty) {
|
||||
"less"
|
||||
} else {
|
||||
"more"
|
||||
}
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# NUSHELL SPECIFIC CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# Nushell library directories
|
||||
$env.NU_LIB_DIRS = [
|
||||
($nu.config-path | path dirname | path join "scripts")
|
||||
($nu.config-path | path dirname | path join "lib")
|
||||
($nu.config-path | path dirname | path join "modules")
|
||||
]
|
||||
|
||||
# Plugin directories
|
||||
$env.NU_PLUGIN_DIRS = [
|
||||
($nu.config-path | path dirname | path join "plugins")
|
||||
]
|
||||
|
||||
# Custom prompt indicators
|
||||
$env.PROMPT_INDICATOR = "〉"
|
||||
$env.PROMPT_INDICATOR_VI_INSERT = ": "
|
||||
$env.PROMPT_INDICATOR_VI_NORMAL = "〉"
|
||||
$env.PROMPT_MULTILINE_INDICATOR = "::: "
|
||||
|
||||
# =============================================================================
|
||||
# DEVELOPMENT ENVIRONMENT
|
||||
# =============================================================================
|
||||
|
||||
# Rust environment
|
||||
if (which cargo | is-not-empty) {
|
||||
# Add cargo bin to PATH if not already there
|
||||
let cargo_bin = ($env.HOME | path join ".cargo" "bin")
|
||||
if ($cargo_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $cargo_bin | is-empty) {
|
||||
$env.PATH = ($env.PATH | split row (char esep) | append $cargo_bin | str join (char esep))
|
||||
}
|
||||
}
|
||||
|
||||
# Go environment
|
||||
if (which go | is-not-empty) {
|
||||
$env.GOPATH = ($env.HOME | path join "go")
|
||||
let go_bin = ($env.GOPATH | path join "bin")
|
||||
if ($go_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $go_bin | is-empty) {
|
||||
$env.PATH = ($env.PATH | split row (char esep) | append $go_bin | str join (char esep))
|
||||
}
|
||||
}
|
||||
|
||||
# Node.js environment
|
||||
if (which npm | is-not-empty) {
|
||||
# Add global npm bin to PATH
|
||||
try {
|
||||
let npm_bin = (npm config get prefix | str trim | path join "bin")
|
||||
if ($npm_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $npm_bin | is-empty) {
|
||||
$env.PATH = ($env.PATH | split row (char esep) | append $npm_bin | str join (char esep))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Python environment
|
||||
if (which python3 | is-not-empty) or (which python | is-not-empty) {
|
||||
# Add user Python bin to PATH
|
||||
let python_cmd = if (which python3 | is-not-empty) { "python3" } else { "python" }
|
||||
try {
|
||||
let python_user_bin = (run-external $python_cmd "-m" "site" "--user-base" | str trim | path join "bin")
|
||||
if ($python_user_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $python_user_bin | is-empty) {
|
||||
$env.PATH = ($env.PATH | split row (char esep) | append $python_user_bin | str join (char esep))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# SYSTEM SPECIFIC CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# Linux specific
|
||||
if $nu.os-info.name == "linux" {
|
||||
# Add local bin directories to PATH
|
||||
let local_bins = [
|
||||
($env.HOME | path join ".local" "bin")
|
||||
"/usr/local/bin"
|
||||
]
|
||||
|
||||
for local_bin in $local_bins {
|
||||
if ($local_bin | path exists) and ($env.PATH | split row (char esep) | where $it == $local_bin | is-empty) {
|
||||
$env.PATH = ($env.PATH | split row (char esep) | append $local_bin | str join (char esep))
|
||||
}
|
||||
}
|
||||
|
||||
# XDG directories
|
||||
$env.XDG_CONFIG_HOME = ($env.HOME | path join ".config")
|
||||
$env.XDG_DATA_HOME = ($env.HOME | path join ".local" "share")
|
||||
$env.XDG_CACHE_HOME = ($env.HOME | path join ".cache")
|
||||
}
|
||||
|
||||
# macOS specific
|
||||
if $nu.os-info.name == "macos" {
|
||||
# Add Homebrew to PATH if it exists
|
||||
let homebrew_paths = [
|
||||
"/opt/homebrew/bin" # Apple Silicon
|
||||
"/usr/local/bin" # Intel
|
||||
]
|
||||
|
||||
for homebrew_path in $homebrew_paths {
|
||||
if ($homebrew_path | path exists) and ($env.PATH | split row (char esep) | where $it == $homebrew_path | is-empty) {
|
||||
$env.PATH = ($env.PATH | split row (char esep) | prepend $homebrew_path | str join (char esep))
|
||||
}
|
||||
}
|
||||
|
||||
# macOS specific environment variables
|
||||
if (which brew | is-not-empty) {
|
||||
$env.HOMEBREW_NO_ANALYTICS = "1"
|
||||
$env.HOMEBREW_NO_AUTO_UPDATE = "1"
|
||||
}
|
||||
}
|
||||
|
||||
# Windows specific
|
||||
if $nu.os-info.name == "windows" {
|
||||
# Windows specific environment (most variables should already be set by system)
|
||||
# Add user local bin if it exists
|
||||
let local_bin = ($env.USERPROFILE | path join ".local" "bin")
|
||||
if ($local_bin | path exists) and ($env.PATH | split row ";" | where $it == $local_bin | is-empty) {
|
||||
$env.PATH = ($env.PATH + ";" + $local_bin)
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# PERFORMANCE AND BEHAVIOR
|
||||
# =============================================================================
|
||||
|
||||
# History configuration
|
||||
$env.NU_HISTORY_SIZE = 10000
|
||||
|
||||
# Completions
|
||||
$env.NU_COMPLETION_ALGORITHM = "prefix" # or "fuzzy"
|
||||
|
||||
# Plugin timeout (in seconds)
|
||||
$env.NU_PLUGIN_TIMEOUT = 60
|
||||
|
||||
# =============================================================================
|
||||
# CUSTOM ALIASES AND SHORTCUTS
|
||||
# =============================================================================
|
||||
|
||||
# Common directory shortcuts
|
||||
$env.REPOS = ($env.HOME | path join "repos")
|
||||
$env.PROJECTS = ($env.HOME | path join "projects")
|
||||
$env.DOWNLOADS = (
|
||||
if $nu.os-info.name == "windows" {
|
||||
($env.USERPROFILE | path join "Downloads")
|
||||
} else {
|
||||
($env.HOME | path join "Downloads")
|
||||
}
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# THEME AND APPEARANCE
|
||||
# =============================================================================
|
||||
|
||||
# Terminal colors (if supported)
|
||||
$env.COLORTERM = "truecolor"
|
||||
|
||||
# LS_COLORS for better directory listings
|
||||
if $nu.os-info.name != "windows" {
|
||||
$env.LS_COLORS = "di=1;34:ln=1;36:so=1;35:pi=1;33:ex=1;32:bd=1;33:cd=1;33:su=1;31:sg=1;31:tw=1;34:ow=1;34"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CONDITIONAL FEATURES
|
||||
# =============================================================================
|
||||
|
||||
# Enable starship prompt if available
|
||||
if (which starship | is-not-empty) {
|
||||
$env.STARSHIP_CONFIG = ($nu.config-path | path dirname | path join "starship.toml")
|
||||
}
|
||||
|
||||
# Enable zoxide if available
|
||||
if (which zoxide | is-not-empty) {
|
||||
$env._ZO_DATA_DIR = ($nu.config-path | path dirname | path join "zoxide")
|
||||
}
|
||||
|
||||
# Enable direnv if available
|
||||
if (which direnv | is-not-empty) {
|
||||
$env.DIRENV_LOG_FORMAT = "" # Quiet direnv output
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# PLUGIN SPECIFIC CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# Clipboard plugin settings
|
||||
$env.NU_CLIPBOARD_PROVIDER = (
|
||||
if $nu.os-info.name == "linux" and (which xclip | is-not-empty) {
|
||||
"xclip"
|
||||
} else if $nu.os-info.name == "linux" and (which wl-copy | is-not-empty) {
|
||||
"wl-clipboard"
|
||||
} else if $nu.os-info.name == "macos" {
|
||||
"pbcopy"
|
||||
} else if $nu.os-info.name == "windows" {
|
||||
"windows"
|
||||
} else {
|
||||
"none"
|
||||
}
|
||||
)
|
||||
|
||||
# Image plugin settings
|
||||
$env.NU_IMAGE_VIEWER = (
|
||||
if $nu.os-info.name == "macos" {
|
||||
"open"
|
||||
} else if $nu.os-info.name == "windows" {
|
||||
"mspaint"
|
||||
} else if (which feh | is-not-empty) {
|
||||
"feh"
|
||||
} else if (which eog | is-not-empty) {
|
||||
"eog"
|
||||
} else {
|
||||
"xdg-open"
|
||||
}
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# USER CUSTOMIZATION SECTION
|
||||
# =============================================================================
|
||||
|
||||
# Load user-specific environment file if it exists
|
||||
let user_env_file = ($nu.config-path | path dirname | path join "user_env.nu")
|
||||
if ($user_env_file | path exists) {
|
||||
source $user_env_file
|
||||
}
|
||||
|
||||
# Load local environment file if it exists (for project-specific settings)
|
||||
let local_env_file = (pwd | path join ".nu_env")
|
||||
if ($local_env_file | path exists) {
|
||||
source $local_env_file
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# FINAL PATH CLEANUP
|
||||
# =============================================================================
|
||||
|
||||
# Remove duplicate PATH entries while preserving order
|
||||
$env.PATH = (
|
||||
$env.PATH
|
||||
| split row (char esep)
|
||||
| uniq
|
||||
| where ($it | path exists)
|
||||
| str join (char esep)
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# STARTUP INFORMATION (Optional)
|
||||
# =============================================================================
|
||||
|
||||
# Uncomment to show environment info on startup
|
||||
# print $"🚀 Nushell environment loaded"
|
||||
# print $"📁 Config: ($nu.config-path)"
|
||||
# print $"🧩 Plugins: (plugin list | length) loaded"
|
||||
# print $"💻 Platform: ($nu.os-info.name) ($nu.os-info.arch)"
|
||||
|
||||
# =============================================================================
|
||||
# NOTES FOR CUSTOMIZATION
|
||||
# =============================================================================
|
||||
|
||||
# This file contains sensible defaults for a Nushell + Plugins environment.
|
||||
# To customize:
|
||||
#
|
||||
# 1. Create user_env.nu in the same directory for user-specific settings
|
||||
# 2. Create .nu_env in project directories for project-specific settings
|
||||
# 3. Modify this file directly (but changes may be lost on updates)
|
||||
#
|
||||
# For more information:
|
||||
# - Nushell Book: https://nushell.sh/book/
|
||||
# - Environment Variables: https://nushell.sh/book/environment.html
|
||||
# - Custom Commands: https://nushell.sh/book/custom_commands.html
|
841
scripts/templates/install.ps1
Normal file
841
scripts/templates/install.ps1
Normal file
@ -0,0 +1,841 @@
|
||||
# Universal Nushell + Plugins Bootstrap Installer for Windows
|
||||
# PowerShell script that installs Nushell and plugins without any prerequisites
|
||||
#
|
||||
# This script:
|
||||
# - Detects Windows platform (x86_64/arm64)
|
||||
# - Downloads or builds Nushell + plugins
|
||||
# - Installs to user location (%USERPROFILE%\.local\bin) or system (C:\Program Files\Nushell)
|
||||
# - Updates PATH in system/user environment
|
||||
# - Creates initial Nushell configuration
|
||||
# - Registers all plugins automatically
|
||||
# - Verifies installation
|
||||
|
||||
param(
|
||||
[switch]$System,
|
||||
[switch]$User = $true,
|
||||
[switch]$NoPath,
|
||||
[switch]$NoConfig,
|
||||
[switch]$NoPlugins,
|
||||
[switch]$BuildFromSource,
|
||||
[switch]$Verify,
|
||||
[switch]$Uninstall,
|
||||
[string]$Version = "",
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
# Configuration
|
||||
$RepoUrl = "https://github.com/your-org/nushell-plugins"
|
||||
$BinaryRepoUrl = "$RepoUrl/releases/download"
|
||||
$InstallDirUser = "$env:USERPROFILE\.local\bin"
|
||||
$InstallDirSystem = "${env:ProgramFiles}\Nushell\bin"
|
||||
$ConfigDir = "$env:USERPROFILE\.config\nushell"
|
||||
$TempDir = "$env:TEMP\nushell-install-$(Get-Random)"
|
||||
|
||||
# Colors for output
|
||||
$Colors = @{
|
||||
Red = "Red"
|
||||
Green = "Green"
|
||||
Yellow = "Yellow"
|
||||
Blue = "Blue"
|
||||
Magenta = "Magenta"
|
||||
Cyan = "Cyan"
|
||||
}
|
||||
|
||||
# Logging functions
|
||||
function Write-LogInfo {
|
||||
param([string]$Message)
|
||||
Write-Host "ℹ️ $Message" -ForegroundColor $Colors.Blue
|
||||
}
|
||||
|
||||
function Write-LogSuccess {
|
||||
param([string]$Message)
|
||||
Write-Host "✅ $Message" -ForegroundColor $Colors.Green
|
||||
}
|
||||
|
||||
function Write-LogWarn {
|
||||
param([string]$Message)
|
||||
Write-Host "⚠️ $Message" -ForegroundColor $Colors.Yellow
|
||||
}
|
||||
|
||||
function Write-LogError {
|
||||
param([string]$Message)
|
||||
Write-Host "❌ $Message" -ForegroundColor $Colors.Red
|
||||
}
|
||||
|
||||
function Write-LogHeader {
|
||||
param([string]$Message)
|
||||
Write-Host ""
|
||||
Write-Host "🚀 $Message" -ForegroundColor $Colors.Magenta
|
||||
Write-Host ("=" * $Message.Length) -ForegroundColor $Colors.Magenta
|
||||
}
|
||||
|
||||
# Usage information
|
||||
function Show-Usage {
|
||||
@"
|
||||
Nushell + Plugins Bootstrap Installer for Windows
|
||||
|
||||
USAGE:
|
||||
# Download and run:
|
||||
Invoke-WebRequest -Uri "install-url/install.ps1" | Invoke-Expression
|
||||
|
||||
# Or download and run with parameters:
|
||||
.\install.ps1 [-System] [-User] [-NoPath] [-NoConfig] [-NoPlugins] [-BuildFromSource] [-Verify] [-Uninstall] [-Version <version>] [-Help]
|
||||
|
||||
PARAMETERS:
|
||||
-System Install to system directory (C:\Program Files\Nushell, requires admin)
|
||||
-User Install to user directory (~\.local\bin) [default]
|
||||
-NoPath Don't modify PATH environment variable
|
||||
-NoConfig Don't create initial nushell configuration
|
||||
-NoPlugins Install only nushell, skip plugins
|
||||
-BuildFromSource Build from source instead of downloading binaries
|
||||
-Verify Verify installation after completion
|
||||
-Uninstall Remove nushell and plugins
|
||||
-Version <version> Install specific version (default: latest)
|
||||
-Help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
# Default installation (user directory, with plugins)
|
||||
.\install.ps1
|
||||
|
||||
# System installation (requires admin)
|
||||
.\install.ps1 -System
|
||||
|
||||
# Install without plugins
|
||||
.\install.ps1 -NoPlugins
|
||||
|
||||
# Build from source
|
||||
.\install.ps1 -BuildFromSource
|
||||
|
||||
# Install specific version
|
||||
.\install.ps1 -Version "v0.107.1"
|
||||
"@
|
||||
}
|
||||
|
||||
# Platform detection
|
||||
function Get-Platform {
|
||||
$arch = $env:PROCESSOR_ARCHITECTURE
|
||||
switch ($arch) {
|
||||
"AMD64" { return "windows-x86_64" }
|
||||
"ARM64" { return "windows-aarch64" }
|
||||
default {
|
||||
Write-LogError "Unsupported architecture: $arch"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check if running as administrator
|
||||
function Test-Admin {
|
||||
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
|
||||
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
# Check if command exists
|
||||
function Test-Command {
|
||||
param([string]$Command)
|
||||
try {
|
||||
Get-Command $Command -ErrorAction Stop | Out-Null
|
||||
return $true
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Check dependencies for building from source
|
||||
function Test-BuildDependencies {
|
||||
$missing = @()
|
||||
|
||||
if (-not (Test-Command "git")) {
|
||||
$missing += "git"
|
||||
}
|
||||
|
||||
if (-not (Test-Command "cargo")) {
|
||||
$missing += "cargo"
|
||||
}
|
||||
|
||||
if (-not (Test-Command "rustc")) {
|
||||
$missing += "rust"
|
||||
}
|
||||
|
||||
if ($missing.Count -gt 0) {
|
||||
Write-LogError "Missing build dependencies: $($missing -join ', ')"
|
||||
Write-LogInfo "Please install these tools or use binary installation instead"
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Download file with progress
|
||||
function Get-FileWithProgress {
|
||||
param(
|
||||
[string]$Url,
|
||||
[string]$OutputPath,
|
||||
[string]$Description = "file"
|
||||
)
|
||||
|
||||
Write-LogInfo "Downloading $Description..."
|
||||
|
||||
try {
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.DownloadFile($Url, $OutputPath)
|
||||
Write-LogSuccess "Downloaded $Description"
|
||||
return $true
|
||||
} catch {
|
||||
Write-LogError "Failed to download $Description from $Url"
|
||||
Write-LogError $_.Exception.Message
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Extract archive
|
||||
function Expand-Archive {
|
||||
param(
|
||||
[string]$ArchivePath,
|
||||
[string]$DestinationPath
|
||||
)
|
||||
|
||||
Write-LogInfo "Extracting archive..."
|
||||
|
||||
try {
|
||||
if ($ArchivePath -like "*.zip") {
|
||||
Expand-Archive -Path $ArchivePath -DestinationPath $DestinationPath -Force
|
||||
} elseif ($ArchivePath -like "*.tar.gz") {
|
||||
# Use tar if available (Windows 10 build 17063 and later)
|
||||
if (Test-Command "tar") {
|
||||
& tar -xzf $ArchivePath -C $DestinationPath
|
||||
} else {
|
||||
throw "tar command not found for .tar.gz extraction"
|
||||
}
|
||||
} else {
|
||||
throw "Unsupported archive format: $ArchivePath"
|
||||
}
|
||||
Write-LogSuccess "Extracted archive"
|
||||
return $true
|
||||
} catch {
|
||||
Write-LogError "Failed to extract $ArchivePath"
|
||||
Write-LogError $_.Exception.Message
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Get latest release version
|
||||
function Get-LatestVersion {
|
||||
try {
|
||||
$response = Invoke-RestMethod -Uri "https://api.github.com/repos/your-org/nushell-plugins/releases/latest"
|
||||
return $response.tag_name
|
||||
} catch {
|
||||
Write-LogWarn "Could not detect latest version, using v0.107.1"
|
||||
return "v0.107.1"
|
||||
}
|
||||
}
|
||||
|
||||
# Download and install binaries
|
||||
function Install-FromBinaries {
|
||||
param(
|
||||
[string]$Platform,
|
||||
[string]$Version,
|
||||
[string]$InstallDir,
|
||||
[bool]$IncludePlugins
|
||||
)
|
||||
|
||||
Write-LogHeader "Installing from Pre-built Binaries"
|
||||
|
||||
# Create temporary directory
|
||||
New-Item -ItemType Directory -Path $TempDir -Force | Out-Null
|
||||
Set-Location $TempDir
|
||||
|
||||
# Determine archive name and URL
|
||||
$archiveFormat = if ($Platform -like "*windows*") { "zip" } else { "tar.gz" }
|
||||
$archiveName = "nushell-plugins-$Platform-$Version.$archiveFormat"
|
||||
$downloadUrl = "$BinaryRepoUrl/$Version/$archiveName"
|
||||
|
||||
# Download archive
|
||||
if (-not (Get-FileWithProgress -Url $downloadUrl -OutputPath $archiveName -Description "Nushell distribution")) {
|
||||
Write-LogWarn "Binary download failed, trying alternative..."
|
||||
# Try without version prefix
|
||||
$archiveName = "nushell-plugins-$Platform.$archiveFormat"
|
||||
$downloadUrl = "$BinaryRepoUrl/latest/$archiveName"
|
||||
if (-not (Get-FileWithProgress -Url $downloadUrl -OutputPath $archiveName -Description "Nushell distribution (latest)")) {
|
||||
Write-LogError "Failed to download binaries"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Extract archive
|
||||
if (-not (Expand-Archive -ArchivePath $archiveName -DestinationPath ".")) {
|
||||
return $false
|
||||
}
|
||||
|
||||
# Find extracted directory
|
||||
$extractDir = Get-ChildItem -Directory | Select-Object -First 1
|
||||
if (-not $extractDir) {
|
||||
Write-LogError "No extracted directory found"
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-LogInfo "Installing binaries to $InstallDir..."
|
||||
|
||||
# Create install directory
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
|
||||
# Install nushell binary
|
||||
$nuBinary = "nu.exe"
|
||||
$nuSourcePath = Join-Path $extractDir.FullName $nuBinary
|
||||
|
||||
if (Test-Path $nuSourcePath) {
|
||||
Copy-Item $nuSourcePath -Destination $InstallDir
|
||||
Write-LogSuccess "Installed nushell binary"
|
||||
} else {
|
||||
Write-LogError "Nushell binary not found in archive"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Install plugins if requested
|
||||
if ($IncludePlugins) {
|
||||
$pluginFiles = Get-ChildItem -Path $extractDir.FullName -Name "nu_plugin_*.exe"
|
||||
$pluginCount = 0
|
||||
|
||||
foreach ($pluginFile in $pluginFiles) {
|
||||
$pluginSourcePath = Join-Path $extractDir.FullName $pluginFile
|
||||
Copy-Item $pluginSourcePath -Destination $InstallDir
|
||||
$pluginCount++
|
||||
}
|
||||
|
||||
if ($pluginCount -gt 0) {
|
||||
Write-LogSuccess "Installed $pluginCount plugins"
|
||||
} else {
|
||||
Write-LogWarn "No plugins found in archive"
|
||||
}
|
||||
}
|
||||
|
||||
# Copy configuration files if they exist
|
||||
$configSourceDir = Join-Path $extractDir.FullName "config"
|
||||
if (Test-Path $configSourceDir) {
|
||||
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
|
||||
Copy-Item -Path "$configSourceDir\*" -Destination $ConfigDir -Recurse -Force
|
||||
Write-LogSuccess "Installed configuration files"
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Build and install from source
|
||||
function Install-FromSource {
|
||||
param(
|
||||
[string]$InstallDir,
|
||||
[bool]$IncludePlugins
|
||||
)
|
||||
|
||||
Write-LogHeader "Building from Source"
|
||||
|
||||
# Check dependencies
|
||||
if (-not (Test-BuildDependencies)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
# Create temporary directory
|
||||
New-Item -ItemType Directory -Path $TempDir -Force | Out-Null
|
||||
Set-Location $TempDir
|
||||
|
||||
# Clone repository
|
||||
Write-LogInfo "Cloning repository..."
|
||||
try {
|
||||
& git clone --recursive $RepoUrl nushell-plugins
|
||||
Set-Location nushell-plugins
|
||||
} catch {
|
||||
Write-LogError "Failed to clone repository"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Build nushell
|
||||
Write-LogInfo "Building nushell..."
|
||||
try {
|
||||
if (Test-Command "just") {
|
||||
& just build-nushell
|
||||
} else {
|
||||
# Fallback to manual build
|
||||
Set-Location nushell
|
||||
& cargo build --release --features "plugin,network,sqlite,trash-support,rustls-tls"
|
||||
Set-Location ..
|
||||
}
|
||||
} catch {
|
||||
Write-LogError "Failed to build nushell"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Build plugins if requested
|
||||
if ($IncludePlugins) {
|
||||
Write-LogInfo "Building plugins..."
|
||||
try {
|
||||
if (Test-Command "just") {
|
||||
& just build
|
||||
} else {
|
||||
# Build plugins manually
|
||||
$pluginDirs = Get-ChildItem -Directory -Name "nu_plugin_*" | Where-Object { $_ -ne "nushell" }
|
||||
foreach ($pluginDir in $pluginDirs) {
|
||||
Write-LogInfo "Building $pluginDir..."
|
||||
Set-Location $pluginDir
|
||||
try {
|
||||
& cargo build --release
|
||||
Write-LogSuccess "Built $pluginDir"
|
||||
} catch {
|
||||
Write-LogWarn "Failed to build $pluginDir"
|
||||
}
|
||||
Set-Location ..
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-LogWarn "Failed to build some plugins"
|
||||
}
|
||||
}
|
||||
|
||||
# Install binaries
|
||||
Write-LogInfo "Installing binaries to $InstallDir..."
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
|
||||
# Install nushell
|
||||
$nuBinary = "nushell\target\release\nu.exe"
|
||||
if (Test-Path $nuBinary) {
|
||||
Copy-Item $nuBinary -Destination "$InstallDir\nu.exe"
|
||||
Write-LogSuccess "Installed nushell binary"
|
||||
} else {
|
||||
Write-LogError "Nushell binary not found"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Install plugins
|
||||
if ($IncludePlugins) {
|
||||
$pluginCount = 0
|
||||
$pluginDirs = Get-ChildItem -Directory -Name "nu_plugin_*" | Where-Object { $_ -ne "nushell" }
|
||||
|
||||
foreach ($pluginDir in $pluginDirs) {
|
||||
$pluginBinary = "$pluginDir\target\release\$pluginDir.exe"
|
||||
if (Test-Path $pluginBinary) {
|
||||
Copy-Item $pluginBinary -Destination "$InstallDir\$pluginDir.exe"
|
||||
$pluginCount++
|
||||
}
|
||||
}
|
||||
|
||||
if ($pluginCount -gt 0) {
|
||||
Write-LogSuccess "Installed $pluginCount plugins"
|
||||
} else {
|
||||
Write-LogWarn "No plugins were built successfully"
|
||||
}
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Register plugins with nushell
|
||||
function Register-Plugins {
|
||||
param(
|
||||
[string]$InstallDir
|
||||
)
|
||||
|
||||
$nuBinary = Join-Path $InstallDir "nu.exe"
|
||||
|
||||
if (-not (Test-Path $nuBinary)) {
|
||||
Write-LogError "Nushell binary not found: $nuBinary"
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-LogHeader "Registering Plugins"
|
||||
|
||||
# Find all plugin binaries
|
||||
$pluginFiles = Get-ChildItem -Path $InstallDir -Name "nu_plugin_*.exe"
|
||||
$pluginCount = 0
|
||||
|
||||
foreach ($pluginFile in $pluginFiles) {
|
||||
$pluginPath = Join-Path $InstallDir $pluginFile
|
||||
$pluginName = [System.IO.Path]::GetFileNameWithoutExtension($pluginFile)
|
||||
|
||||
Write-LogInfo "Registering $pluginName..."
|
||||
|
||||
try {
|
||||
& $nuBinary -c "plugin add '$pluginPath'"
|
||||
Write-LogSuccess "Registered $pluginName"
|
||||
$pluginCount++
|
||||
} catch {
|
||||
Write-LogWarn "Failed to register $pluginName"
|
||||
}
|
||||
}
|
||||
|
||||
if ($pluginCount -gt 0) {
|
||||
Write-LogSuccess "Successfully registered $pluginCount plugins"
|
||||
} else {
|
||||
Write-LogWarn "No plugins were registered"
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Update PATH environment variable
|
||||
function Update-PathEnvironment {
|
||||
param(
|
||||
[string]$InstallDir,
|
||||
[bool]$SystemInstall
|
||||
)
|
||||
|
||||
Write-LogHeader "Updating PATH Environment"
|
||||
|
||||
# Determine scope
|
||||
$scope = if ($SystemInstall) { "Machine" } else { "User" }
|
||||
|
||||
try {
|
||||
# Get current PATH
|
||||
$currentPath = [Environment]::GetEnvironmentVariable("PATH", $scope)
|
||||
|
||||
# Check if already in PATH
|
||||
if ($currentPath -split ';' -contains $InstallDir) {
|
||||
Write-LogInfo "PATH already contains $InstallDir"
|
||||
return $true
|
||||
}
|
||||
|
||||
# Add to PATH
|
||||
$newPath = "$InstallDir;$currentPath"
|
||||
[Environment]::SetEnvironmentVariable("PATH", $newPath, $scope)
|
||||
|
||||
# Update current session
|
||||
$env:PATH = "$InstallDir;$env:PATH"
|
||||
|
||||
Write-LogSuccess "Updated PATH environment variable"
|
||||
Write-LogInfo "Restart your terminal to apply changes globally"
|
||||
|
||||
return $true
|
||||
} catch {
|
||||
Write-LogError "Failed to update PATH environment variable"
|
||||
Write-LogError $_.Exception.Message
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Create initial nushell configuration
|
||||
function New-NushellConfig {
|
||||
Write-LogHeader "Creating Nushell Configuration"
|
||||
|
||||
# Create config directory
|
||||
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
|
||||
|
||||
# Create basic config.nu if it doesn't exist
|
||||
$configFile = Join-Path $ConfigDir "config.nu"
|
||||
if (-not (Test-Path $configFile)) {
|
||||
@"
|
||||
# Nushell Configuration
|
||||
# Created by nushell-plugins installer
|
||||
|
||||
# Set up basic configuration
|
||||
`$env.config = {
|
||||
show_banner: false
|
||||
edit_mode: emacs
|
||||
shell_integration: true
|
||||
|
||||
table: {
|
||||
mode: rounded
|
||||
index_mode: always
|
||||
show_empty: true
|
||||
padding: { left: 1, right: 1 }
|
||||
}
|
||||
|
||||
completions: {
|
||||
case_sensitive: false
|
||||
quick: true
|
||||
partial: true
|
||||
algorithm: "prefix"
|
||||
}
|
||||
|
||||
history: {
|
||||
max_size: 10000
|
||||
sync_on_enter: true
|
||||
file_format: "plaintext"
|
||||
}
|
||||
|
||||
filesize: {
|
||||
metric: false
|
||||
format: "auto"
|
||||
}
|
||||
}
|
||||
|
||||
# Load custom commands and aliases
|
||||
# Add your custom configuration below
|
||||
"@ | Out-File -FilePath $configFile -Encoding utf8
|
||||
Write-LogSuccess "Created config.nu"
|
||||
} else {
|
||||
Write-LogInfo "config.nu already exists, skipping"
|
||||
}
|
||||
|
||||
# Create basic env.nu if it doesn't exist
|
||||
$envFile = Join-Path $ConfigDir "env.nu"
|
||||
if (-not (Test-Path $envFile)) {
|
||||
@"
|
||||
# Nushell Environment Configuration
|
||||
# Created by nushell-plugins installer
|
||||
|
||||
# Environment variables
|
||||
`$env.EDITOR = "notepad"
|
||||
`$env.BROWSER = "msedge"
|
||||
|
||||
# Nushell specific environment
|
||||
`$env.NU_LIB_DIRS = [
|
||||
(`$nu.config-path | path dirname | path join "scripts")
|
||||
]
|
||||
|
||||
`$env.NU_PLUGIN_DIRS = [
|
||||
(`$nu.config-path | path dirname | path join "plugins")
|
||||
]
|
||||
|
||||
# Add your custom environment variables below
|
||||
"@ | Out-File -FilePath $envFile -Encoding utf8
|
||||
Write-LogSuccess "Created env.nu"
|
||||
} else {
|
||||
Write-LogInfo "env.nu already exists, skipping"
|
||||
}
|
||||
|
||||
# Create directories
|
||||
$scriptsDir = Join-Path $ConfigDir "scripts"
|
||||
$pluginsDir = Join-Path $ConfigDir "plugins"
|
||||
New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $pluginsDir -Force | Out-Null
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Verify installation
|
||||
function Test-Installation {
|
||||
param(
|
||||
[string]$InstallDir
|
||||
)
|
||||
|
||||
$nuBinary = Join-Path $InstallDir "nu.exe"
|
||||
|
||||
Write-LogHeader "Verifying Installation"
|
||||
|
||||
# Check if nushell binary exists
|
||||
if (-not (Test-Path $nuBinary)) {
|
||||
Write-LogError "Nushell binary not found: $nuBinary"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Test nushell version
|
||||
Write-LogInfo "Testing nushell binary..."
|
||||
try {
|
||||
$versionOutput = & $nuBinary --version
|
||||
Write-LogSuccess "Nushell version: $versionOutput"
|
||||
} catch {
|
||||
Write-LogError "Failed to run nushell binary"
|
||||
Write-LogError $_.Exception.Message
|
||||
return $false
|
||||
}
|
||||
|
||||
# Test basic nushell command
|
||||
Write-LogInfo "Testing basic nushell functionality..."
|
||||
try {
|
||||
$testOutput = & $nuBinary -c "echo 'Hello from Nushell'"
|
||||
if ($testOutput -eq "Hello from Nushell") {
|
||||
Write-LogSuccess "Basic nushell functionality works"
|
||||
} else {
|
||||
Write-LogWarn "Unexpected output from basic test"
|
||||
}
|
||||
} catch {
|
||||
Write-LogError "Basic nushell functionality failed"
|
||||
return $false
|
||||
}
|
||||
|
||||
# List registered plugins
|
||||
Write-LogInfo "Checking registered plugins..."
|
||||
try {
|
||||
$pluginOutput = & $nuBinary -c "plugin list"
|
||||
$pluginCount = ($pluginOutput | Select-String "nu_plugin_").Count
|
||||
if ($pluginCount -gt 0) {
|
||||
Write-LogSuccess "Found $pluginCount registered plugins"
|
||||
} else {
|
||||
Write-LogWarn "No plugins are registered"
|
||||
}
|
||||
} catch {
|
||||
Write-LogWarn "Could not check plugin status"
|
||||
}
|
||||
|
||||
# Check PATH
|
||||
Write-LogInfo "Checking PATH configuration..."
|
||||
try {
|
||||
$nuInPath = Get-Command nu -ErrorAction SilentlyContinue
|
||||
if ($nuInPath) {
|
||||
Write-LogSuccess "Nushell is available in PATH"
|
||||
} else {
|
||||
Write-LogWarn "Nushell is not in PATH. You may need to restart your terminal."
|
||||
}
|
||||
} catch {
|
||||
Write-LogWarn "Could not verify PATH configuration"
|
||||
}
|
||||
|
||||
Write-LogSuccess "Installation verification complete!"
|
||||
return $true
|
||||
}
|
||||
|
||||
# Uninstall function
|
||||
function Uninstall-Nushell {
|
||||
Write-LogHeader "Uninstalling Nushell"
|
||||
|
||||
$removedFiles = 0
|
||||
|
||||
# Remove from user directory
|
||||
if (Test-Path $InstallDirUser) {
|
||||
$userBinaries = Get-ChildItem -Path $InstallDirUser -Name "nu.exe", "nu_plugin_*.exe" -ErrorAction SilentlyContinue
|
||||
foreach ($binary in $userBinaries) {
|
||||
$filePath = Join-Path $InstallDirUser $binary
|
||||
Remove-Item $filePath -Force -ErrorAction SilentlyContinue
|
||||
Write-LogSuccess "Removed $binary from $InstallDirUser"
|
||||
$removedFiles++
|
||||
}
|
||||
}
|
||||
|
||||
# Remove from system directory (if accessible)
|
||||
if ((Test-Admin) -and (Test-Path $InstallDirSystem)) {
|
||||
$systemBinaries = Get-ChildItem -Path $InstallDirSystem -Name "nu.exe", "nu_plugin_*.exe" -ErrorAction SilentlyContinue
|
||||
foreach ($binary in $systemBinaries) {
|
||||
$filePath = Join-Path $InstallDirSystem $binary
|
||||
Remove-Item $filePath -Force -ErrorAction SilentlyContinue
|
||||
Write-LogSuccess "Removed $binary from $InstallDirSystem"
|
||||
$removedFiles++
|
||||
}
|
||||
}
|
||||
|
||||
# Option to remove configuration
|
||||
$response = Read-Host "Remove nushell configuration directory ($ConfigDir)? [y/N]"
|
||||
if ($response -match "^[yY]") {
|
||||
if (Test-Path $ConfigDir) {
|
||||
Remove-Item $ConfigDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-LogSuccess "Removed configuration directory"
|
||||
}
|
||||
} else {
|
||||
Write-LogInfo "Configuration directory preserved"
|
||||
}
|
||||
|
||||
if ($removedFiles -gt 0) {
|
||||
Write-LogSuccess "Uninstallation complete ($removedFiles files removed)"
|
||||
Write-LogWarn "You may need to manually remove PATH entries from your environment variables"
|
||||
} else {
|
||||
Write-LogWarn "No nushell files found to remove"
|
||||
}
|
||||
}
|
||||
|
||||
# Main installation function
|
||||
function Main {
|
||||
# Handle help
|
||||
if ($Help) {
|
||||
Show-Usage
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Handle uninstall
|
||||
if ($Uninstall) {
|
||||
Uninstall-Nushell
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Show header
|
||||
Write-LogHeader "Nushell + Plugins Installer for Windows"
|
||||
Write-LogInfo "Universal bootstrap installer for Nushell and plugins"
|
||||
Write-LogInfo ""
|
||||
|
||||
# Detect platform
|
||||
$platform = Get-Platform
|
||||
Write-LogInfo "Detected platform: $platform"
|
||||
|
||||
# Determine installation directory and check privileges
|
||||
if ($System) {
|
||||
$installDir = $InstallDirSystem
|
||||
if (-not (Test-Admin)) {
|
||||
Write-LogError "System installation requires administrator privileges"
|
||||
Write-LogInfo "Run PowerShell as Administrator or use -User for user installation"
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
$installDir = $InstallDirUser
|
||||
}
|
||||
|
||||
Write-LogInfo "Installing to: $installDir"
|
||||
|
||||
# Get version if not specified
|
||||
if (-not $Version) {
|
||||
$Version = Get-LatestVersion
|
||||
}
|
||||
Write-LogInfo "Version: $Version"
|
||||
|
||||
# Cleanup function
|
||||
$cleanup = {
|
||||
if (Test-Path $TempDir) {
|
||||
Remove-Item $TempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
# Install based on method
|
||||
if ($BuildFromSource) {
|
||||
if (-not (Install-FromSource -InstallDir $installDir -IncludePlugins (-not $NoPlugins))) {
|
||||
Write-LogError "Source installation failed"
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
if (-not (Install-FromBinaries -Platform $platform -Version $Version -InstallDir $installDir -IncludePlugins (-not $NoPlugins))) {
|
||||
Write-LogError "Binary installation failed"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Register plugins
|
||||
if (-not $NoPlugins) {
|
||||
Register-Plugins -InstallDir $installDir
|
||||
}
|
||||
|
||||
# Update PATH
|
||||
if (-not $NoPath) {
|
||||
Update-PathEnvironment -InstallDir $installDir -SystemInstall $System
|
||||
}
|
||||
|
||||
# Create configuration
|
||||
if (-not $NoConfig) {
|
||||
New-NushellConfig
|
||||
}
|
||||
|
||||
# Verify installation
|
||||
if ($Verify) {
|
||||
if (-not (Test-Installation -InstallDir $installDir)) {
|
||||
Write-LogError "Installation verification failed"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Final success message
|
||||
Write-LogHeader "Installation Complete!"
|
||||
Write-LogSuccess "Nushell has been successfully installed to $installDir"
|
||||
|
||||
if (-not $NoPlugins) {
|
||||
Write-LogSuccess "Plugins have been registered with Nushell"
|
||||
}
|
||||
|
||||
if (-not $NoPath) {
|
||||
Write-LogInfo "To use Nushell, restart your terminal or start a new PowerShell session"
|
||||
}
|
||||
|
||||
Write-LogInfo ""
|
||||
Write-LogInfo "Try running: nu --version"
|
||||
Write-LogInfo "Or start Nushell with: nu"
|
||||
|
||||
if (-not $NoPlugins) {
|
||||
Write-LogInfo "Check plugins with: nu -c 'plugin list'"
|
||||
}
|
||||
|
||||
Write-LogInfo ""
|
||||
Write-LogInfo "For more information, visit: https://nushell.sh"
|
||||
Write-LogInfo ""
|
||||
Write-LogSuccess "Happy shell scripting! 🚀"
|
||||
|
||||
} finally {
|
||||
& $cleanup
|
||||
}
|
||||
}
|
||||
|
||||
# Run main function
|
||||
Main
|
923
scripts/templates/install.sh
Executable file
923
scripts/templates/install.sh
Executable file
@ -0,0 +1,923 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Universal Nushell + Plugins Bootstrap Installer
|
||||
# POSIX compliant shell script that installs Nushell and plugins without any prerequisites
|
||||
#
|
||||
# This script:
|
||||
# - Detects platform (Linux/macOS, x86_64/arm64)
|
||||
# - Downloads or builds Nushell + plugins
|
||||
# - Installs to user location (~/.local/bin) or system (/usr/local/bin)
|
||||
# - Updates PATH in shell configuration files
|
||||
# - Creates initial Nushell configuration
|
||||
# - Registers all plugins automatically
|
||||
# - Verifies installation
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Configuration
|
||||
REPO_URL="https://github.com/your-org/nushell-plugins"
|
||||
BINARY_REPO_URL="$REPO_URL/releases/download"
|
||||
INSTALL_DIR_USER="$HOME/.local/bin"
|
||||
INSTALL_DIR_SYSTEM="/usr/local/bin"
|
||||
CONFIG_DIR="$HOME/.config/nushell"
|
||||
TEMP_DIR="/tmp/nushell-install-$$"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
PURPLE='\033[0;35m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
printf "${BLUE}ℹ️ %s${NC}\n" "$1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
printf "${GREEN}✅ %s${NC}\n" "$1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
printf "${YELLOW}⚠️ %s${NC}\n" "$1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
printf "${RED}❌ %s${NC}\n" "$1" >&2
|
||||
}
|
||||
|
||||
log_header() {
|
||||
printf "\n${PURPLE}🚀 %s${NC}\n" "$1"
|
||||
printf "${PURPLE}%s${NC}\n" "$(printf '=%.0s' $(seq 1 ${#1}))"
|
||||
}
|
||||
|
||||
# Usage information
|
||||
usage() {
|
||||
cat << 'EOF'
|
||||
Nushell + Plugins Bootstrap Installer
|
||||
|
||||
USAGE:
|
||||
curl -L install-url/install.sh | sh
|
||||
# or
|
||||
./install.sh [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--system Install to system directory (/usr/local/bin, requires sudo)
|
||||
--user Install to user directory (~/.local/bin) [default]
|
||||
--no-path Don't modify shell PATH configuration
|
||||
--no-config Don't create initial nushell configuration
|
||||
--no-plugins Install only nushell, skip plugins
|
||||
--build-from-source Build from source instead of downloading binaries
|
||||
--verify Verify installation after completion
|
||||
--uninstall Remove nushell and plugins
|
||||
--version VERSION Install specific version (default: latest)
|
||||
--help Show this help message
|
||||
|
||||
EXAMPLES:
|
||||
# Default installation (user directory, with plugins)
|
||||
curl -L install-url/install.sh | sh
|
||||
|
||||
# System installation
|
||||
curl -L install-url/install.sh | sh -s -- --system
|
||||
|
||||
# Install without plugins
|
||||
curl -L install-url/install.sh | sh -s -- --no-plugins
|
||||
|
||||
# Build from source
|
||||
curl -L install-url/install.sh | sh -s -- --build-from-source
|
||||
|
||||
# Install specific version
|
||||
curl -L install-url/install.sh | sh -s -- --version v0.107.1
|
||||
EOF
|
||||
}
|
||||
|
||||
# Platform detection
|
||||
detect_platform() {
|
||||
local os arch
|
||||
|
||||
# Detect OS
|
||||
case "$(uname -s)" in
|
||||
Linux) os="linux" ;;
|
||||
Darwin) os="darwin" ;;
|
||||
CYGWIN*|MINGW*|MSYS*) os="windows" ;;
|
||||
*) log_error "Unsupported OS: $(uname -s)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Detect architecture
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) arch="x86_64" ;;
|
||||
aarch64|arm64) arch="arm64" ;;
|
||||
armv7l) arch="armv7" ;;
|
||||
*) log_error "Unsupported architecture: $(uname -m)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Special handling for Darwin arm64
|
||||
if [ "$os" = "darwin" ] && [ "$arch" = "arm64" ]; then
|
||||
arch="arm64"
|
||||
fi
|
||||
|
||||
echo "${os}-${arch}"
|
||||
}
|
||||
|
||||
# Check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Check dependencies for building from source
|
||||
check_build_dependencies() {
|
||||
local missing=""
|
||||
|
||||
if ! command_exists "git"; then
|
||||
missing="$missing git"
|
||||
fi
|
||||
|
||||
if ! command_exists "cargo"; then
|
||||
missing="$missing cargo"
|
||||
fi
|
||||
|
||||
if ! command_exists "rustc"; then
|
||||
missing="$missing rust"
|
||||
fi
|
||||
|
||||
if [ -n "$missing" ]; then
|
||||
log_error "Missing build dependencies:$missing"
|
||||
log_info "Please install these tools or use binary installation instead"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Download file with progress
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local output="$2"
|
||||
local desc="${3:-file}"
|
||||
|
||||
log_info "Downloading $desc..."
|
||||
|
||||
if command_exists "curl"; then
|
||||
if ! curl -L --fail --progress-bar "$url" -o "$output"; then
|
||||
log_error "Failed to download $desc from $url"
|
||||
return 1
|
||||
fi
|
||||
elif command_exists "wget"; then
|
||||
if ! wget --progress=bar:force "$url" -O "$output"; then
|
||||
log_error "Failed to download $desc from $url"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "Neither curl nor wget is available"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Downloaded $desc"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Extract archive
|
||||
extract_archive() {
|
||||
local archive="$1"
|
||||
local destination="$2"
|
||||
|
||||
log_info "Extracting archive..."
|
||||
|
||||
case "$archive" in
|
||||
*.tar.gz)
|
||||
if ! tar -xzf "$archive" -C "$destination"; then
|
||||
log_error "Failed to extract $archive"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*.zip)
|
||||
if command_exists "unzip"; then
|
||||
if ! unzip -q "$archive" -d "$destination"; then
|
||||
log_error "Failed to extract $archive"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "unzip command not found"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
log_error "Unsupported archive format: $archive"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log_success "Extracted archive"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Get latest release version
|
||||
get_latest_version() {
|
||||
local version=""
|
||||
|
||||
if command_exists "curl"; then
|
||||
version=$(curl -s "https://api.github.com/repos/your-org/nushell-plugins/releases/latest" | \
|
||||
grep '"tag_name":' | \
|
||||
sed -E 's/.*"([^"]+)".*/\1/')
|
||||
elif command_exists "wget"; then
|
||||
version=$(wget -qO- "https://api.github.com/repos/your-org/nushell-plugins/releases/latest" | \
|
||||
grep '"tag_name":' | \
|
||||
sed -E 's/.*"([^"]+)".*/\1/')
|
||||
fi
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
# Fallback to a reasonable default
|
||||
version="v0.107.1"
|
||||
log_warn "Could not detect latest version, using $version"
|
||||
fi
|
||||
|
||||
echo "$version"
|
||||
}
|
||||
|
||||
# Download and install binaries
|
||||
install_from_binaries() {
|
||||
local platform="$1"
|
||||
local version="$2"
|
||||
local install_dir="$3"
|
||||
local include_plugins="$4"
|
||||
|
||||
log_header "Installing from Pre-built Binaries"
|
||||
|
||||
# Create temporary directory
|
||||
mkdir -p "$TEMP_DIR"
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
# Determine archive name and URL
|
||||
local archive_name="nushell-plugins-${platform}-${version}.tar.gz"
|
||||
local download_url="${BINARY_REPO_URL}/${version}/${archive_name}"
|
||||
|
||||
# Download archive
|
||||
if ! download_file "$download_url" "$archive_name" "Nushell distribution"; then
|
||||
log_warn "Binary download failed, trying alternative..."
|
||||
# Try without version prefix
|
||||
archive_name="nushell-plugins-${platform}.tar.gz"
|
||||
download_url="${BINARY_REPO_URL}/latest/${archive_name}"
|
||||
if ! download_file "$download_url" "$archive_name" "Nushell distribution (latest)"; then
|
||||
log_error "Failed to download binaries"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Extract archive
|
||||
if ! extract_archive "$archive_name" "."; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Find extracted directory
|
||||
local extract_dir=""
|
||||
for dir in */; do
|
||||
if [ -d "$dir" ]; then
|
||||
extract_dir="$dir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$extract_dir" ]; then
|
||||
log_error "No extracted directory found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Installing binaries to $install_dir..."
|
||||
|
||||
# Create install directory
|
||||
mkdir -p "$install_dir"
|
||||
|
||||
# Install nushell binary
|
||||
local nu_binary="nu"
|
||||
if [ "$platform" = "windows-x86_64" ]; then
|
||||
nu_binary="nu.exe"
|
||||
fi
|
||||
|
||||
if [ -f "${extract_dir}${nu_binary}" ]; then
|
||||
cp "${extract_dir}${nu_binary}" "$install_dir/"
|
||||
chmod +x "${install_dir}/${nu_binary}"
|
||||
log_success "Installed nushell binary"
|
||||
else
|
||||
log_error "Nushell binary not found in archive"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Install plugins if requested
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
local plugin_count=0
|
||||
for plugin_file in "${extract_dir}"nu_plugin_*; do
|
||||
if [ -f "$plugin_file" ]; then
|
||||
local plugin_name=$(basename "$plugin_file")
|
||||
cp "$plugin_file" "$install_dir/"
|
||||
chmod +x "${install_dir}/${plugin_name}"
|
||||
plugin_count=$((plugin_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $plugin_count -gt 0 ]; then
|
||||
log_success "Installed $plugin_count plugins"
|
||||
else
|
||||
log_warn "No plugins found in archive"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copy configuration files if they exist
|
||||
if [ -d "${extract_dir}config/" ]; then
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
cp -r "${extract_dir}config/"* "$CONFIG_DIR/"
|
||||
log_success "Installed configuration files"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Build and install from source
|
||||
install_from_source() {
|
||||
local install_dir="$1"
|
||||
local include_plugins="$2"
|
||||
|
||||
log_header "Building from Source"
|
||||
|
||||
# Check dependencies
|
||||
if ! check_build_dependencies; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Create temporary directory
|
||||
mkdir -p "$TEMP_DIR"
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
# Clone repository
|
||||
log_info "Cloning repository..."
|
||||
if ! git clone --recursive "$REPO_URL" nushell-plugins; then
|
||||
log_error "Failed to clone repository"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cd nushell-plugins
|
||||
|
||||
# Build nushell
|
||||
log_info "Building nushell..."
|
||||
if command_exists "just"; then
|
||||
if ! just build-nushell; then
|
||||
log_error "Failed to build nushell with just"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
# Fallback to manual build
|
||||
cd nushell
|
||||
if ! cargo build --release --features "plugin,network,sqlite,trash-support,rustls-tls"; then
|
||||
log_error "Failed to build nushell"
|
||||
return 1
|
||||
fi
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# Build plugins if requested
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
log_info "Building plugins..."
|
||||
if command_exists "just"; then
|
||||
if ! just build; then
|
||||
log_warn "Failed to build some plugins"
|
||||
fi
|
||||
else
|
||||
# Build plugins manually
|
||||
for plugin_dir in nu_plugin_*; do
|
||||
if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then
|
||||
log_info "Building $plugin_dir..."
|
||||
cd "$plugin_dir"
|
||||
if cargo build --release; then
|
||||
log_success "Built $plugin_dir"
|
||||
else
|
||||
log_warn "Failed to build $plugin_dir"
|
||||
fi
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install binaries
|
||||
log_info "Installing binaries to $install_dir..."
|
||||
mkdir -p "$install_dir"
|
||||
|
||||
# Install nushell
|
||||
local nu_binary="nushell/target/release/nu"
|
||||
if [ -f "$nu_binary" ]; then
|
||||
cp "$nu_binary" "$install_dir/"
|
||||
chmod +x "${install_dir}/nu"
|
||||
log_success "Installed nushell binary"
|
||||
else
|
||||
log_error "Nushell binary not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Install plugins
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
local plugin_count=0
|
||||
for plugin_dir in nu_plugin_*; do
|
||||
if [ -d "$plugin_dir" ] && [ "$plugin_dir" != "nushell" ]; then
|
||||
local plugin_binary="${plugin_dir}/target/release/${plugin_dir}"
|
||||
if [ -f "$plugin_binary" ]; then
|
||||
cp "$plugin_binary" "$install_dir/"
|
||||
chmod +x "${install_dir}/${plugin_dir}"
|
||||
plugin_count=$((plugin_count + 1))
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [ $plugin_count -gt 0 ]; then
|
||||
log_success "Installed $plugin_count plugins"
|
||||
else
|
||||
log_warn "No plugins were built successfully"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Register plugins with nushell
|
||||
register_plugins() {
|
||||
local install_dir="$1"
|
||||
local nu_binary="${install_dir}/nu"
|
||||
|
||||
if [ ! -f "$nu_binary" ]; then
|
||||
log_error "Nushell binary not found: $nu_binary"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_header "Registering Plugins"
|
||||
|
||||
# Find all plugin binaries
|
||||
local plugin_count=0
|
||||
for plugin_file in "${install_dir}"/nu_plugin_*; do
|
||||
if [ -f "$plugin_file" ] && [ -x "$plugin_file" ]; then
|
||||
local plugin_name=$(basename "$plugin_file")
|
||||
log_info "Registering $plugin_name..."
|
||||
|
||||
if "$nu_binary" -c "plugin add '$plugin_file'"; then
|
||||
log_success "Registered $plugin_name"
|
||||
plugin_count=$((plugin_count + 1))
|
||||
else
|
||||
log_warn "Failed to register $plugin_name"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $plugin_count -gt 0 ]; then
|
||||
log_success "Successfully registered $plugin_count plugins"
|
||||
else
|
||||
log_warn "No plugins were registered"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Update PATH in shell configuration
|
||||
update_shell_path() {
|
||||
local install_dir="$1"
|
||||
|
||||
log_header "Updating Shell Configuration"
|
||||
|
||||
# List of shell configuration files to update
|
||||
local shell_configs=""
|
||||
|
||||
# Detect current shell and add its config file first
|
||||
case "$SHELL" in
|
||||
*/bash) shell_configs="$HOME/.bashrc $HOME/.bash_profile" ;;
|
||||
*/zsh) shell_configs="$HOME/.zshrc" ;;
|
||||
*/fish) shell_configs="$HOME/.config/fish/config.fish" ;;
|
||||
*/nu) shell_configs="$HOME/.config/nushell/env.nu" ;;
|
||||
esac
|
||||
|
||||
# Add common configuration files
|
||||
shell_configs="$shell_configs $HOME/.profile"
|
||||
|
||||
local updated=false
|
||||
|
||||
for config_file in $shell_configs; do
|
||||
if [ -f "$config_file" ] || [ "$config_file" = "$HOME/.bashrc" ] || [ "$config_file" = "$HOME/.profile" ]; then
|
||||
# Check if already in PATH
|
||||
if grep -q "$install_dir" "$config_file" 2>/dev/null; then
|
||||
log_info "PATH already updated in $(basename "$config_file")"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Create config file if it doesn't exist
|
||||
if [ ! -f "$config_file" ]; then
|
||||
touch "$config_file"
|
||||
fi
|
||||
|
||||
# Add PATH update to config file
|
||||
case "$config_file" in
|
||||
*.fish)
|
||||
echo "fish_add_path $install_dir" >> "$config_file"
|
||||
;;
|
||||
*/env.nu)
|
||||
echo "\$env.PATH = (\$env.PATH | split row (char esep) | append \"$install_dir\" | uniq)" >> "$config_file"
|
||||
;;
|
||||
*)
|
||||
echo "" >> "$config_file"
|
||||
echo "# Added by nushell installer" >> "$config_file"
|
||||
echo "export PATH=\"$install_dir:\$PATH\"" >> "$config_file"
|
||||
;;
|
||||
esac
|
||||
|
||||
log_success "Updated $(basename "$config_file")"
|
||||
updated=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$updated" = "true" ]; then
|
||||
log_info "Please restart your terminal or run 'source ~/.bashrc' to update PATH"
|
||||
else
|
||||
log_warn "No shell configuration files were updated"
|
||||
log_info "Please manually add $install_dir to your PATH"
|
||||
fi
|
||||
|
||||
# Update current session PATH
|
||||
export PATH="$install_dir:$PATH"
|
||||
log_success "Updated PATH for current session"
|
||||
}
|
||||
|
||||
# Create initial nushell configuration
|
||||
create_nushell_config() {
|
||||
log_header "Creating Nushell Configuration"
|
||||
|
||||
# Create config directory
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
# Create basic config.nu if it doesn't exist
|
||||
local config_file="$CONFIG_DIR/config.nu"
|
||||
if [ ! -f "$config_file" ]; then
|
||||
cat > "$config_file" << 'EOF'
|
||||
# Nushell Configuration
|
||||
# Created by nushell-plugins installer
|
||||
|
||||
# Set up basic configuration
|
||||
$env.config = {
|
||||
show_banner: false
|
||||
edit_mode: emacs
|
||||
shell_integration: true
|
||||
|
||||
table: {
|
||||
mode: rounded
|
||||
index_mode: always
|
||||
show_empty: true
|
||||
padding: { left: 1, right: 1 }
|
||||
}
|
||||
|
||||
completions: {
|
||||
case_sensitive: false
|
||||
quick: true
|
||||
partial: true
|
||||
algorithm: "prefix"
|
||||
}
|
||||
|
||||
history: {
|
||||
max_size: 10000
|
||||
sync_on_enter: true
|
||||
file_format: "plaintext"
|
||||
}
|
||||
|
||||
filesize: {
|
||||
metric: false
|
||||
format: "auto"
|
||||
}
|
||||
}
|
||||
|
||||
# Load custom commands and aliases
|
||||
# Add your custom configuration below
|
||||
EOF
|
||||
log_success "Created config.nu"
|
||||
else
|
||||
log_info "config.nu already exists, skipping"
|
||||
fi
|
||||
|
||||
# Create basic env.nu if it doesn't exist
|
||||
local env_file="$CONFIG_DIR/env.nu"
|
||||
if [ ! -f "$env_file" ]; then
|
||||
cat > "$env_file" << 'EOF'
|
||||
# Nushell Environment Configuration
|
||||
# Created by nushell-plugins installer
|
||||
|
||||
# Environment variables
|
||||
$env.EDITOR = "nano"
|
||||
$env.BROWSER = "firefox"
|
||||
|
||||
# Nushell specific environment
|
||||
$env.NU_LIB_DIRS = [
|
||||
($nu.config-path | path dirname | path join "scripts")
|
||||
]
|
||||
|
||||
$env.NU_PLUGIN_DIRS = [
|
||||
($nu.config-path | path dirname | path join "plugins")
|
||||
]
|
||||
|
||||
# Add your custom environment variables below
|
||||
EOF
|
||||
log_success "Created env.nu"
|
||||
else
|
||||
log_info "env.nu already exists, skipping"
|
||||
fi
|
||||
|
||||
# Create scripts directory
|
||||
local scripts_dir="$CONFIG_DIR/scripts"
|
||||
mkdir -p "$scripts_dir"
|
||||
|
||||
# Create plugins directory
|
||||
local plugins_dir="$CONFIG_DIR/plugins"
|
||||
mkdir -p "$plugins_dir"
|
||||
}
|
||||
|
||||
# Verify installation
|
||||
verify_installation() {
|
||||
local install_dir="$1"
|
||||
local nu_binary="${install_dir}/nu"
|
||||
|
||||
log_header "Verifying Installation"
|
||||
|
||||
# Check if nushell binary exists and is executable
|
||||
if [ ! -f "$nu_binary" ]; then
|
||||
log_error "Nushell binary not found: $nu_binary"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$nu_binary" ]; then
|
||||
log_error "Nushell binary is not executable: $nu_binary"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test nushell version
|
||||
log_info "Testing nushell binary..."
|
||||
local version_output
|
||||
if version_output=$("$nu_binary" --version 2>&1); then
|
||||
log_success "Nushell version: $version_output"
|
||||
else
|
||||
log_error "Failed to run nushell binary"
|
||||
log_error "Output: $version_output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test basic nushell command
|
||||
log_info "Testing basic nushell functionality..."
|
||||
if "$nu_binary" -c "echo 'Hello from Nushell'" >/dev/null 2>&1; then
|
||||
log_success "Basic nushell functionality works"
|
||||
else
|
||||
log_error "Basic nushell functionality failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# List registered plugins
|
||||
log_info "Checking registered plugins..."
|
||||
local plugin_output
|
||||
if plugin_output=$("$nu_binary" -c "plugin list" 2>&1); then
|
||||
local plugin_count=$(echo "$plugin_output" | grep -c "nu_plugin_" || true)
|
||||
if [ "$plugin_count" -gt 0 ]; then
|
||||
log_success "Found $plugin_count registered plugins"
|
||||
else
|
||||
log_warn "No plugins are registered"
|
||||
fi
|
||||
else
|
||||
log_warn "Could not check plugin status"
|
||||
fi
|
||||
|
||||
# Check PATH
|
||||
log_info "Checking PATH configuration..."
|
||||
if command -v nu >/dev/null 2>&1; then
|
||||
log_success "Nushell is available in PATH"
|
||||
else
|
||||
log_warn "Nushell is not in PATH. You may need to restart your terminal."
|
||||
fi
|
||||
|
||||
log_success "Installation verification complete!"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Uninstall function
|
||||
uninstall_nushell() {
|
||||
log_header "Uninstalling Nushell"
|
||||
|
||||
local removed_files=0
|
||||
|
||||
# Remove from user directory
|
||||
if [ -d "$INSTALL_DIR_USER" ]; then
|
||||
for binary in nu nu_plugin_*; do
|
||||
local file_path="$INSTALL_DIR_USER/$binary"
|
||||
if [ -f "$file_path" ]; then
|
||||
rm -f "$file_path"
|
||||
log_success "Removed $binary from $INSTALL_DIR_USER"
|
||||
removed_files=$((removed_files + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Remove from system directory (if accessible)
|
||||
if [ -w "$INSTALL_DIR_SYSTEM" ] 2>/dev/null; then
|
||||
for binary in nu nu_plugin_*; do
|
||||
local file_path="$INSTALL_DIR_SYSTEM/$binary"
|
||||
if [ -f "$file_path" ]; then
|
||||
rm -f "$file_path"
|
||||
log_success "Removed $binary from $INSTALL_DIR_SYSTEM"
|
||||
removed_files=$((removed_files + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Option to remove configuration
|
||||
printf "Remove nushell configuration directory ($CONFIG_DIR)? [y/N]: "
|
||||
read -r response
|
||||
case "$response" in
|
||||
[yY]|[yY][eE][sS])
|
||||
if [ -d "$CONFIG_DIR" ]; then
|
||||
rm -rf "$CONFIG_DIR"
|
||||
log_success "Removed configuration directory"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
log_info "Configuration directory preserved"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ $removed_files -gt 0 ]; then
|
||||
log_success "Uninstallation complete ($removed_files files removed)"
|
||||
log_warn "You may need to manually remove PATH entries from your shell configuration"
|
||||
else
|
||||
log_warn "No nushell files found to remove"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main installation function
|
||||
main() {
|
||||
local install_mode="user"
|
||||
local modify_path="true"
|
||||
local create_config="true"
|
||||
local include_plugins="true"
|
||||
local build_from_source="false"
|
||||
local verify_install="false"
|
||||
local do_uninstall="false"
|
||||
local version=""
|
||||
|
||||
# Parse command line arguments
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--system)
|
||||
install_mode="system"
|
||||
shift
|
||||
;;
|
||||
--user)
|
||||
install_mode="user"
|
||||
shift
|
||||
;;
|
||||
--no-path)
|
||||
modify_path="false"
|
||||
shift
|
||||
;;
|
||||
--no-config)
|
||||
create_config="false"
|
||||
shift
|
||||
;;
|
||||
--no-plugins)
|
||||
include_plugins="false"
|
||||
shift
|
||||
;;
|
||||
--build-from-source)
|
||||
build_from_source="true"
|
||||
shift
|
||||
;;
|
||||
--verify)
|
||||
verify_install="true"
|
||||
shift
|
||||
;;
|
||||
--uninstall)
|
||||
do_uninstall="true"
|
||||
shift
|
||||
;;
|
||||
--version)
|
||||
version="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Handle uninstall
|
||||
if [ "$do_uninstall" = "true" ]; then
|
||||
uninstall_nushell
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Show header
|
||||
log_header "Nushell + Plugins Installer"
|
||||
log_info "Universal bootstrap installer for Nushell and plugins"
|
||||
log_info ""
|
||||
|
||||
# Detect platform
|
||||
local platform
|
||||
platform=$(detect_platform)
|
||||
log_info "Detected platform: $platform"
|
||||
|
||||
# Determine installation directory
|
||||
local install_dir
|
||||
if [ "$install_mode" = "system" ]; then
|
||||
install_dir="$INSTALL_DIR_SYSTEM"
|
||||
if [ "$(id -u)" != "0" ] && [ ! -w "$(dirname "$install_dir")" ]; then
|
||||
log_error "System installation requires root privileges"
|
||||
log_info "Run with sudo or use --user for user installation"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
install_dir="$INSTALL_DIR_USER"
|
||||
fi
|
||||
|
||||
log_info "Installing to: $install_dir"
|
||||
|
||||
# Get version if not specified
|
||||
if [ -z "$version" ]; then
|
||||
version=$(get_latest_version)
|
||||
fi
|
||||
log_info "Version: $version"
|
||||
|
||||
# Cleanup function
|
||||
cleanup() {
|
||||
if [ -d "$TEMP_DIR" ]; then
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Install based on method
|
||||
if [ "$build_from_source" = "true" ]; then
|
||||
if ! install_from_source "$install_dir" "$include_plugins"; then
|
||||
log_error "Source installation failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! install_from_binaries "$platform" "$version" "$install_dir" "$include_plugins"; then
|
||||
log_error "Binary installation failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Register plugins
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
register_plugins "$install_dir"
|
||||
fi
|
||||
|
||||
# Update PATH
|
||||
if [ "$modify_path" = "true" ]; then
|
||||
update_shell_path "$install_dir"
|
||||
fi
|
||||
|
||||
# Create configuration
|
||||
if [ "$create_config" = "true" ]; then
|
||||
create_nushell_config
|
||||
fi
|
||||
|
||||
# Verify installation
|
||||
if [ "$verify_install" = "true" ]; then
|
||||
if ! verify_installation "$install_dir"; then
|
||||
log_error "Installation verification failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Final success message
|
||||
log_header "Installation Complete!"
|
||||
log_success "Nushell has been successfully installed to $install_dir"
|
||||
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
log_success "Plugins have been registered with Nushell"
|
||||
fi
|
||||
|
||||
if [ "$modify_path" = "true" ]; then
|
||||
log_info "To use Nushell, restart your terminal or run:"
|
||||
log_info " source ~/.bashrc # or your shell's config file"
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
log_info "Try running: nu --version"
|
||||
log_info "Or start Nushell with: nu"
|
||||
|
||||
if [ "$include_plugins" = "true" ]; then
|
||||
log_info "Check plugins with: nu -c 'plugin list'"
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
log_info "For more information, visit: https://nushell.sh"
|
||||
log_info ""
|
||||
log_success "Happy shell scripting! 🚀"
|
||||
}
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
528
scripts/templates/uninstall.ps1
Normal file
528
scripts/templates/uninstall.ps1
Normal file
@ -0,0 +1,528 @@
|
||||
# Universal Nushell + Plugins Uninstaller for Windows
|
||||
# PowerShell script that cleanly removes Nushell and plugins installation
|
||||
#
|
||||
# This script:
|
||||
# - Detects installation locations (user or system Program Files)
|
||||
# - Removes Nushell binary and plugin binaries
|
||||
# - Cleans up configuration files (with backup option)
|
||||
# - Removes PATH entries from environment variables
|
||||
# - Unregisters plugins from Nushell
|
||||
# - Provides detailed removal report
|
||||
|
||||
param(
|
||||
[switch]$Help,
|
||||
[switch]$Yes,
|
||||
[switch]$KeepConfig,
|
||||
[switch]$BackupConfig,
|
||||
[switch]$System,
|
||||
[switch]$User,
|
||||
[switch]$DryRun,
|
||||
[switch]$Debug
|
||||
)
|
||||
|
||||
# Configuration
|
||||
$InstallDirUser = "$env:USERPROFILE\.local\bin"
|
||||
$InstallDirSystem = "$env:ProgramFiles\nushell"
|
||||
$ConfigDir = "$env:APPDATA\nushell"
|
||||
$BackupSuffix = "uninstall-backup-$(Get-Date -Format 'yyyyMMdd_HHmmss')"
|
||||
|
||||
# Logging functions
|
||||
function Write-Info {
|
||||
param([string]$Message)
|
||||
Write-Host "ℹ️ $Message" -ForegroundColor Blue
|
||||
}
|
||||
|
||||
function Write-Success {
|
||||
param([string]$Message)
|
||||
Write-Host "✅ $Message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Warning {
|
||||
param([string]$Message)
|
||||
Write-Host "⚠️ $Message" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
function Write-Error {
|
||||
param([string]$Message)
|
||||
Write-Host "❌ $Message" -ForegroundColor Red
|
||||
}
|
||||
|
||||
function Write-Debug {
|
||||
param([string]$Message)
|
||||
if ($Debug) {
|
||||
Write-Host "🐛 DEBUG: $Message" -ForegroundColor Magenta
|
||||
}
|
||||
}
|
||||
|
||||
# Usage information
|
||||
function Show-Usage {
|
||||
Write-Host @"
|
||||
Nushell Full Distribution Uninstaller for Windows
|
||||
|
||||
USAGE:
|
||||
.\uninstall.ps1 [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
-Help Show this help message
|
||||
-Yes Non-interactive mode (assume yes to prompts)
|
||||
-KeepConfig Keep configuration files (don't remove $ConfigDir)
|
||||
-BackupConfig Backup configuration before removal
|
||||
-System Remove from system location (Program Files) - requires admin
|
||||
-User Remove from user location (~\.local\bin) - default
|
||||
-DryRun Show what would be removed without actually removing
|
||||
-Debug Enable debug output
|
||||
|
||||
EXAMPLES:
|
||||
.\uninstall.ps1 # Interactive removal from user location
|
||||
.\uninstall.ps1 -Yes # Non-interactive removal
|
||||
.\uninstall.ps1 -BackupConfig -Yes # Remove with config backup
|
||||
.\uninstall.ps1 -System # Remove system installation (needs admin)
|
||||
.\uninstall.ps1 -DryRun # Show what would be removed
|
||||
|
||||
"@
|
||||
}
|
||||
|
||||
# Show usage and exit if help requested
|
||||
if ($Help) {
|
||||
Show-Usage
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Determine installation directory
|
||||
$Interactive = -not $Yes
|
||||
$IsSystemInstall = $System
|
||||
|
||||
if ($IsSystemInstall) {
|
||||
$InstallDir = $InstallDirSystem
|
||||
Write-Info "Targeting system installation: $InstallDir"
|
||||
|
||||
# Check if we have admin privileges
|
||||
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
||||
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
|
||||
if (-not $isAdmin -and -not $DryRun) {
|
||||
Write-Warning "System installation requires administrative privileges"
|
||||
if ($Interactive) {
|
||||
$response = Read-Host "Continue with elevated privileges? [y/N]"
|
||||
if ($response -notmatch '^[yY]([eE][sS])?$') {
|
||||
Write-Info "Uninstallation cancelled"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# Re-run as administrator
|
||||
if (-not $isAdmin) {
|
||||
Write-Info "Re-running with elevated privileges..."
|
||||
$arguments = $MyInvocation.BoundParameters.Keys | ForEach-Object { "-$_" }
|
||||
Start-Process PowerShell.exe -Argument "-File `"$PSCommandPath`" $($arguments -join ' ')" -Verb RunAs
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$InstallDir = $InstallDirUser
|
||||
Write-Info "Targeting user installation: $InstallDir"
|
||||
}
|
||||
|
||||
# Detection functions
|
||||
function Get-NushellInstallation {
|
||||
param([string]$InstallDir)
|
||||
|
||||
$foundItems = @()
|
||||
Write-Debug "Detecting Nushell installation in $InstallDir"
|
||||
|
||||
# Check for nu binary
|
||||
$nuPath = Join-Path $InstallDir "nu.exe"
|
||||
if (Test-Path $nuPath) {
|
||||
$foundItems += "nu.exe"
|
||||
Write-Debug "Found nu binary: $nuPath"
|
||||
}
|
||||
|
||||
# Check for plugin binaries
|
||||
$pluginPattern = Join-Path $InstallDir "nu_plugin_*.exe"
|
||||
$pluginBinaries = Get-ChildItem -Path $pluginPattern -ErrorAction SilentlyContinue
|
||||
foreach ($plugin in $pluginBinaries) {
|
||||
$foundItems += $plugin.Name
|
||||
Write-Debug "Found plugin binary: $($plugin.FullName)"
|
||||
}
|
||||
|
||||
return $foundItems
|
||||
}
|
||||
|
||||
function Get-ConfigInstallation {
|
||||
param([string]$ConfigDir)
|
||||
|
||||
$foundItems = @()
|
||||
Write-Debug "Detecting configuration in $ConfigDir"
|
||||
|
||||
if (Test-Path $ConfigDir) {
|
||||
# Check for main config files
|
||||
$configFiles = @("config.nu", "env.nu", "distribution_config.toml")
|
||||
foreach ($configFile in $configFiles) {
|
||||
$configPath = Join-Path $ConfigDir $configFile
|
||||
if (Test-Path $configPath) {
|
||||
$foundItems += $configFile
|
||||
Write-Debug "Found config file: $configPath"
|
||||
}
|
||||
}
|
||||
|
||||
# Check for plugin registration
|
||||
$pluginFiles = @("plugin.nu", "registry.dat")
|
||||
foreach ($pluginFile in $pluginFiles) {
|
||||
$pluginPath = Join-Path $ConfigDir $pluginFile
|
||||
if (Test-Path $pluginPath) {
|
||||
$foundItems += "plugin-registry"
|
||||
Write-Debug "Found plugin registry files"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $foundItems
|
||||
}
|
||||
|
||||
function Get-PathEntries {
|
||||
param([string]$InstallDir)
|
||||
|
||||
Write-Debug "Detecting PATH entries for $InstallDir"
|
||||
$foundIn = @()
|
||||
|
||||
# Check user PATH
|
||||
$userPath = [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::User)
|
||||
if ($userPath -and $userPath.Contains($InstallDir)) {
|
||||
$foundIn += "User PATH"
|
||||
Write-Debug "Found in User PATH"
|
||||
}
|
||||
|
||||
# Check system PATH (if we have access)
|
||||
try {
|
||||
$systemPath = [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine)
|
||||
if ($systemPath -and $systemPath.Contains($InstallDir)) {
|
||||
$foundIn += "System PATH"
|
||||
Write-Debug "Found in System PATH"
|
||||
}
|
||||
} catch {
|
||||
Write-Debug "Cannot access System PATH (insufficient privileges)"
|
||||
}
|
||||
|
||||
return $foundIn
|
||||
}
|
||||
|
||||
# Removal functions
|
||||
function Remove-Binaries {
|
||||
param(
|
||||
[string]$InstallDir,
|
||||
[array]$Binaries
|
||||
)
|
||||
|
||||
Write-Info "Removing binaries from $InstallDir..."
|
||||
$removedCount = 0
|
||||
|
||||
foreach ($binary in $Binaries) {
|
||||
$binaryPath = Join-Path $InstallDir $binary
|
||||
if (Test-Path $binaryPath) {
|
||||
Write-Info "Removing binary: $binary"
|
||||
if (-not $DryRun) {
|
||||
try {
|
||||
Remove-Item -Path $binaryPath -Force
|
||||
Write-Success "Removed: $binary"
|
||||
$removedCount++
|
||||
} catch {
|
||||
Write-Error "Failed to remove: $binary - $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Info "[DRY RUN] Would remove: $binaryPath"
|
||||
$removedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Success "Removed $removedCount binaries"
|
||||
}
|
||||
|
||||
function Backup-Configuration {
|
||||
param([string]$ConfigDir)
|
||||
|
||||
if (Test-Path $ConfigDir) {
|
||||
$backupDir = "$ConfigDir.$BackupSuffix"
|
||||
Write-Info "Backing up configuration to: $backupDir"
|
||||
|
||||
if (-not $DryRun) {
|
||||
try {
|
||||
Copy-Item -Path $ConfigDir -Destination $backupDir -Recurse -Force
|
||||
Write-Success "Configuration backed up successfully"
|
||||
} catch {
|
||||
Write-Error "Failed to backup configuration: $($_.Exception.Message)"
|
||||
return $false
|
||||
}
|
||||
} else {
|
||||
Write-Info "[DRY RUN] Would backup configuration to: $backupDir"
|
||||
}
|
||||
return $true
|
||||
} else {
|
||||
Write-Info "No configuration directory to backup"
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-Configuration {
|
||||
param(
|
||||
[string]$ConfigDir,
|
||||
[array]$ConfigFiles
|
||||
)
|
||||
|
||||
if ($KeepConfig) {
|
||||
Write-Info "Keeping configuration files as requested"
|
||||
return
|
||||
}
|
||||
|
||||
if ($BackupConfig) {
|
||||
Backup-Configuration $ConfigDir
|
||||
}
|
||||
|
||||
if (Test-Path $ConfigDir) {
|
||||
Write-Info "Removing configuration directory: $ConfigDir"
|
||||
|
||||
# Ask for confirmation if interactive and not just removing empty dir
|
||||
if ($Interactive -and $ConfigFiles.Count -gt 0) {
|
||||
$response = Read-Host "Remove configuration directory $ConfigDir? [y/N]"
|
||||
if ($response -notmatch '^[yY]([eE][sS])?$') {
|
||||
Write-Info "Keeping configuration directory"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $DryRun) {
|
||||
try {
|
||||
Remove-Item -Path $ConfigDir -Recurse -Force
|
||||
Write-Success "Configuration directory removed"
|
||||
} catch {
|
||||
Write-Error "Failed to remove configuration directory: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Info "[DRY RUN] Would remove configuration directory: $ConfigDir"
|
||||
}
|
||||
} else {
|
||||
Write-Info "No configuration directory found"
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-PathEntries {
|
||||
param(
|
||||
[string]$InstallDir,
|
||||
[array]$PathLocations
|
||||
)
|
||||
|
||||
if ($PathLocations.Count -eq 0) {
|
||||
Write-Info "No PATH entries found"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Info "Removing PATH entries..."
|
||||
|
||||
foreach ($location in $PathLocations) {
|
||||
Write-Info "Cleaning PATH entries from: $location"
|
||||
|
||||
$target = if ($location -eq "User PATH") {
|
||||
[EnvironmentVariableTarget]::User
|
||||
} else {
|
||||
[EnvironmentVariableTarget]::Machine
|
||||
}
|
||||
|
||||
if (-not $DryRun) {
|
||||
try {
|
||||
$currentPath = [Environment]::GetEnvironmentVariable("Path", $target)
|
||||
$pathEntries = $currentPath -split ';'
|
||||
$newPathEntries = $pathEntries | Where-Object { $_ -ne $InstallDir }
|
||||
$newPath = $newPathEntries -join ';'
|
||||
|
||||
[Environment]::SetEnvironmentVariable("Path", $newPath, $target)
|
||||
Write-Success "Cleaned PATH entries from: $location"
|
||||
} catch {
|
||||
Write-Error "Failed to clean PATH entries from: $location - $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Info "[DRY RUN] Would remove PATH entries from: $location"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Unregister-Plugins {
|
||||
param([string]$InstallDir)
|
||||
|
||||
# Only try to unregister if nu is still available somewhere
|
||||
$nuBinary = $null
|
||||
|
||||
if (Get-Command nu -ErrorAction SilentlyContinue) {
|
||||
$nuBinary = (Get-Command nu).Path
|
||||
} elseif (Test-Path (Join-Path $InstallDir "nu.exe")) {
|
||||
$nuBinary = Join-Path $InstallDir "nu.exe"
|
||||
} else {
|
||||
Write-Info "Nushell not available for plugin unregistration"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Info "Attempting to unregister plugins..."
|
||||
|
||||
if (-not $DryRun) {
|
||||
try {
|
||||
# Try to get list of registered plugins
|
||||
$registeredPlugins = & $nuBinary -c "plugin list | get name" 2>$null
|
||||
|
||||
if ($registeredPlugins) {
|
||||
Write-Info "Unregistering plugins from nushell..."
|
||||
foreach ($plugin in $registeredPlugins) {
|
||||
try {
|
||||
& $nuBinary -c "plugin rm $plugin" 2>$null
|
||||
} catch {
|
||||
Write-Warning "Could not unregister plugin: $plugin"
|
||||
}
|
||||
}
|
||||
Write-Success "Plugin unregistration completed"
|
||||
} else {
|
||||
Write-Info "No registered plugins found"
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Could not retrieve plugin list"
|
||||
}
|
||||
} else {
|
||||
Write-Info "[DRY RUN] Would attempt to unregister plugins"
|
||||
}
|
||||
}
|
||||
|
||||
# Main uninstallation process
|
||||
function Main {
|
||||
Write-Info "🗑️ Nushell Full Distribution Uninstaller"
|
||||
Write-Info "========================================"
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Warning "DRY RUN MODE - No files will be modified"
|
||||
}
|
||||
|
||||
# Detect current installation
|
||||
Write-Info ""
|
||||
Write-Info "🔍 Detecting current installation..."
|
||||
|
||||
$userBinaries = Get-NushellInstallation $InstallDirUser
|
||||
$systemBinaries = Get-NushellInstallation $InstallDirSystem
|
||||
$configFiles = Get-ConfigInstallation $ConfigDir
|
||||
$userPathEntries = Get-PathEntries $InstallDirUser
|
||||
$systemPathEntries = Get-PathEntries $InstallDirSystem
|
||||
|
||||
# Determine what we're removing based on target
|
||||
if ($IsSystemInstall) {
|
||||
$binaries = $systemBinaries
|
||||
$pathEntries = $systemPathEntries
|
||||
} else {
|
||||
$binaries = $userBinaries
|
||||
$pathEntries = $userPathEntries
|
||||
}
|
||||
|
||||
# Show detection results
|
||||
Write-Info "Installation Status:"
|
||||
if ($userBinaries.Count -gt 0) {
|
||||
Write-Info " 📁 User binaries ($InstallDirUser): $($userBinaries -join ', ')"
|
||||
} else {
|
||||
Write-Info " 📁 User binaries ($InstallDirUser): none found"
|
||||
}
|
||||
|
||||
if ($systemBinaries.Count -gt 0) {
|
||||
Write-Info " 📁 System binaries ($InstallDirSystem): $($systemBinaries -join ', ')"
|
||||
} else {
|
||||
Write-Info " 📁 System binaries ($InstallDirSystem): none found"
|
||||
}
|
||||
|
||||
if ($configFiles.Count -gt 0) {
|
||||
Write-Info " ⚙️ Configuration ($ConfigDir): $($configFiles -join ', ')"
|
||||
} else {
|
||||
Write-Info " ⚙️ Configuration ($ConfigDir): none found"
|
||||
}
|
||||
|
||||
if ($pathEntries.Count -gt 0) {
|
||||
Write-Info " 🛣️ PATH entries: $($pathEntries -join ', ')"
|
||||
} else {
|
||||
Write-Info " 🛣️ PATH entries: none found"
|
||||
}
|
||||
|
||||
# Check if anything was found
|
||||
if ($binaries.Count -eq 0 -and $configFiles.Count -eq 0 -and $pathEntries.Count -eq 0) {
|
||||
Write-Warning "No Nushell installation detected"
|
||||
if ($Interactive) {
|
||||
$response = Read-Host "Continue anyway? [y/N]"
|
||||
if ($response -notmatch '^[yY]([eE][sS])?$') {
|
||||
Write-Info "Uninstallation cancelled"
|
||||
exit 0
|
||||
}
|
||||
} else {
|
||||
Write-Info "Nothing to remove"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# Confirmation prompt
|
||||
if ($Interactive) {
|
||||
Write-Info ""
|
||||
Write-Warning "This will remove the detected Nushell installation components."
|
||||
if ($KeepConfig) {
|
||||
Write-Info "Configuration files will be kept as requested."
|
||||
} elseif ($BackupConfig) {
|
||||
Write-Info "Configuration files will be backed up before removal."
|
||||
}
|
||||
|
||||
$response = Read-Host "Proceed with uninstallation? [y/N]"
|
||||
if ($response -notmatch '^[yY]([eE][sS])?$') {
|
||||
Write-Info "Uninstallation cancelled"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# Perform uninstallation
|
||||
Write-Info ""
|
||||
Write-Info "🗑️ Starting uninstallation..."
|
||||
|
||||
# Unregister plugins before removing binaries
|
||||
if ($binaries.Count -gt 0) {
|
||||
Unregister-Plugins $InstallDir
|
||||
}
|
||||
|
||||
# Remove binaries
|
||||
if ($binaries.Count -gt 0) {
|
||||
Remove-Binaries $InstallDir $binaries
|
||||
}
|
||||
|
||||
# Remove configuration
|
||||
if ($configFiles.Count -gt 0) {
|
||||
Remove-Configuration $ConfigDir $configFiles
|
||||
}
|
||||
|
||||
# Clean PATH entries
|
||||
if ($pathEntries.Count -gt 0) {
|
||||
Remove-PathEntries $InstallDir $pathEntries
|
||||
}
|
||||
|
||||
# Final summary
|
||||
Write-Info ""
|
||||
Write-Success "🎉 Uninstallation completed!"
|
||||
Write-Info "Summary:"
|
||||
Write-Info " 🗑️ Removed from: $InstallDir"
|
||||
if ($configFiles.Count -gt 0 -and -not $KeepConfig) {
|
||||
Write-Info " 🗑️ Configuration removed: $ConfigDir"
|
||||
} elseif ($KeepConfig) {
|
||||
Write-Info " 💾 Configuration kept: $ConfigDir"
|
||||
}
|
||||
if ($BackupConfig) {
|
||||
Write-Info " 💾 Configuration backed up with suffix: .$BackupSuffix"
|
||||
}
|
||||
|
||||
Write-Info ""
|
||||
Write-Info "🔄 To complete removal:"
|
||||
Write-Info "1. Restart your terminal or refresh environment variables"
|
||||
Write-Info "2. Verify removal: Get-Command nu (should return nothing)"
|
||||
|
||||
if ($KeepConfig -or $BackupConfig) {
|
||||
Write-Info ""
|
||||
Write-Info "📝 Note: Configuration files were preserved as requested"
|
||||
}
|
||||
}
|
||||
|
||||
# Run main function
|
||||
Main
|
563
scripts/templates/uninstall.sh
Executable file
563
scripts/templates/uninstall.sh
Executable file
@ -0,0 +1,563 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Universal Nushell + Plugins Uninstaller
|
||||
# POSIX compliant shell script that cleanly removes Nushell and plugins installation
|
||||
#
|
||||
# This script:
|
||||
# - Detects installation locations (user ~/.local/bin or system /usr/local/bin)
|
||||
# - Removes Nushell binary and plugin binaries
|
||||
# - Cleans up configuration files (with backup option)
|
||||
# - Removes PATH entries from shell configuration files
|
||||
# - Unregisters plugins from Nushell
|
||||
# - Provides detailed removal report
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Configuration
|
||||
INSTALL_DIR_USER="$HOME/.local/bin"
|
||||
INSTALL_DIR_SYSTEM="/usr/local/bin"
|
||||
CONFIG_DIR="$HOME/.config/nushell"
|
||||
BACKUP_SUFFIX="uninstall-backup-$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
PURPLE='\033[0;35m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
printf "${BLUE}ℹ️ %s${NC}\\n" "$1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
printf "${GREEN}✅ %s${NC}\\n" "$1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
printf "${YELLOW}⚠️ %s${NC}\\n" "$1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
printf "${RED}❌ %s${NC}\\n" "$1" >&2
|
||||
}
|
||||
|
||||
log_debug() {
|
||||
if [ "$DEBUG" = "1" ]; then
|
||||
printf "${PURPLE}🐛 DEBUG: %s${NC}\\n" "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Usage information
|
||||
usage() {
|
||||
cat << EOF
|
||||
Nushell Full Distribution Uninstaller
|
||||
|
||||
USAGE:
|
||||
$0 [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
-y, --yes Non-interactive mode (assume yes to prompts)
|
||||
-k, --keep-config Keep configuration files (don't remove ~/.config/nushell)
|
||||
-b, --backup-config Backup configuration before removal
|
||||
--system Remove from system location (/usr/local/bin) - requires sudo
|
||||
--user Remove from user location (~/.local/bin) - default
|
||||
--dry-run Show what would be removed without actually removing
|
||||
--debug Enable debug output
|
||||
|
||||
EXAMPLES:
|
||||
$0 # Interactive removal from user location
|
||||
$0 -y # Non-interactive removal
|
||||
$0 --backup-config -y # Remove with config backup
|
||||
$0 --system # Remove system installation (needs sudo)
|
||||
$0 --dry-run # Show what would be removed
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
INTERACTIVE=1
|
||||
KEEP_CONFIG=0
|
||||
BACKUP_CONFIG=0
|
||||
SYSTEM_INSTALL=0
|
||||
DRY_RUN=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-y|--yes)
|
||||
INTERACTIVE=0
|
||||
shift
|
||||
;;
|
||||
-k|--keep-config)
|
||||
KEEP_CONFIG=1
|
||||
shift
|
||||
;;
|
||||
-b|--backup-config)
|
||||
BACKUP_CONFIG=1
|
||||
shift
|
||||
;;
|
||||
--system)
|
||||
SYSTEM_INSTALL=1
|
||||
shift
|
||||
;;
|
||||
--user)
|
||||
SYSTEM_INSTALL=0
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift
|
||||
;;
|
||||
--debug)
|
||||
DEBUG=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Determine installation directory
|
||||
if [ "$SYSTEM_INSTALL" = "1" ]; then
|
||||
INSTALL_DIR="$INSTALL_DIR_SYSTEM"
|
||||
log_info "Targeting system installation: $INSTALL_DIR"
|
||||
|
||||
# Check if we need sudo
|
||||
if [ "$(id -u)" -ne 0 ] && [ "$DRY_RUN" = "0" ]; then
|
||||
log_warn "System installation requires administrative privileges"
|
||||
if [ "$INTERACTIVE" = "1" ]; then
|
||||
printf "Continue with sudo? [y/N]: "
|
||||
read -r response
|
||||
case "$response" in
|
||||
[yY]|[yY][eE][sS])
|
||||
;;
|
||||
*)
|
||||
log_info "Uninstallation cancelled"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Re-run with sudo if not already root
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
log_info "Re-running with sudo..."
|
||||
exec sudo "$0" "$@"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
INSTALL_DIR="$INSTALL_DIR_USER"
|
||||
log_info "Targeting user installation: $INSTALL_DIR"
|
||||
fi
|
||||
|
||||
# Detection functions
|
||||
detect_nushell_installation() {
|
||||
local install_dir="$1"
|
||||
local found_items=""
|
||||
|
||||
log_debug "Detecting Nushell installation in $install_dir"
|
||||
|
||||
# Check for nu binary
|
||||
if [ -f "$install_dir/nu" ]; then
|
||||
found_items="$found_items nu"
|
||||
log_debug "Found nu binary: $install_dir/nu"
|
||||
fi
|
||||
|
||||
# Check for plugin binaries
|
||||
for plugin_binary in "$install_dir"/nu_plugin_*; do
|
||||
if [ -f "$plugin_binary" ]; then
|
||||
local plugin_name=$(basename "$plugin_binary")
|
||||
found_items="$found_items $plugin_name"
|
||||
log_debug "Found plugin binary: $plugin_binary"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$found_items"
|
||||
}
|
||||
|
||||
detect_config_installation() {
|
||||
local config_dir="$1"
|
||||
local found_items=""
|
||||
|
||||
log_debug "Detecting configuration in $config_dir"
|
||||
|
||||
if [ -d "$config_dir" ]; then
|
||||
# Check for main config files
|
||||
for config_file in config.nu env.nu distribution_config.toml; do
|
||||
if [ -f "$config_dir/$config_file" ]; then
|
||||
found_items="$found_items $config_file"
|
||||
log_debug "Found config file: $config_dir/$config_file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for plugin registration
|
||||
if [ -f "$config_dir/plugin.nu" ] || [ -f "$config_dir/registry.dat" ]; then
|
||||
found_items="$found_items plugin-registry"
|
||||
log_debug "Found plugin registry files"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$found_items"
|
||||
}
|
||||
|
||||
detect_shell_profile_entries() {
|
||||
local install_dir="$1"
|
||||
local found_profiles=""
|
||||
|
||||
log_debug "Detecting shell profile entries for $install_dir"
|
||||
|
||||
# Check common shell profile files
|
||||
for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile" "$HOME/.bash_profile"; do
|
||||
if [ -f "$profile" ]; then
|
||||
if grep -q "$install_dir" "$profile" 2>/dev/null; then
|
||||
found_profiles="$found_profiles $(basename "$profile")"
|
||||
log_debug "Found PATH entry in: $profile"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$found_profiles"
|
||||
}
|
||||
|
||||
# Removal functions
|
||||
remove_binaries() {
|
||||
local install_dir="$1"
|
||||
local binaries="$2"
|
||||
local removed_count=0
|
||||
|
||||
log_info "Removing binaries from $install_dir..."
|
||||
|
||||
for binary in $binaries; do
|
||||
local binary_path="$install_dir/$binary"
|
||||
if [ -f "$binary_path" ]; then
|
||||
log_info "Removing binary: $binary"
|
||||
if [ "$DRY_RUN" = "0" ]; then
|
||||
rm -f "$binary_path"
|
||||
if [ $? -eq 0 ]; then
|
||||
log_success "Removed: $binary"
|
||||
removed_count=$((removed_count + 1))
|
||||
else
|
||||
log_error "Failed to remove: $binary"
|
||||
fi
|
||||
else
|
||||
log_info "[DRY RUN] Would remove: $binary_path"
|
||||
removed_count=$((removed_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "Removed $removed_count binaries"
|
||||
}
|
||||
|
||||
backup_configuration() {
|
||||
local config_dir="$1"
|
||||
local backup_dir="$config_dir.$BACKUP_SUFFIX"
|
||||
|
||||
if [ -d "$config_dir" ]; then
|
||||
log_info "Backing up configuration to: $backup_dir"
|
||||
if [ "$DRY_RUN" = "0" ]; then
|
||||
cp -r "$config_dir" "$backup_dir"
|
||||
if [ $? -eq 0 ]; then
|
||||
log_success "Configuration backed up successfully"
|
||||
else
|
||||
log_error "Failed to backup configuration"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_info "[DRY RUN] Would backup configuration to: $backup_dir"
|
||||
fi
|
||||
else
|
||||
log_info "No configuration directory to backup"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_configuration() {
|
||||
local config_dir="$1"
|
||||
local config_files="$2"
|
||||
|
||||
if [ "$KEEP_CONFIG" = "1" ]; then
|
||||
log_info "Keeping configuration files as requested"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$BACKUP_CONFIG" = "1" ]; then
|
||||
backup_configuration "$config_dir"
|
||||
fi
|
||||
|
||||
if [ -d "$config_dir" ]; then
|
||||
log_info "Removing configuration directory: $config_dir"
|
||||
|
||||
# Ask for confirmation if interactive and not just removing empty dir
|
||||
if [ "$INTERACTIVE" = "1" ] && [ -n "$config_files" ]; then
|
||||
printf "Remove configuration directory $config_dir? [y/N]: "
|
||||
read -r response
|
||||
case "$response" in
|
||||
[yY]|[yY][eE][sS])
|
||||
;;
|
||||
*)
|
||||
log_info "Keeping configuration directory"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = "0" ]; then
|
||||
rm -rf "$config_dir"
|
||||
if [ $? -eq 0 ]; then
|
||||
log_success "Configuration directory removed"
|
||||
else
|
||||
log_error "Failed to remove configuration directory"
|
||||
fi
|
||||
else
|
||||
log_info "[DRY RUN] Would remove configuration directory: $config_dir"
|
||||
fi
|
||||
else
|
||||
log_info "No configuration directory found"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_shell_profile_entries() {
|
||||
local install_dir="$1"
|
||||
local profiles="$2"
|
||||
|
||||
if [ -z "$profiles" ]; then
|
||||
log_info "No shell profile entries found"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Removing PATH entries from shell profiles..."
|
||||
|
||||
for profile_name in $profiles; do
|
||||
local profile_path="$HOME/.$profile_name"
|
||||
if [ -f "$profile_path" ]; then
|
||||
log_info "Cleaning PATH entries from: $profile_name"
|
||||
|
||||
if [ "$DRY_RUN" = "0" ]; then
|
||||
# Create backup
|
||||
cp "$profile_path" "$profile_path.$BACKUP_SUFFIX"
|
||||
|
||||
# Remove lines containing the install directory
|
||||
grep -v "$install_dir" "$profile_path.$BACKUP_SUFFIX" > "$profile_path"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log_success "Cleaned PATH entries from: $profile_name"
|
||||
else
|
||||
log_error "Failed to clean PATH entries from: $profile_name"
|
||||
# Restore backup
|
||||
mv "$profile_path.$BACKUP_SUFFIX" "$profile_path"
|
||||
fi
|
||||
else
|
||||
log_info "[DRY RUN] Would remove PATH entries from: $profile_path"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Unregister plugins from nushell (if nu is still available)
|
||||
unregister_plugins() {
|
||||
local install_dir="$1"
|
||||
|
||||
# Only try to unregister if nu is still available somewhere
|
||||
local nu_binary=""
|
||||
if command -v nu >/dev/null 2>&1; then
|
||||
nu_binary=$(command -v nu)
|
||||
elif [ -f "$install_dir/nu" ]; then
|
||||
nu_binary="$install_dir/nu"
|
||||
else
|
||||
log_info "Nushell not available for plugin unregistration"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Attempting to unregister plugins..."
|
||||
|
||||
if [ "$DRY_RUN" = "0" ]; then
|
||||
# Try to get list of registered plugins
|
||||
local registered_plugins=""
|
||||
registered_plugins=$("$nu_binary" -c "plugin list | get name" 2>/dev/null) || {
|
||||
log_warn "Could not retrieve plugin list"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ -n "$registered_plugins" ]; then
|
||||
log_info "Unregistering plugins from nushell..."
|
||||
for plugin in $registered_plugins; do
|
||||
"$nu_binary" -c "plugin rm $plugin" 2>/dev/null || {
|
||||
log_warn "Could not unregister plugin: $plugin"
|
||||
}
|
||||
done
|
||||
log_success "Plugin unregistration completed"
|
||||
else
|
||||
log_info "No registered plugins found"
|
||||
fi
|
||||
else
|
||||
log_info "[DRY RUN] Would attempt to unregister plugins"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main uninstallation process
|
||||
main() {
|
||||
log_info "🗑️ Nushell Full Distribution Uninstaller"
|
||||
log_info "========================================"
|
||||
|
||||
if [ "$DRY_RUN" = "1" ]; then
|
||||
log_warn "DRY RUN MODE - No files will be modified"
|
||||
fi
|
||||
|
||||
# Detect current installation
|
||||
log_info ""
|
||||
log_info "🔍 Detecting current installation..."
|
||||
|
||||
local binaries=""
|
||||
local config_files=""
|
||||
local shell_profiles=""
|
||||
|
||||
# Check user installation
|
||||
local user_binaries=$(detect_nushell_installation "$INSTALL_DIR_USER")
|
||||
local user_config=$(detect_config_installation "$CONFIG_DIR")
|
||||
local user_profiles=$(detect_shell_profile_entries "$INSTALL_DIR_USER")
|
||||
|
||||
# Check system installation
|
||||
local system_binaries=$(detect_nushell_installation "$INSTALL_DIR_SYSTEM")
|
||||
local system_profiles=$(detect_shell_profile_entries "$INSTALL_DIR_SYSTEM")
|
||||
|
||||
# Determine what we're removing based on target
|
||||
if [ "$SYSTEM_INSTALL" = "1" ]; then
|
||||
binaries="$system_binaries"
|
||||
shell_profiles="$system_profiles"
|
||||
# Config is always user-level
|
||||
config_files="$user_config"
|
||||
else
|
||||
binaries="$user_binaries"
|
||||
shell_profiles="$user_profiles"
|
||||
config_files="$user_config"
|
||||
fi
|
||||
|
||||
# Show detection results
|
||||
log_info "Installation Status:"
|
||||
if [ -n "$user_binaries" ]; then
|
||||
log_info " 📁 User binaries ($INSTALL_DIR_USER): $user_binaries"
|
||||
else
|
||||
log_info " 📁 User binaries ($INSTALL_DIR_USER): none found"
|
||||
fi
|
||||
|
||||
if [ -n "$system_binaries" ]; then
|
||||
log_info " 📁 System binaries ($INSTALL_DIR_SYSTEM): $system_binaries"
|
||||
else
|
||||
log_info " 📁 System binaries ($INSTALL_DIR_SYSTEM): none found"
|
||||
fi
|
||||
|
||||
if [ -n "$config_files" ]; then
|
||||
log_info " ⚙️ Configuration ($CONFIG_DIR): $config_files"
|
||||
else
|
||||
log_info " ⚙️ Configuration ($CONFIG_DIR): none found"
|
||||
fi
|
||||
|
||||
if [ -n "$shell_profiles" ]; then
|
||||
log_info " 🐚 Shell profiles: $shell_profiles"
|
||||
else
|
||||
log_info " 🐚 Shell profiles: no PATH entries found"
|
||||
fi
|
||||
|
||||
# Check if anything was found
|
||||
if [ -z "$binaries" ] && [ -z "$config_files" ] && [ -z "$shell_profiles" ]; then
|
||||
log_warn "No Nushell installation detected"
|
||||
if [ "$INTERACTIVE" = "1" ]; then
|
||||
printf "Continue anyway? [y/N]: "
|
||||
read -r response
|
||||
case "$response" in
|
||||
[yY]|[yY][eE][sS])
|
||||
;;
|
||||
*)
|
||||
log_info "Uninstallation cancelled"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
else
|
||||
log_info "Nothing to remove"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Confirmation prompt
|
||||
if [ "$INTERACTIVE" = "1" ]; then
|
||||
log_info ""
|
||||
log_warn "This will remove the detected Nushell installation components."
|
||||
if [ "$KEEP_CONFIG" = "1" ]; then
|
||||
log_info "Configuration files will be kept as requested."
|
||||
elif [ "$BACKUP_CONFIG" = "1" ]; then
|
||||
log_info "Configuration files will be backed up before removal."
|
||||
fi
|
||||
|
||||
printf "Proceed with uninstallation? [y/N]: "
|
||||
read -r response
|
||||
case "$response" in
|
||||
[yY]|[yY][eE][sS])
|
||||
;;
|
||||
*)
|
||||
log_info "Uninstallation cancelled"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Perform uninstallation
|
||||
log_info ""
|
||||
log_info "🗑️ Starting uninstallation..."
|
||||
|
||||
# Unregister plugins before removing binaries
|
||||
if [ -n "$binaries" ]; then
|
||||
unregister_plugins "$INSTALL_DIR"
|
||||
fi
|
||||
|
||||
# Remove binaries
|
||||
if [ -n "$binaries" ]; then
|
||||
remove_binaries "$INSTALL_DIR" "$binaries"
|
||||
fi
|
||||
|
||||
# Remove configuration
|
||||
if [ -n "$config_files" ]; then
|
||||
remove_configuration "$CONFIG_DIR" "$config_files"
|
||||
fi
|
||||
|
||||
# Clean shell profiles
|
||||
if [ -n "$shell_profiles" ]; then
|
||||
remove_shell_profile_entries "$INSTALL_DIR" "$shell_profiles"
|
||||
fi
|
||||
|
||||
# Final summary
|
||||
log_info ""
|
||||
log_success "🎉 Uninstallation completed!"
|
||||
log_info "Summary:"
|
||||
log_info " 🗑️ Removed from: $INSTALL_DIR"
|
||||
if [ -n "$config_files" ] && [ "$KEEP_CONFIG" = "0" ]; then
|
||||
log_info " 🗑️ Configuration removed: $CONFIG_DIR"
|
||||
elif [ "$KEEP_CONFIG" = "1" ]; then
|
||||
log_info " 💾 Configuration kept: $CONFIG_DIR"
|
||||
fi
|
||||
if [ "$BACKUP_CONFIG" = "1" ]; then
|
||||
log_info " 💾 Configuration backed up with suffix: .$BACKUP_SUFFIX"
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
log_info "🔄 To complete removal:"
|
||||
log_info "1. Restart your terminal or run: source ~/.bashrc (or equivalent)"
|
||||
log_info "2. Verify removal: command -v nu (should return nothing)"
|
||||
|
||||
if [ "$KEEP_CONFIG" = "1" ] || [ "$BACKUP_CONFIG" = "1" ]; then
|
||||
log_info ""
|
||||
log_info "📝 Note: Configuration files were preserved as requested"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
843
scripts/verify_installation.nu
Executable file
843
scripts/verify_installation.nu
Executable file
@ -0,0 +1,843 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
# Verify Installation Script
|
||||
# Comprehensive installation verification for Nushell full distribution
|
||||
# Tests nu binary, all plugins, configuration, and generates detailed report
|
||||
|
||||
use lib/common_lib.nu [
|
||||
log_info, log_error, log_success, log_warn, log_debug,
|
||||
get_current_platform, check_binary
|
||||
]
|
||||
|
||||
def main [
|
||||
--platform (-p): string = "" # Target platform for verification
|
||||
--install-dir (-i): string = "" # Installation directory to check
|
||||
--config-dir (-c): string = "" # Configuration directory to check
|
||||
--plugins (-P): list<string> = [] # Specific plugins to verify
|
||||
--all-plugins (-a) # Verify all found plugins
|
||||
--report (-r): string = "" # Generate report to file
|
||||
--json # Output report in JSON format
|
||||
--quick (-q) # Quick verification (basic checks only)
|
||||
--verbose (-v) # Verbose output with detailed information
|
||||
--fix-permissions # Attempt to fix permission issues
|
||||
] {
|
||||
log_info "🔍 Nushell Full Distribution Installation Verification"
|
||||
log_info "================================================="
|
||||
|
||||
if $verbose != null {
|
||||
log_debug "Verbose mode enabled"
|
||||
}
|
||||
|
||||
# Get verification configuration
|
||||
let config = get_verification_config $platform $install_dir $config_dir
|
||||
|
||||
log_info ""
|
||||
log_info "📍 Verification Configuration:"
|
||||
log_info $" Platform: ($config.platform)"
|
||||
log_info $" Binary directory: ($config.bin_dir)"
|
||||
log_info $" Config directory: ($config.config_dir)"
|
||||
if $quick != null {
|
||||
log_info " Mode: Quick verification"
|
||||
} else {
|
||||
log_info " Mode: Comprehensive verification"
|
||||
}
|
||||
|
||||
# Run verification checks
|
||||
let verification_results = run_verification_suite $config $plugins $all_plugins $quick $verbose $fix_permissions
|
||||
|
||||
# Generate and display report
|
||||
let report = generate_verification_report $verification_results $config
|
||||
display_verification_report $report $verbose $json
|
||||
|
||||
# Save report to file if requested
|
||||
if ($report | str length) > 0 {
|
||||
save_verification_report $report $json $report
|
||||
}
|
||||
|
||||
# Exit with appropriate code
|
||||
let overall_status = $report.summary.overall_status
|
||||
if $overall_status == "passed" {
|
||||
log_success "🎉 All verification checks passed!"
|
||||
exit 0
|
||||
} else if $overall_status == "warning" {
|
||||
log_warn "⚠️ Verification completed with warnings"
|
||||
exit 0
|
||||
} else {
|
||||
log_error "❌ Verification failed"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Get verification configuration
|
||||
def get_verification_config [platform: string, install_dir: string, config_dir: string] -> record {
|
||||
let current_platform = if ($platform | str length) > 0 {
|
||||
$platform
|
||||
} else {
|
||||
get_current_platform
|
||||
}
|
||||
|
||||
let home_dir = ($env.HOME | path expand)
|
||||
|
||||
# Determine binary directory
|
||||
let bin_dir = if ($install_dir | str length) > 0 {
|
||||
($install_dir | path expand)
|
||||
} else if (which nu | length) > 0 {
|
||||
(which nu | get 0.path | path dirname)
|
||||
} else {
|
||||
$"($home_dir)/.local/bin"
|
||||
}
|
||||
|
||||
# Determine configuration directory
|
||||
let config_dir = if ($config_dir | str length) > 0 {
|
||||
($config_dir | path expand)
|
||||
} else if ($env.XDG_CONFIG_HOME? | is-not-empty) {
|
||||
$"($env.XDG_CONFIG_HOME)/nushell"
|
||||
} else {
|
||||
$"($home_dir)/.config/nushell"
|
||||
}
|
||||
|
||||
{
|
||||
platform: $current_platform,
|
||||
bin_dir: $bin_dir,
|
||||
config_dir: $config_dir,
|
||||
binary_extension: (if ($current_platform | str starts-with "windows") { ".exe" } else { "" }),
|
||||
home_dir: $home_dir
|
||||
}
|
||||
}
|
||||
|
||||
# Run complete verification suite
|
||||
def run_verification_suite [
|
||||
config: record,
|
||||
plugins: list<string>,
|
||||
all_plugins: bool,
|
||||
quick: bool,
|
||||
verbose: bool,
|
||||
fix_permissions: bool
|
||||
] -> record {
|
||||
let results = {}
|
||||
|
||||
# Verify nushell binary
|
||||
log_info "🚀 Verifying Nushell binary..."
|
||||
let nushell_result = verify_nushell_binary $config $verbose $fix_permissions
|
||||
$results = ($results | insert "nushell" $nushell_result)
|
||||
|
||||
# Verify plugins
|
||||
log_info "🔌 Verifying plugins..."
|
||||
let plugin_results = verify_plugins $config $plugins $all_plugins $verbose $fix_permissions
|
||||
$results = ($results | insert "plugins" $plugin_results)
|
||||
|
||||
# Verify configuration
|
||||
log_info "⚙️ Verifying configuration..."
|
||||
let config_results = verify_configuration $config $verbose
|
||||
$results = ($results | insert "configuration" $config_results)
|
||||
|
||||
# Verify PATH integration
|
||||
log_info "🛣️ Verifying PATH integration..."
|
||||
let path_results = verify_path_integration $config $verbose
|
||||
$results = ($results | insert "path_integration" $path_results)
|
||||
|
||||
# Extended checks for comprehensive verification
|
||||
if not $quick {
|
||||
# Verify plugin registration
|
||||
log_info "📝 Verifying plugin registration..."
|
||||
let registration_results = verify_plugin_registration $config $verbose
|
||||
$results = ($results | insert "plugin_registration" $registration_results)
|
||||
|
||||
# Test basic functionality
|
||||
log_info "🧪 Testing basic functionality..."
|
||||
let functionality_results = test_basic_functionality $config $verbose
|
||||
$results = ($results | insert "functionality" $functionality_results)
|
||||
|
||||
# System integration checks
|
||||
log_info "🔗 Verifying system integration..."
|
||||
let system_results = verify_system_integration $config $verbose
|
||||
$results = ($results | insert "system_integration" $system_results)
|
||||
}
|
||||
|
||||
$results
|
||||
}
|
||||
|
||||
# Verify nushell binary
|
||||
def verify_nushell_binary [config: record, verbose: bool, fix_permissions: bool] -> record {
|
||||
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
|
||||
let result = {
|
||||
component: "nushell_binary",
|
||||
checks: []
|
||||
}
|
||||
|
||||
# Check if binary exists
|
||||
let exists_check = if ($nu_path | path exists) {
|
||||
{name: "binary_exists", status: "passed", message: $"Binary found at ($nu_path)"}
|
||||
} else {
|
||||
{name: "binary_exists", status: "failed", message: $"Binary not found at ($nu_path)"}
|
||||
}
|
||||
$result.checks = ($result.checks | append $exists_check)
|
||||
|
||||
if $exists_check.status == "passed" {
|
||||
# Check if binary is executable
|
||||
let executable_check = if (check_binary $nu_path) {
|
||||
{name: "binary_executable", status: "passed", message: "Binary is executable"}
|
||||
} else {
|
||||
let check_result = if $fix_permissions {
|
||||
try {
|
||||
chmod +x $nu_path
|
||||
if (check_binary $nu_path) {
|
||||
{name: "binary_executable", status: "passed", message: "Binary permissions fixed and is now executable"}
|
||||
} else {
|
||||
{name: "binary_executable", status: "failed", message: "Binary not executable (permission fix failed)"}
|
||||
}
|
||||
} catch {
|
||||
{name: "binary_executable", status: "failed", message: "Binary not executable (cannot fix permissions)"}
|
||||
}
|
||||
} else {
|
||||
{name: "binary_executable", status: "failed", message: "Binary not executable (use --fix-permissions to attempt fix)"}
|
||||
}
|
||||
$check_result
|
||||
}
|
||||
$result.checks = ($result.checks | append $executable_check)
|
||||
|
||||
# Test version command
|
||||
if $executable_check.status == "passed" {
|
||||
let version_check = try {
|
||||
let version_output = (run-external $nu_path "--version")
|
||||
{name: "version_command", status: "passed", message: $"Version: ($version_output)", details: $version_output}
|
||||
} catch {|err|
|
||||
{name: "version_command", status: "failed", message: $"Version command failed: ($err.msg)"}
|
||||
}
|
||||
$result.checks = ($result.checks | append $version_check)
|
||||
|
||||
# Test basic command
|
||||
let basic_test = try {
|
||||
let test_output = (run-external $nu_path "-c" "echo 'test'")
|
||||
if $test_output == "test" {
|
||||
{name: "basic_command", status: "passed", message: "Basic command execution works"}
|
||||
} else {
|
||||
{name: "basic_command", status: "warning", message: $"Unexpected output: ($test_output)"}
|
||||
}
|
||||
} catch {|err|
|
||||
{name: "basic_command", status: "failed", message: $"Basic command failed: ($err.msg)"}
|
||||
}
|
||||
$result.checks = ($result.checks | append $basic_test)
|
||||
}
|
||||
}
|
||||
|
||||
$result
|
||||
}
|
||||
|
||||
# Verify plugins
|
||||
def verify_plugins [config: record, specific_plugins: list<string>, all_plugins: bool, verbose: bool, fix_permissions: bool] -> record {
|
||||
let plugin_list = if $all_plugins {
|
||||
# Find all plugin binaries in bin directory
|
||||
if ($config.bin_dir | path exists) {
|
||||
ls $config.bin_dir
|
||||
| where name =~ $"nu_plugin_.*($config.binary_extension)$"
|
||||
| get name
|
||||
| each {|path| ($path | path basename | str replace $config.binary_extension "")}
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
} else if ($specific_plugins | length) > 0 {
|
||||
$specific_plugins
|
||||
} else {
|
||||
# Default plugins to check
|
||||
[
|
||||
"nu_plugin_clipboard",
|
||||
"nu_plugin_hashes",
|
||||
"nu_plugin_desktop_notifications",
|
||||
"nu_plugin_highlight"
|
||||
]
|
||||
}
|
||||
|
||||
let results = {
|
||||
component: "plugins",
|
||||
plugin_count: ($plugin_list | length),
|
||||
plugins: {}
|
||||
}
|
||||
|
||||
if ($plugin_list | length) == 0 {
|
||||
$results = ($results | insert "message" "No plugins to verify")
|
||||
return $results
|
||||
}
|
||||
|
||||
for plugin in $plugin_list {
|
||||
let plugin_path = $"($config.bin_dir)/($plugin)($config.binary_extension)"
|
||||
let plugin_result = {
|
||||
name: $plugin,
|
||||
path: $plugin_path,
|
||||
checks: []
|
||||
}
|
||||
|
||||
# Check if plugin binary exists
|
||||
let exists_check = if ($plugin_path | path exists) {
|
||||
{name: "binary_exists", status: "passed", message: $"Plugin binary found at ($plugin_path)"}
|
||||
} else {
|
||||
{name: "binary_exists", status: "failed", message: $"Plugin binary not found at ($plugin_path)"}
|
||||
}
|
||||
$plugin_result.checks = ($plugin_result.checks | append $exists_check)
|
||||
|
||||
if $exists_check.status == "passed" {
|
||||
# Check if plugin is executable
|
||||
let executable_check = if (check_binary $plugin_path) {
|
||||
{name: "binary_executable", status: "passed", message: "Plugin binary is executable"}
|
||||
} else {
|
||||
let check_result = if $fix_permissions {
|
||||
try {
|
||||
chmod +x $plugin_path
|
||||
if (check_binary $plugin_path) {
|
||||
{name: "binary_executable", status: "passed", message: "Plugin permissions fixed"}
|
||||
} else {
|
||||
{name: "binary_executable", status: "failed", message: "Plugin not executable (fix failed)"}
|
||||
}
|
||||
} catch {
|
||||
{name: "binary_executable", status: "failed", message: "Plugin not executable (cannot fix)"}
|
||||
}
|
||||
} else {
|
||||
{name: "binary_executable", status: "failed", message: "Plugin not executable"}
|
||||
}
|
||||
$check_result
|
||||
}
|
||||
$plugin_result.checks = ($plugin_result.checks | append $executable_check)
|
||||
|
||||
# Test plugin help command (if executable)
|
||||
if $executable_check.status == "passed" {
|
||||
let help_check = try {
|
||||
let help_output = (run-external $plugin_path "--help")
|
||||
{name: "help_command", status: "passed", message: "Plugin help command works"}
|
||||
} catch {|err|
|
||||
{name: "help_command", status: "warning", message: $"Plugin help command issue: ($err.msg)"}
|
||||
}
|
||||
$plugin_result.checks = ($plugin_result.checks | append $help_check)
|
||||
}
|
||||
}
|
||||
|
||||
$results.plugins = ($results.plugins | insert $plugin $plugin_result)
|
||||
}
|
||||
|
||||
$results
|
||||
}
|
||||
|
||||
# Verify configuration files
|
||||
def verify_configuration [config: record, verbose: bool] -> record {
|
||||
let config_files = [
|
||||
{name: "config.nu", required: true, description: "Main nushell configuration"},
|
||||
{name: "env.nu", required: true, description: "Environment configuration"},
|
||||
{name: "distribution_config.toml", required: false, description: "Distribution metadata"}
|
||||
]
|
||||
|
||||
let results = {
|
||||
component: "configuration",
|
||||
config_dir: $config.config_dir,
|
||||
files: {}
|
||||
}
|
||||
|
||||
for file_info in $config_files {
|
||||
let file_path = $"($config.config_dir)/($file_info.name)"
|
||||
let file_result = {
|
||||
name: $file_info.name,
|
||||
path: $file_path,
|
||||
required: $file_info.required,
|
||||
description: $file_info.description,
|
||||
checks: []
|
||||
}
|
||||
|
||||
# Check if file exists
|
||||
let exists_check = if ($file_path | path exists) {
|
||||
{name: "file_exists", status: "passed", message: $"Configuration file found: ($file_info.name)"}
|
||||
} else {
|
||||
let status = if $file_info.required { "failed" } else { "warning" }
|
||||
{name: "file_exists", status: $status, message: $"Configuration file not found: ($file_info.name)"}
|
||||
}
|
||||
$file_result.checks = ($file_result.checks | append $exists_check)
|
||||
|
||||
if $exists_check.status == "passed" {
|
||||
# Check file readability
|
||||
let readable_check = try {
|
||||
let content = (open $file_path --raw)
|
||||
let size = ($content | str length)
|
||||
{name: "file_readable", status: "passed", message: $"File readable (($size) characters)"}
|
||||
} catch {|err|
|
||||
{name: "file_readable", status: "failed", message: $"File not readable: ($err.msg)"}
|
||||
}
|
||||
$file_result.checks = ($file_result.checks | append $readable_check)
|
||||
|
||||
# Syntax check for .nu files
|
||||
if ($file_info.name | str ends-with ".nu") and $readable_check.status == "passed" {
|
||||
let syntax_check = try {
|
||||
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
|
||||
if ($nu_path | path exists) {
|
||||
# Test parsing the configuration file
|
||||
run-external $nu_path "-c" $"source ($file_path); echo 'syntax ok'"
|
||||
{name: "syntax_check", status: "passed", message: "Configuration syntax is valid"}
|
||||
} else {
|
||||
{name: "syntax_check", status: "warning", message: "Cannot verify syntax (nu binary not available)"}
|
||||
}
|
||||
} catch {|err|
|
||||
{name: "syntax_check", status: "failed", message: $"Syntax error in configuration: ($err.msg)"}
|
||||
}
|
||||
$file_result.checks = ($file_result.checks | append $syntax_check)
|
||||
}
|
||||
}
|
||||
|
||||
$results.files = ($results.files | insert $file_info.name $file_result)
|
||||
}
|
||||
|
||||
$results
|
||||
}
|
||||
|
||||
# Verify PATH integration
|
||||
def verify_path_integration [config: record, verbose: bool] -> record {
|
||||
let results = {
|
||||
component: "path_integration",
|
||||
bin_dir: $config.bin_dir,
|
||||
checks: []
|
||||
}
|
||||
|
||||
# Check if bin directory is in PATH
|
||||
let path_entries = ($env.PATH | split row (if (sys host | get name) == "Windows" { ";" } else { ":" }))
|
||||
let in_path_check = if ($config.bin_dir in $path_entries) {
|
||||
{name: "bin_dir_in_path", status: "passed", message: $"Binary directory is in PATH: ($config.bin_dir)"}
|
||||
} else {
|
||||
{name: "bin_dir_in_path", status: "warning", message: $"Binary directory not in PATH: ($config.bin_dir)"}
|
||||
}
|
||||
$results.checks = ($results.checks | append $in_path_check)
|
||||
|
||||
# Check if nu command is available globally
|
||||
let global_nu_check = if (which nu | length) > 0 {
|
||||
let nu_location = (which nu | get 0.path)
|
||||
{name: "nu_globally_available", status: "passed", message: $"nu command available globally at: ($nu_location)"}
|
||||
} else {
|
||||
{name: "nu_globally_available", status: "failed", message: "nu command not available globally"}
|
||||
}
|
||||
$results.checks = ($results.checks | append $global_nu_check)
|
||||
|
||||
$results
|
||||
}
|
||||
|
||||
# Verify plugin registration
|
||||
def verify_plugin_registration [config: record, verbose: bool] -> record {
|
||||
let results = {
|
||||
component: "plugin_registration",
|
||||
checks: []
|
||||
}
|
||||
|
||||
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
|
||||
if not ($nu_path | path exists) {
|
||||
$results.checks = ($results.checks | append {
|
||||
name: "nu_binary_available",
|
||||
status: "failed",
|
||||
message: "Cannot verify plugin registration - nu binary not available"
|
||||
})
|
||||
return $results
|
||||
}
|
||||
|
||||
# Get list of registered plugins
|
||||
let registered_plugins_check = try {
|
||||
let plugin_list = (run-external $nu_path "-c" "plugin list | get name")
|
||||
{
|
||||
name: "list_registered_plugins",
|
||||
status: "passed",
|
||||
message: $"Found (($plugin_list | length)) registered plugins",
|
||||
details: $plugin_list
|
||||
}
|
||||
} catch {|err|
|
||||
{
|
||||
name: "list_registered_plugins",
|
||||
status: "warning",
|
||||
message: $"Could not list registered plugins: ($err.msg)"
|
||||
}
|
||||
}
|
||||
$results.checks = ($results.checks | append $registered_plugins_check)
|
||||
|
||||
# Test plugin functionality if registration check passed
|
||||
if $registered_plugins_check.status == "passed" and ($registered_plugins_check.details? | is-not-empty) {
|
||||
let plugin_test_check = try {
|
||||
# Test a simple plugin command if clipboard plugin is available
|
||||
if "nu_plugin_clipboard" in $registered_plugins_check.details {
|
||||
run-external $nu_path "-c" "clipboard --help"
|
||||
{name: "plugin_functionality", status: "passed", message: "Plugin commands are functional"}
|
||||
} else {
|
||||
{name: "plugin_functionality", status: "warning", message: "No testable plugins found"}
|
||||
}
|
||||
} catch {|err|
|
||||
{name: "plugin_functionality", status: "warning", message: $"Plugin functionality test failed: ($err.msg)"}
|
||||
}
|
||||
$results.checks = ($results.checks | append $plugin_test_check)
|
||||
}
|
||||
|
||||
$results
|
||||
}
|
||||
|
||||
# Test basic functionality
|
||||
def test_basic_functionality [config: record, verbose: bool] -> record {
|
||||
let results = {
|
||||
component: "basic_functionality",
|
||||
checks: []
|
||||
}
|
||||
|
||||
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
|
||||
if not ($nu_path | path exists) {
|
||||
$results.checks = ($results.checks | append {
|
||||
name: "nu_binary_available",
|
||||
status: "failed",
|
||||
message: "Cannot test functionality - nu binary not available"
|
||||
})
|
||||
return $results
|
||||
}
|
||||
|
||||
# Test basic arithmetic
|
||||
let arithmetic_test = try {
|
||||
let result = (run-external $nu_path "-c" "2 + 2")
|
||||
if ($result | str trim) == "4" {
|
||||
{name: "arithmetic", status: "passed", message: "Basic arithmetic works"}
|
||||
} else {
|
||||
{name: "arithmetic", status: "warning", message: $"Unexpected arithmetic result: ($result)"}
|
||||
}
|
||||
} catch {|err|
|
||||
{name: "arithmetic", status: "failed", message: $"Arithmetic test failed: ($err.msg)"}
|
||||
}
|
||||
$results.checks = ($results.checks | append $arithmetic_test)
|
||||
|
||||
# Test piping
|
||||
let pipe_test = try {
|
||||
let result = (run-external $nu_path "-c" "echo 'hello world' | str upcase")
|
||||
if ($result | str trim) == "HELLO WORLD" {
|
||||
{name: "piping", status: "passed", message: "Command piping works"}
|
||||
} else {
|
||||
{name: "piping", status: "warning", message: $"Unexpected piping result: ($result)"}
|
||||
}
|
||||
} catch {|err|
|
||||
{name: "piping", status: "failed", message: $"Piping test failed: ($err.msg)"}
|
||||
}
|
||||
$results.checks = ($results.checks | append $pipe_test)
|
||||
|
||||
# Test table operations
|
||||
let table_test = try {
|
||||
let result = (run-external $nu_path "-c" "[{name: 'test', value: 42}] | get 0.value")
|
||||
if ($result | str trim) == "42" {
|
||||
{name: "tables", status: "passed", message: "Table operations work"}
|
||||
} else {
|
||||
{name: "tables", status: "warning", message: $"Unexpected table result: ($result)"}
|
||||
}
|
||||
} catch {|err|
|
||||
{name: "tables", status: "failed", message: $"Table test failed: ($err.msg)"}
|
||||
}
|
||||
$results.checks = ($results.checks | append $table_test)
|
||||
|
||||
$results
|
||||
}
|
||||
|
||||
# Verify system integration
|
||||
def verify_system_integration [config: record, verbose: bool] -> record {
|
||||
let results = {
|
||||
component: "system_integration",
|
||||
checks: []
|
||||
}
|
||||
|
||||
# Check shell profile integration
|
||||
let profile_files = [
|
||||
"~/.bashrc",
|
||||
"~/.zshrc",
|
||||
"~/.profile",
|
||||
"~/.bash_profile"
|
||||
]
|
||||
|
||||
let profile_check = {
|
||||
name: "shell_profile_integration",
|
||||
status: "warning",
|
||||
message: "Shell profile integration not detected",
|
||||
details: []
|
||||
}
|
||||
|
||||
for profile_file in $profile_files {
|
||||
let expanded_path = ($profile_file | path expand)
|
||||
if ($expanded_path | path exists) {
|
||||
try {
|
||||
let content = (open $expanded_path --raw)
|
||||
if ($content | str contains $config.bin_dir) {
|
||||
$profile_check.status = "passed"
|
||||
$profile_check.message = $"Shell profile integration found in ($profile_file)"
|
||||
break
|
||||
}
|
||||
} catch {
|
||||
# Ignore errors reading profile files
|
||||
}
|
||||
}
|
||||
}
|
||||
$results.checks = ($results.checks | append $profile_check)
|
||||
|
||||
# Check terminal integration (can nu be started as a shell)
|
||||
let terminal_test = try {
|
||||
let nu_path = $"($config.bin_dir)/nu($config.binary_extension)"
|
||||
if ($nu_path | path exists) {
|
||||
# Test if nu can be started (quick test)
|
||||
run-external $nu_path "-c" "exit"
|
||||
{name: "terminal_integration", status: "passed", message: "Nu can be started as shell"}
|
||||
} else {
|
||||
{name: "terminal_integration", status: "failed", message: "Nu binary not available for terminal test"}
|
||||
}
|
||||
} catch {|err|
|
||||
{name: "terminal_integration", status: "warning", message: $"Terminal integration test issue: ($err.msg)"}
|
||||
}
|
||||
$results.checks = ($results.checks | append $terminal_test)
|
||||
|
||||
$results
|
||||
}
|
||||
|
||||
# Generate verification report
|
||||
def generate_verification_report [results: record, config: record] -> record {
|
||||
let summary = calculate_summary $results
|
||||
|
||||
{
|
||||
metadata: {
|
||||
verification_time: (date now | format date "%Y-%m-%d %H:%M:%S UTC"),
|
||||
platform: $config.platform,
|
||||
bin_directory: $config.bin_dir,
|
||||
config_directory: $config.config_dir,
|
||||
nushell_version: (try { (nu --version) } catch { "unknown" })
|
||||
},
|
||||
summary: $summary,
|
||||
detailed_results: $results
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate summary statistics
|
||||
def calculate_summary [results: record] -> record {
|
||||
mut total_checks = 0
|
||||
mut passed_checks = 0
|
||||
mut warning_checks = 0
|
||||
mut failed_checks = 0
|
||||
|
||||
let components = []
|
||||
|
||||
for component in ($results | items) {
|
||||
let component_name = $component.key
|
||||
let component_data = $component.value
|
||||
|
||||
let component_summary = if "checks" in ($component_data | columns) {
|
||||
# Direct checks in component
|
||||
let checks = $component_data.checks
|
||||
$total_checks = $total_checks + ($checks | length)
|
||||
|
||||
let component_passed = ($checks | where status == "passed" | length)
|
||||
let component_warning = ($checks | where status == "warning" | length)
|
||||
let component_failed = ($checks | where status == "failed" | length)
|
||||
|
||||
$passed_checks = $passed_checks + $component_passed
|
||||
$warning_checks = $warning_checks + $component_warning
|
||||
$failed_checks = $failed_checks + $component_failed
|
||||
|
||||
{
|
||||
name: $component_name,
|
||||
total: ($checks | length),
|
||||
passed: $component_passed,
|
||||
warning: $component_warning,
|
||||
failed: $component_failed
|
||||
}
|
||||
} else if "plugins" in ($component_data | columns) {
|
||||
# Plugin component with nested structure
|
||||
mut plugin_total = 0
|
||||
mut plugin_passed = 0
|
||||
mut plugin_warning = 0
|
||||
mut plugin_failed = 0
|
||||
|
||||
for plugin in ($component_data.plugins | items) {
|
||||
let plugin_checks = $plugin.value.checks
|
||||
$plugin_total = $plugin_total + ($plugin_checks | length)
|
||||
$plugin_passed = $plugin_passed + ($plugin_checks | where status == "passed" | length)
|
||||
$plugin_warning = $plugin_warning + ($plugin_checks | where status == "warning" | length)
|
||||
$plugin_failed = $plugin_failed + ($plugin_checks | where status == "failed" | length)
|
||||
}
|
||||
|
||||
$total_checks = $total_checks + $plugin_total
|
||||
$passed_checks = $passed_checks + $plugin_passed
|
||||
$warning_checks = $warning_checks + $plugin_warning
|
||||
$failed_checks = $failed_checks + $plugin_failed
|
||||
|
||||
{
|
||||
name: $component_name,
|
||||
total: $plugin_total,
|
||||
passed: $plugin_passed,
|
||||
warning: $plugin_warning,
|
||||
failed: $plugin_failed
|
||||
}
|
||||
} else if "files" in ($component_data | columns) {
|
||||
# Configuration component with files
|
||||
mut config_total = 0
|
||||
mut config_passed = 0
|
||||
mut config_warning = 0
|
||||
mut config_failed = 0
|
||||
|
||||
for file in ($component_data.files | items) {
|
||||
let file_checks = $file.value.checks
|
||||
$config_total = $config_total + ($file_checks | length)
|
||||
$config_passed = $config_passed + ($file_checks | where status == "passed" | length)
|
||||
$config_warning = $config_warning + ($file_checks | where status == "warning" | length)
|
||||
$config_failed = $config_failed + ($file_checks | where status == "failed" | length)
|
||||
}
|
||||
|
||||
$total_checks = $total_checks + $config_total
|
||||
$passed_checks = $passed_checks + $config_passed
|
||||
$warning_checks = $warning_checks + $config_warning
|
||||
$failed_checks = $failed_checks + $config_failed
|
||||
|
||||
{
|
||||
name: $component_name,
|
||||
total: $config_total,
|
||||
passed: $config_passed,
|
||||
warning: $config_warning,
|
||||
failed: $config_failed
|
||||
}
|
||||
} else {
|
||||
{
|
||||
name: $component_name,
|
||||
total: 0,
|
||||
passed: 0,
|
||||
warning: 0,
|
||||
failed: 0
|
||||
}
|
||||
}
|
||||
|
||||
$components = ($components | append $component_summary)
|
||||
}
|
||||
|
||||
let overall_status = if $failed_checks > 0 {
|
||||
"failed"
|
||||
} else if $warning_checks > 0 {
|
||||
"warning"
|
||||
} else {
|
||||
"passed"
|
||||
}
|
||||
|
||||
{
|
||||
overall_status: $overall_status,
|
||||
total_checks: $total_checks,
|
||||
passed_checks: $passed_checks,
|
||||
warning_checks: $warning_checks,
|
||||
failed_checks: $failed_checks,
|
||||
components: $components
|
||||
}
|
||||
}
|
||||
|
||||
# Display verification report
|
||||
def display_verification_report [report: record, verbose: bool, json_format: bool] {
|
||||
if $json_format {
|
||||
$report | to json
|
||||
} else {
|
||||
log_info ""
|
||||
log_info "📊 Verification Summary"
|
||||
log_info "======================"
|
||||
|
||||
let summary = $report.summary
|
||||
let status_icon = match $summary.overall_status {
|
||||
"passed" => "✅",
|
||||
"warning" => "⚠️",
|
||||
"failed" => "❌"
|
||||
}
|
||||
|
||||
log_info $"Overall Status: ($status_icon) ($summary.overall_status | str upcase)"
|
||||
log_info $"Total Checks: ($summary.total_checks)"
|
||||
log_info $"✅ Passed: ($summary.passed_checks)"
|
||||
log_info $"⚠️ Warnings: ($summary.warning_checks)"
|
||||
log_info $"❌ Failed: ($summary.failed_checks)"
|
||||
|
||||
log_info ""
|
||||
log_info "📋 Component Summary:"
|
||||
for component in $summary.components {
|
||||
let comp_status = if $component.failed > 0 {
|
||||
"❌"
|
||||
} else if $component.warning > 0 {
|
||||
"⚠️"
|
||||
} else {
|
||||
"✅"
|
||||
}
|
||||
log_info $" ($comp_status) ($component.name): ($component.passed)/($component.total) passed"
|
||||
}
|
||||
|
||||
if $verbose {
|
||||
log_info ""
|
||||
log_info "🔍 Detailed Results:"
|
||||
display_detailed_results $report.detailed_results
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Display detailed results
|
||||
def display_detailed_results [results: record] {
|
||||
for component in ($results | items) {
|
||||
log_info $""
|
||||
log_info $"📂 ($component.key | str upcase)"
|
||||
log_info $" {'=' | str repeat (($component.key | str length) + 2)}"
|
||||
|
||||
let data = $component.value
|
||||
|
||||
if "checks" in ($data | columns) {
|
||||
display_checks $data.checks " "
|
||||
} else if "plugins" in ($data | columns) {
|
||||
for plugin in ($data.plugins | items) {
|
||||
log_info $" 🔌 ($plugin.key):"
|
||||
display_checks $plugin.value.checks " "
|
||||
}
|
||||
} else if "files" in ($data | columns) {
|
||||
for file in ($data.files | items) {
|
||||
log_info $" 📄 ($file.key):"
|
||||
display_checks $file.value.checks " "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Display individual checks
|
||||
def display_checks [checks: list, indent: string] {
|
||||
for check in $checks {
|
||||
let status_icon = match $check.status {
|
||||
"passed" => "✅",
|
||||
"warning" => "⚠️",
|
||||
"failed" => "❌"
|
||||
}
|
||||
log_info $"($indent)($status_icon) ($check.name): ($check.message)"
|
||||
}
|
||||
}
|
||||
|
||||
# Save verification report to file
|
||||
def save_verification_report [report: record, json_format: bool, output_path: string] {
|
||||
let content = if $json_format {
|
||||
($report | to json)
|
||||
} else {
|
||||
generate_text_report $report
|
||||
}
|
||||
|
||||
$content | save -f $output_path
|
||||
log_success $"✅ Verification report saved: ($output_path)"
|
||||
}
|
||||
|
||||
# Generate text format report
|
||||
def generate_text_report [report: record] -> string {
|
||||
let lines = [
|
||||
"Nushell Full Distribution - Installation Verification Report",
|
||||
"=" | str repeat 58,
|
||||
"",
|
||||
$"Generated: ($report.metadata.verification_time)",
|
||||
$"Platform: ($report.metadata.platform)",
|
||||
$"Binary Directory: ($report.metadata.bin_directory)",
|
||||
$"Config Directory: ($report.metadata.config_directory)",
|
||||
$"Nushell Version: ($report.metadata.nushell_version)",
|
||||
"",
|
||||
"SUMMARY",
|
||||
"-------",
|
||||
$"Overall Status: ($report.summary.overall_status | str upcase)",
|
||||
$"Total Checks: ($report.summary.total_checks)",
|
||||
$"Passed: ($report.summary.passed_checks)",
|
||||
$"Warnings: ($report.summary.warning_checks)",
|
||||
$"Failed: ($report.summary.failed_checks)",
|
||||
""
|
||||
]
|
||||
|
||||
# Add component details
|
||||
let component_lines = $report.summary.components | each {|comp|
|
||||
[
|
||||
$"($comp.name): ($comp.passed)/($comp.total) passed"
|
||||
]
|
||||
} | flatten
|
||||
|
||||
$lines | append $component_lines | str join "\n"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user