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