
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
503 lines
17 KiB
Plaintext
Executable File
503 lines
17 KiB
Plaintext
Executable File
#!/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\""
|
|
}
|
|
} |