nushell-plugins/scripts/update_nu_versions.nu
Jesús Pérez b99dcc83c3 feat: major repository modernization and tracking cleanup
## Summary

Comprehensive repository cleanup focusing on plugin dependency management, documentation improvements, and git tracking optimization.

## Key Changes

### 🔧 Core Infrastructure
- Synchronized all nu-* dependencies across plugins for version consistency
- Enhanced upstream tracking and automation systems
- Removed nushell directory from git tracking for cleaner repository management

### 📚 Documentation
- Significantly expanded README.md with comprehensive development guides
- Added detailed workflow documentation and command references
- Improved plugin collection overview and usage examples

### 🧹 Repository Cleanup
- Removed legacy bash scripts (build-all.sh, collect-install.sh, make_plugin.sh)
- Streamlined automation through unified justfile and nushell script approach
- Updated .gitignore with nushell directory and archive patterns
- Removed nushell directory from git tracking to prevent unwanted changes

### 🔌 Plugin Updates
- **nu_plugin_image**: Major refactoring with modular architecture improvements
- **nu_plugin_hashes**: Enhanced functionality and build system improvements
- **nu_plugin_highlight**: Updated for new plugin API compatibility
- **nu_plugin_clipboard**: Dependency synchronization
- **nu_plugin_desktop_notifications**: Version alignment
- **nu_plugin_port_extension & nu_plugin_qr_maker**: Consistency updates
- **nu_plugin_kcl & nu_plugin_tera**: Submodule synchronization

### 🏗️ Git Tracking Optimization
- Removed nushell directory from version control for cleaner repository management
- Added comprehensive .gitignore patterns for build artifacts and archives

## Statistics
- 2,082 files changed
- 2,373 insertions, 339,936 deletions
- Net reduction of 337,563 lines (primarily from removing nushell directory tracking)

## Benefits
- Complete version consistency across all plugins
- Cleaner repository with optimized git tracking
- Improved developer experience with streamlined workflows
- Enhanced documentation and automation
- Reduced repository size and complexity

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-20 15:18:58 +01:00

550 lines
19 KiB
Plaintext
Executable File

#!/usr/bin/env nu
# Nu Version Manager - Nushell implementation
# Usage: nu update_nu_versions.nu [list|ls|update|help]
# Function to get version from nushell Cargo.toml files
def get_nushell_version [cargo_file: path] {
if ($cargo_file | path exists) {
try {
open --raw $cargo_file | lines | where $it =~ '^version = ' | first | parse 'version = "{version}"' | get version.0
} catch { null }
} else {
null
}
}
# Function to find version for a nu-* dependency in nushell crates
def get_target_version_for_dependency [dep_name: string, nushell_dir: string] {
let target_path = ($nushell_dir | path join $"crates/($dep_name)/Cargo.toml")
if ($target_path | path exists) {
get_nushell_version $target_path
} else {
null
}
}
# Function to extract the correct dependency name from a path
def get_correct_dep_name_from_path [line: string] {
try {
let path_match = ($line | parse --regex 'path = "([^"]*)"' | get capture0.0)
# Extract the crate name from the path (last component after /crates/)
$path_match | str replace --regex '.*/crates/' '' | str replace --regex '/.*' ''
} catch {
null
}
}
# Function to fix dependency name conflicts
def fix_dependency_name_conflicts [content: list<string>] {
mut updated_content = $content
mut changes = []
# Find lines with nu-* dependencies that have paths
let conflicts = (
$content
| enumerate
| where ($it.item | str contains "nu-") and ($it.item | str contains "path =")
| each { |line|
let declared_name = try {
$line.item | parse --regex '^[[:space:]]*(nu-[a-zA-Z0-9_-]+)[[:space:]]*=' | get capture0.0
} catch { null }
let correct_name = get_correct_dep_name_from_path $line.item
if ($declared_name | is-not-empty) and ($correct_name | is-not-empty) and $declared_name != $correct_name {
{
index: $line.index,
declared_name: $declared_name,
correct_name: $correct_name,
line: $line.item
}
} else {
null
}
}
| where $it != null
)
for $conflict in $conflicts {
let new_line = ($conflict.line | str replace $conflict.declared_name $conflict.correct_name)
$updated_content = ($updated_content | update $conflict.index $new_line)
$changes = ($changes | append $"⚠ Fixed dependency name conflict: ($conflict.declared_name) -> ($conflict.correct_name)")
}
{ content: $updated_content, changes: $changes }
}
# Auto-detect versions from nushell source
let NUSHELL_DIR = if ("nushell" | path exists) {
if ("nushell" | path type) == "symlink" {
# Follow symlink to get actual directory
"nushell" | path expand
} else {
"nushell"
}
} else {
error make { msg: "Nushell directory not found" }
}
# Get target versions from nushell source
let NU_PLUGIN_VERSION = get_nushell_version ($NUSHELL_DIR | path join "crates/nu-plugin/Cargo.toml")
let NU_PROTOCOL_VERSION = get_nushell_version ($NUSHELL_DIR | path join "crates/nu-protocol/Cargo.toml")
let NU_PLUGIN_TEST_VERSION = get_nushell_version ($NUSHELL_DIR | path join "crates/nu-plugin-test-support/Cargo.toml")
# Use nu-plugin version as the primary version (they should all be the same)
let NEW_VERSION = $NU_PLUGIN_VERSION
if ($NEW_VERSION | is-empty) {
error make { msg: "Could not determine target version from nushell source. Make sure the nushell directory exists and contains the expected Cargo.toml files" }
}
# Function to get all unique versions currently used in plugin Cargo.toml files for nu-* dependencies
def get_current_versions [] {
let plugin_dirs = get_plugin_dirs
$plugin_dirs
| each { |dir|
let cargo_file = ($dir | path join "Cargo.toml")
if ($cargo_file | path exists) {
try {
open --raw $cargo_file
| lines
| where $it =~ 'nu-[a-zA-Z0-9_-]+.*version = '
| each { |line|
try {
$line | parse --regex 'version = "([^"]*)"' | get capture0.0
} catch { null }
}
| where $it != null
} catch { [] }
} else { [] }
}
| flatten
| uniq
| where $it != $NEW_VERSION
}
# Function to show usage information
def show_usage [] {
print $"(ansi blue)Nu Version Manager(ansi reset)"
print ""
print "Usage: nu update_nu_versions.nu [command]"
print ""
print "Commands:"
print $" (ansi green)list, ls(ansi reset) Show current versions in all Cargo.toml files"
print $" (ansi green)update(ansi reset) Update versions to match nushell source ((ansi green)($NEW_VERSION)(ansi reset))"
print $" (ansi green)help, -h(ansi reset) Show this help message"
print ""
print "If no command is specified, 'update' is assumed."
print ""
}
# Function to extract version from a line
def extract_version [line: string] {
let version_match = ($line | parse --regex 'version = "([^"]*)"')
if ($version_match | length) > 0 {
$version_match.0.capture0
} else {
null
}
}
# Function to check if line has path dependency
def has_path_dependency [line: string] {
$line | str contains "path ="
}
# Function to get plugin directories
def get_plugin_dirs [] {
glob "nu_plugin_*" | where ($it | path type) == "dir" | sort
}
# Function to parse cargo.toml for version information
def parse_cargo_toml [cargo_file: path] {
if not ($cargo_file | path exists) {
return {
package_version: null
nu_plugin: null
nu_protocol: null
nu_json: null
nu_plugin_test_support: null
error: "Cargo.toml not found"
}
}
let content = open --raw $cargo_file | lines
# Get package version
let package_version = try {
$content
| where $it =~ '^version = '
| first
| extract_version $in
} catch { null }
# Get nu-plugin version
let nu_plugin_line = try {
$content
| where ($it =~ 'nu-plugin' and not ($it =~ 'nu-plugin-test-support'))
| first
} catch { null }
let nu_plugin_info = if $nu_plugin_line != null {
let version = extract_version $nu_plugin_line
let has_path = has_path_dependency $nu_plugin_line
{
version: $version
has_path: $has_path
status: (if $version != null {
if $has_path { "version_with_path" } else { "version_only" }
} else {
if $has_path { "path_only" } else { "not_found" }
})
}
} else {
{ version: null, has_path: false, status: "not_found" }
}
# Get nu-protocol version
let nu_protocol_line = try {
$content
| where $it =~ 'nu-protocol'
| first
} catch { null }
let nu_protocol_info = if $nu_protocol_line != null {
let version = extract_version $nu_protocol_line
let has_path = has_path_dependency $nu_protocol_line
{
version: $version
has_path: $has_path
status: (if $version != null {
if $has_path { "version_with_path" } else { "version_only" }
} else {
if $has_path { "path_only" } else { "not_found" }
})
}
} else {
{ version: null, has_path: false, status: "not_found" }
}
# Get nu-json version
let nu_json_line = try {
$content
| where $it =~ 'nu-json'
| first
} catch { null }
let nu_json_info = if $nu_json_line != null {
let version = extract_version $nu_json_line
let has_path = has_path_dependency $nu_json_line
{
version: $version
has_path: $has_path
status: (if $version != null {
if $has_path { "version_with_path" } else { "version_only" }
} else {
if $has_path { "path_only" } else { "not_found" }
})
}
} else {
{ version: null, has_path: false, status: "not_found" }
}
# Get nu-plugin-test-support version
let nu_test_line = try {
$content
| where $it =~ 'nu-plugin-test-support'
| first
} catch { null }
let nu_test_info = if $nu_test_line != null {
let version = extract_version $nu_test_line
let has_path = has_path_dependency $nu_test_line
{
version: $version
has_path: $has_path
status: (if $version != null {
if $has_path { "version_with_path" } else { "version_only" }
} else {
if $has_path { "path_only" } else { "not_found" }
})
}
} else {
{ version: null, has_path: false, status: "not_found" }
}
{
package_version: $package_version
nu_plugin: $nu_plugin_info
nu_protocol: $nu_protocol_info
nu_json: $nu_json_info
nu_plugin_test_support: $nu_test_info
error: null
}
}
# Function to format dependency info for display
def format_dependency [dep_info: record] {
match $dep_info.status {
"version_with_path" => ($dep_info.version + " " + (ansi yellow) + "(path)" + (ansi reset))
"version_only" => $dep_info.version
"path_only" => ((ansi yellow) + "path dependency only" + (ansi reset))
"not_found" => ((ansi red) + "not found" + (ansi reset))
}
}
# Function to list current versions
def list_versions [] {
print $"(ansi blue)Nu Version Listing(ansi reset)"
print "Current versions in all nu_plugin_* directories:"
print ""
let plugin_dirs = get_plugin_dirs
if ($plugin_dirs | length) == 0 {
print $"(ansi yellow)No nu_plugin_* directories found(ansi reset)"
return
}
for $dir in $plugin_dirs {
let plugin_name = ($dir | path basename)
let cargo_file = ($dir | path join "Cargo.toml")
print $"(ansi cyan) ($plugin_name)(ansi reset):"
let cargo_info = parse_cargo_toml $cargo_file
if $cargo_info.error != null {
print $" (ansi red)Error: ($cargo_info.error)(ansi reset)"
print ""
continue
}
# Package version
let pkg_version = if $cargo_info.package_version != null {
$cargo_info.package_version
} else {
$"(ansi yellow)not specified(ansi reset)"
}
print $" (ansi purple)Package(ansi reset): ($pkg_version)"
# nu-plugin
print $" (ansi purple)nu-plugin(ansi reset): (format_dependency $cargo_info.nu_plugin)"
# nu-protocol
print $" (ansi purple)nu-protocol(ansi reset): (format_dependency $cargo_info.nu_protocol)"
# nu-json (only if found)
if $cargo_info.nu_json.status != "not_found" {
print $" (ansi purple)nu-json(ansi reset): (format_dependency $cargo_info.nu_json)"
}
# nu-plugin-test-support (only if found)
if $cargo_info.nu_plugin_test_support.status != "not_found" {
print $" (ansi purple)nu-plugin-test-support(ansi reset): (format_dependency $cargo_info.nu_plugin_test_support)"
}
print ""
}
}
# Function to update a line with version replacement
def update_version_line [line: string, old_version: string, new_version: string] {
$line | str replace $'version = "($old_version)"' $'version = "($new_version)"'
}
# Function to update a single cargo.toml file
def update_cargo_toml [cargo_file: path, nushell_dir: string] {
if not ($cargo_file | path exists) {
return { success: false, message: "Cargo.toml not found", changes: [] }
}
let content = open --raw $cargo_file | lines
let backup_file = $"($cargo_file).backup"
# Create backup
$content | str join (char newline) | save -f --raw $backup_file
# First, fix any dependency name conflicts
let conflict_fix = fix_dependency_name_conflicts $content
mut updated_content = $conflict_fix.content
mut changes = $conflict_fix.changes
# Find all nu-* dependencies in the file (use updated content after conflict fixes)
let nu_dependencies = (
$updated_content
| enumerate
| where $it.item =~ '^[[:space:]]*nu-[a-zA-Z0-9_-]+[[:space:]]*='
| each { |line|
let dep_name = ($line.item | parse --regex '^[[:space:]]*(nu-[a-zA-Z0-9_-]+)[[:space:]]*=' | get capture0.0)
{ index: $line.index, dep_name: $dep_name, line: $line.item }
}
| group-by dep_name
| transpose dep_name lines
| each { |group| { dep_name: $group.dep_name, lines: $group.lines } }
)
for $dep_group in $nu_dependencies {
let dep_name = $dep_group.dep_name
let target_version = get_target_version_for_dependency $dep_name $nushell_dir
if ($target_version | is-empty) {
$changes = ($changes | append $"⚠ ($dep_name): No corresponding crate found in nushell source - skipping")
continue
}
# Process each line for this dependency
for $line_info in $dep_group.lines {
let current_line = $line_info.line
let line_index = $line_info.index
# Check if it already has a path dependency
if ($current_line | str contains "path =") {
# Update version but keep path
if ($current_line | str contains "version =") {
let old_version = try {
$current_line | parse --regex 'version = "([^"]*)"' | get capture0.0
} catch { null }
if ($old_version | is-not-empty) and $old_version != $target_version {
let new_line = ($current_line | str replace $'version = "($old_version)"' $'version = "($target_version)"')
$updated_content = ($updated_content | update $line_index $new_line)
$changes = ($changes | append $"✓ Updated ($dep_name) version from ($old_version) to ($target_version) path preserved")
} else if ($old_version | is-not-empty) and $old_version == $target_version {
$changes = ($changes | append $"→ ($dep_name): Already at target version ($target_version) with path")
}
} else {
# Add version to existing path dependency
let new_line = ($current_line | str replace "{" $'{ version = "($target_version)",')
$updated_content = ($updated_content | update $line_index $new_line)
$changes = ($changes | append $"✓ Added version ($target_version) to ($dep_name) path dependency")
}
} else {
# No path dependency - add both version and path
let current_version = try {
$current_line | parse --regex 'version = "([^"]*)"' | get capture0.0
} catch { null }
if ($current_version | is-not-empty) {
# Replace version-only dependency with version + path
let relative_path = $"../nushell/crates/($dep_name)"
let new_line = ($current_line | str replace $'version = "($current_version)"' $'version = "($target_version)", path = "($relative_path)"')
$updated_content = ($updated_content | update $line_index $new_line)
$changes = ($changes | append $"✓ Updated ($dep_name) from ($current_version) to ($target_version) and added path dependency")
} else {
$changes = ($changes | append $"⚠ ($dep_name): Could not parse current version - skipping")
}
}
}
}
# Save updated content if changes were made
let actual_changes = ($changes | where not ($it | str starts-with "→") and not ($it | str starts-with "⚠"))
if ($actual_changes | length) > 0 {
$updated_content | str join (char newline) | save -f --raw $cargo_file
{
success: true
message: $"Backup created: ($backup_file)"
changes: $changes
}
} else {
# Remove backup if no changes
rm $backup_file
{
success: true
message: "No version updates needed"
changes: $changes
}
}
}
# Function to run the update process
def run_update [] {
print $"(ansi blue)Nu Version Updater(ansi reset)"
print $"Target version from nushell source: (ansi green)($NEW_VERSION)(ansi reset)"
# Show detected current versions
let current_versions = get_current_versions
if ($current_versions | length) > 0 {
print $"Current versions found in plugins: (ansi red)($current_versions | str join ', ')(ansi reset)"
} else {
print $"(ansi yellow)All plugins are already at target version(ansi reset)"
}
print ""
let plugin_dirs = get_plugin_dirs
if ($plugin_dirs | length) == 0 {
print $"(ansi yellow)No nu_plugin_* directories found(ansi reset)"
return
}
print $"(ansi blue)Found plugin directories:(ansi reset)"
for $dir in $plugin_dirs {
print $" - ($dir)"
}
print ""
# Process each plugin directory
for $dir in $plugin_dirs {
print $"(ansi blue)Processing ($dir)...(ansi reset)"
let cargo_file = ($dir | path join "Cargo.toml")
let result = update_cargo_toml $cargo_file $NUSHELL_DIR
for $change in $result.changes {
print $" (ansi green)✓(ansi reset) ($change)"
}
if ($result.changes | length) > 0 {
print $" (ansi blue)($result.message)(ansi reset)"
} else {
print $" (ansi yellow)($result.message)(ansi reset)"
}
print ""
}
print $"(ansi green)Version update process completed!(ansi reset)"
print ""
print ((ansi yellow) + "Note:" + (ansi reset) + " Backup files (.backup) have been created for modified files.")
print ("You can restore them with: " + (ansi blue) + "mv file.backup file" + (ansi reset))
print ""
print $"(ansi yellow)Don't forget to:(ansi reset)"
print "1. Test the plugins after the update"
print "2. Update any documentation if needed"
print "3. Commit the changes to version control"
}
# Main function
def main [command?: string] {
# Ensure we're in the right directory (assume script is in the plugin directory)
# cd (pwd)
let cmd = $command | default "update"
match $cmd {
"list" | "ls" => { list_versions }
"update" => { run_update }
"help" | "-h" | "--help" => { show_usage }
_ => {
print $"(ansi red)Error: Unknown command '($cmd)'(ansi reset)"
print ""
show_usage
}
}
}
# Export functions for use
export def "nu-version list" [] { list_versions }
export def "nu-version ls" [] { list_versions }
export def "nu-version update" [] { run_update }
export def "nu-version help" [] { show_usage }
# Main entry point function
export def "nu-version" [command?: string] {
main ($command | default "help")
}