feat: bootstrap installer with robust archive extraction, version mismatch handling, and improved PATH messaging ## Detailed Description This commit implements a production-ready bootstrap installer with comprehensive error handling, version-agnostic archive extraction, and clear user messaging. All improvements follow DRY principles using symlink-based architecture for single-source-of-truth maintenance. ## Major Changes (Session 2025-10-19) ### 0. Bootstrap Installer & Distribution Improvements (NEW) #### 0a. Install Script Architecture - DRY Design **Issue**: Code duplication across installation paths - `./install.sh`, `./scripts/templates/install.sh`, `installers/bootstrap/install.sh` were separate copies - Changes required updating multiple files - Risk of divergence and inconsistency **Solution**: Implemented symlink-based DRY architecture - Single source of truth: `installers/bootstrap/install.sh` (1,247 lines) - Symlinks created: - `./install.sh` → `installers/bootstrap/install.sh` - `./scripts/templates/install.sh` → `installers/bootstrap/install.sh` - All changes automatically propagate through symlinks - No code duplication **Impact**: - ✅ Single maintenance point for all install scripts - ✅ Consistent behavior across all paths - ✅ Reduced maintenance burden - ✅ No divergence risk #### 0b. Archive Extraction - Version-Agnostic Binary Detection **Issue**: "No Nushell binary found in extracted archive" - Script failed when extracting from `nushell-full-0.108.0-darwin-arm64.tar.gz` - Error occurred even though binaries were present at `nushell-full-0.108.0/bin/nu` **Root Cause**: `find` command returning parent directory itself in results ```bash # ❌ OLD - find returns parent directory first local extracted=$(find "$extract_dir" -maxdepth 1 -type d -name "nushell-*" | head -1) # Returns: /tmp/nushell-install-22919/nushell-extract (parent!) # Should return: /tmp/nushell-install-22919/nushell-extract/nushell-full-0.108.0 (subdirectory) ``` **Solution**: Added `-not -path` to exclude starting directory ```bash # ✅ NEW - exclude parent directory local extracted=$(find "$extract_dir" -maxdepth 1 -type d -name "nushell-*" -not -path "$extract_dir" | head -1) # Now correctly returns the subdirectory ``` **Additional Improvements**: - 4-level fallback detection strategy: 1. Check `$extracted/bin/nu` (subdirectory structure) 2. Check `$extracted/nu` (flat structure) 3. Fallback search for any `nushell-*` subdirectory with `bin/nu` 4. Last resort recursive search for any executable named `nu` - Validates binaries exist before using them - Clear error reporting with all search locations **Version-Agnostic**: - Uses `nushell-*` pattern (not hardcoded version numbers) - Works with any Nushell version: 0.107, 0.108, 0.109, etc. - Supports both `.tar.gz` and `.zip` archive formats **Impact**: - ✅ Archive extraction works reliably - ✅ Works with any Nushell version - ✅ Clear error messages guide users - ✅ Multiple archive structure support #### 0c. Plugin Registration - Version Mismatch Handling **Issue**: Plugin registration failed with version incompatibility errors ``` Error: nu:🐚:plugin_failed_to_load × Plugin `polars` is compiled for nushell version 0.107.1, which is not compatible with version 0.108.0 ``` **Solution**: Implemented intelligent error classification - Captures both stdout and stderr from plugin add commands - Detects version incompatibility: "is not compatible with version" or "is compiled for nushell version" - Classifies errors into three categories: 1. **Success**: Plugin registered successfully ✅ 2. **Incompatible**: Version mismatch (skipped gracefully) ⚠️ 3. **Failed**: Other registration failures ❌ - Reports summary with counts of each category - Installation continues successfully even with version mismatches **New Error Reporting**: ``` ✅ Registered nu_plugin_auth ⚠️ Skipping nu_plugin_polars: Version mismatch (built for different Nushell version) ✅ Successfully registered 13 plugins ⚠️ Skipped 1 incompatible plugins (version mismatch): - nu_plugin_polars ``` **Impact**: - ✅ No installation failures due to version mismatches - ✅ Users informed of incompatible plugins - ✅ Clear distinction between error types - ✅ Installation completes successfully #### 0d. Shell Configuration PATH Update - Clear Messaging **Issue**: Confusing PATH update messages - User sees: "PATH already updated" for all files - Then sees: "No shell configuration files were updated" warning - Then sees: "Please manually add to your PATH" error - **Problem**: Contradictory messages when PATH is already configured everywhere **Root Cause**: Script conflated two states - State 1: "Was PATH found in files?" (skips updating if found) - State 2: "Did we add PATH to any file?" (used for messaging) - Both states ignored means no update was made, but PATH might already exist **Solution**: Track two separate states ```bash local updated=false # Was PATH ADDED to any file? local path_found=false # Was PATH FOUND in any file? # In loop: if grep -q "$install_dir" "$config_file"; then path_found=true # Found it! Mark as true continue fi # After loop: if [ "$updated" = "true" ]; then log_success "Shell configuration updated" elif [ "$path_found" = "true" ]; then log_success "PATH is already configured in your shell configuration files" else log_warn "Could not find or update shell configuration" fi ``` **New Clear Messages**: - ✅ "PATH is already configured in your shell configuration files" (when found everywhere) - ✅ "Shell configuration updated" (when just added) - ⚠️ "Could not find or update shell configuration" (when file missing) **Impact**: - ✅ Non-contradictory messages - ✅ Users understand what happened - ✅ No false warnings when PATH already configured - ✅ Clear guidance when manual action needed #### 0e. Installation Features **`--source-path` Option** (Local Installation): - Install from local archive: `./install.sh --source-path archive.tar.gz` - Install from local directory: `./install.sh --source-path /path/to/binaries` - Default behavior: `./install.sh --source-path` uses `./bin_archives` - Works with custom `--install-dir` paths - No download needed, offline installation support **`--uninstall` with Configuration Management**: - Prompts user: "Remove ~/.config/nushell? [y/N]" - Removes all installed binaries and plugins - Preserves user choice for configuration - Clean uninstall before fresh reinstall #### 0f. Documentation Updates **CLAUDE.md**: - Added "Install Script Architecture (DRY Design)" section - Documents source of truth and symlink structure - Explains `--source-path` feature - Shows version-agnostic archive detection - Lists DRY architecture benefits **README.md**: - Added "Install Script Architecture (DRY Design)" subsection - Shows symlink structure with arrows - Provides `--source-path` usage examples - Explains version-agnostic detection - "How DRY Works" 3-step explanation #### 0g. Files Modified Core Changes: - `installers/bootstrap/install.sh` (+3 lines for PATH messaging fix) - Archive extraction fix with `-not -path` - Plugin registration error classification - Clear PATH update messaging - Total: 1,247 lines (unified) Auto-Updated via Symlinks: - `./install.sh` - Auto-updated (1,247 lines) - `./scripts/templates/install.sh` - Auto-updated (1,247 lines) Documentation: - `CLAUDE.md` - Added install architecture section - `README.md` - Added install architecture subsection - `CHANGELOG.md` - Added comprehensive entry (+100 lines) #### 0h. Testing & Verification All scenarios tested and verified: - [x] Archive extraction works with version-agnostic detection - [x] Installation to `~/.local` successful (16 binaries) - [x] Installation to `~/.local/bin` successful (21 plugins loaded) - [x] Plugin registration handles version mismatches gracefully - [x] PATH messaging is clear and non-contradictory - [x] Clean uninstall followed by fresh reinstall works perfectly #### 0i. Impact User-Facing Benefits: - ✅ Users can install from any version of nushell-full archive - ✅ Version mismatch plugins skipped without breaking installation - ✅ Clear, honest error messages - ✅ Non-confusing PATH update messages - ✅ Offline installation support via `--source-path` - ✅ Clean uninstall/reinstall workflow Developer Benefits: - ✅ Single source of truth eliminates code duplication - ✅ Changes propagate automatically through symlinks - ✅ Reduced maintenance burden - ✅ Consistent behavior across all paths - ✅ Production-ready installation process --- ### 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
550 lines
19 KiB
Plaintext
Executable File
550 lines
19 KiB
Plaintext
Executable File
#!/usr/bin/env nu
|
|
|
|
# Create Full Distribution - Complete Packaging Workflow
|
|
# Creates both full distributions (nushell + plugins) and bin archives (plugins only)
|
|
#
|
|
# Usage:
|
|
# create_full_distribution.nu # Current platform
|
|
# create_full_distribution.nu --all-platforms # All platforms
|
|
# create_full_distribution.nu --bin-only # Only create bin archives
|
|
|
|
use lib/common_lib.nu *
|
|
|
|
# Main entry point
|
|
def main [
|
|
--all-platforms # Create packages for all platforms
|
|
--bin-only # Only create bin archives
|
|
--checksums # Generate SHA256 checksums
|
|
--verify # Verify packages after creation
|
|
] {
|
|
print_banner
|
|
|
|
if not $bin_only {
|
|
# Phase 1: Collect binaries
|
|
print "╔══════════════════════════════════════════════════════════╗"
|
|
print "║ Phase 1: Collect Binaries ║"
|
|
print "╚══════════════════════════════════════════════════════════╝"
|
|
|
|
collect_binaries $all_platforms
|
|
}
|
|
|
|
# Phase 2: Create full distribution packages
|
|
if not $bin_only {
|
|
print ""
|
|
print "╔══════════════════════════════════════════════════════════╗"
|
|
print "║ Phase 2: Create Full Distribution Packages ║"
|
|
print "╚══════════════════════════════════════════════════════════╝"
|
|
|
|
create_distribution_packages $all_platforms $checksums
|
|
}
|
|
|
|
# Phase 3: Create bin archives
|
|
print ""
|
|
print "╔══════════════════════════════════════════════════════════╗"
|
|
print "║ Phase 3: Create Bin Archives ║"
|
|
print "╚══════════════════════════════════════════════════════════╝"
|
|
|
|
create_bin_archives
|
|
|
|
# Phase 4: Verification
|
|
if $verify {
|
|
print ""
|
|
print "╔══════════════════════════════════════════════════════════╗"
|
|
print "║ Phase 4: Verify Packages ║"
|
|
print "╚══════════════════════════════════════════════════════════╝"
|
|
|
|
verify_packages
|
|
}
|
|
|
|
# Final summary
|
|
generate_distribution_summary $all_platforms $bin_only
|
|
}
|
|
|
|
# Print banner
|
|
def print_banner [] {
|
|
print $"
|
|
(ansi blue)╔════════════════════════════════════════════════════════════╗
|
|
║ ║
|
|
║ 📦 Complete Distribution Creator 📦 ║
|
|
║ ║
|
|
║ Creates: ║
|
|
║ • Full distribution packages \(nu + plugins\) ║
|
|
║ • Bin archives \(plugins only\) ║
|
|
║ • Checksums and manifests ║
|
|
║ ║
|
|
╚════════════════════════════════════════════════════════════╝(ansi reset)
|
|
"
|
|
}
|
|
|
|
# Collect binaries for distribution
|
|
def collect_binaries [all_platforms: bool] {
|
|
log_info "📥 Collecting binaries..."
|
|
|
|
if ("./scripts/collect_full_binaries.nu" | path exists) {
|
|
# Use built nu binary, not system nu (which may be broken)
|
|
let built_nu = "./nushell/target/release/nu"
|
|
let nu_bin = if ($built_nu | path exists) { $built_nu } else { "nu" }
|
|
|
|
let result = (do {
|
|
if $all_platforms {
|
|
^$nu_bin ./scripts/collect_full_binaries.nu --all-platforms --force
|
|
} else {
|
|
^$nu_bin ./scripts/collect_full_binaries.nu --force
|
|
}
|
|
} | complete)
|
|
|
|
print $result.stdout
|
|
|
|
if ($result.stderr | is-not-empty) {
|
|
print $result.stderr
|
|
}
|
|
|
|
if $result.exit_code == 0 {
|
|
log_success "Binaries collected successfully"
|
|
} else {
|
|
log_error $"Failed to collect binaries \(exit code: ($result.exit_code)\)"
|
|
if ($result.stderr | is-not-empty) {
|
|
log_error $result.stderr
|
|
}
|
|
exit 1
|
|
}
|
|
} else {
|
|
log_warn "collect_full_binaries.nu not found, using fallback..."
|
|
collect_binaries_fallback
|
|
}
|
|
}
|
|
|
|
# Fallback binary collection
|
|
def collect_binaries_fallback [] {
|
|
let platform = detect_platform
|
|
|
|
log_info $"Collecting for platform: ($platform)"
|
|
|
|
# Create distribution directory
|
|
let dist_dir = $"./distribution/($platform)"
|
|
ensure_dir $dist_dir
|
|
|
|
# Copy nushell binary
|
|
if ("./nushell/target/release/nu" | path exists) {
|
|
cp ./nushell/target/release/nu $dist_dir
|
|
log_success "Copied nushell binary"
|
|
} else {
|
|
log_error "Nushell binary not found. Build it first with: just build-nushell"
|
|
exit 1
|
|
}
|
|
|
|
# Copy system plugins
|
|
let system_plugins = (try { ls ./nushell/target/release/nu_plugin_* } catch { [] } | where type == file)
|
|
for plugin in $system_plugins {
|
|
cp $plugin.name $dist_dir
|
|
}
|
|
|
|
if ($system_plugins | length) > 0 {
|
|
log_success $"Copied ($system_plugins | length) system plugins"
|
|
}
|
|
|
|
# Copy custom plugins
|
|
let custom_plugins = (try { ls nu_plugin_*/target/release/nu_plugin_* } catch { [] } | where type == file)
|
|
for plugin in $custom_plugins {
|
|
cp $plugin.name $dist_dir
|
|
}
|
|
|
|
if ($custom_plugins | length) > 0 {
|
|
log_success $"Copied ($custom_plugins | length) custom plugins"
|
|
}
|
|
|
|
let total = 1 + ($system_plugins | length) + ($custom_plugins | length)
|
|
log_success $"Total binaries collected: ($total)"
|
|
}
|
|
|
|
# Create distribution packages
|
|
def create_distribution_packages [
|
|
all_platforms: bool
|
|
checksums: bool
|
|
] {
|
|
log_info "📦 Creating distribution packages..."
|
|
|
|
if ("./scripts/create_distribution_packages.nu" | path exists) {
|
|
# Use project binary for consistency
|
|
let built_nu = "./nushell/target/release/nu"
|
|
let nu_bin = if ($built_nu | path exists) { $built_nu } else { "nu" }
|
|
|
|
let result = (do {
|
|
if $all_platforms and $checksums {
|
|
^$nu_bin ./scripts/create_distribution_packages.nu --all-platforms --checksums
|
|
} else if $all_platforms {
|
|
^$nu_bin ./scripts/create_distribution_packages.nu --all-platforms
|
|
} else if $checksums {
|
|
^$nu_bin ./scripts/create_distribution_packages.nu --checksums
|
|
} else {
|
|
^$nu_bin ./scripts/create_distribution_packages.nu
|
|
}
|
|
} | complete)
|
|
|
|
print $result.stdout
|
|
|
|
if $result.exit_code == 0 {
|
|
log_success "Distribution packages created"
|
|
}
|
|
} else {
|
|
log_warn "create_distribution_packages.nu not found, using justfile..."
|
|
|
|
let pack_result = (do {
|
|
if $all_platforms {
|
|
just pack-full-all
|
|
} else {
|
|
just pack-full
|
|
}
|
|
} | complete)
|
|
|
|
if $pack_result.exit_code == 0 {
|
|
log_success "Packages created via justfile"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Create bin archives (plugin-only) - directly from built plugins
|
|
def create_bin_archives [] {
|
|
log_info "📦 Creating bin archives (plugin-only)..."
|
|
|
|
# Create output directory
|
|
if not ("./bin_archives" | path exists) {
|
|
mkdir ./bin_archives
|
|
}
|
|
|
|
# Get version for naming
|
|
let version = try {
|
|
open ./nushell/Cargo.toml | get package.version
|
|
} catch {
|
|
"unknown"
|
|
}
|
|
|
|
# Get actually built platforms from distribution directory
|
|
let built_platforms = try {
|
|
ls ./distribution/*/nu | each {|p| $p.name | path dirname | path basename}
|
|
} catch {
|
|
[]
|
|
}
|
|
|
|
# If no platforms found, skip Phase 3
|
|
if ($built_platforms | length) == 0 {
|
|
log_warn "No platforms found in distribution directory - skipping plugin archive creation"
|
|
return
|
|
}
|
|
|
|
# Create archive for each built platform
|
|
for platform in $built_platforms {
|
|
let archive_name = $"plugins-only-($version)-($platform).tar.gz"
|
|
let archive_path = (pwd | append "bin_archives" | path join | append $archive_name | path join)
|
|
|
|
# Create temporary directory for archive contents
|
|
let temp_dir = (pwd | append "bin_archives" | path join | append $"plugins-temp-($version)-($platform)" | path join)
|
|
mkdir $temp_dir
|
|
|
|
# Collect all built plugin binaries
|
|
log_info $"📦 Collecting plugins for ($platform)..."
|
|
|
|
# Get list of plugin binaries (exclude .d dependency files and other metadata)
|
|
# Only include executable binaries: nu_plugin_name (no extension on Unix, .exe on Windows)
|
|
let plugins_to_copy = (
|
|
try {
|
|
ls nu_plugin_*/target/release/nu_plugin_*
|
|
| where type == "file"
|
|
| where ($it.name | str ends-with ".d") == false # Exclude .d dependency files
|
|
| where ($it.name | regex match "nu_plugin_[a-z_]+$") != null # Match base plugin names only
|
|
} catch {
|
|
[]
|
|
}
|
|
)
|
|
|
|
let plugin_count = ($plugins_to_copy | length)
|
|
|
|
if $plugin_count > 0 {
|
|
log_success $"Collected ($plugin_count) plugins"
|
|
|
|
# Copy plugins to temp directory
|
|
try {
|
|
$plugins_to_copy | each {|p|
|
|
cp $p.name $temp_dir
|
|
}
|
|
} catch {
|
|
log_error "Failed to copy plugins to temp directory"
|
|
}
|
|
|
|
# Create archive using absolute path (tar -C changes directory safely)
|
|
log_info "Creating archive..."
|
|
let tar_result = (do {
|
|
tar -C $temp_dir -czf $archive_path .
|
|
} | complete)
|
|
|
|
if $tar_result.exit_code == 0 {
|
|
if ($archive_path | path exists) {
|
|
let size = (ls $archive_path | get 0.size) / 1024 / 1024
|
|
log_success $"Archive created: ($archive_name) - (($size | into int))MB"
|
|
} else {
|
|
log_error $"Archive file exists but cannot be accessed: ($archive_path)"
|
|
}
|
|
} else {
|
|
log_error $"Failed to create archive. Exit code: ($tar_result.exit_code)"
|
|
if ($tar_result.stderr | is-not-empty) {
|
|
log_error $"Tar error: ($tar_result.stderr)"
|
|
}
|
|
}
|
|
} else {
|
|
log_warn $"No plugins found to package for ($platform)"
|
|
}
|
|
|
|
# Clean up temp directory
|
|
if ($temp_dir | path exists) {
|
|
try {
|
|
rm -r $temp_dir
|
|
} catch {
|
|
log_warn "Could not clean up temporary directory"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Verify packages
|
|
def verify_packages [] {
|
|
log_info "✅ Verifying packages..."
|
|
|
|
# Check distribution packages
|
|
if ("./distribution/packages" | path exists) {
|
|
let packages = (try { ls ./distribution/packages/*.tar.gz ./distribution/packages/*.zip } catch { [] })
|
|
|
|
if ($packages | length) > 0 {
|
|
log_success $"Found ($packages | length) distribution packages"
|
|
|
|
for pkg in $packages {
|
|
let name = $pkg.name | path basename
|
|
let size = $pkg.size / 1024 / 1024
|
|
|
|
if $size < 1 {
|
|
log_error $" ✗ ($name): Too small (($size | into int)MB)"
|
|
} else if $size > 500 {
|
|
log_warn $" ⚠ ($name): Very large (($size | into int)MB)"
|
|
} else {
|
|
log_success $" ✓ ($name): (($size | into int)MB)"
|
|
}
|
|
}
|
|
} else {
|
|
log_warn "No distribution packages found"
|
|
}
|
|
}
|
|
|
|
# Check bin archives
|
|
if ("./bin_archives" | path exists) {
|
|
let archives = (try { ls ./bin_archives/*.tar.gz } catch { [] })
|
|
|
|
if ($archives | length) > 0 {
|
|
log_success $"Found ($archives | length) bin archives"
|
|
} else {
|
|
log_warn "No bin archives found"
|
|
}
|
|
}
|
|
|
|
# Verify checksums if present
|
|
if ("./distribution/packages/checksums.txt" | path exists) {
|
|
log_info "Verifying checksums..."
|
|
|
|
let check_result = (do {
|
|
cd distribution/packages
|
|
shasum -c checksums.txt
|
|
} | complete)
|
|
|
|
if $check_result.exit_code == 0 {
|
|
log_success "All checksums verified"
|
|
} else {
|
|
log_error "Checksum verification failed"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Generate distribution summary
|
|
def generate_distribution_summary [
|
|
all_platforms: bool
|
|
bin_only: bool
|
|
] {
|
|
log_info "\n📊 Distribution Summary\n"
|
|
|
|
# Get nushell version
|
|
let version = if ("./nushell/Cargo.toml" | path exists) {
|
|
open ./nushell/Cargo.toml | get package.version
|
|
} else {
|
|
"unknown"
|
|
}
|
|
|
|
log_success $"Nushell version: ($version)"
|
|
|
|
# Count distribution packages
|
|
if not $bin_only and ("./distribution/packages" | path exists) {
|
|
let packages = (try { ls ./distribution/packages/*.tar.gz ./distribution/packages/*.zip } catch { [] })
|
|
|
|
if ($packages | length) > 0 {
|
|
log_success $"Distribution packages: ($packages | length)"
|
|
|
|
let total_size = ($packages | get size | math sum) / 1024 / 1024
|
|
log_info $"Total size: (($total_size | into int))MB"
|
|
}
|
|
}
|
|
|
|
# Count bin archives
|
|
if ("./bin_archives" | path exists) {
|
|
let archives = (try { ls ./bin_archives/*.tar.gz } catch { [] })
|
|
|
|
if ($archives | length) > 0 {
|
|
log_success $"Bin archives: ($archives | length)"
|
|
|
|
let total_size = ($archives | get size | math sum) / 1024 / 1024
|
|
log_info $"Total size: (($total_size | into int))MB"
|
|
}
|
|
}
|
|
|
|
# List platforms
|
|
if not $bin_only and ("./distribution" | path exists) {
|
|
let platforms = (try { ls ./distribution/*/nu } catch { [] } | each {|p| $p.name | path dirname | path basename})
|
|
|
|
if ($platforms | length) > 0 {
|
|
log_info $"\nPlatforms built: ($platforms | str join ', ')"
|
|
|
|
# Next steps - use actual built platforms
|
|
log_info "\n📝 Next Steps:"
|
|
log_info " 1. Test installation:"
|
|
# Show example for first built platform
|
|
let first_platform = $platforms | first 1 | get 0
|
|
log_info $" cd distribution/($first_platform) && ./install.nu --verify"
|
|
}
|
|
} else {
|
|
# If bin_only, no next steps for distribution
|
|
log_info "\n📝 Next Steps:"
|
|
}
|
|
|
|
if not $bin_only {
|
|
# Continue with other next steps
|
|
}
|
|
|
|
log_info " 2. Upload to release:"
|
|
log_info " gh release create v($version) distribution/packages/*"
|
|
|
|
log_info " 3. Update documentation:"
|
|
log_info " Update README.md with new version"
|
|
}
|
|
|
|
# Detect current platform
|
|
def detect_platform []: nothing -> string {
|
|
let host_info = sys host
|
|
let os = $host_info.name
|
|
let arch = (sys cpu | get 0.name)
|
|
|
|
if $os == "Darwin" {
|
|
if $arch == "aarch64" {
|
|
"darwin-arm64"
|
|
} else {
|
|
"darwin-x86_64"
|
|
}
|
|
} else if $os == "Linux" {
|
|
if $arch == "aarch64" {
|
|
"linux-arm64"
|
|
} else {
|
|
"linux-x86_64"
|
|
}
|
|
} else if $os == "Windows" {
|
|
"windows-x86_64"
|
|
} else {
|
|
$"($os | str downcase)-($arch)"
|
|
}
|
|
}
|
|
|
|
# Ensure directory exists
|
|
def ensure_dir [dir: string] {
|
|
if not ($dir | path exists) {
|
|
mkdir $dir
|
|
}
|
|
}
|
|
|
|
# Quick command to rebuild and redistribute
|
|
def "main rebuild" [] {
|
|
log_info "🔄 Rebuild and redistribute workflow\n"
|
|
|
|
# Build nushell
|
|
log_info "Building nushell..."
|
|
let build_result = (do {
|
|
just build-nushell
|
|
} | complete)
|
|
|
|
if $build_result.exit_code != 0 {
|
|
log_error "Nushell build failed"
|
|
exit 1
|
|
}
|
|
|
|
# Build plugins
|
|
log_info "Building plugins..."
|
|
let plugins_result = (do {
|
|
just build
|
|
} | complete)
|
|
|
|
if $plugins_result.exit_code != 0 {
|
|
log_warn "Some plugins failed to build"
|
|
}
|
|
|
|
# Create distributions
|
|
main --checksums
|
|
}
|
|
|
|
# Show distribution status
|
|
def "main status" [] {
|
|
log_info "📊 Distribution Status\n"
|
|
|
|
# Check if nushell is built
|
|
if ("./nushell/target/release/nu" | path exists) {
|
|
let version_result = (do {
|
|
./nushell/target/release/nu -c "version | get version"
|
|
} | complete)
|
|
|
|
log_success $"Nushell binary: (str trim $version_result.stdout)"
|
|
} else {
|
|
log_warn "Nushell binary: Not built"
|
|
}
|
|
|
|
# Check distribution directory
|
|
if ("./distribution" | path exists) {
|
|
let platforms = (try { ls ./distribution/*/nu } catch { [] })
|
|
|
|
if ($platforms | length) > 0 {
|
|
log_success $"Distribution platforms: ($platforms | length)"
|
|
|
|
for p in $platforms {
|
|
let platform = $p.name | path dirname | path basename
|
|
print $" • ($platform)"
|
|
}
|
|
} else {
|
|
log_warn "Distribution: No binaries collected"
|
|
}
|
|
} else {
|
|
log_warn "Distribution: Not created"
|
|
}
|
|
|
|
# Check packages
|
|
if ("./distribution/packages" | path exists) {
|
|
let packages = (try { ls ./distribution/packages/*.tar.gz ./distribution/packages/*.zip } catch { [] })
|
|
|
|
if ($packages | length) > 0 {
|
|
log_success $"Packages: ($packages | length) created"
|
|
} else {
|
|
log_warn "Packages: None created"
|
|
}
|
|
}
|
|
|
|
# Check bin archives
|
|
if ("./bin_archives" | path exists) {
|
|
let archives = (try { ls ./bin_archives/*.tar.gz } catch { [] })
|
|
|
|
if ($archives | length) > 0 {
|
|
log_success $"Bin archives: ($archives | length) created"
|
|
} else {
|
|
log_warn "Bin archives: None created"
|
|
}
|
|
}
|
|
}
|