#!/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) # Force overwrite existing files --list (-l) # List available binaries --list-platforms # List available platforms --all-platforms # Collect for all available platforms --include-nushell (-n) # Include nushell binary (default: true) --release (-r) # Use release builds (default: debug) --profile: string = "" # Build profile to use ] { # Convert flags to variables for easier use let force_flag = ($force | default false) let list_flag = ($list | default false) let list_platforms_flag = ($list_platforms | default false) let all_platforms_flag = ($all_platforms | default false) let include_nushell_flag = true # always include nushell binary by default let release_flag = ($release | default false) # Validate nushell version consistency first validate_nushell_version if $list_platforms_flag { list_available_platforms return } if $list_flag { list_available_binaries $platform $release_flag $profile return } if $all_platforms_flag { collect_all_platforms $output $force_flag $include_nushell_flag $release_flag $profile return } # Collect binaries for specific or current platform collect_binaries $platform $output $force_flag $include_nushell_flag $release_flag $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 | get msg | default 'Unknown error'))" } } 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" 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 mut profile_dir = if ($profile | is-not-empty) { $profile } else if $use_release { "release" } else { "debug" } let binary_name = if ($platform | str contains "windows") or $nu.os-info.name == "windows" { "nu.exe" } else { "nu" } let binary_path = $"($target_dir)/($profile_dir)/($binary_name)" if ($binary_path | path exists) { let file_info = ls $binary_path | get 0 return { name: "nu" path: $binary_path size: $file_info.size modified: $file_info.modified } } return {} } # Get workspace plugins information def get_workspace_plugins_info [ platform: string use_release: bool profile: string ]: nothing -> list { let nushell_dir = $"($env.PWD)/nushell" 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 mut profile_dir = if ($profile | is-not-empty) { $profile } else if $use_release { "release" } else { "debug" } let workspace_plugins = [ "nu_plugin_custom_values" "nu_plugin_example" "nu_plugin_formats" "nu_plugin_gstat" "nu_plugin_inc" "nu_plugin_polars" "nu_plugin_query" "nu_plugin_stress_internals" ] mut found_plugins = [] for plugin in $workspace_plugins { let binary_name = if ($platform | str contains "windows") or $nu.os-info.name == "windows" { $"($plugin).exe" } else { $plugin } let binary_path = $"($target_dir)/($profile_dir)/($binary_name)" if ($binary_path | path exists) { let file_info = ls $binary_path | get 0 $found_plugins = ($found_plugins | append { name: $plugin path: $binary_path size: $file_info.size modified: $file_info.modified }) } } return $found_plugins } # Get custom plugins information def get_custom_plugins_info [ platform: string use_release: bool profile: string ]: nothing -> list { # Get list of plugin directories (nu_plugin_*) let plugin_dirs = ls nu_plugin_* | where type == "dir" | where name != "nushell" # Exclude nushell submodule | get name mut found_plugins = [] let target_triple = convert_platform_to_target $platform for plugin_dir in $plugin_dirs { 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 mut profile_dir = if ($profile | is-not-empty) { $profile } else if $use_release { "release" } else { "debug" } let binary_name = if ($platform | str contains "windows") or $nu.os-info.name == "windows" { $"($plugin_dir).exe" } else { $plugin_dir } let binary_path = $"($target_dir)/($profile_dir)/($binary_name)" if ($binary_path | path exists) { let file_info = ls $binary_path | get 0 $found_plugins = ($found_plugins | append { name: $plugin_dir path: $binary_path size: $file_info.size modified: $file_info.modified }) } } return $found_plugins } # Utility functions def detect_current_platform []: nothing -> string { let os = $nu.os-info.name let arch = $nu.os-info.arch match [$os, $arch] { ["linux", "x86_64"] => "linux-x86_64" ["linux", "aarch64"] => "linux-aarch64" ["macos", "x86_64"] => "darwin-x86_64" ["macos", "aarch64"] => "darwin-arm64" ["windows", "x86_64"] => "windows-x86_64" ["windows", "aarch64"] => "windows-aarch64" _ => $"($os)-($arch)" } } def detect_current_target []: nothing -> string { let os = $nu.os-info.name let arch = $nu.os-info.arch match [$os, $arch] { ["linux", "x86_64"] => "x86_64-unknown-linux-gnu" ["linux", "aarch64"] => "aarch64-unknown-linux-gnu" ["macos", "x86_64"] => "x86_64-apple-darwin" ["macos", "aarch64"] => "aarch64-apple-darwin" ["windows", "x86_64"] => "x86_64-pc-windows-msvc" ["windows", "aarch64"] => "aarch64-pc-windows-msvc" _ => $"($arch)-unknown-($os)" } } def convert_platform_to_target [platform: string]: nothing -> string { match $platform { "linux-x86_64" => "x86_64-unknown-linux-gnu" "linux-aarch64" => "aarch64-unknown-linux-gnu" "darwin-x86_64" => "x86_64-apple-darwin" "darwin-arm64" => "aarch64-apple-darwin" "windows-x86_64" => "x86_64-pc-windows-msvc" "windows-aarch64" => "aarch64-pc-windows-msvc" _ => $platform } } def detect_available_platforms []: nothing -> list { mut platforms = [] # Check nushell target directory for built platforms let nushell_target = $"($env.PWD)/nushell/target" if ($nushell_target | path exists) { let target_dirs = try { ls $nushell_target | where type == "dir" | get name } catch { [] } for target_dir in $target_dirs { if ($target_dir | str ends-with "target") { continue } # Skip base target dir let target_name = ($target_dir | path basename) let platform_name = convert_target_to_platform $target_name # Check if this target has binaries let release_dir = $"($target_dir)/release" let debug_dir = $"($target_dir)/debug" let has_release = ($release_dir | path exists) and (try { ls $release_dir | where name =~ "nu" | length } catch { 0 }) > 0 let has_debug = ($debug_dir | path exists) and (try { ls $debug_dir | where name =~ "nu" | length } catch { 0 }) > 0 if $has_release or $has_debug { $platforms = ($platforms | append { name: $platform_name target: $target_name has_nushell: true plugin_count: 0 # Will be calculated }) } } } # If no cross-compiled targets found, add current platform if ($platforms | is-empty) { let current_platform = detect_current_platform let current_target = detect_current_target $platforms = ($platforms | append { name: $current_platform target: $current_target has_nushell: true plugin_count: 0 }) } return $platforms } def convert_target_to_platform [target: string]: nothing -> string { match $target { "x86_64-unknown-linux-gnu" => "linux-x86_64" "aarch64-unknown-linux-gnu" => "linux-aarch64" "x86_64-apple-darwin" => "darwin-x86_64" "aarch64-apple-darwin" => "darwin-arm64" "x86_64-pc-windows-msvc" => "windows-x86_64" "aarch64-pc-windows-msvc" => "windows-aarch64" _ => $target } } def create_target_structure [target_path: string, force: bool] { if ($target_path | path exists) and not $force { log_error $"Target directory exists: ($target_path). Use --force to overwrite." exit 1 } if ($target_path | path exists) and $force { rm -rf $target_path log_warn $"🗑️ Removed existing directory: ($target_path)" } mkdir $target_path log_info $"📁 Created target directory: ($target_path)" # Create subdirectories let subdirs = ["scripts", "config", "docs"] for subdir in $subdirs { mkdir $"($target_path)/($subdir)" } } def copy_binary [src: string, dest: string, force: bool] { if ($dest | path exists) and not $force { log_warn $"⚠️ File exists, skipping: ($dest)" return } cp $src $dest chmod +x $dest } def copy_additional_files [target_path: string, force: bool] { let files_to_copy = [ {src: "LICENSE", dest: "LICENSE", required: false} {src: "README.md", dest: "README.md", required: false} {src: "CHANGELOG.md", dest: "CHANGELOG.md", required: false} ] for file in $files_to_copy { if ($file.src | path exists) { let dest_path = $"($target_path)/($file.dest)" if not ($dest_path | path exists) or $force { cp $file.src $dest_path log_info $" 📄 Copied ($file.src) → ($file.dest)" } } } } def create_manifest [ target_path: string platform: string files: list includes_nushell: bool ] { let manifest = { platform: $platform created: (date now | format date %Y-%m-%d_%H-%M-%S) includes_nushell: $includes_nushell total_files: ($files | length) files: $files nushell_version: (try { if $includes_nushell { let nu_binary = ($files | where type == "nushell" | get 0) if ($nu_binary | is-not-empty) { (run-external $nu_binary.path "--version" | complete | get stdout | str trim) } else { "unknown" } } else { "not_included" } } catch { "unknown" }) } $manifest | to json | save --force $"($target_path)/manifest.json" log_info $"📋 Created manifest: ($target_path)/manifest.json" } def create_installation_scripts [target_path: string] { # Create a basic installation script that registers plugins let install_script = '#!/usr/bin/env nu # Installation script for nushell and plugins # This script registers all plugins with the nushell binary def main [ --verify # 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 { let file_info = ls $path | get 0 $file_info.size } catch { 0 } }