# Plugin Exclusion System (v1.0.0) ## Overview The Plugin Exclusion System is a configuration-driven mechanism that allows selective exclusion of plugins from distributions, collections, and installations while maintaining full availability for development, testing, and reference purposes. **Status**: Implemented (2025-12-03) **Purpose**: Exclude reference/documentation plugins (like `nu_plugin_example`) from end-user distributions --- ## Architecture ### Design Principle **Single Source of Truth**: All plugin exclusions are centrally defined in `etc/plugin_registry.toml`, ensuring consistency across all distribution-related operations. ``` plugin_registry.toml (source of truth) ↓ ┌───┴────────────────────────────────────┐ ↓ ↓ collect_full_binaries.nu create_distribution_packages.nu (collection operations) (packaging operations) ↓ ↓ distribution/ directories bin_archives/ packages ``` ### Configuration #### Plugin Registry Entry **File**: `etc/plugin_registry.toml` ```toml [distribution] excluded_plugins = [ "nu_plugin_example" ] reason = "Reference/documentation plugin - excluded from distributions, installations, and collections. Still included in build and test for validation." ``` **Structure**: - `excluded_plugins` (required): List of plugin names to exclude from distributions - `reason` (optional): Documentation of why plugins are excluded **Adding New Exclusions**: ```toml [distribution] excluded_plugins = [ "nu_plugin_example", "nu_plugin_new_reference" # Add here ] ``` --- ## Implementation ### 1. Collection System (`scripts/collect_full_binaries.nu`) #### Helper Function ```nu def get_excluded_plugins []: nothing -> list { try { let registry_path = "./etc/plugin_registry.toml" if not ($registry_path | path exists) { return [] } let registry_content = open $registry_path let excluded = try { $registry_content.distribution.excluded_plugins } catch { [] } return $excluded } catch { return [] } } ``` **Key Features**: - Reads exclusion list from registry - Graceful error handling (returns empty list if registry missing/malformed) - Non-blocking (collection continues even if registry unavailable) #### Workspace Plugins Filtering ```nu def get_workspace_plugins_info [platform: string, use_release: bool, profile: string]: nothing -> list { let excluded_plugins = (get_excluded_plugins) let workspace_plugins = [ "nu_plugin_custom_values" "nu_plugin_example" "nu_plugin_formats" # ... other plugins ] # Filter out excluded plugins let available_plugins = $workspace_plugins | where { |p| $p not-in $excluded_plugins } # Process only available plugins for plugin in $available_plugins { # ... collection logic } } ``` #### Custom Plugins Filtering ```nu def get_custom_plugins_info [platform: string, use_release: bool, profile: string]: nothing -> list { let excluded_plugins = (get_excluded_plugins) let plugin_dirs = (glob $"nu_plugin_*") | where ($it | path type) == "dir" | where ($it | path basename) != "nushell" | where { |p| ($p | path basename) not-in $excluded_plugins } # Filter excluded | each { |p| $p | path basename } # Process remaining plugins } ``` ### 2. Packaging System (`scripts/create_distribution_packages.nu`) #### Helper Function ```nu def get_excluded_plugins_dist []: nothing -> list { try { let registry_path = "./etc/plugin_registry.toml" if not ($registry_path | path exists) { return [] } let registry_content = open $registry_path let excluded = try { $registry_content.distribution.excluded_plugins } catch { [] } return $excluded } catch { return [] } } ``` #### Plugin Components Filtering ```nu def get_plugin_components [platform: string, version: string] { let extension = get_binary_extension $platform let excluded_plugins = (get_excluded_plugins_dist) # Get custom plugins - skip excluded ones let custom_plugin_binaries = ( glob "nu_plugin_*" | where ($it | path type) == "dir" | each {|plugin_dir| let plugin_name = ($plugin_dir | path basename) if $plugin_name in $excluded_plugins { null } else { # ... process plugin } } | compact ) # Get workspace plugins - filter excluded let workspace_plugins = [ "nu_plugin_custom_values" "nu_plugin_example" # ... other plugins ] let workspace_plugin_binaries = ( $workspace_plugins | where { |p| $p not-in $excluded_plugins } # Filter excluded | each {|plugin_name| # ... process plugin } | compact ) { binaries: ($custom_plugin_binaries | append $workspace_plugin_binaries) } } ``` ### 3. Installation Configuration (`scripts/templates/default_config.nu`) #### Auto-load Plugin List **Before**: ```nu let plugin_binaries = [ "nu_plugin_clipboard" "nu_plugin_desktop_notifications" "nu_plugin_hashes" "nu_plugin_highlight" "nu_plugin_image" "nu_plugin_kcl" "nu_plugin_tera" "nu_plugin_custom_values" "nu_plugin_example" # ❌ Would be auto-loaded "nu_plugin_formats" # ... ] ``` **After**: ```nu # Auto-load common plugins if they're available # NOTE: nu_plugin_example is excluded from distributions - it's for reference and development only let plugin_binaries = [ "nu_plugin_clipboard" "nu_plugin_desktop_notifications" "nu_plugin_hashes" "nu_plugin_highlight" "nu_plugin_image" "nu_plugin_kcl" "nu_plugin_tera" "nu_plugin_custom_values" "nu_plugin_formats" # ✅ Auto-loaded (example removed) # ... ] ``` --- ## Behavior Matrix | Operation | Excluded Plugin | Included Plugin | |-----------|-----------------|-----------------| | `just build` | ✅ Built | ✅ Built | | `just build-nushell` | ✅ Built | ✅ Built | | `just test` | ✅ Tested | ✅ Tested | | `just collect` | ❌ Excluded | ✅ Collected | | `just collect-full` | ❌ Excluded | ✅ Collected | | `just pack` | ❌ Excluded | ✅ Packaged | | `just pack-full` | ❌ Excluded | ✅ Packaged | | Distribution Installation | ❌ Not auto-loaded | ✅ Auto-loaded | | Manual Reference Use | ✅ Available | ✅ Available | --- ## Use Cases ### Use Case 1: Reference Plugin **Scenario**: Plugin serves as a template/documentation reference but shouldn't ship with distributions **Configuration**: ```toml [distribution] excluded_plugins = [ "nu_plugin_example" ] reason = "Template for plugin developers. Not intended for end users." ``` **Result**: - Developers can still use it: `./nushell/target/release/nu_plugin_example` - End-user distributions don't include it - Documentation can reference it as a learning resource ### Use Case 2: Experimental Plugin **Scenario**: Plugin is under development and not yet stable **Configuration**: ```toml [distribution] excluded_plugins = [ "nu_plugin_example", "nu_plugin_experimental" ] reason = "Experimental features. Stable once API is finalized." ``` **Result**: - Can be tested internally - Not distributed to users until ready - Easily re-enabled by removing from list ### Use Case 3: Conditional Exclusion **Scenario**: Plugin should only be excluded for specific use cases **Implementation Note**: The current system excludes globally. For conditional exclusion, extend the registry: ```toml [distribution] excluded_plugins = ["nu_plugin_example"] [distribution.profiles] enterprise = ["nu_plugin_example", "nu_plugin_dev_tools"] minimal = ["nu_plugin_example", "nu_plugin_kcl", "nu_plugin_tera"] ``` Then update scripts to support profile-based filtering. --- ## Error Handling ### Scenario: Registry File Missing **Behavior**: Scripts return empty exclusion list, all plugins included ```nu if not ($registry_path | path exists) { return [] # No exclusions } ``` **Result**: Safe degradation - system works without registry ### Scenario: Registry Parse Error **Behavior**: Catches exception, returns empty list ```nu let excluded = try { $registry_content.distribution.excluded_plugins } catch { [] # If key missing or malformed } ``` **Result**: Malformed registry doesn't break distribution process ### Scenario: Invalid Plugin Name **Behavior**: Non-existent plugins in exclusion list are silently skipped ```nu | where { |p| $p not-in $excluded_plugins } # No match = included ``` **Result**: Future-proofs against plugin renames or removals --- ## Integration Points ### 1. Collection Workflow ``` just collect ↓ collect_full_binaries.nu main ↓ get_excluded_plugins() → registry.toml ↓ get_workspace_plugins_info() → [filtered list] get_custom_plugins_info() → [filtered list] ↓ distribution/ (without excluded plugins) ``` ### 2. Packaging Workflow ``` just pack-full ↓ create_distribution_packages.nu main ↓ get_excluded_plugins_dist() → registry.toml ↓ get_plugin_components() → [filtered list] ↓ bin_archives/ (without excluded plugins) ``` ### 3. Build Workflow ``` just build (unchanged) ↓ build_all.nu ↓ cargo build (all plugins including excluded) ↓ target/release/ (includes ALL plugins) ``` ### 4. Installation Workflow ``` distribution/platform/ ↓ default_config.nu (filters excluded at config level) ↓ User's Nushell config (excluded plugins not auto-loaded) ``` --- ## Maintenance ### Adding a Plugin to Exclusion List 1. **Update Registry**: ```bash # Edit: etc/plugin_registry.toml [distribution] excluded_plugins = [ "nu_plugin_example", "nu_plugin_new_ref" # ← Add here ] ``` 2. **Optional: Update Default Config**: ```bash # Edit: scripts/templates/default_config.nu # Add comment explaining why it's excluded ``` 3. **Test**: ```bash just collect # Should exclude both plugins just pack-full # Should package without both just build # Should still build both ``` ### Removing a Plugin from Exclusion List 1. **Update Registry**: ```bash # Edit: etc/plugin_registry.toml [distribution] excluded_plugins = [ "nu_plugin_example" # ← Removed ] ``` 2. **Test**: ```bash just collect # Should now include it just pack-full # Should now package it ``` --- ## Files Modified | File | Changes | Type | |------|---------|------| | `etc/plugin_registry.toml` | Added `[distribution]` section | Config | | `scripts/collect_full_binaries.nu` | Added `get_excluded_plugins()`, updated workspace/custom filtering | Feature | | `scripts/create_distribution_packages.nu` | Added `get_excluded_plugins_dist()`, updated component filtering | Feature | | `scripts/templates/default_config.nu` | Removed excluded plugin from auto-load list | Config | --- ## Performance Impact - **Collection**: Negligible (single registry read, O(n) filtering where n = excluded count) - **Packaging**: Negligible (same as collection) - **Build**: None (excluded plugins still built) - **Installation**: None (config parsing is same cost) --- ## Future Enhancements 1. **Profile-Based Exclusion**: Support different exclusion lists per distribution profile ```toml [distribution.profiles] enterprise = [...] minimal = [...] ``` 2. **Conditional Compilation**: Exclude from build based on feature flags ```rust #[cfg(feature = "include_example")] pub mod example; ``` 3. **Deprecation Timeline**: Mark plugins as deprecated with removal date ```toml [distribution.deprecated] "nu_plugin_old" = "2025-12-31" # Will be removed after date ``` 4. **Exclusion Reasoning**: Rich metadata about why plugins are excluded ```toml [distribution.exclusions."nu_plugin_example"] reason = "reference_plugin" since_version = "0.109.0" target_inclusion = "never" # or "1.0.0" ``` --- ## References - **Registry**: `etc/plugin_registry.toml` - **Collection Scripts**: `scripts/collect_full_binaries.nu` - **Packaging Scripts**: `scripts/create_distribution_packages.nu` - **Configuration**: `scripts/templates/default_config.nu` - **Build System**: `justfile`, `justfiles/build.just`, `scripts/build_all.nu` --- ## Testing ### Verification Checklist - [ ] Registry reads correctly: `nu -c "open ./etc/plugin_registry.toml | get distribution.excluded_plugins"` - [ ] Collection excludes: `just collect && ls distribution/ | grep example` (should be empty) - [ ] Packaging excludes: `just pack-full && tar -tzf bin_archives/*.tar.gz | grep example` (should be empty) - [ ] Build includes: `just build-nushell && ls nushell/target/release/ | grep example` (should exist) - [ ] Config doesn't auto-load: `grep nu_plugin_example scripts/templates/default_config.nu` (should not appear in plugin_binaries list) --- **Version**: 1.0.0 **Last Updated**: 2025-12-03 **Status**: Stable