
Some checks failed
Build and Test / Validate Setup (push) Has been cancelled
Build and Test / Build (darwin-amd64) (push) Has been cancelled
Build and Test / Build (darwin-arm64) (push) Has been cancelled
Build and Test / Build (linux-amd64) (push) Has been cancelled
Build and Test / Build (windows-amd64) (push) Has been cancelled
Build and Test / Build (linux-arm64) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Package Results (push) Has been cancelled
Build and Test / Quality Gate (push) Has been cancelled
## Major Features Added - **Complete distribution infrastructure**: Build, package, and distribute Nushell binary alongside plugins - **Zero-prerequisite installation**: Bootstrap installers work on fresh systems without Rust/Cargo/Nushell - **Cross-platform support**: Linux, macOS, Windows (x86_64, ARM64) - **Self-contained packages**: Everything needed for complete Nushell environment ## New Components ### Build System - `scripts/build_nushell.nu` - Build nushell with all workspace plugins - `scripts/collect_full_binaries.nu` - Collect nu binary + all plugins - `justfiles/full_distro.just` - 40+ new recipes for distribution workflows ### Bootstrap Installers (Zero Prerequisites) - `installers/bootstrap/install.sh` - Universal POSIX installer (900+ lines) - `installers/bootstrap/install.ps1` - Windows PowerShell installer (800+ lines) - Complete platform detection, PATH setup, plugin registration ### Distribution System - `scripts/create_distribution_packages.nu` - Multi-platform package creator - `scripts/install_full_nushell.nu` - Advanced nu-based installer - `scripts/verify_installation.nu` - Installation verification suite - `scripts/lib/common_lib.nu` - Shared utilities and logging ### Configuration Management - `scripts/templates/default_config.nu` - Complete nushell configuration (500+ lines) - `scripts/templates/default_env.nu` - Cross-platform environment setup - `etc/distribution_config.toml` - Central distribution settings - `scripts/templates/uninstall.sh` & `uninstall.ps1` - Clean removal ## Key Workflows ```bash just build-full # Build nushell + all plugins just pack-full-all # Create packages for all platforms just verify-full # Verify installation just release-full-cross # Complete cross-platform release ``` ## Installation Experience - One-liner: `curl -sSf https://your-url/install.sh | sh` - Multiple modes: user, system, portable installation - Automatic plugin registration with verification - Professional uninstall capability ## Benefits - ✅ Solves bootstrap problem - no prerequisites needed - ✅ Production-ready distribution system - ✅ Complete cross-platform support - ✅ Professional installation experience - ✅ Integrates seamlessly with existing plugin workflows - ✅ Enterprise-grade verification and logging
599 lines
19 KiB
Plaintext
Executable File
599 lines
19 KiB
Plaintext
Executable File
#!/usr/bin/env nu
|
|
|
|
# Create Distribution Packages Script
|
|
# Creates platform-specific distribution packages with nushell binary, plugins, and installers
|
|
|
|
use lib/common_lib.nu [
|
|
log_info, log_error, log_success, log_warn, log_debug,
|
|
validate_nushell_version, get_current_platform, get_supported_platforms,
|
|
get_binary_extension, get_archive_extension, ensure_dir, remove_dir,
|
|
copy_file, create_checksums, get_workspace_version, create_manifest
|
|
]
|
|
|
|
def main [
|
|
--platform (-p): string = "" # Target platform (e.g., linux-x86_64, darwin-arm64)
|
|
--output (-o): string = "bin_archives" # Output directory for packages
|
|
--force (-f) # Force overwrite existing packages
|
|
--list (-l) # List what would be packaged
|
|
--all-platforms # Create packages for all platforms
|
|
--checksums # Generate checksums for packages
|
|
--bootstrap # Include bootstrap installers
|
|
--include-docs # Include documentation
|
|
--version (-v): string = "" # Version override
|
|
] {
|
|
# Validate nushell version consistency first
|
|
validate_nushell_version
|
|
|
|
# Get version
|
|
let version = if ($version | str length) > 0 {
|
|
$version
|
|
} else {
|
|
get_workspace_version
|
|
}
|
|
|
|
if $list {
|
|
list_packageable_components $platform $version
|
|
return
|
|
}
|
|
|
|
if $all_platforms {
|
|
create_all_platform_packages $output $force $checksums $bootstrap $include_docs $version
|
|
return
|
|
}
|
|
|
|
# Create package for specific or current platform
|
|
let target_platform = if ($platform | str length) > 0 {
|
|
$platform
|
|
} else {
|
|
get_current_platform
|
|
}
|
|
|
|
create_platform_package $target_platform $output $force $checksums $bootstrap $include_docs $version
|
|
}
|
|
|
|
# List components that would be packaged
|
|
def list_packageable_components [platform: string, version: string] {
|
|
let target_platform = if ($platform | str length) > 0 {
|
|
$platform
|
|
} else {
|
|
get_current_platform
|
|
}
|
|
|
|
log_info $"📋 Components for platform: ($target_platform)"
|
|
log_info "================================================"
|
|
|
|
# Check nushell binary
|
|
let nushell_components = get_nushell_components $target_platform $version
|
|
log_info "🚀 Nushell Components:"
|
|
$nushell_components.binaries | each {|binary|
|
|
let status = if ($binary.path | path exists) { "✅" } else { "❌" }
|
|
log_info $" ($status) ($binary.name) - ($binary.path)"
|
|
}
|
|
|
|
# Check plugins
|
|
let plugin_components = get_plugin_components $target_platform $version
|
|
log_info ""
|
|
log_info "🔌 Plugin Components:"
|
|
$plugin_components.binaries | each {|binary|
|
|
let status = if ($binary.path | path exists) { "✅" } else { "❌" }
|
|
log_info $" ($status) ($binary.name) - ($binary.path)"
|
|
}
|
|
|
|
# Check configuration templates
|
|
let config_components = get_config_components $version
|
|
log_info ""
|
|
log_info "⚙️ Configuration Components:"
|
|
$config_components | each {|config|
|
|
let status = if ($config.path | path exists) { "✅" } else { "❌" }
|
|
log_info $" ($status) ($config.name) - ($config.path)"
|
|
}
|
|
|
|
# Check installers
|
|
let installer_components = get_installer_components $target_platform
|
|
log_info ""
|
|
log_info "📥 Installer Components:"
|
|
$installer_components | each {|installer|
|
|
let status = if ($installer.path | path exists) { "✅" } else { "❌" }
|
|
log_info $" ($status) ($installer.name) - ($installer.path)"
|
|
}
|
|
}
|
|
|
|
# Create packages for all supported platforms
|
|
def create_all_platform_packages [
|
|
output: string,
|
|
force: bool,
|
|
checksums: bool,
|
|
bootstrap: bool,
|
|
include_docs: bool,
|
|
version: string
|
|
] {
|
|
log_info "🌍 Creating packages for all supported platforms..."
|
|
|
|
let platforms = get_supported_platforms
|
|
mut successful_packages = []
|
|
mut failed_packages = []
|
|
|
|
for platform in $platforms {
|
|
log_info $""
|
|
log_info $"📦 Processing platform: ($platform)"
|
|
log_info "================================"
|
|
|
|
let result = try {
|
|
create_platform_package $platform $output $force $checksums $bootstrap $include_docs $version
|
|
"success"
|
|
} catch {|err|
|
|
log_error $"Failed to create package for ($platform): ($err.msg)"
|
|
"failed"
|
|
}
|
|
|
|
if $result == "success" {
|
|
$successful_packages = ($successful_packages | append $platform)
|
|
} else {
|
|
$failed_packages = ($failed_packages | append $platform)
|
|
}
|
|
}
|
|
|
|
# Summary
|
|
log_info ""
|
|
log_info "📊 Package Creation Summary"
|
|
log_info "========================="
|
|
log_success $"✅ Successful: ($successful_packages | length) platforms"
|
|
if ($successful_packages | length) > 0 {
|
|
$successful_packages | each {|platform| log_success $" - ($platform)"}
|
|
}
|
|
|
|
if ($failed_packages | length) > 0 {
|
|
log_error $"❌ Failed: ($failed_packages | length) platforms"
|
|
$failed_packages | each {|platform| log_error $" - ($platform)"}
|
|
}
|
|
|
|
# Create cross-platform checksums if requested
|
|
if $checksums != null {
|
|
create_cross_platform_checksums $output
|
|
}
|
|
}
|
|
|
|
# Create package for a specific platform
|
|
def create_platform_package [
|
|
platform: string,
|
|
output: string,
|
|
force: bool,
|
|
checksums: bool,
|
|
bootstrap: bool,
|
|
include_docs: bool,
|
|
version: string
|
|
] {
|
|
log_info $"📦 Creating package for platform: ($platform)"
|
|
|
|
# Validate platform
|
|
if not ($platform in (get_supported_platforms)) {
|
|
log_error $"Unsupported platform: ($platform). Supported: (get_supported_platforms | str join ', ')"
|
|
return
|
|
}
|
|
|
|
# Setup package directory structure
|
|
let package_name = $"nushell-full-($version)-($platform)"
|
|
let package_dir = $"($output)/($package_name)"
|
|
let archive_extension = get_archive_extension $platform
|
|
|
|
# Check if package already exists
|
|
let archive_path = $"($output)/($package_name)($archive_extension)"
|
|
if ($archive_path | path exists) and ($force == null) {
|
|
log_warn $"Package already exists: ($archive_path). Use --force to overwrite."
|
|
return
|
|
}
|
|
|
|
# Clean and create package directory
|
|
ensure_dir $output
|
|
remove_dir $package_dir
|
|
ensure_dir $package_dir
|
|
|
|
# Create package structure
|
|
ensure_dir $"($package_dir)/bin"
|
|
ensure_dir $"($package_dir)/config"
|
|
ensure_dir $"($package_dir)/scripts"
|
|
if $include_docs != null {
|
|
ensure_dir $"($package_dir)/docs"
|
|
}
|
|
|
|
# Package components
|
|
mut components = {}
|
|
|
|
# Package nushell binary
|
|
let nushell_components = package_nushell_components $platform $package_dir $version
|
|
$components = ($components | merge {nushell: $nushell_components})
|
|
|
|
# Package plugins
|
|
let plugin_components = package_plugin_components $platform $package_dir $version
|
|
$components = ($components | merge {plugins: $plugin_components})
|
|
|
|
# Package configuration
|
|
let config_components = package_config_components $package_dir $version
|
|
$components = ($components | merge {configs: $config_components})
|
|
|
|
# Package installers
|
|
let installer_components = package_installer_components $platform $package_dir $bootstrap
|
|
$components = ($components | merge {installers: $installer_components})
|
|
|
|
# Package documentation if requested
|
|
if $include_docs != null {
|
|
let docs_components = package_docs_components $package_dir
|
|
$components = ($components | merge {docs: $docs_components})
|
|
}
|
|
|
|
# Create manifest
|
|
create_manifest $version $platform $components $"($package_dir)/manifest.json"
|
|
|
|
# Create README for package
|
|
create_package_readme $platform $package_dir $version
|
|
|
|
# Create archive
|
|
create_package_archive $package_dir $archive_path $platform
|
|
|
|
# Create checksums if requested
|
|
if $checksums != null {
|
|
create_checksums [$archive_path] $output
|
|
}
|
|
|
|
# Clean up temporary directory
|
|
remove_dir $package_dir
|
|
|
|
log_success $"✅ Package created: ($archive_path)"
|
|
}
|
|
|
|
# Package nushell components
|
|
def package_nushell_components [platform: string, package_dir: string, version: string] {
|
|
log_info "📦 Packaging nushell components..."
|
|
|
|
let components = get_nushell_components $platform $version
|
|
mut packaged = []
|
|
|
|
for binary in $components.binaries {
|
|
if ($binary.path | path exists) {
|
|
let dest_path = $"($package_dir)/bin/($binary.name)"
|
|
if (copy_file $binary.path $dest_path) {
|
|
$packaged = ($packaged | append $binary.name)
|
|
}
|
|
} else {
|
|
log_warn $"Nushell binary not found: ($binary.path)"
|
|
}
|
|
}
|
|
|
|
{
|
|
binaries: $packaged,
|
|
version: $version,
|
|
platform: $platform
|
|
}
|
|
}
|
|
|
|
# Package plugin components
|
|
def package_plugin_components [platform: string, package_dir: string, version: string] {
|
|
log_info "📦 Packaging plugin components..."
|
|
|
|
let components = get_plugin_components $platform $version
|
|
mut packaged = []
|
|
|
|
for binary in $components.binaries {
|
|
if ($binary.path | path exists) {
|
|
let dest_path = $"($package_dir)/bin/($binary.name)"
|
|
if (copy_file $binary.path $dest_path) {
|
|
$packaged = ($packaged | append $binary.name)
|
|
}
|
|
} else {
|
|
log_warn $"Plugin binary not found: ($binary.path)"
|
|
}
|
|
}
|
|
|
|
{
|
|
binaries: $packaged,
|
|
count: ($packaged | length)
|
|
}
|
|
}
|
|
|
|
# Package configuration components
|
|
def package_config_components [package_dir: string, version: string] {
|
|
log_info "📦 Packaging configuration components..."
|
|
|
|
let components = get_config_components $version
|
|
mut packaged = []
|
|
|
|
for config in $components {
|
|
if ($config.path | path exists) {
|
|
let dest_path = $"($package_dir)/config/($config.name)"
|
|
if (copy_file $config.path $dest_path) {
|
|
$packaged = ($packaged | append $config.name)
|
|
}
|
|
} else {
|
|
log_warn $"Configuration file not found: ($config.path)"
|
|
}
|
|
}
|
|
|
|
# Create distribution-specific config
|
|
create_distribution_config $package_dir $version
|
|
$packaged = ($packaged | append "distribution_config.toml")
|
|
|
|
{
|
|
files: $packaged,
|
|
count: ($packaged | length)
|
|
}
|
|
}
|
|
|
|
# Package installer components
|
|
def package_installer_components [platform: string, package_dir: string, bootstrap: bool] {
|
|
log_info "📦 Packaging installer components..."
|
|
|
|
let components = get_installer_components $platform
|
|
mut packaged = []
|
|
|
|
for installer in $components {
|
|
if ($installer.path | path exists) {
|
|
let dest_path = $"($package_dir)/($installer.name)"
|
|
if (copy_file $installer.path $dest_path) {
|
|
# Make installer executable on Unix-like systems
|
|
if not ($platform | str starts-with "windows") {
|
|
try { chmod +x $dest_path }
|
|
}
|
|
$packaged = ($packaged | append $installer.name)
|
|
}
|
|
} else {
|
|
log_warn $"Installer not found: ($installer.path)"
|
|
}
|
|
}
|
|
|
|
# Create uninstaller
|
|
create_uninstaller $platform $package_dir
|
|
$packaged = ($packaged | append "uninstall.sh")
|
|
|
|
{
|
|
files: $packaged,
|
|
bootstrap: $bootstrap
|
|
}
|
|
}
|
|
|
|
# Package documentation components
|
|
def package_docs_components [package_dir: string] {
|
|
log_info "📦 Packaging documentation components..."
|
|
|
|
let docs = [
|
|
{name: "README.md", path: "./README.md"},
|
|
{name: "CLAUDE.md", path: "./CLAUDE.md"},
|
|
{name: "LICENSE", path: "./LICENSE"}
|
|
]
|
|
|
|
mut packaged = []
|
|
|
|
for doc in $docs {
|
|
if ($doc.path | path exists) {
|
|
let dest_path = $"($package_dir)/docs/($doc.name)"
|
|
if (copy_file $doc.path $dest_path) {
|
|
$packaged = ($packaged | append $doc.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
files: $packaged
|
|
}
|
|
}
|
|
|
|
# Get nushell components for platform
|
|
def get_nushell_components [platform: string, version: string] {
|
|
let extension = get_binary_extension $platform
|
|
let profile_dir = if ($platform | str contains "release") or true { "release" } else { "debug" }
|
|
|
|
# Check if platform-specific build exists in distribution
|
|
let dist_binary = $"./distribution/($platform)/nu($extension)"
|
|
let workspace_binary = $"./nushell/target/($profile_dir)/nu($extension)"
|
|
|
|
let nushell_path = if ($dist_binary | path exists) {
|
|
$dist_binary
|
|
} else if ($workspace_binary | path exists) {
|
|
$workspace_binary
|
|
} else {
|
|
$workspace_binary # Will be reported as missing in list
|
|
}
|
|
|
|
{
|
|
binaries: [
|
|
{
|
|
name: $"nu($extension)",
|
|
path: $nushell_path,
|
|
component: "nushell"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
# Get plugin components for platform
|
|
def get_plugin_components [platform: string, version: string] {
|
|
let extension = get_binary_extension $platform
|
|
let dist_dir = $"./distribution/($platform)"
|
|
|
|
# Get plugins from distribution directory if it exists
|
|
let plugin_binaries = if ($dist_dir | path exists) {
|
|
glob $"($dist_dir)/nu_plugin_*($extension)"
|
|
| each {|path|
|
|
let name = ($path | path basename)
|
|
{
|
|
name: $name,
|
|
path: $path,
|
|
component: "plugin"
|
|
}
|
|
}
|
|
} else {
|
|
# Fallback to individual plugin target directories
|
|
glob "nu_plugin_*"
|
|
| where ($it | path type) == "dir"
|
|
| each {|plugin_dir|
|
|
let plugin_name = ($plugin_dir | path basename)
|
|
let binary_name = $"($plugin_name)($extension)"
|
|
mut binary_path = $"($plugin_dir)/target/release/($binary_name)"
|
|
|
|
if not ($binary_path | path exists) {
|
|
# Try debug build
|
|
$binary_path = $"($plugin_dir)/target/debug/($binary_name)"
|
|
}
|
|
|
|
{
|
|
name: $binary_name,
|
|
path: $binary_path,
|
|
component: "plugin"
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
binaries: $plugin_binaries
|
|
}
|
|
}
|
|
|
|
# Get configuration components
|
|
def get_config_components [version: string] {
|
|
[
|
|
{name: "default_config.nu", path: "./scripts/templates/default_config.nu"},
|
|
{name: "default_env.nu", path: "./scripts/templates/default_env.nu"}
|
|
]
|
|
}
|
|
|
|
# Get installer components
|
|
def get_installer_components [platform: string] {
|
|
if ($platform | str starts-with "windows") {
|
|
[
|
|
{name: "install.ps1", path: "./scripts/templates/install.ps1"}
|
|
]
|
|
} else {
|
|
[
|
|
{name: "install.sh", path: "./scripts/templates/install.sh"}
|
|
]
|
|
}
|
|
}
|
|
|
|
# Create distribution-specific configuration
|
|
def create_distribution_config [package_dir: string, version: string] {
|
|
let config = {
|
|
distribution: {
|
|
version: $version,
|
|
created_at: (date now | format date "%Y-%m-%d %H:%M:%S UTC"),
|
|
full_distribution: true
|
|
}
|
|
}
|
|
|
|
$config | to toml | save -f $"($package_dir)/config/distribution_config.toml"
|
|
}
|
|
|
|
# Create package README
|
|
def create_package_readme [platform: string, package_dir: string, version: string] {
|
|
let readme_content = [
|
|
$"# Nushell Full Distribution v($version)",
|
|
"",
|
|
"This package contains a complete Nushell distribution with all plugins and configurations.",
|
|
"",
|
|
"## Platform",
|
|
$"- Target: ($platform)",
|
|
$"- Created: (date now | format date '%Y-%m-%d %H:%M:%S UTC')",
|
|
"",
|
|
"## Contents",
|
|
"",
|
|
"### Binaries (bin/)",
|
|
"- nu - Nushell shell binary",
|
|
"- nu_plugin_* - Plugin binaries",
|
|
"",
|
|
"### Configuration (config/)",
|
|
"- default_config.nu - Default nushell configuration",
|
|
"- default_env.nu - Default environment setup",
|
|
"- distribution_config.toml - Distribution metadata",
|
|
"",
|
|
"### Installation",
|
|
"Run the installer for your platform:",
|
|
"- Unix/Linux/macOS: ./install.sh",
|
|
"- Windows: ./install.ps1",
|
|
"",
|
|
"### Manual Installation",
|
|
"1. Copy binaries from bin/ to a directory in your PATH",
|
|
"2. Copy configuration files from config/ to your nushell config directory",
|
|
"3. Register plugins: nu -c 'plugin add path/to/plugin'",
|
|
"",
|
|
"### Verification",
|
|
"After installation, verify with:",
|
|
"nu --version",
|
|
"",
|
|
"### Uninstallation",
|
|
"Run: ./uninstall.sh (or ./uninstall.ps1 on Windows)",
|
|
"",
|
|
"## Documentation",
|
|
"See docs/ directory for additional documentation.",
|
|
"",
|
|
"## Support",
|
|
"For issues and support, visit: https://github.com/nushell/nushell"
|
|
] | str join "\n"
|
|
|
|
$readme_content | save -f $"($package_dir)/README.md"
|
|
}
|
|
|
|
# Create uninstaller script
|
|
def create_uninstaller [platform: string, package_dir: string] {
|
|
let uninstaller_path = if ($platform | str starts-with "windows") {
|
|
"./scripts/templates/uninstall.ps1"
|
|
} else {
|
|
"./scripts/templates/uninstall.sh"
|
|
}
|
|
|
|
if ($uninstaller_path | path exists) {
|
|
let dest = if ($platform | str starts-with "windows") {
|
|
$"($package_dir)/uninstall.ps1"
|
|
} else {
|
|
$"($package_dir)/uninstall.sh"
|
|
}
|
|
copy_file $uninstaller_path $dest
|
|
}
|
|
}
|
|
|
|
# Create package archive
|
|
def create_package_archive [package_dir: string, archive_path: string, platform: string] {
|
|
log_info $"📦 Creating archive: ($archive_path)"
|
|
|
|
let package_name = ($package_dir | path basename)
|
|
let archive_name = ($archive_path | path basename)
|
|
let work_dir = ($package_dir | path dirname)
|
|
|
|
if ($platform | str starts-with "windows") {
|
|
# Create ZIP archive for Windows
|
|
cd $work_dir
|
|
try {
|
|
run-external "zip" "-r" $archive_name $package_name
|
|
} catch {
|
|
# Fallback to tar if zip not available
|
|
run-external "tar" "-czf" $archive_name $package_name
|
|
}
|
|
} else {
|
|
# Create tar.gz archive for Unix-like systems
|
|
cd $work_dir
|
|
run-external "tar" "-czf" $archive_name $package_name
|
|
}
|
|
|
|
# Move archive to final location if needed
|
|
let temp_archive = $"($work_dir)/($archive_name)"
|
|
if ($temp_archive | path exists) and ($temp_archive != $archive_path) {
|
|
mv $temp_archive $archive_path
|
|
}
|
|
|
|
if ($archive_path | path exists) {
|
|
let size = (ls $archive_path | get 0.size)
|
|
log_success $"✅ Archive created: ($archive_path) (($size))"
|
|
} else {
|
|
log_error $"❌ Failed to create archive: ($archive_path)"
|
|
}
|
|
}
|
|
|
|
# Create cross-platform checksums
|
|
def create_cross_platform_checksums [output: string] {
|
|
log_info "🔐 Creating cross-platform checksums..."
|
|
|
|
let archives = glob $"($output)/*.tar.gz" | append (glob $"($output)/*.zip")
|
|
if ($archives | length) > 0 {
|
|
create_checksums $archives $output
|
|
log_success $"✅ Cross-platform checksums created in ($output)/checksums.txt"
|
|
} else {
|
|
log_warn "No archives found for checksum generation"
|
|
}
|
|
} |