13 KiB
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
[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 distributionsreason(optional): Documentation of why plugins are excluded
Adding New Exclusions:
[distribution]
excluded_plugins = [
"nu_plugin_example",
"nu_plugin_new_reference" # Add here
]
Implementation
1. Collection System (scripts/collect_full_binaries.nu)
Helper Function
def get_excluded_plugins []: nothing -> list<string> {
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
def get_workspace_plugins_info [platform: string, use_release: bool, profile: string]: nothing -> list<record> {
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
def get_custom_plugins_info [platform: string, use_release: bool, profile: string]: nothing -> list<record> {
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
def get_excluded_plugins_dist []: nothing -> list<string> {
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
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:
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:
# 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:
[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:
[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:
[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
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
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
| 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
- Update Registry:
# Edit: etc/plugin_registry.toml
[distribution]
excluded_plugins = [
"nu_plugin_example",
"nu_plugin_new_ref" # ← Add here
]
- Optional: Update Default Config:
# Edit: scripts/templates/default_config.nu
# Add comment explaining why it's excluded
- Test:
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
- Update Registry:
# Edit: etc/plugin_registry.toml
[distribution]
excluded_plugins = [
"nu_plugin_example" # ← Removed
]
- Test:
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
-
Profile-Based Exclusion: Support different exclusion lists per distribution profile
[distribution.profiles] enterprise = [...] minimal = [...] -
Conditional Compilation: Exclude from build based on feature flags
#[cfg(feature = "include_example")] pub mod example; -
Deprecation Timeline: Mark plugins as deprecated with removal date
[distribution.deprecated] "nu_plugin_old" = "2025-12-31" # Will be removed after date -
Exclusion Reasoning: Rich metadata about why plugins are excluded
[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