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