nushell-plugins/docs/architecture/PLUGIN_EXCLUSION_SYSTEM.md

525 lines
13 KiB
Markdown
Raw Normal View History

# 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<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
```nu
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
```nu
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
```nu
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
```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