#!/usr/bin/env nu # Main version management interface # Completely configuration-driven, no hardcoded components # Selective imports (ADR-025 Phase 3 Layer 2). use domain/version/core.nu [check-version] use domain/version/loader.nu [discover-configurations load-configuration-file] use domain/version/formatter.nu [format-results] use primitives/io/interface.nu [_print] # Check versions for discovered components export def check-versions [ --path: string = "" # Base path to search --types: list = [] # Filter by types --fetch-latest = false # Fetch latest versions --respect-fixed = true # Respect fixed flag --config-file: string = "" # Use specific config file ] { # Load configurations let configs = if ($config_file | is-not-empty) { load-configuration-file $config_file } else { discover-configurations --base-path=$path --types=$types } # Check each configuration $configs | each { |config| check-version $config --fetch-latest=$fetch_latest --respect-fixed=$respect_fixed } } # Display version status export def show-versions [ --path: string = "" --types: list = [] --fetch-latest = true --group-by: string = "type" --format: string = "table" # table, json, yaml ] { let results = (check-versions --path=$path --types=$types --fetch-latest=$fetch_latest) match $format { "table" => { format-results $results --group-by=$group_by } "json" => { print ($results | to json -i 2) } "yaml" => { print ($results | to yaml) } _ => { format-results $results } } } # Check for available updates (does not modify configs) export def check-available-updates [ --path: string = "" --types: list = [] ] { let results = (check-versions --path=$path --types=$types --fetch-latest=true --respect-fixed=true) let updates = ($results | where status == "update_available") if ($updates | is-empty) { _print "āœ… All components are up to date" return } _print "Updates available:" _print ($updates | select id configured latest | rename id configured "latest available" | table) # Show installation guidance for each update for update in $updates { let config = (discover-configurations --types=[$update.type] | where id == $update.id | first | default null) if ($config | is-not-empty) { show-installation-guidance $config $update.latest } } _print $"\nšŸ’” After installing, run 'tools apply-updates' to update configuration files" } # Apply updates to configuration files export def apply-config-updates [ --path: string = "" --types: list = [] --dry-run = false --force = false # Update even if fixed --auto-yes = false # Skip prompts and auto-confirm ] { # Separate types from component ids (types are "provider", "generic"; ids are "upctl", "aws", etc.) let all_configs = (discover-configurations --base-path=$path) let known_types = ($all_configs | get type | uniq) let filter_types = ($types | where { |t| $t in $known_types }) let filter_ids = ($types | where { |t| $t not-in $known_types }) # Check locally only (don't fetch latest to avoid GitHub API rate limits) let results_local = if ($filter_types | is-empty) { (check-versions --path=$path --fetch-latest=false --respect-fixed=(not $force)) } else { (check-versions --path=$path --types=$filter_types --fetch-latest=false --respect-fixed=(not $force)) } # Apply id filters if provided let results = if ($filter_ids | length) > 0 { $results_local | where id in $filter_ids } else { $results_local } # Find mismatches between installed and configured let all_updates = ($results | where status in ["ahead_config", "behind_config"]) if ($all_updates | is-empty) { _print "āœ… All configurations match installed versions" return } _print "Configuration updates available to match installed versions:" _print ($all_updates | select id configured installed status | table) if $dry_run { _print "\nšŸ” Dry run mode - no changes will be made" return } if not $auto_yes { let proceed = (input "Update configurations to match installed versions? (y/n): ") if $proceed != "y" { return } } else { _print "Auto-confirming updates..." } # Update each component's configuration file for update in $all_updates { let config = (discover-configurations --types=[$update.type] | where id == $update.id | first | default null) if ($config | is-not-empty) { let source_file = $config.metadata.source_file # Update to match installed version update-configuration-file $source_file $update.id $update.installed _print $"āœ… Updated config ($update.id): ($update.configured) -> ($update.installed)" } } } # Show agnostic installation guidance export def show-installation-guidance [ config: record version: string ] { _print $"\nšŸ“¦ To install ($config.id) ($version):" # Show documentation/site links from configuration if ($config.metadata.site | is-not-empty) { _print $" • Documentation: ($config.metadata.site)" } # Show source repository if available if ($config.source.type? | default "" | str contains "github") { let repo = ($config.source.repo? | default "") if ($repo | is-not-empty) { _print $" • Releases: https://github.com/($repo)/releases" } } # Show generic installation command if available in metadata if ($config.metadata.install_cmd? | default "" | is-not-empty) { _print $" • Install: ($config.metadata.install_cmd)" } _print $"\nšŸ” Configuration updated, manual installation required" _print $"šŸ’” Run 'tools check ($config.id)' after installation to verify" } # Update configuration file export def update-configuration-file [ file_path: string component_id: string new_version: string ] { if not ($file_path | path exists) { return } let ext = ($file_path | path parse | get extension) match $ext { "yaml" | "yml" => { let data = (open $file_path) let updated = ($data | upsert $component_id ($data | get $component_id | upsert version $new_version)) $updated | save -f $file_path } "json" => { let data = (open $file_path) let updated = ($data | upsert $component_id ($data | get $component_id | upsert version $new_version)) $updated | to json -i 2 | save -f $file_path } "toml" => { # TOML update would need proper TOML writer print $"āš ļø TOML update not implemented for ($file_path)" } "k" => { # Nickel update would need Nickel parser/writer print $"āš ļø Nickel update not implemented for ($file_path)" } _ => { print $"āš ļø Unknown file type: ($ext)" } } } # Pin/unpin component version export def set-fixed [ component_id: string fixed: bool --path: string = "" ] { let configs = (discover-configurations --base-path=$path) let config = ($configs | where id == $component_id | first | default null) if ($config | is-empty) { print $"āŒ Component '($component_id)' not found" return } let source_file = $config.metadata.source_file let data = (open $source_file) let updated = ($data | upsert $component_id ($data | get $component_id | upsert fixed $fixed)) $updated | save -f $source_file if $fixed { print $"šŸ”’ Pinned ($component_id) to version ($config.version)" } else { print $"šŸ”“ Unpinned ($component_id)" } }