#!/usr/bin/env nu # syntaxis Installer (Dynamic Binary Discovery) # # Dynamically discovers binary crates from workspace Cargo.toml # and installs them with wrappers, configs, and Leptos deployment # # USAGE: # install-syntaxis.nu # Show help # install-syntaxis.nu help # Show help # install-syntaxis.nu all # Install all binaries (keeps target/) # install-syntaxis.nu all --rm-target # Install all (remove target/) # install-syntaxis.nu status # Show installation status # install-syntaxis.nu list # List available binaries # Source shared libraries source syntaxis-lib.nu def print_help [] { print "" print "๐Ÿ› ๏ธ syntaxis Binary Installer" print "" print "Dynamically installs binaries from workspace with configs and Leptos dashboard" print "" print "USAGE:" print " install-syntaxis.nu # Show this help message" print " install-syntaxis.nu help # Show this help message" print " install-syntaxis.nu all # Install all binaries (keeps target/)" print " install-syntaxis.nu all --rm-target # Install all and remove target/" print " install-syntaxis.nu status # Show installation status" print " install-syntaxis.nu list # List available binaries" print "" print "INSTALL SPECIFIC BINARIES:" print " install-syntaxis.nu cli # Install syntaxis-cli" print " install-syntaxis.nu tui # Install syntaxis-tui" print " install-syntaxis.nu api # Install syntaxis-api" print " install-syntaxis.nu client # Install dashboard-client" print "" print " # Or use full binary/crate names:" print " install-syntaxis.nu syntaxis-cli # Install syntaxis-cli" print " install-syntaxis.nu syntaxis-tui # Install syntaxis-tui" print " install-syntaxis.nu syntaxis-api # Install syntaxis-api" print "" print "OPTIONS:" print " --rm-target # Remove target/ directories after build" print " # (default: false, keep target/)" print "" print "WHAT GETS INSTALLED (PER BINARY):" print " 1. Binary crate (auto-discovered from Cargo.toml [[bin]] sections)" print " 2. Wrapper script (for binaries that need configuration)" print " โ€ข Injects environment variables for auto-discovery binaries" print " โ€ข Injects default --config path" print " 3. Target-specific config file" print "" print "GLOBAL RESOURCES:" print " โ€ข Installation manifest in .syntaxis/manifest.toml" print " โ€ข Configuration directories at ~/.config/syntaxis/" print "" } def discover_binary_crates [] { # Load workspace members from root Cargo.toml and find binaries in core/crates/* let workspace_toml = "Cargo.toml" if not ($workspace_toml | path exists) { print "โŒ Cargo.toml not found" return [] } try { # Read root Cargo.toml to get workspace members let workspace_content = (open $workspace_toml) # Extract all lines with members entries let members = ( ($workspace_content | get -o workspace | get -o members) | each { |line| # let trimmed = ($line | str trim) # Match entries like "core/crates/syntaxis-api", if ($line | str starts-with 'core/crates/') { # Extract the path: "core/crates/syntaxis-api", โ†’ core/crates/syntaxis-api # Split by quotes and get the middle part ($line | str trim) #let parts = ($line | split row '"') #if ($parts | length) >= 2 { # $parts | get 1 #} } } | compact ) # For each member in core/crates/*, extract metadata let binaries = ( $members | each { |member_path| let member_toml = $"($member_path)/Cargo.toml" if ($member_toml | path exists) { let member_content = (open $member_toml) # Extract package name let package_name = ($member_content | get -o package | get -o name) if ($package_name | is-not-empty) { # Extract metadata from [package.metadata.syntaxis] let metadata = ($member_content | get -o package.metadata | get -o syntaxis) if ($metadata | is-not-empty) { # Extract use_wrapper let use_wrapper = ($metadata | get -o use_wrapper) # Extract install_mode let install_mode = ($metadata | get -o install_mode) # Extract config_src let config_src = ($metadata | get -o config_src) # Extract binary name from [[bin]] section let parts_bin = ($member_content | get -o bin) let bin_name = if ($parts_bin | length) > 0 { $parts_bin | first | get -o name } else { "" } let binary_name = if ($bin_name | is-not-empty) { $bin_name } else { $package_name } # Return record directly { crate_name: $package_name, binary_name: $binary_name, crate_path: $member_path, use_wrapper: $use_wrapper, install_mode: $install_mode, config_src: $config_src } } } } } | compact ) return $binaries } catch { |err| print $"โŒ Failed to discover binaries: ($err)" return [] } } def deploy_libs [binaries: list] { # Copy syntaxis-lib.nu and target-specific scripts/libs to ~/.config/syntaxis/scripts/ # so wrappers can import them via NU_LIB_DIRS # Only archives/backs up scripts for the specific binaries being installed (NOT the shared lib) let config_scripts_dir = ($env.HOME + "/.config/syntaxis/scripts") let config_dir = ($env.HOME + "/.config/syntaxis") let scripts_dir = ($env.PWD + "/scripts") try { # Create config scripts directory ^mkdir -p $config_scripts_dir # Copy common library WITHOUT backup (shared across all binaries) let lib_path = ($scripts_dir + "/syntaxis-lib.nu") if ($lib_path | path exists) { let lib_dest = ($config_scripts_dir + "/syntaxis-lib.nu") # Deploy without backup - syntaxis-lib.nu is shared and shouldn't be archived on every install ^cp $lib_path $lib_dest print " โœ… Deployed syntaxis-lib.nu to config (no backup)" } else { print $" โš ๏ธ syntaxis-lib.nu not found at ($lib_path)" } # Extract binary names from the binaries list to determine which scripts to deploy let binary_names = ($binaries | each { |b| $b.binary_name }) # Copy target-specific libraries and scripts only for binaries being installed # For each binary with binary_name "syntaxis-tui", deploy: syntaxis-tui.nu and syntaxis-tui-lib.nu # These ARE backed up to archives before overwriting let target_files = (glob ($scripts_dir + "/syntaxis-*.nu") | compact) if ($target_files | length) > 0 { for file_path in $target_files { let file_name = ($file_path | path basename) # Skip the shared library (already handled above without backup) if ($file_name == "syntaxis-lib.nu") { continue } # Check if this script belongs to any of the binaries being installed # binary_name already includes "syntaxis-" prefix (e.g., "syntaxis-tui") # so we check for exact filename matches like "syntaxis-tui.nu" and "syntaxis-tui-lib.nu" let should_deploy = ($binary_names | any { |bn| ($file_name == $"($bn).nu") or ($file_name == $"($bn)-lib.nu") }) if $should_deploy { let script_dest = ($config_scripts_dir + "/" + $file_name) # Backup existing script if it exists (to parent config_dir archives) backup_if_exists $script_dest $config_dir ^cp $file_path $script_dest print $" โœ… Deployed ($file_name)" } } } else { print " โ„น๏ธ No target-specific scripts found" } } catch { |err| print $" โš ๏ธ Failed to deploy libs: ($err)" } } def install_binary [item: record , rm_target: bool] { let display_name = $"(ansi blue)($item.crate_name)(ansi reset)" print $"๐Ÿ“ฆ Building ($display_name) [from: ($item.crate_path) wrapper: ($item.use_wrapper) mode: ($item.install_mode)] config: ($item.config_src)] ..." try { if ($item.install_mode == "cargo") or ($item.install_mode | str starts-with "cargo") { # Standard cargo install cargo install --path $item.crate_path out+err> /dev/null print $"โœ… ($display_name): Installed successfully" } else if ($item.install_mode == "leptos") or ($item.install_mode | str starts-with "leptos") { # Leptos CSR build using build-dashboard.nu script print $" ๐ŸŽจ Building Leptos WASM dashboard..." # Call build-dashboard.nu to handle the full build (CSS + WASM) # This script must be run from core/ directory try { cd core nu ../scripts/core/build-dashboard.nu build out+err> /dev/null cd .. print $" โœ… Leptos WASM build completed" } catch { |err| cd .. print $" โŒ Leptos WASM build failed: ($err)" return { success: false, crate_name: $item.crate_name, binary_name: $item.binary_name, crate_path: $item.crate_path, reason: $"Leptos build failed: ($err)" } } # Deploy artifacts to public folder print $" ๐Ÿ“ฆ Deploying Leptos artifacts for ($display_name)..." let deployment_ok = (deploy_leptos_artifacts) if not $deployment_ok { print $" โš ๏ธ Leptos artifact deployment had issues (build output may not exist yet)" # Don't fail installation - artifacts will be available after first build } } else { print $"โš ๏ธ Unknown install_mode: ($item.install_mode), skipping" return { success: false, crate_name: $item.crate_name, binary_name: $item.binary_name, crate_path: $item.crate_path, reason: $"Unknown install_mode: ($item.install_mode)" } } if $rm_target { let target_dir = "target" if ($target_dir | path exists) { rm -r $target_dir print $" ๐Ÿงน Cleaned up build artifacts for ($display_name)" } } return { success: true, crate_name: $item.crate_name, binary_name: $item.binary_name, crate_path: $item.crate_path } } catch { |err| print $"โŒ ($display_name): Build failed - ($err)" return { success: false, crate_name: $item.crate_name, binary_name: $item.binary_name, crate_path: $item.crate_path, reason: ($err | to_string) } } } def deploy_leptos_artifacts [] { # Deploy compiled Leptos artifacts to public directory # This is called AFTER build-dashboard.nu has generated artifacts in core/target/site/ let home = $env.HOME let public_dir = $"($home)/.config/syntaxis/public" let leptos_build_dir = "core/target/site" print " ๐Ÿ“‚ Preparing artifact deployment..." # Create public directory if doesn't exist try { ^mkdir -p $public_dir } catch { |err| print $" โŒ Failed to create public directory: ($err)" return false } # Check if Leptos build artifacts exist if not ($leptos_build_dir | path exists) { print $" โš ๏ธ Leptos build artifacts not found at: ($leptos_build_dir)" print $" ๐Ÿ“Œ This is expected if build-dashboard.nu hasn't run yet" return false } # Verify artifact structure before copying print " ๐Ÿ” Verifying artifact structure..." mut all_artifacts_present = true if not ("($leptos_build_dir)/index.html" | path exists) { print $" โš ๏ธ index.html not found" $all_artifacts_present = false } else { print $" โœ… index.html verified" } if not ("($leptos_build_dir)/pkg" | path exists) { print $" โš ๏ธ WASM artifacts (pkg/) not found" $all_artifacts_present = false } else { let wasm_files = (glob $"($leptos_build_dir)/pkg/**/*.wasm" | length) print ($" โœ… WASM artifacts verified " + ($wasm_files | into string) + " files") } if not ("($leptos_build_dir)/styles/app.css" | path exists) { print $" โš ๏ธ CSS file (styles/app.css) not found" $all_artifacts_present = false } else { let size_bytes = (stat $"($leptos_build_dir)/styles/app.css" | get size) let size_kb = (($size_bytes / 1024) | math round --precision 2) print ($" โœ… CSS verified " + ($size_kb | into string) + "KB") } if not $all_artifacts_present { print $" โŒ Some artifacts are missing. Cannot deploy." return false } # Copy Leptos artifacts to public directory print " ๐Ÿ“ฅ Copying artifacts to ($public_dir)..." try { # Copy all contents of leptos_build_dir to public_dir # Using -r for recursive, and ensuring we copy the contents, not the directory itself ^cp -r $"($leptos_build_dir)/." $"($public_dir)/" # Verify deployment if ("($public_dir)/index.html" | path exists) { print $" โœ… Deployed Leptos artifacts successfully" print $" - index.html: SPA entry point" print $" - pkg/: WASM binaries and JS bindings" print $" - styles/app.css: Generated CSS" return true } else { print $" โŒ Deployment verification failed" return false } } catch { |err| print $" โŒ Failed to deploy Leptos artifacts: ($err)" return false } } def deploy_leptos_dashboard [] { # Build and deploy Leptos CSR dashboard print "" print "๐Ÿš€ Building and deploying Leptos CSR dashboard..." let home = $env.HOME let public_dir = $"($home)/.config/syntaxis/public" let leptos_build_dir = "core/crates/client/target/site" # Create public directory ^mkdir -p $public_dir print $"๐Ÿ“‚ Created public directory: ($public_dir)" # Build Leptos CSR (if cargo-leptos is available) try { print " Building Leptos CSR..." cd core/crates/dashboard-client cargo build --release out+err> /dev/null cd ../../.. if ($leptos_build_dir | path exists) { # Copy compiled site to public directory cp -r $"($leptos_build_dir)/*" $public_dir print $" โœ… Deployed Leptos site to ($public_dir)" } else { print $" โš ๏ธ Leptos build directory not found at ($leptos_build_dir)" print $" Ensure cargo-leptos is installed: cargo install cargo-leptos" } } catch { |err| print $" โš ๏ธ Leptos build skipped: ($err)" print $" You can manually build with: cargo leptos build --release" } } def backup_if_exists [path: string, archive_root: string] { # Move existing file to timestamped archive folder if it exists # archive_root: parent directory to contain archives/YYYY-MM-DD-HH-MM/ if ($path | path exists) { let filename = ($path | path basename) try { # Create archive directory with timestamp (YYYY-MM-DD-HH-MM) let now = (date now | format date "%Y-%m-%d-%H-%M") let archive_dir = ($archive_root + "/archives/" + $now) # Create archive directory if it doesn't exist ^mkdir -p $archive_dir # Move file to archive let backup_path = ($archive_dir + "/" + $filename) ^mv $path $backup_path print $" โš ๏ธ Previous version of ($filename) exists" print $" ๐Ÿ“ฆ Archived to: archives/($now)/" print $" โ„น๏ธ Previous version preserved - you can restore if needed" } catch { |err| print $" โš ๏ธ Failed to archive ($filename): ($err)" } } } def deploy_config_for_binary [crate_name: string, config_src: string] { # Deploy configuration from config_src (relative to project root) to ~/.config/syntaxis/ # Backs up existing configs to archives/YYYY-MM-DD-HH-MM/ before installing new ones if ($config_src | str length) == 0 { # No config to deploy return } let config_dir = ($env.HOME + "/.config/syntaxis") # config_src is relative to project root let src_path = $config_src # Ensure source exists if not ($src_path | path exists) { print $" โš ๏ธ Config source not found: ($src_path)" return } try { # Create config directory if doesn't exist ^mkdir -p $config_dir # Copy config files from source to config directory if ($src_path | path type) == "dir" { # For directories, backup existing files in config_dir that come from src let files_to_deploy = (glob $"($src_path)/*" | compact) for file_path in $files_to_deploy { let filename = ($file_path | path basename) let dest_path = $"($config_dir)/($filename)" # Backup existing file if it exists backup_if_exists $dest_path $config_dir } # Copy all files from source directory ^cp -r $"($src_path)/." $"($config_dir)/" out+err> /dev/null print $" โœ… Deployed config from ($src_path)" } else if ($src_path | path type) == "file" { # Copy single file let filename = ($src_path | path basename) let dest_path = $"($config_dir)/($filename)" # Backup existing file if it exists backup_if_exists $dest_path $config_dir ^cp $src_path $dest_path print $" โœ… Deployed config: ($filename)" } } catch { |err| print $" โš ๏ธ Failed to deploy config: ($err)" } } def deploy_configurations [] { # Create config directories and default configs let config_dir = ($env.HOME + "/.config/syntaxis") ^mkdir -p $config_dir ^mkdir -p $"($config_dir)/public" ^mkdir -p $"($config_dir)/features" print "" print "๐Ÿ“‹ Deploying global configuration structure..." # Create default main config if doesn't exist let default_main_config = $"($config_dir)/syntaxis.toml" if not ($default_main_config | path exists) { let config_content = "# Syntaxis Global Configuration # Main config file for all syntaxis applications [server] host = \"127.0.0.1\" port = 8080 [api] host = \"127.0.0.1\" port = 8080 [database] url = \"sqlite:///($env.HOME)/.local/share/syntaxis/syntaxis.db\" [logging] level = \"info\" format = \"compact\" " $config_content | save $default_main_config print $" โœ… Created: syntaxis.toml" } print "" print $"๐Ÿ“‚ Configuration directories created at: ($config_dir)" } def create_wrapper_for_binary [binary_name: string, use_wrapper: bool, config_src: string] { # Create wrapper script for a single binary # Backs up existing wrapper scripts to archives/YYYY-MM-DD-HH-MM/ before installing new ones if not $use_wrapper { return } let cargo_bin = ($env.HOME + "/.cargo/bin") let config_dir = ($env.HOME + "/.config/syntaxis") let binary_path = ($cargo_bin + "/" + $binary_name) let real_binary_path = ($cargo_bin + "/" + $binary_name + ".real") # Check if binary exists if ($binary_path | path exists) { let display_name = $"(ansi blue)($binary_name)(ansi reset)" try { # Rename actual binary to .real (don't backup the binary, it's rebuilt each time) ^mv $binary_path $real_binary_path # Create bash wrapper script that calls the NuShell wrapper # The bash script sets NU_LIB_DIRS and calls the .nu script let bash_wrapper = $"#!/bin/bash # Bash wrapper for ($binary_name) # Calls the NuShell wrapper with proper library paths export NU_LIB_DIRS=\"\$HOME/.config/syntaxis/scripts\" exec nu \"\$HOME/.config/syntaxis/scripts/($binary_name).nu\" \"\$@\"" $bash_wrapper | save --force $binary_path ^chmod +x $binary_path print $" โœ… Created wrapper for ($display_name)" print $" Location: ($binary_path)" } catch { |err| print $" โš ๏ธ Failed to create wrapper for ($display_name): ($err)" } } } def needs_wrapper [has_clap: bool, has_wasm: bool, has_leptos: bool] { # RULE: Determine if binary needs a wrapper script # - If HAS clap โ†’ NO wrapper (user provides args via CLI) # - If NO clap AND NO wasm AND NO leptos โ†’ WRAPPER needed (auto-discovery) # - If has wasm or leptos โ†’ WASM/frontend, no wrapper needed if $has_clap { # Has Clap: user provides arguments explicitly return false } else if ($has_wasm or $has_leptos) { # WASM frontend: no CLI wrapper needed return false } else { # No Clap, no WASM/Leptos: needs wrapper for auto-discovery config return true } } def register_installations [installed_binaries: list] { # Create manifest file tracking with version and integrity info let manifest_path = ".syntaxis/manifest.toml" ^mkdir -p ".syntaxis" print "" print "๐Ÿ“Š Registering installations..." let now = (date now | format date "%Y-%m-%d %H:%M:%S") let home = $env.HOME let config_dir = $"($home)/.config/syntaxis" # Build binaries section let binaries_toml = ( $installed_binaries | each { |binary| let section = $"[binaries.($binary.crate_name)] installed = true version = \"0.1.0\" installed_at = \"($now)\" " $section } | str join "\n" ) # Create comprehensive manifest let manifest_content = $"# syntaxis Installation Manifest # Auto-generated during installation # Use this to track versions, verify integrity, and enable rollback # Generated: ($now) [installation] created_at = \"($now)\" last_updated = \"($now)\" installation_root = \"(pwd)\" ecosystem_version = \"0.1.0\" ($binaries_toml) [configurations] config_dir = \"($config_dir)\" public_dir = \"($config_dir)/public\" data_dir = \"($home)/.local/share/syntaxis\" [leptos_dashboard] # Leptos WASM dashboard configuration deployed_at = \"($now)\" artifacts_location = \"($config_dir)/public\" wasm_target = \"wasm32-unknown-unknown\" build_tool = \"trunk\" css_generator = \"unocss\" api_url_config = \"auto-detect [window.SYNTAXIS_CONFIG]\" [environment] SYNTAXIS_CONFIG_DIR = \"($config_dir)\" SYNTAXIS_DATA_DIR = \"($home)/.local/share/syntaxis\" SYNTAXIS_PUBLIC_DIR = \"($config_dir)/public\" [verification] all_binaries_installed = true all_configs_deployed = true wrappers_created = true leptos_dashboard_deployed = true manifest_version = \"1.0\" " try { $manifest_content | save --force $manifest_path print $" โœ… Registered installations in manifest" print $" ๐Ÿ“ Manifest saved to: .syntaxis/manifest.toml" } catch { print $" โš ๏ธ Failed to create manifest" } } def show_status [] { print "" print "๐Ÿ“Š Installation Status" print "" let cargo_bin = ($env.HOME + "/.cargo/bin") let binaries = (discover_binary_crates) if ($binaries | length) == 0 { print "โš ๏ธ No binary crates found" return } let status_rows = ( $binaries | each { |binary| let bin_path = ($cargo_bin + "/" + $binary.binary_name) let status = if ($bin_path | path exists) { "โœ… Installed" } else { "โš ๏ธ Not installed" } { binary: $binary.binary_name, crate: $binary.crate_name, status: $status, path: $binary.crate_path, wrapper: $binary.use_wrapper, mode: $binary.install_mode, config: $binary.config_src } } ) print ($status_rows | table -i false | ansi strip) print "" print "๐Ÿ“ Note: Add ~/.cargo/bin to your PATH if binaries show as not installed" print $" export PATH=\"\$HOME/.cargo/bin:\$PATH\"" print "" } def resolve_binary_from_action [action: string, all_binaries: list] { # Resolve binary from action, supporting: # 1. Full binary name: "syntaxis-cli" # 2. Short name: "cli" (extracts last component from crate_path core/crates/cli) # 3. Crate name: "syntaxis-cli" let matching = ( $all_binaries | where { |b| (($b.binary_name == $action) or ($b.crate_name == $action) or (($b.crate_path | path basename) == $action)) } ) return $matching } def list_binaries [] { print "" print "๐Ÿ“ฆ Available Binaries (Auto-discovered)" print "" let binaries = (discover_binary_crates) if ($binaries | length) == 0 { print "โš ๏ธ No binary crates found in workspace" return } print ($binaries | table -i false | ansi strip) #$binaries | each { |binary| # print $" ($binary.crate_name) โ†’ ($binary.crate_path)" #} print "" } def main [ action?: string # Action: "all", "status", "list", or binary name --rm-target # Remove target directories after build (default: false) ] { show_logo # Show help if no arguments or help requested if ($action == null or $action == "help") { print_help return } # Handle status command if ($action == "status") { show_status return } # Handle list command if ($action == "list") { list_binaries return } # Discover available binaries let all_binaries = (discover_binary_crates) if ($all_binaries | length) == 0 { print "โŒ No binary crates found in workspace" return } # Determine which binaries to install let binaries_to_install = if ($action == "all") { $all_binaries } else { # Find matching binary by binary_name, crate_name, or path let matching = (resolve_binary_from_action $action $all_binaries) if ($matching | length) == 0 { print $"โŒ Unknown binary: ($action)" print "" print "Available binaries:" let binaries_list = $all_binaries $binaries_list | each { |b| let short_name = ($b.crate_path | path basename) let display = $" - ($short_name) \(or ($b.binary_name), or ($b.crate_name)\)" print $display } return } $matching } print "" let binaries_count = ($binaries_to_install | length) print $"๐Ÿ”จ Installing ($binaries_count) binary crate\(s\)... Remove target: ($rm_target)" print "" # Deploy library files first, before creating wrappers # This ensures wrappers can import the libs when needed print "๐Ÿ“‹ Deploying library files..." deploy_libs $binaries_to_install print "" # Install each binary with immediate wrapper and config setup # Stop on first failure let install_result = ( $binaries_to_install | reduce {|binary, acc| if $acc.failed { # Already failed, skip remaining binaries $acc } else { let display_name = $"(ansi blue)($binary.crate_name)(ansi reset)" print $"๐Ÿ“ฆ Step 1/3: Building ($display_name)..." let result = (install_binary $binary $rm_target) if $result.success { # Build succeeded - immediately set up wrapper and config let config_dir = ($env.HOME + "/.config/syntaxis") let cargo_bin = ($env.HOME + "/.cargo/bin") print $"๐Ÿ“ฆ Step 2/3: Setting up wrapper and configuration..." print $" Config Path: ($config_dir)" print $" Wrapper Path: ($cargo_bin)" # Create wrapper if applicable if $binary.use_wrapper { create_wrapper_for_binary $binary.binary_name $binary.use_wrapper $binary.config_src } # Deploy config if applicable if ($binary.config_src | str length) > 0 { deploy_config_for_binary $binary.crate_name $binary.config_src } print $"๐Ÿ“ฆ Step 3/3: Registered ($display_name)" print "" { installed: ($acc.installed | append $result) failed: false } } else { # Installation failed - stop processing print "โŒ Installation failed - stopping further installations" print "" { installed: $acc.installed failed: true } } } } -f {installed: [], failed: false} ) let installed = $install_result.installed let installation_failed = $install_result.failed # Deploy additional global resources only if any binaries were installed if ($installed | length) > 0 { # deploy_leptos_dashboard # Deploy default configuration (may already exist per-binary) print "" print "๐Ÿ“‹ Deploying global configuration..." let config_dir = ($env.HOME + "/.config/syntaxis") ^mkdir -p $config_dir ^mkdir -p $"($config_dir)/features" print $" โœ… Ensured config directory: ($config_dir)" register_installations $installed } # Summary print "" print "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" let installed_count = ($installed | length) let total_count = ($binaries_to_install | length) print $"โœจ Summary: ($installed_count)/($total_count) binaries installed" if $installation_failed { print "โŒ Installation stopped due to failure" } else if $installed_count == $total_count { print "๐ŸŽ‰ All binaries installed successfully!" } else if $installed_count > 0 { let failed = ($total_count - $installed_count) print $"โš ๏ธ ($failed) installation(s) failed" } else { print "โŒ No binaries were installed" } print "" if ($installed | length) > 0 { print "Next steps:" $installed | each { |binary| print $" ($binary.crate_name) --help # Get help" } } print "" }