nushell-plugins/scripts/lib/common_lib.nu
Jesús Pérez 2b3e574e3d # Summary
fix: help system integration, build process optimization, and plugin rebuild efficiency

## Detailed Description

This commit addresses critical issues in the help system discoverability, build process robustness, and plugin rebuild efficiency.

### 1. Help System Integration (New Feature)

**Issue**: Version-update module recipes were not discoverable
- Not shown in `just help modules`
- Not referenced in `just help`
- Not included in help navigation system
- Users had to manually run `just --list` to find update commands

**Solution**:
- Added version-update module to all help outputs
- Updated `justfiles/help.just` to document all 30+ version-update recipes
- Created new `just commands` recipe as discoverable alias for `just --list`
- Integrated version-update into help-all workflow

**Impact**:
- Version-update commands now fully discoverable via help system
- Users can find update commands with: `just help modules`, `just help`, `just commands`
- Improved overall help system navigation

**Files Modified**:
- `justfiles/help.just` (+23 lines)
  - Added version-update module to help sections
  - Added to modules list
  - Added to help-all workflow
  - New `commands` recipe showing all recipes by group

### 2. Build Process Fixes (Phase 3: Bin Archives)

#### 2a. Plugin Archive Collection Bug

**Issue**: "No plugins found to package" warning in Phase 3
- Collected 26 plugin binaries but reported 0
- Archive creation skipped because count was wrong

**Root Cause**: `each` command returns null, so `| length` returned 0
```nushell
#  OLD - each returns null
let plugin_count = (ls nu_plugin_*/target/release/nu_plugin_* | each {|p|
    cp $p.name $"($temp_dir)/"
} | length)  # Returns 0!
```

**Solution**: Separated counting from copying with proper filtering
```nushell
#  NEW - count before operations
let plugins_to_copy = (ls nu_plugin_*/target/release/nu_plugin_* | where type == "file")
let plugin_count = ($plugins_to_copy | length)
```

**Impact**:
- Now correctly collects and reports 26 plugins
- Filters out .d dependency files automatically
- Warning eliminated

#### 2b. Tar Archive Path Handling

**Issue**: Tar command failing silently with relative paths in subshell
- `cd $temp_dir` changes context unpredictably
- Relative path `../$archive_name` fails in subshell
- Archive file not created despite exit code 0

**Root Cause**: Shell context and relative path issues in Nushell `do` block

**Solution**: Used `tar -C` with absolute paths instead of `cd`
```nushell
#  OLD - unreliable context switching
do {
    cd $temp_dir
    tar -czf ../$archive_name .
}

#  NEW - absolute paths, no context switching
tar -C $temp_dir -czf $archive_path .
```

**Additional Improvements**:
- Absolute path construction using `pwd | path join`
- Better error diagnostics with exit code and stderr output
- File verification after creation

**Impact**:
- Tar archives now created successfully
- Robust path handling across platforms
- Clear error messages for debugging

#### 2c. File Size Calculation Type Error

**Issue**: Runtime error when calculating archive size
```
Error: The '/' operator does not work on values of type 'list<filesize>'
```

**Root Cause**: `ls` returns list of records, so `.size` was a list
```nushell
#  OLD - returns list<filesize>
(ls $archive_path).size / 1024 / 1024

#  NEW - returns filesize
(ls $archive_path | get 0.size) / 1024 / 1024
```

**Impact**:
- Proper file size calculation in MB
- No more type errors

**Files Modified**:
- `scripts/create_full_distribution.nu` (+58 lines, refactored plugin collection)
  - Fixed plugin counting logic
  - Improved path handling with absolute paths
  - Enhanced error diagnostics

### 3. Plugin Rebuild Optimization

**Issue**: All plugins marked for rebuild even when dependencies unchanged
- Step 4 (`update_all_plugins.nu`) touched all Cargo.toml files at 01:00:32
- Step 5 saw all files as "newer" than binaries
- Marked ALL plugins for rebuild, though cargo only rebuilt changed ones

**Root Cause**: Script always saved files, even when no changes made
```nushell
#  OLD - always saves, touching file timestamp
$updated_content | to toml | save -f $cargo_toml
```

**Solution**: Only save if content actually changed
```nushell
#  NEW - compare before writing
let original_toml = $content | to toml
let new_toml = $updated_content | to toml

if $original_toml != $new_toml {
    $updated_content | to toml | save -f $cargo_toml
}
```

**Impact**:
- Unchanged files preserve original timestamps
- Only plugins with actual dependency changes are rebuilt
- Efficient rebuild process with accurate file modification detection

**Files Modified**:
- `scripts/update_all_plugins.nu` (+12 lines, added content comparison)
  - Only touches files with real changes
  - Preserves timestamps for efficiency
  - Clearer logic and comments

### 4. Documentation

**Files Modified**:
- `CHANGELOG.md` (+56 lines)
  - Added comprehensive 2025-10-19 entry
  - Documented all fixes with root causes
  - Listed files modified and impact summary

## Technical Details

### Nushell Patterns Used

1. **Proper List Handling**:
   - `ls` returns list of records, access with `| get 0.size`
   - Filter with `where type == "file"` to exclude metadata

2. **Absolute Path Construction**:
   - `pwd | append "path" | path join` for cross-platform paths
   - Safer than string concatenation with `/`

3. **Content Comparison**:
   - Compare TOML string representation before saving
   - Preserves file timestamps for efficiency

4. **Error Diagnostics**:
   - Capture `stderr` from commands
   - Report exit codes and error messages separately

## Testing

- [x] Help system shows version-update module
- [x] `just commands` displays all recipes by group
- [x] Phase 3 bin archive creation works
- [x] Plugin collection reports correct count (26)
- [x] Tar archives created successfully
- [x] File size calculated correctly
- [x] Plugin rebuild only touches changed files
- [x] CHANGELOG updated with all changes

## Files Changed

```
38 files changed, 2721 insertions(+), 2548 deletions(-)

Core Changes:
- justfiles/help.just                  (+23)  Help system integration
- scripts/create_full_distribution.nu  (+58)  Build process fixes
- scripts/update_all_plugins.nu        (+12)  Rebuild optimization
- CHANGELOG.md                         (+56)  Documentation

Dependency Updates:
- All plugin Cargo.toml and Cargo.lock files (version consistency)
```

## Breaking Changes

None. These are bug fixes and optimizations that maintain backward compatibility.

## Migration Notes

No migration needed. Improvements are transparent to users.

## Related Issues

- Help system discoverability
- Build process Phase 3 failures
- Unnecessary plugin rebuilds
- Build process reliability

## Checklist

- [x] Changes follow Rust/Nushell idioms
- [x] Code is well-commented
- [x] Error handling is comprehensive
- [x] Documentation is updated
- [x] All changes tested
- [x] No breaking changes introduced
2025-10-19 01:17:13 +01:00

297 lines
7.9 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env nu
# Common Library for Nushell Plugins Distribution Scripts
# Provides shared utilities, logging, and validation functions
# Logging functions with consistent formatting
export def log_info [message: string] {
print $"(ansi blue) ($message)(ansi reset)"
}
export def log_success [message: string] {
print $"(ansi green)✅ ($message)(ansi reset)"
}
export def log_warn [message: string] {
print $"(ansi yellow)⚠️ ($message)(ansi reset)"
}
export def log_error [message: string] {
print $"(ansi red)❌ ($message)(ansi reset)"
}
export def log_debug [message: string] {
if ($env.DEBUG? | default false) {
print $"(ansi dim)🐛 DEBUG: ($message)(ansi reset)"
}
}
# Validate nushell version consistency between system and workspace
export def validate_nushell_version [] {
log_debug "Validating nushell version consistency..."
# Check if nushell submodule exists
let nushell_dir = "./nushell"
if not ($nushell_dir | path exists) {
log_error $"Nushell submodule not found at ($nushell_dir)"
exit 1
}
# Get nu version - try project binary first, then system
let system_version = try {
let built_nu = "./nushell/target/release/nu"
let version_str = if ($built_nu | path exists) {
let version_output = (do { ^$built_nu --version } | complete)
if $version_output.exit_code == 0 {
$version_output.stdout
} else {
(nu --version)
}
} else {
(nu --version)
}
# Clean up: remove "nushell " prefix and trim
($version_str | str replace "nushell " "" | str trim)
} catch {
log_error "Could not determine nushell version (project or system binary unavailable)"
exit 1
}
# Get workspace nu version from Cargo.toml
let cargo_toml_path = $"($nushell_dir)/Cargo.toml"
let workspace_version = try {
(open $cargo_toml_path | get package.version)
} catch {
log_error $"Could not read package version from ($cargo_toml_path)"
exit 1
}
if $system_version != $workspace_version {
log_error $"Version mismatch: system nu ($system_version) != workspace nu ($workspace_version)"
log_info "Run 'just fix-nushell' to resolve version inconsistencies"
exit 1
}
log_success $"Nushell version validation passed: ($system_version)"
}
# Get current platform identifier
export def get_current_platform [] {
let os = (sys host | get name)
let arch = (run-external "uname" "-m" | str trim)
let platform_name = match $os {
"Linux" => "linux",
"Darwin" => "darwin",
"Windows" => "windows",
_ => ($os | str downcase)
}
let arch_name = match $arch {
"x86_64" => "x86_64",
"aarch64" => "arm64",
"arm64" => "arm64",
_ => $arch
}
$"($platform_name)-($arch_name)"
}
# Get all supported platforms
export def get_supported_platforms [] {
[
"linux-x86_64",
"linux-arm64",
"darwin-x86_64",
"darwin-arm64",
"windows-x86_64"
]
}
# Get binary extension for platform
export def get_binary_extension [platform: string] {
if ($platform | str starts-with "windows") {
".exe"
} else {
""
}
}
# Get archive extension for platform
export def get_archive_extension [platform: string] {
if ($platform | str starts-with "windows") {
".zip"
} else {
".tar.gz"
}
}
# Ensure directory exists
export def ensure_dir [path: string] {
if not ($path | path exists) {
mkdir $path
log_info $"Created directory: ($path)"
}
}
# Remove directory if it exists
export def remove_dir [path: string] {
if ($path | path exists) {
rm -rf $path
log_info $"Removed directory: ($path)"
}
}
# Copy file with logging
export def copy_file [src: string, dest: string] {
if not ($src | path exists) {
log_error $"Source file does not exist: ($src)"
return false
}
let dest_dir = ($dest | path dirname)
ensure_dir $dest_dir
cp $src $dest
log_debug $"Copied ($src) -> ($dest)"
true
}
# Create checksums for files
export def create_checksums [files: list<string>, output_dir: string] {
let checksum_file = $"($output_dir)/checksums.txt"
let checksums = $files | each {|file|
if ($file | path exists) {
let hash = (open $file --raw | hash sha256)
let filename = ($file | path basename)
$"($hash) ($filename)"
}
} | compact
$checksums | save -f $checksum_file
log_success $"Created checksums file: ($checksum_file)"
}
# Detect available plugin directories
export def get_plugin_directories [] {
glob "nu_plugin_*" | where {|it| ($it | path exists) and (($it | path type) == "dir")}
}
# Get plugin name from directory
export def get_plugin_name [dir: string] {
$dir | path basename
}
# Check if path is a plugin directory
export def is_plugin_directory [path: string] {
($path | path basename | str starts-with "nu_plugin_") and ($path | path type) == "dir"
}
# Get version from current workspace
export def get_workspace_version [] {
let version_from_git = try {
(do -i { git describe --tags --abbrev=0 err> /dev/null } | complete | get stdout | str trim | str replace "^v" "")
} catch {
""
}
if ($version_from_git | str length) > 0 {
$version_from_git
} else {
# Fallback to version from nushell Cargo.toml
try {
(open "./nushell/Cargo.toml" | get package.version)
} catch {
"0.1.0"
}
}
}
# Create manifest for distribution
export def create_manifest [
version: string,
platform: string,
components: record,
output_file: string
] {
let manifest = {
version: $version,
platform: $platform,
created_at: (date now | format date "%Y-%m-%d %H:%M:%S UTC"),
components: $components,
nushell_version: (try { (nu --version) } catch { "unknown" })
}
$manifest | to json | save -f $output_file
log_success $"Created manifest: ($output_file)"
}
# Parse command line flags into structured data
export def parse_flags [args: list] {
mut flags = {}
mut i = 0
while $i < ($args | length) {
let arg = ($args | get $i)
if ($arg | str starts-with "--") {
let flag_name = ($arg | str replace "--" "")
# Check if next arg is a value or another flag
let next_i = ($i + 1)
if $next_i < ($args | length) {
let next_arg = ($args | get $next_i)
if not ($next_arg | str starts-with "--") {
$flags = ($flags | insert $flag_name $next_arg)
$i = ($i + 2)
} else {
$flags = ($flags | insert $flag_name true)
$i = ($i + 1)
}
} else {
$flags = ($flags | insert $flag_name true)
$i = ($i + 1)
}
} else {
$i = ($i + 1)
}
}
$flags
}
# Execute command with error handling
export def exec_with_error [command: string] {
log_debug $"Executing: ($command)"
let result = try {
(bash -c $command)
} catch {|err|
log_error $"Command failed: ($command)"
log_error $"Error: ($err.msg)"
exit 1
}
$result
}
# Check if binary exists and is executable
export def check_binary [path: string] {
if not ($path | path exists) {
false
} else if ($path | path type) != "file" {
false
} else {
# On Unix-like systems, check if executable
if (sys host | get name) != "Windows" {
try {
(ls -l $path | get 0.mode | str contains "x")
} catch {
false
}
} else {
true
}
}
}