#!/usr/bin/env nu # Check Upstream Changes Script # Automatically checks for upstream changes and marks plugins as OK when only nu_* dependencies differ use lib/cargo_toml_diff.nu * # Version check - mandatory for all plugin operations def version_check [] { try { nu scripts/check_version.nu --quiet | ignore } catch { print "āŒ Nushell version mismatch detected!" print "šŸ”§ Run: nu scripts/check_version.nu --fix" exit 1 } } # Load plugin registry def load_registry [] { let registry_path = "etc/plugin_registry.toml" if not ($registry_path | path exists) { error make {msg: "Plugin registry not found. Please run from repository root directory."} } open $registry_path } # Load exclusions configuration def load_exclusions [] { let exclude_path = "etc/upstream_exclude.toml" if ($exclude_path | path exists) { let config = open $exclude_path { all: ($config.exclude.plugins? | default []), check: ($config.exclude.check.plugins? | default []), merge: ($config.exclude.merge.plugins? | default []), patterns: ($config.exclude.patterns.plugins? | default []) } } else { {all: [], check: [], merge: [], patterns: []} } } # Check if plugin should be excluded def is_plugin_excluded [plugin_name: string, operation: string, exclusions: record] { # Check direct exclusions if $plugin_name in $exclusions.all { return true } # Check operation-specific exclusions let op_exclusions = match $operation { "check" => $exclusions.check, "merge" => $exclusions.merge, _ => [] } if $plugin_name in $op_exclusions { return true } # Check pattern exclusions for pattern in $exclusions.patterns { if ($plugin_name | str contains ($pattern | str replace "*" "")) { return true } } false } # Save plugin registry def save_registry [registry: record] { $registry | to toml | save -f "etc/plugin_registry.toml" } # Get current date in ISO format def current_date [] { date now | format date "%Y-%m-%d %H:%M:%S" } # Add or update git remote for upstream def setup_upstream_remote [plugin_path: string, upstream_url: string] { if ($upstream_url | is-empty) { return } cd $plugin_path # Check if upstream remote exists let remotes = git remote | lines if not ("upstream" in $remotes) { print $"Adding upstream remote for ($plugin_path)..." git remote add upstream $upstream_url } else { # Update upstream URL if it changed let current_url = git remote get-url upstream | str trim if $current_url != $upstream_url { print $"Updating upstream remote URL for ($plugin_path)..." git remote set-url upstream $upstream_url } } } # Fetch upstream changes without merging def fetch_upstream [plugin_path: string, upstream_branch: string] { cd $plugin_path try { print $"Fetching upstream changes for ($plugin_path)..." git fetch upstream $upstream_branch true } catch { print $"Failed to fetch upstream for ($plugin_path)" false } } # Get latest upstream commit hash def get_upstream_commit [plugin_path: string, upstream_branch: string] { cd $plugin_path try { git rev-parse $"upstream/($upstream_branch)" | str trim } catch { "" } } # Check if there are differences between local and upstream def has_upstream_changes [plugin_path: string, upstream_branch: string] { cd $plugin_path try { let diff_output = git diff $"HEAD..upstream/($upstream_branch)" --name-only | lines ($diff_output | length) > 0 } catch { false } } # Create temporary upstream version of Cargo.toml for comparison def create_upstream_cargo_toml [plugin_path: string, upstream_branch: string] { cd $plugin_path let temp_file = "Cargo.toml.upstream" try { git show $"upstream/($upstream_branch):Cargo.toml" | save $temp_file $temp_file } catch { "" } } # Check for source code changes (non-Cargo.toml) def has_source_changes [plugin_path: string, upstream_branch: string] { cd $plugin_path try { let changed_files = git diff $"HEAD..upstream/($upstream_branch)" --name-only | lines let source_changes = $changed_files | where ($it =~ "\\.rs$") or ($it == "README.md") or ($it == "LICENSE") ($source_changes | length) > 0 } catch { false } } # Process a single plugin def process_plugin [plugin_name: string, plugin_config: record, nu_managed_deps: list, exclusions: record] { print $"\nšŸ” Checking ($plugin_name)..." # Check if plugin is excluded if (is_plugin_excluded $plugin_name "check" $exclusions) { print $" 🚫 Skipping ($plugin_name) - excluded from upstream checking" return $plugin_config } let plugin_path = $plugin_config.local_path # Skip if no upstream URL if ($plugin_config.upstream_url | is-empty) { print $" ā­ļø Skipping ($plugin_name) - no upstream URL" return $plugin_config } # Check if plugin directory exists if not ($plugin_path | path exists) { print $" āŒ Plugin directory not found: ($plugin_path)" return ($plugin_config | upsert status "error") } # Setup upstream remote setup_upstream_remote $plugin_path $plugin_config.upstream_url # Fetch upstream changes if not (fetch_upstream $plugin_path $plugin_config.upstream_branch) { print $" āŒ Failed to fetch upstream changes" return ($plugin_config | upsert status "error") } # Get latest upstream commit let upstream_commit = get_upstream_commit $plugin_path $plugin_config.upstream_branch if ($upstream_commit | is-empty) { print $" āŒ Could not get upstream commit hash" return ($plugin_config | upsert status "error") } # Check if commit changed since last check if $plugin_config.last_checked_commit == $upstream_commit { print $" āœ… No new upstream changes since last check" return ($plugin_config | upsert last_checked_date (current_date)) } # Check if there are any changes if not (has_upstream_changes $plugin_path $plugin_config.upstream_branch) { print $" āœ… Local is up to date with upstream" return ($plugin_config | upsert status "ok" | upsert last_checked_commit $upstream_commit | upsert last_checked_date (current_date)) } print $" šŸ“‹ Changes detected, analyzing..." # Check for source code changes let has_source = has_source_changes $plugin_path $plugin_config.upstream_branch if $has_source { print $" āš ļø Source code changes detected - requires manual review" return ($plugin_config | upsert status "pending" | upsert last_checked_commit $upstream_commit | upsert last_checked_date (current_date)) } # Create upstream Cargo.toml for comparison let upstream_cargo_path = create_upstream_cargo_toml $plugin_path $plugin_config.upstream_branch if ($upstream_cargo_path | is-empty) { print $" āŒ Could not extract upstream Cargo.toml" return ($plugin_config | upsert status "error") } # Compare Cargo.toml files let local_cargo = $"($plugin_path)/Cargo.toml" try { let diff_result = compare_cargo_tomls $local_cargo $"($plugin_path)/($upstream_cargo_path)" # Clean up temporary file rm $"($plugin_path)/($upstream_cargo_path)" if not $diff_result.analysis.has_changes { print $" āœ… No meaningful changes in Cargo.toml" return ($plugin_config | upsert status "ok" | upsert last_checked_commit $upstream_commit | upsert last_checked_date (current_date)) } # Check if safe for auto-OK if ($plugin_config.auto_ok_on_nu_deps_only) and (is_safe_for_auto_ok $diff_result $nu_managed_deps) { print $" āœ… AUTO-OK: Only nu_* dependency changes detected" let summary = generate_diff_summary $diff_result print $" Changes:\n($summary | str replace --all '\n' '\n ')" return ($plugin_config | upsert status "ok" | upsert last_checked_commit $upstream_commit | upsert last_checked_date (current_date)) } else { print $" āš ļø PENDING: Non-nu dependency changes require manual review" let summary = generate_diff_summary $diff_result print $" Changes:\n($summary | str replace --all '\n' '\n ')" return ($plugin_config | upsert status "pending" | upsert last_checked_commit $upstream_commit | upsert last_checked_date (current_date)) } } catch {|err| print $" āŒ Error comparing Cargo.toml files: ($err.msg)" # Clean up on error try { rm $"($plugin_path)/($upstream_cargo_path)" } catch { } return ($plugin_config | upsert status "error") } } # Main function def main [ --plugin (-p): string = "" # Check specific plugin only --verbose (-v) # Verbose output ] { # Mandatory version check before any plugin operations version_check print "šŸš€ Checking upstream changes for nushell plugins..." # 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"} } # Load registry and exclusions let registry = load_registry let exclusions = load_exclusions # Get managed nu dependencies let nu_managed_deps = $registry.settings.nu_managed_dependencies # Process plugins mut updated_registry = $registry let plugins_to_check = if ($plugin | is-empty) { $registry.plugins | transpose key value } else { if $plugin in ($registry.plugins | columns) { [{key: $plugin, value: ($registry.plugins | get $plugin)}] } else { error make {msg: $"Plugin '($plugin)' not found in registry"} } } for plugin_entry in $plugins_to_check { let plugin_name = $plugin_entry.key let plugin_config = $plugin_entry.value let updated_config = process_plugin $plugin_name $plugin_config $nu_managed_deps $exclusions $updated_registry = ($updated_registry | upsert $"plugins.($plugin_name)" $updated_config) } # Save updated registry save_registry $updated_registry print "\nšŸ“Š Summary:" let status_counts = $updated_registry.plugins | values | group-by status | transpose status count | each {|entry| {status: $entry.status, count: ($entry.count | length)}} for status in $status_counts { let emoji = match $status.status { "ok" => "āœ…", "pending" => "āš ļø", "error" => "āŒ", "local_only" => "šŸ ", "unknown" => "ā“", _ => "šŸ“¦" } print $" ($emoji) ($status.status): ($status.count) plugins" } print $"\nšŸ”„ Registry updated: etc/plugin_registry.toml" print "šŸ’” Run 'bash scripts/run.sh plugin_status.nu' to see detailed status" } if ($env.NUSHELL_EXECUTION_CONTEXT? | default "" | str contains "run") { main }