nushell-plugins/scripts/verify_installation.nu
Jesús Pérez 41455c5b3e
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
feat: Add complete Nushell full distribution system
## 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
2025-09-24 18:52:07 +01:00

843 lines
31 KiB
Plaintext
Executable File

#!/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"
}