#!/usr/bin/env nu # Update All Plugins - Bulk Plugin Updater # Updates all nu_plugin_* Cargo.toml dependencies to a new Nushell version # # Usage: # update_all_plugins.nu 0.108.0 # Update dependencies to version # update_all_plugins.nu 0.108.0 --auto-approve # Skip confirmation # update_all_plugins.nu --list # List current versions # update_all_plugins.nu replace-version 0.109.1 0.110.0 # Replace [package] version # update_all_plugins.nu replace-version 0.109.1 0.110.0 --dry-run # Preview changes use lib/common_lib.nu * # Main entry point def main [ target_version?: string # Target Nushell version (e.g., "0.108.0") --auto-approve # Skip confirmation prompts --dry-run # Show what would be updated without changing --list # List current plugin versions --plugin: string # Update only specific plugin ] { if $list { list_plugin_versions return } if ($target_version | is-empty) { log_error "Please specify a target version" show_usage exit 1 } log_info $"šŸ”„ Updating all plugins to Nushell ($target_version)" # Get all plugin directories let plugins = if ($plugin | is-not-empty) { [{ name: $plugin, path: $plugin }] } else { get_plugin_directories } if ($plugins | length) == 0 { log_error "No plugin directories found" exit 1 } log_info $"Found ($plugins | length) plugins to update" # Show current versions log_info "\nšŸ“‹ Current Versions:" for p in $plugins { let current = get_plugin_version $p.path print $" ($p.name): ($current)" } # Confirm update if not $auto_approve and not $dry_run { print "" let response = input $"Update all plugins to ($target_version)? \(yes/no\): " if $response != "yes" { log_info "Update cancelled" exit 0 } } # Update each plugin mut updated = 0 mut failed = [] print "" for p in $plugins { if $dry_run { log_info $"\(DRY RUN\) Would update ($p.name)" $updated = $updated + 1 } else { let result = update_plugin $p.path $target_version if $result { log_success $"āœ“ Updated ($p.name)" $updated = $updated + 1 } else { log_error $"āœ— Failed to update ($p.name)" $failed = ($failed | append $p.name) } } } # Summary print "" if ($failed | length) == 0 { log_success $"āœ… Successfully updated ($updated) plugins to ($target_version)" } else { log_warn $"āš ļø Updated ($updated) plugins, ($failed | length) failed:" for f in $failed { log_error $" • ($f)" } exit 1 } if not $dry_run { log_info "\nšŸ“ Next steps:" log_info " 1. Build plugins: just build" log_info " 2. Test plugins: just test" log_info " 3. Create distribution: just pack-full" } } # Show usage def show_usage [] { print "Usage:" print " update_all_plugins.nu # Update to version" print " update_all_plugins.nu --auto-approve" print " update_all_plugins.nu --list # List current versions" print "" print "Examples:" print " update_all_plugins.nu 0.108.0" print " update_all_plugins.nu 0.108.0 --dry-run" print " update_all_plugins.nu 0.108.0 --plugin nu_plugin_image" } # Get plugin directories def get_plugin_directories []: nothing -> list { ls nu_plugin_* | where type == dir | each {|row| { name: (get_plugin_name $row.name) path: $row.name } } } # Get plugin name from directory def get_plugin_name [dir: string]: nothing -> string { $dir | path basename } # Get current plugin version def get_plugin_version [plugin_dir: string]: nothing -> string { let cargo_toml = $"($plugin_dir)/Cargo.toml" if not ($cargo_toml | path exists) { return "N/A" } try { let cargo_data = open $cargo_toml let deps = $cargo_data | get dependencies # Try to get nu-plugin version if "nu-plugin" in ($deps | columns) { let nu_plugin_spec = $deps | get nu-plugin if ($nu_plugin_spec | describe) == "record" { return ($nu_plugin_spec | get version) } else { return $nu_plugin_spec } } return "unknown" } catch { return "error" } } # Update single plugin def update_plugin [ plugin_dir: string target_version: string ]: nothing -> bool { let cargo_toml = $"($plugin_dir)/Cargo.toml" if not ($cargo_toml | path exists) { log_error $"Cargo.toml not found in ($plugin_dir)" return false } try { # Read current Cargo.toml let content = open $cargo_toml # Update [dependencies] nu-* crates let updated_deps = update_nu_dependencies ($content | get dependencies) $target_version let updated_content = $content | update dependencies $updated_deps # Update [dev-dependencies] nu-* crates if section exists let updated_content = if "dev-dependencies" in ($updated_content | columns) { let updated_dev_deps = update_nu_dependencies ($updated_content | get dev-dependencies) $target_version $updated_content | update dev-dependencies $updated_dev_deps } else { $updated_content } # Update [package] version — these plugins always track the nushell release cycle let final_content = if "package" in ($updated_content | columns) { $updated_content | update package.version $target_version } else { $updated_content } # Only save if content actually changed (avoid unnecessary file timestamp updates) let original_toml = $content | to toml let new_toml = $final_content | to toml if $original_toml != $new_toml { # Content changed - save the file $final_content | to toml | save -f $cargo_toml return true } else { # No changes needed - don't touch the file return true } } catch {|err| log_error $"Error updating ($plugin_dir): ($err.msg)" return false } } # Update nu-* dependencies in dependencies table def update_nu_dependencies [ deps: record target_version: string ]: nothing -> record { mut updated_deps = $deps # List of nu-* crates to update (covers both [dependencies] and [dev-dependencies]) let nu_crates = [ "nu-plugin" "nu-protocol" "nu-engine" "nu-parser" "nu-cmd-base" "nu-color-config" "nu-utils" "nu-path" "nu-glob" "nu-json" "nu-pretty-hex" "nu-system" "nu-table" "nu-term-grid" "nu-plugin-test-support" ] for crate in $nu_crates { if $crate in ($deps | columns) { let spec = $deps | get $crate # Update version based on spec type if ($spec | describe) == "record" { # It's a detailed spec with version, path, features, etc. let updated_spec = $spec | update version $target_version $updated_deps = ($updated_deps | update $crate $updated_spec) } else { # It's just a version string $updated_deps = ($updated_deps | update $crate $target_version) } } } $updated_deps } # List all plugin versions def list_plugin_versions [] { log_info "šŸ“‹ Current Plugin Versions\n" let plugins = get_plugin_directories # Create table of versions let versions = $plugins | each {|p| { plugin: $p.name nu_plugin_version: (get_plugin_version $p.path) } } $versions | table print "" log_info $"Total plugins: ($plugins | length)" } # Check for version mismatches def "main check" [] { log_info "šŸ” Checking for version mismatches\n" let plugins = get_plugin_directories # Get all unique versions let versions = $plugins | each {|p| get_plugin_version $p.path} | uniq if ($versions | length) == 1 { log_success $"āœ… All plugins use consistent version: ($versions | first)" } else { log_warn $"āš ļø Version mismatch detected! Found ($versions | length) different versions:" for v in $versions { let count = $plugins | each {|p| get_plugin_version $p.path} | where {|ver| $ver == $v} | length log_info $" ($v): ($count) plugins" } # Show which plugins have which versions print "\nDetailed breakdown:" for p in $plugins { let version = get_plugin_version $p.path print $" ($p.name): ($version)" } } } # Update to match nushell submodule version def "main sync" [] { log_info "šŸ”„ Syncing plugin versions with nushell submodule\n" # Get version from nushell submodule let nushell_cargo = "./nushell/Cargo.toml" if not ($nushell_cargo | path exists) { log_error "Nushell submodule not found at ./nushell/" log_info "Download it first with: ./scripts/download_nushell.nu" exit 1 } let nushell_version = try { open $nushell_cargo | get package.version } catch { log_error "Failed to read nushell version" exit 1 } log_info $"Nushell submodule version: ($nushell_version)" log_info "Updating all plugins to match...\n" # Run main update main $nushell_version --auto-approve } # Replace package version (from -> to) # Only updates plugins with exact match of from_version def "main replace-version" [ from_version: string # Source version to replace (e.g., "0.109.1") to_version: string # Target version (e.g., "0.110.0") --dry-run # Show what would be updated without changing ] { log_info $"šŸ”„ Replacing [package] version from ($from_version) to ($to_version)" # Get all plugin directories let all_plugins = get_plugin_directories if ($all_plugins | is-empty) { log_error "No plugin directories found" exit 1 } log_info $"Found ($all_plugins | length) plugin directories" # Find plugins with matching version let matching = $all_plugins | where {|plugin| (get_package_version $plugin.path) == $from_version } let non_matching = $all_plugins | where {|plugin| (get_package_version $plugin.path) != $from_version } # Show results print "" log_info "šŸ“‹ Analysis Results:" log_success $" āœ“ Matching ($from_version): ($matching | length) plugins" log_info $" • Other versions: ($non_matching | length) plugins" if ($matching | is-empty) { log_warn $"\nNo plugins found with [package] version ($from_version)" log_info "Nothing to update." exit 0 } # Show which plugins will be updated print "\nšŸŽÆ Plugins to update:" $matching | each {|m| print $" ($m.name)"} if ($non_matching | is-not-empty) { print "\nā­ļø Skipping (different versions):" $non_matching | each {|nm| let ver = get_package_version $nm.path print $" ($nm.name) \(($ver)\)" } } # Confirm update if not $dry_run { print "" let response = input $"Update ($matching | length) plugins from ($from_version) to ($to_version)? \(yes/no\): " if $response != "yes" { log_info "Update cancelled" exit 0 } } # Update each matching plugin let results = $matching | each {|m| if $dry_run { log_info $"\(DRY RUN\) Would update ($m.name)" {plugin: $m.name, success: true} } else { let success = replace_package_version $m.path $to_version if $success { log_success $"āœ“ Updated ($m.name)" } else { log_error $"āœ— Failed to update ($m.name)" } {plugin: $m.name, success: $success} } } let updated = $results | where success | length let failed = $results | where {|r| not $r.success} # Summary print "" if ($failed | is-empty) { log_success $"āœ… Successfully updated ($updated) plugins to ($to_version)" } else { log_warn $"āš ļø Updated ($updated) plugins, ($failed | length) failed:" $failed | each {|f| log_error $" • ($f.plugin)"} exit 1 } if not $dry_run { log_info "\nšŸ“ Next steps:" log_info " 1. Verify changes: git diff" log_info " 2. Build plugins: just build" log_info " 3. Test plugins: just test" } } # Get package version (not dependency version) def get_package_version [plugin_dir: string]: nothing -> string { let cargo_toml = $"($plugin_dir)/Cargo.toml" if not ($cargo_toml | path exists) { return "N/A" } let cargo_data = open $cargo_toml if "package" not-in ($cargo_data | columns) { return "N/A" } $cargo_data.package.version } # Replace package version in Cargo.toml def replace_package_version [ plugin_dir: string new_version: string ]: nothing -> bool { let cargo_toml = $"($plugin_dir)/Cargo.toml" if not ($cargo_toml | path exists) { log_error $"Cargo.toml not found in ($plugin_dir)" return false } let content = open $cargo_toml if "package" not-in ($content | columns) { log_warn $"No [package] section found in ($plugin_dir)" return false } # Update package version let updated_content = $content | update package.version $new_version # Only save if content actually changed let original_toml = $content | to toml let new_toml = $updated_content | to toml if $original_toml == $new_toml { return true } # Save the file $updated_content | to toml | save -f $cargo_toml true }