#!/usr/bin/env nu # Core distribution preparation tool - prepares core files for distribution # # Prepares: # - Nushell core libraries and modules # - Configuration templates and schemas # - CLI wrapper scripts and entry points # - Plugin definitions and extensions # - Template system and utilities use std log def main [ --source-root: string = "" # Source root directory (auto-detected if empty) --output-dir: string = "dist/core" # Output directory for core distribution --include-dev: bool = false # Include development files and tools --minify-scripts: bool = false # Minify Nushell scripts (remove comments/whitespace) --validate-syntax: bool = true # Validate all Nushell scripts --generate-index: bool = true # Generate module index files --bundle-plugins: bool = true # Bundle plugin definitions --create-stubs: bool = false # Create type definition stubs --verbose: bool = false # Enable verbose logging ] -> record { let repo_root = if $source_root == "" { ($env.PWD | path dirname | path dirname | path dirname) } else { ($source_root | path expand) } let core_config = { source_root: $repo_root output_dir: ($output_dir | path expand) include_dev: $include_dev minify_scripts: $minify_scripts validate_syntax: $validate_syntax generate_index: $generate_index bundle_plugins: $bundle_plugins create_stubs: $create_stubs verbose: $verbose } log info $"Starting core distribution preparation with config: ($core_config)" # Ensure output directory exists mkdir ($core_config.output_dir) let preparation_results = [] try { # Phase 1: Discover and validate core components let discovery_result = discover_core_components $core_config let preparation_results = ($preparation_results | append { phase: "discovery", result: $discovery_result }) if $discovery_result.status != "success" { log error $"Core component discovery failed: ($discovery_result.reason)" exit 1 } # Phase 2: Prepare core libraries let libraries_result = prepare_core_libraries $core_config $discovery_result let preparation_results = ($preparation_results | append { phase: "libraries", result: $libraries_result }) # Phase 3: Prepare CLI components let cli_result = prepare_cli_components $core_config $discovery_result let preparation_results = ($preparation_results | append { phase: "cli", result: $cli_result }) # Phase 4: Prepare configuration system let config_result = prepare_configuration_system $core_config $discovery_result let preparation_results = ($preparation_results | append { phase: "configuration", result: $config_result }) # Phase 5: Bundle plugins and extensions let plugins_result = if $core_config.bundle_plugins { prepare_plugin_system $core_config $discovery_result } else { { status: "skipped", reason: "plugin bundling disabled" } } let preparation_results = ($preparation_results | append { phase: "plugins", result: $plugins_result }) # Phase 6: Generate indexes and metadata let index_result = if $core_config.generate_index { generate_core_indexes $core_config $preparation_results } else { { status: "skipped", reason: "index generation disabled" } } let preparation_results = ($preparation_results | append { phase: "indexes", result: $index_result }) # Phase 7: Create distribution metadata let metadata_result = create_core_metadata $core_config $preparation_results let preparation_results = ($preparation_results | append { phase: "metadata", result: $metadata_result }) let summary = { source_root: $core_config.source_root output_directory: $core_config.output_dir successful_phases: ($preparation_results | where {|r| $r.result.status == "success"} | length) total_phases: ($preparation_results | length) core_files_prepared: ($preparation_results | get result.files_processed | math sum) total_size: (get_directory_size $core_config.output_dir) core_config: $core_config phases: $preparation_results } log info $"Core distribution preparation completed successfully - ($summary.core_files_prepared) files prepared" return $summary } catch {|err| log error $"Core distribution preparation failed: ($err.msg)" exit 1 } } # Discover core components in the source tree def discover_core_components [core_config: record] -> record { log info "Discovering core components..." let start_time = (date now) try { # Define core component locations let core_locations = { provisioning_cli: ($core_config.source_root | path join "provisioning" "core" "nulib" "provisioning") core_libraries: ($core_config.source_root | path join "provisioning" "core" "nulib" "lib_provisioning") workflows: ($core_config.source_root | path join "provisioning" "core" "nulib" "workflows") servers: ($core_config.source_root | path join "provisioning" "core" "nulib" "servers") extensions: ($core_config.source_root | path join "provisioning" "extensions") config_system: ($core_config.source_root | path join "provisioning" "config.defaults.toml") } # Discover Nushell files let nu_files = [] for location_name in ($core_locations | columns) { let location_path = ($core_locations | get $location_name) if ($location_path | path exists) { let found_files = (find $location_path -name "*.nu" -type f) let nu_files = ($nu_files | append ($found_files | each {|file| { path: $file component: $location_name relative_path: ($file | str replace $core_config.source_root "") size: (ls $file | get 0.size) } })) } } # Discover configuration files let config_files = (find ($core_config.source_root | path join "provisioning") -name "*.toml" -type f) # Discover template files let template_files = (find ($core_config.source_root | path join "provisioning") -name "*.j2" -o -name "*.template" -type f) # Validate critical components exist let missing_components = [] let critical_components = ["provisioning_cli", "core_libraries"] for component in $critical_components { let component_path = ($core_locations | get $component) if not ($component_path | path exists) { let missing_components = ($missing_components | append $component) } } if ($missing_components | length) > 0 { return { status: "failed" reason: $"Missing critical components: ($missing_components | str join ', ')" duration: ((date now) - $start_time) } } { status: "success" core_locations: $core_locations nu_files: $nu_files config_files: $config_files template_files: $template_files total_nu_files: ($nu_files | length) total_config_files: ($config_files | length) total_template_files: ($template_files | length) duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Prepare core libraries for distribution def prepare_core_libraries [ core_config: record discovery_result: record ] -> record { log info "Preparing core libraries..." let start_time = (date now) let lib_output_dir = ($core_config.output_dir | path join "lib") mkdir $lib_output_dir try { let mut prepared_files = [] let mut validation_errors = [] # Process core library files let core_lib_files = ($discovery_result.nu_files | where component == "core_libraries") for file_info in $core_lib_files { let processing_result = process_nushell_file $file_info $core_config $lib_output_dir if $processing_result.status == "success" { $prepared_files = ($prepared_files | append $processing_result) } else { $validation_errors = ($validation_errors | append $processing_result.errors) } } # Process workflow files let workflow_files = ($discovery_result.nu_files | where component == "workflows") for file_info in $workflow_files { let processing_result = process_nushell_file $file_info $core_config ($lib_output_dir | path join "workflows") if $processing_result.status == "success" { $prepared_files = ($prepared_files | append $processing_result) } else { $validation_errors = ($validation_errors | append $processing_result.errors) } } # Process server management files let server_files = ($discovery_result.nu_files | where component == "servers") for file_info in $server_files { let processing_result = process_nushell_file $file_info $core_config ($lib_output_dir | path join "servers") if $processing_result.status == "success" { $prepared_files = ($prepared_files | append $processing_result) } else { $validation_errors = ($validation_errors | append $processing_result.errors) } } let status = if ($validation_errors | length) > 0 { "partial" } else { "success" } { status: $status files_processed: ($prepared_files | length) validation_errors: ($validation_errors | length) prepared_files: $prepared_files errors: $validation_errors lib_output_dir: $lib_output_dir duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Prepare CLI components for distribution def prepare_cli_components [ core_config: record discovery_result: record ] -> record { log info "Preparing CLI components..." let start_time = (date now) let cli_output_dir = ($core_config.output_dir | path join "bin") mkdir $cli_output_dir try { # Process main provisioning CLI let cli_location = ($discovery_result.core_locations.provisioning_cli) if ($cli_location | path exists) { let target_cli = ($cli_output_dir | path join "provisioning") # Copy CLI script cp $cli_location $target_cli # Make executable on Unix-like systems if $nu.os-info.name != "windows" { chmod +x $target_cli } # Create wrapper scripts for different environments create_cli_wrappers $cli_output_dir $core_config { status: "success" cli_prepared: $target_cli wrappers_created: 3 # Unix, Windows, Development files_processed: 4 duration: ((date now) - $start_time) } } else { { status: "failed" reason: "provisioning CLI not found" duration: ((date now) - $start_time) } } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Create CLI wrapper scripts def create_cli_wrappers [cli_output_dir: string, core_config: record] { # Unix shell wrapper let unix_wrapper = $"#!/bin/bash # Provisioning System CLI Wrapper # This wrapper sets up the environment and executes the main CLI # Set environment variables export PROVISIONING_HOME=\"$(dirname \"$(readlink -f \"$0\")\")/../\" export PROVISIONING_LIB=\"$PROVISIONING_HOME/lib\" export PROVISIONING_CONFIG=\"$PROVISIONING_HOME/../config\" # Execute the main CLI with proper library path exec nu \"$PROVISIONING_HOME/bin/provisioning\" \"$@\" " $unix_wrapper | save ($cli_output_dir | path join "provisioning.sh") chmod +x ($cli_output_dir | path join "provisioning.sh") # Windows batch wrapper let windows_wrapper = $"@echo off REM Provisioning System CLI Wrapper REM This wrapper sets up the environment and executes the main CLI REM Set environment variables set \"PROVISIONING_HOME=%~dp0..\\\" set \"PROVISIONING_LIB=%PROVISIONING_HOME%lib\" set \"PROVISIONING_CONFIG=%PROVISIONING_HOME%..\\config\" REM Execute the main CLI nu \"%PROVISIONING_HOME%bin\\provisioning\" %* " $windows_wrapper | save ($cli_output_dir | path join "provisioning.bat") # Development wrapper (preserves source paths) let dev_wrapper = $"#!/usr/bin/env nu # Provisioning Development CLI Wrapper # This wrapper is used during development with source paths # Set development paths $env.PROVISIONING_HOME = ($env.PWD | path dirname) $env.PROVISIONING_LIB = ($env.PROVISIONING_HOME | path join \"lib\") $env.PROVISIONING_CONFIG = ($env.PROVISIONING_HOME | path join \"../config\") $env.PROVISIONING_DEV = true # Execute the main CLI source ($env.PROVISIONING_HOME | path join \"bin\" \"provisioning\") " $dev_wrapper | save ($cli_output_dir | path join "provisioning-dev.nu") chmod +x ($cli_output_dir | path join "provisioning-dev.nu") } # Prepare configuration system for distribution def prepare_configuration_system [ core_config: record discovery_result: record ] -> record { log info "Preparing configuration system..." let start_time = (date now) let config_output_dir = ($core_config.output_dir | path join "config") mkdir $config_output_dir try { let mut processed_configs = [] # Process configuration files for config_file in $discovery_result.config_files { let config_name = ($config_file | path basename) let target_config = ($config_output_dir | path join $config_name) # Process configuration file (validate and optionally minify) let processing_result = process_config_file $config_file $target_config $core_config if $processing_result.status == "success" { $processed_configs = ($processed_configs | append $processing_result) } } # Create configuration templates create_config_templates $config_output_dir $core_config { status: "success" files_processed: ($processed_configs | length) config_files: $processed_configs templates_created: 3 # user, dev, prod config_output_dir: $config_output_dir duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Process a single Nushell file def process_nushell_file [ file_info: record core_config: record output_dir: string ] -> record { let relative_path = ($file_info.relative_path | str trim-left "/") let target_file = ($output_dir | path join ($file_info.path | path basename)) # Ensure target directory exists mkdir ($target_file | path dirname) try { let content = (open $file_info.path --raw) # Validate syntax if requested if $core_config.validate_syntax { let validation_result = validate_nu_syntax $file_info.path $content if $validation_result.status != "success" { return { status: "failed" file: $file_info.path target: $target_file errors: $validation_result.errors } } } # Process content based on configuration let processed_content = if $core_config.minify_scripts { minify_nushell_script $content $core_config } else { $content } # Filter development code if not including dev files let final_content = if not $core_config.include_dev { filter_development_code $processed_content } else { $processed_content } # Save processed file $final_content | save $target_file { status: "success" source: $file_info.path target: $target_file size: ($final_content | str length) component: $file_info.component } } catch {|err| { status: "failed" file: $file_info.path target: $target_file errors: [$err.msg] } } } # Validate Nushell syntax def validate_nu_syntax [file_path: string, content: string] -> record { try { # Use Nushell's built-in syntax checking nu --check $file_path { status: "success" file: $file_path } } catch {|err| { status: "failed" file: $file_path errors: [$err.msg] } } } # Minify Nushell script by removing comments and extra whitespace def minify_nushell_script [content: string, core_config: record] -> string { if not $core_config.minify_scripts { return $content } # Simple minification - remove comment lines and extra whitespace let lines = ($content | lines) let minified_lines = $lines | each {|line| let trimmed = ($line | str trim) # Keep non-comment lines and preserve some important comments if ($trimmed | str starts-with "#") { if ($trimmed | str contains "!/usr/bin") or ($trimmed | str contains "!/bin/") { $line # Keep shebang lines } else { "" # Remove comment lines } } else { $trimmed # Keep code lines, trimmed } } | where $it != "" return ($minified_lines | str join "\n") } # Filter out development-specific code def filter_development_code [content: string] -> string { let lines = ($content | lines) let mut filtered_lines = [] let mut in_dev_block = false for line in $lines { # Check for development block markers if ($line | str contains "# DEV_START") or ($line | str contains "# DEBUG_START") { $in_dev_block = true continue } if ($line | str contains "# DEV_END") or ($line | str contains "# DEBUG_END") { $in_dev_block = false continue } # Skip lines in development blocks if $in_dev_block { continue } # Skip individual development lines if ($line | str contains "# DEV_ONLY") or ($line | str contains "# DEBUG") { continue } $filtered_lines = ($filtered_lines | append $line) } return ($filtered_lines | str join "\n") } # Process configuration file def process_config_file [source_file: string, target_file: string, core_config: record] -> record { try { # Validate TOML syntax let config_data = (open $source_file) # Copy file (could add processing here if needed) cp $source_file $target_file { status: "success" source: $source_file target: $target_file validated: true } } catch {|err| { status: "failed" source: $source_file target: $target_file error: $err.msg } } } # Create configuration templates def create_config_templates [config_output_dir: string, core_config: record] { # User configuration template let user_template = $"# User Configuration Template # Copy this file to config.user.toml and customize as needed # User-specific paths and preferences [paths] # Override default paths if needed # home = \"/custom/path\" [user] name = \"Your Name\" email = \"your.email@example.com\" # Development settings [dev] debug = false verbose = false " $user_template | save ($config_output_dir | path join "config.user.toml.template") # Development configuration template let dev_template = $"# Development Configuration Template # Copy this file to config.dev.toml for development environment [general] environment = \"development\" debug = true log_level = \"debug\" [paths] cache_ttl = 60 # Short cache for development " $dev_template | save ($config_output_dir | path join "config.dev.toml.template") # Production configuration template let prod_template = $"# Production Configuration Template # Copy this file to config.prod.toml for production environment [general] environment = \"production\" debug = false log_level = \"info\" [security] # Enable security features for production strict_mode = true " $prod_template | save ($config_output_dir | path join "config.prod.toml.template") } # Prepare plugin system for distribution def prepare_plugin_system [ core_config: record discovery_result: record ] -> record { log info "Preparing plugin system..." let start_time = (date now) let plugins_output_dir = ($core_config.output_dir | path join "plugins") mkdir $plugins_output_dir try { # Process extension files let extension_files = ($discovery_result.nu_files | where component == "extensions") let mut processed_extensions = [] for file_info in $extension_files { let processing_result = process_nushell_file $file_info $core_config $plugins_output_dir if $processing_result.status == "success" { $processed_extensions = ($processed_extensions | append $processing_result) } } # Create plugin registry let plugin_registry = create_plugin_registry $processed_extensions $core_config { status: "success" files_processed: ($processed_extensions | length) extensions: $processed_extensions plugin_registry: $plugin_registry plugins_output_dir: $plugins_output_dir duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Create plugin registry def create_plugin_registry [processed_extensions: list, core_config: record] -> record { let plugin_registry = { version: "1.0.0" plugins: ($processed_extensions | each {|ext| { name: ($ext.target | path basename | str replace ".nu" "") path: ($ext.target | path basename) component: $ext.component size: $ext.size } }) created_at: (date now) } let registry_file = ($core_config.output_dir | path join "plugins" "registry.json") $plugin_registry | to json | save $registry_file return $plugin_registry } # Generate core indexes and module listings def generate_core_indexes [ core_config: record preparation_results: list ] -> record { log info "Generating core indexes..." let start_time = (date now) try { # Generate library index let lib_result = ($preparation_results | where {|r| $r.phase == "libraries"} | get 0.result) let lib_index = generate_library_index $lib_result.prepared_files $core_config # Generate CLI index let cli_result = ($preparation_results | where {|r| $r.phase == "cli"} | get 0.result) let cli_index = generate_cli_index $cli_result $core_config # Generate main index let main_index = generate_main_index $preparation_results $core_config { status: "success" indexes_generated: ["library", "cli", "main"] library_index: $lib_index cli_index: $cli_index main_index: $main_index duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Generate library index def generate_library_index [prepared_files: list, core_config: record] -> string { let index_content = $"# Core Library Index This file provides an index of all core library modules included in this distribution. ## Library Modules " let modules = ($prepared_files | group-by component) let mut full_content = [$index_content] for component in ($modules | columns) { let component_files = ($modules | get $component) $full_content = ($full_content | append $"### ($component | str title-case)") $full_content = ($full_content | append "") for file in $component_files { let module_name = ($file.target | path basename | str replace ".nu" "") $full_content = ($full_content | append $"- **($module_name)**: ($file.target)") } $full_content = ($full_content | append "") } let content = ($full_content | str join "\n") $content | save ($core_config.output_dir | path join "lib" "INDEX.md") return $content } # Generate CLI index def generate_cli_index [cli_result: record, core_config: record] -> string { let cli_index = $"# CLI Components Index ## Main CLI - **provisioning**: Main provisioning CLI entry point ## Wrapper Scripts - **provisioning.sh**: Unix/Linux shell wrapper - **provisioning.bat**: Windows batch wrapper - **provisioning-dev.nu**: Development wrapper ## Usage The main CLI provides access to all provisioning functionality: ``` ./bin/provisioning help ``` Generated: (date now | format date '%Y-%m-%d %H:%M:%S') " $cli_index | save ($core_config.output_dir | path join "bin" "INDEX.md") return $cli_index } # Generate main index def generate_main_index [preparation_results: list, core_config: record] -> string { let successful_phases = ($preparation_results | where {|r| $r.result.status == "success"}) let total_files = ($successful_phases | get result.files_processed | math sum) let main_index = $"# Core Distribution Index This distribution contains the core components of the Provisioning System. ## Directory Structure - **bin/**: CLI executables and wrapper scripts - **lib/**: Core Nushell libraries and modules - **config/**: Configuration files and templates - **plugins/**: Extensions and plugin definitions ## Statistics - **Total files processed**: ($total_files) - **Successful phases**: ($successful_phases | length) - **Generated**: (date now | format date '%Y-%m-%d %H:%M:%S') ## Usage To use this core distribution: 1. Add bin/ to your PATH 2. Set PROVISIONING_LIB to point to lib/ 3. Configure PROVISIONING_CONFIG to point to config/ 4. Run `provisioning help` to get started For more information, see the documentation in each subdirectory. " $main_index | save ($core_config.output_dir | path join "INDEX.md") return $main_index } # Create core metadata def create_core_metadata [ core_config: record preparation_results: list ] -> record { log info "Creating core metadata..." let start_time = (date now) try { let metadata = { name: "provisioning-core" version: (detect_version $core_config.source_root) type: "core-distribution" created_at: (date now) created_by: "core-distribution-tool" source_root: $core_config.source_root configuration: $core_config preparation_phases: ($preparation_results | each {|r| { phase: $r.phase status: $r.result.status files_processed: (if "files_processed" in ($r.result | columns) { $r.result.files_processed } else { 0 }) duration: (if "duration" in ($r.result | columns) { $r.result.duration } else { 0 }) } }) statistics: { total_phases: ($preparation_results | length) successful_phases: ($preparation_results | where {|r| $r.result.status == "success"} | length) total_files: ($preparation_results | get result.files_processed | math sum) distribution_size: (get_directory_size $core_config.output_dir) } } let metadata_file = ($core_config.output_dir | path join "core-metadata.json") $metadata | to json | save $metadata_file { status: "success" metadata_file: $metadata_file metadata: $metadata duration: ((date now) - $start_time) } } catch {|err| { status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Detect version from git or other sources def detect_version [repo_root: string] -> string { cd $repo_root try { let git_version = (git describe --tags --always --dirty 2>/dev/null | str trim) if $git_version != "" { return ($git_version | str replace "^v" "") } return $"dev-(date now | format date '%Y%m%d')" } catch { return "unknown" } } # Get directory size helper def get_directory_size [dir: string] -> int { if not ($dir | path exists) { return 0 } try { find $dir -type f | each {|file| ls $file | get 0.size } | math sum | if $in == null { 0 } else { $in } } catch { 0 } } # Show core distribution status def "main status" [] { let repo_root = ($env.PWD | path dirname | path dirname | path dirname) let version = (detect_version $repo_root) # Check for core components let provisioning_cli = ($repo_root | path join "provisioning" "core" "nulib" "provisioning") let core_libraries = ($repo_root | path join "provisioning" "core" "nulib" "lib_provisioning") { repository: $repo_root version: $version core_components: { provisioning_cli: ($provisioning_cli | path exists) core_libraries: ($core_libraries | path exists) } nu_files_found: (try { find $repo_root -name "*.nu" -type f | length } catch { 0 }) config_files_found: (try { find ($repo_root | path join "provisioning") -name "*.toml" -type f | length } catch { 0 }) ready_for_distribution: (($provisioning_cli | path exists) and ($core_libraries | path exists)) } } # Quick core preparation with minimal options def "main quick" [output_dir: string = "dist/core"] { main --output-dir $output_dir --validate-syntax --generate-index }