#!/usr/bin/env nu # Pack Distribution Script # Creates distribution archives for built plugins with multi-platform support # Load environment variables from env file def load_env_vars [] { let env_file = "env" if ($env_file | path exists) { let content = open $env_file | lines | where ($it | str trim | str length) > 0 | where not ($it | str starts-with "#") | each {|line| if ($line | str contains "=") { let parts = ($line | split row "=") if ($parts | length) >= 2 { let key = ($parts | get 0 | str replace "export " "" | str trim) let raw_value = ($parts | get 1 | str trim) # Handle bash-style default values like ${VAR:-default} let value = if ($raw_value | str starts-with "${") and ($raw_value | str contains ":-") { # Extract default value from ${VAR:-default} let parts = ($raw_value | str replace "${" "" | str replace "}" "" | split row ":-") if ($parts | length) >= 2 { $parts | get 1 } else { $raw_value } } else { $raw_value } {$key: $value} } else { {} } } else { {} } } | reduce -f {} {|item, acc| if ($item | is-empty) { $acc } else { $acc | merge $item } } $content } else { { APP_NAME: "nushell-plugins", TARGET_PATH: "distribution", INSTALL_FILE: "install_nu_plugins.nu", INSTALL_BIN_PATH: "/usr/local/bin", ARCHIVE_DIR_PATH: "/tmp", BIN_ARCHIVES_DIR_PATH: "bin_archives" } } } # Load build targets configuration def load_targets_config [] { if ("etc/build_targets.toml" | path exists) { open etc/build_targets.toml } else { {targets: {}} } } # Get system architecture info def get_system_info [] { let arch = match (^uname -m) { "x86_64" => "amd64", "aarch64" => "arm64", $arch if ($arch | str starts-with "arm") => "arm64", $other => $other } let platform = match (^uname -s | str downcase) { $os if ($os | str contains "linux") => "linux", $os if ($os | str contains "darwin") => "darwin", $os if ($os | str contains "windows") => "windows", $other => $other } {platform: $platform, arch: $arch, full: $"($platform)-($arch)"} } # Get available distribution platforms def get_available_platforms [base_path: string] { let config = load_targets_config mut platforms = [] # Check for single platform in base directory let base_plugins = glob ($base_path | path join "nu_plugin_*") | where ($it | path type) == "file" if ($base_plugins | length) > 0 { let host_info = get_system_info $platforms = ($platforms | append { name: "host", platform_name: $host_info.full, path: $base_path, plugin_count: ($base_plugins | length), description: "Host platform distribution" }) } # Check for platform-specific subdirectories if ($config.targets | transpose name config | length) > 0 { for target in ($config.targets | transpose name config) { let platform_path = ($base_path | path join $target.name) if ($platform_path | path exists) { let platform_plugins = glob ($platform_path | path join "nu_plugin_*") | where ($it | path type) == "file" if ($platform_plugins | length) > 0 { $platforms = ($platforms | append { name: $target.name, platform_name: $target.config.platform_name, path: $platform_path, plugin_count: ($platform_plugins | length), description: $target.config.description, archive_format: $target.config.archive_format }) } } } } $platforms } # Generate checksums for a file def generate_checksums [file_path: string] { let sha256 = (open $file_path | hash sha256) let size = (ls $file_path | get 0.size) { file: ($file_path | path basename), sha256: $sha256, size: $size } } # Check if target directory has required files def validate_target [target_path: string] { let required_files = ["LICENSE", "README"] let optional_files = ["env"] let plugin_files = (ls $target_path | where name =~ "nu_plugin_" and type == "file" | get name) mut issues = [] if ($plugin_files | length) == 0 { $issues = ($issues | append "No plugin binaries found") } for file in $required_files { if not (($target_path | path join $file) | path exists) { $issues = ($issues | append $"Required file missing: ($file)") } } {valid: (($issues | length) == 0), issues: $issues, plugin_count: ($plugin_files | length)} } # Create archive def create_archive [target_path: string, archive_path: string, env_vars: record] { # Copy env file to target temporarily if ("env" | path exists) { cp env ($target_path | path join "env") } # Get plugin files using glob let plugin_files = glob ($target_path | path join "nu_plugin_*") | where ($it | path type) == "file" let metadata_files = [ ($target_path | path join $env_vars.INSTALL_FILE), ($target_path | path join "LICENSE"), ($target_path | path join "README"), ($target_path | path join "env") ] # Filter metadata files that actually exist and combine with plugin files let existing_metadata_files = $metadata_files | where ($it | path exists) let existing_files = $plugin_files | append $existing_metadata_files if ($existing_files | length) == 0 { error make {msg: "No files found to archive"} } print $"šŸ“¦ Creating archive with ($existing_files | length) files..." try { # Use tar to create the archive tar czf $archive_path ...$existing_files # Clean up temporary env file if (($target_path | path join "env") | path exists) { rm ($target_path | path join "env") } let archive_size = ls $archive_path | get 0.size print $"āœ… Archive created: ($archive_path) (($archive_size))" true } catch {|err| # Clean up on failure if ($"($target_path)/env" | path exists) { rm $"($target_path)/env" } error make {msg: $"Failed to create archive: ($err.msg)"} } } # Main function def main [ --target (-t): string = "" # Override target path --platform (-p): string = "" # Specific platform to package --all-platforms (-a) # Package all available platforms --output (-o): string = "" # Override output archive path --archive-dir: string = "" # Override archive directory --force (-f) # Force overwrite existing archive --list (-l) # List what would be archived --list-platforms # List available platforms --checksums # Generate checksums file ] { # Ensure we're in the repository root directory if not ("nu_plugin_clipboard" | path exists) { error make {msg: "Please run this script from the nushell-plugins repository root directory"} } print "šŸ“¦ Nushell Plugin Distribution Packager" # Load environment variables let env_vars = load_env_vars let base_target_path = if ($target | str length) > 0 { $target } else { $env_vars.TARGET_PATH } let archive_dir_path = if ($archive_dir | str length) > 0 { $archive_dir } else { $env_vars.BIN_ARCHIVES_DIR_PATH? | default "bin_archives" } # Validate base target directory if not ($base_target_path | path exists) { print $"āŒ Target directory not found: ($base_target_path)" print "šŸ’” Run 'just collect' first to collect plugins" exit 1 } # Get available platforms let available_platforms = get_available_platforms $base_target_path # List platforms mode if $list_platforms { if ($available_platforms | length) == 0 { print "ā“ No platforms found for packaging" print "šŸ’” Run 'just collect --all-platforms' first" } else { print "šŸ“Š Available platforms for packaging:" for platform in $available_platforms { let format = $platform | get archive_format? | default "tar.gz" print $" āœ… ($platform.name) ($platform.platform_name): ($platform.plugin_count) plugins → .($format)" print $" Path: ($platform.path)" print $" ($platform.description)" } } return } # Determine platforms to package let platforms_to_package = if $all_platforms { $available_platforms } else if ($platform | str length) > 0 { let selected = $available_platforms | where name == $platform if ($selected | length) == 0 { print $"āŒ Platform not found: ($platform)" print $"šŸ’” Available platforms: ($available_platforms | get name | str join ', ')" exit 1 } $selected } else { # Default to host platform or first available let host_platform = $available_platforms | where name == "host" if ($host_platform | length) > 0 { $host_platform } else { $available_platforms | first 1 } } if ($platforms_to_package | length) == 0 { print "ā“ No platforms to package" print "šŸ’” Run 'just collect' first to collect plugins" exit 1 } print $"šŸŽÆ Platforms to package: ($platforms_to_package | length)" for platform in $platforms_to_package { print $" - ($platform.name): ($platform.plugin_count) plugins" } # Create archive directory if needed if not ($archive_dir_path | path exists) { mkdir $archive_dir_path print $"šŸ“ Created archive directory: ($archive_dir_path)" } # List mode if $list { print "\nšŸ“‹ Files that would be archived:" for platform in $platforms_to_package { print $"\n($platform.name) ($platform.platform_name):" let files = ls $platform.path | where type == "file" for file in $files { print $" šŸ“„ ($file.name) (($file.size))" } } return } # Package each platform mut created_archives = [] mut total_size = 0 for platform in $platforms_to_package { let platform_name = $platform.platform_name let platform_path = $platform.path let archive_format = $platform | get archive_format? | default "tar.gz" # Validate platform directory let validation = validate_target $platform_path if not $validation.valid { print $"āŒ Platform directory validation failed for ($platform.name):" for issue in $validation.issues { print $" - ($issue)" } continue } # Create archive name let archive_name = $"($platform_name)-($env_vars.APP_NAME).($archive_format)" let archive_path = if ($output | str length) > 0 and ($platforms_to_package | length) == 1 { $output } else { $"($archive_dir_path)/($archive_name)" } # Check if archive already exists if ($archive_path | path exists) and not $force { print $"āš ļø Archive already exists: ($archive_path)" if ($platforms_to_package | length) == 1 { let confirm = input "Overwrite? [y/N]: " if ($confirm | str downcase) != "y" { print "āŒ Skipping ($platform.name)" continue } } else { print "šŸ”„ Overwriting existing archive (use --force to skip this check)" } } print $"\nšŸ“¦ Creating archive for ($platform.name) ($platform_name)..." print $"šŸ“ Source: ($platform_path)" print $"šŸ“¦ Archive: ($archive_path)" try { create_archive $platform_path $archive_path $env_vars let archive_size = ls $archive_path | get 0.size $total_size = $total_size + ($archive_size | into int) $created_archives = ($created_archives | append { platform: $platform.name, platform_name: $platform_name, archive_path: $archive_path, size: $archive_size, plugin_count: $platform.plugin_count }) print $"āœ… Created archive for ($platform.name): ($archive_size)" } catch {|err| print $"āŒ Failed to create archive for ($platform.name): ($err.msg)" } } # Generate checksums if requested if $checksums and ($created_archives | length) > 0 { print "\nšŸ” Generating checksums..." let checksums_file = $"($archive_dir_path)/checksums.txt" mut checksum_data = [] for archive in $created_archives { let checksum_info = generate_checksums $archive.archive_path $checksum_data = ($checksum_data | append $checksum_info) print $" āœ… ($checksum_info.file): ($checksum_info.sha256)" } # Write checksums file let checksums_content = ($checksum_data | each {|item| $"($item.sha256) ($item.file)" } | str join "\n") $checksums_content | save $checksums_file print $"šŸ’¾ Checksums saved to: ($checksums_file)" } # Summary print $"\nšŸŽ‰ Distribution packaging completed!" print $"šŸ“ Archive directory: ($archive_dir_path)" print $"šŸ“¦ Archives created: ($created_archives | length)" print $"šŸ“ Total size: ($total_size | into filesize)" if ($created_archives | length) > 0 { print "\nāœ… Created archives:" for archive in $created_archives { print $" - ($archive.platform_name): ($archive.size) ($archive.plugin_count) plugins" print $" šŸ“¦ ($archive.archive_path)" } } print "\nšŸ’” Next steps:" if ($created_archives | length) > 1 { print " 1. Test archives: tar -tzf bin_archives/*.tar.gz" print " 2. Upload to GitHub Releases or distribution server" print " 3. Update installation documentation" } else if ($created_archives | length) == 1 { let archive = $created_archives.0 print $" 1. Test archive: tar -tzf ($archive.archive_path)" print $" 2. Distribute: Upload or copy to target systems" print $" 3. Install: Extract and run the installation script" } } if ($env.NUSHELL_EXECUTION_CONTEXT? | default "" | str contains "run") { main }