525 lines
13 KiB
Markdown
525 lines
13 KiB
Markdown
|
|
# 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
|