426 lines
11 KiB
Markdown
426 lines
11 KiB
Markdown
|
|
# ADR-001: Plugin Exclusion System
|
||
|
|
|
||
|
|
**Date**: 2025-12-03
|
||
|
|
**Status**: Accepted ✅
|
||
|
|
**Decision**: Implement a centralized, configuration-driven plugin exclusion system
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
The nushell-plugins repository builds and distributes multiple plugins, including some that serve as reference implementations or documentation (e.g., `nu_plugin_example`). These reference plugins are valuable for developers and maintainers but should not be included in end-user distributions.
|
||
|
|
|
||
|
|
### Problem Statement
|
||
|
|
|
||
|
|
**Without exclusion system**:
|
||
|
|
- Reference plugins ship with every distribution (increases download size)
|
||
|
|
- Users install plugins they don't need
|
||
|
|
- No clear mechanism to control distribution contents
|
||
|
|
- Adding/removing exclusions requires manual changes in multiple scripts
|
||
|
|
|
||
|
|
**Key Requirements**:
|
||
|
|
1. Reference plugins must still be built (for testing and reference)
|
||
|
|
2. Reference plugins must be excluded from distributions
|
||
|
|
3. Exclusion list must be maintainable and centralized
|
||
|
|
4. System must work without breaking existing workflows
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Decision
|
||
|
|
|
||
|
|
**Implement a Configuration-Driven Plugin Exclusion System** with:
|
||
|
|
|
||
|
|
1. **Central Registry** (`etc/plugin_registry.toml`) - single source of truth
|
||
|
|
2. **Collection Filtering** (`collect_full_binaries.nu`) - filters during binary collection
|
||
|
|
3. **Packaging Filtering** (`create_distribution_packages.nu`) - filters during package creation
|
||
|
|
4. **Configuration Exclusion** (`default_config.nu`) - manual config-level filtering
|
||
|
|
5. **No Build Changes** - all plugins still built, only excluded from distribution
|
||
|
|
|
||
|
|
### Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────┐
|
||
|
|
│ plugin_registry.toml │
|
||
|
|
│ ├─ [distribution] │
|
||
|
|
│ │ └─ excluded_plugins: [...] │
|
||
|
|
│ └─ (source of truth) │
|
||
|
|
└────────────┬────────────────────┘
|
||
|
|
│
|
||
|
|
┌───────┴────────┐
|
||
|
|
▼ ▼
|
||
|
|
Collection Packaging
|
||
|
|
(collect_full_ (create_distribution_
|
||
|
|
binaries.nu) packages.nu)
|
||
|
|
│ │
|
||
|
|
▼ ▼
|
||
|
|
distribution/ bin_archives/
|
||
|
|
(without (without
|
||
|
|
excluded) excluded)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Implementation Details
|
||
|
|
|
||
|
|
### 1. Registry-Based Configuration
|
||
|
|
|
||
|
|
**File**: `etc/plugin_registry.toml`
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[distribution]
|
||
|
|
excluded_plugins = [
|
||
|
|
"nu_plugin_example"
|
||
|
|
]
|
||
|
|
reason = "Reference/documentation plugin"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- ✅ Single file, easy to maintain
|
||
|
|
- ✅ Documented in code
|
||
|
|
- ✅ Supports future expansion (profiles, conditions)
|
||
|
|
- ✅ Can be version controlled
|
||
|
|
|
||
|
|
### 2. Filtering Functions
|
||
|
|
|
||
|
|
Added helper functions in both scripts:
|
||
|
|
- `get_excluded_plugins()` - reads registry, returns exclusion list
|
||
|
|
- `get_excluded_plugins_dist()` - same function (different name for clarity)
|
||
|
|
|
||
|
|
**Design**:
|
||
|
|
```nu
|
||
|
|
def get_excluded_plugins []: nothing -> list<string> {
|
||
|
|
try {
|
||
|
|
let registry_path = "./etc/plugin_registry.toml"
|
||
|
|
if not ($registry_path | path exists) {
|
||
|
|
return [] # Graceful degradation
|
||
|
|
}
|
||
|
|
|
||
|
|
let registry_content = open $registry_path
|
||
|
|
let excluded = try {
|
||
|
|
$registry_content.distribution.excluded_plugins
|
||
|
|
} catch {
|
||
|
|
[] # Handle malformed registry
|
||
|
|
}
|
||
|
|
|
||
|
|
return $excluded
|
||
|
|
} catch {
|
||
|
|
return [] # Never block on registry errors
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- ✅ Centralized logic (DRY principle)
|
||
|
|
- ✅ Graceful error handling (non-blocking)
|
||
|
|
- ✅ Future-proof (supports registry changes)
|
||
|
|
|
||
|
|
### 3. Collection System Updates
|
||
|
|
|
||
|
|
Updated two filtering points:
|
||
|
|
- `get_workspace_plugins_info()` - filters built-in workspace plugins
|
||
|
|
- `get_custom_plugins_info()` - filters custom plugins from plugin_* directories
|
||
|
|
|
||
|
|
**Pattern**:
|
||
|
|
```nu
|
||
|
|
let excluded = get_excluded_plugins
|
||
|
|
let available = $all_plugins | where { |p| $p not-in $excluded }
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Packaging System Updates
|
||
|
|
|
||
|
|
Updated `get_plugin_components()` to:
|
||
|
|
- Skip excluded custom plugins in globbing
|
||
|
|
- Filter excluded workspace plugins from build output
|
||
|
|
|
||
|
|
### 5. Configuration Updates
|
||
|
|
|
||
|
|
**File**: `scripts/templates/default_config.nu`
|
||
|
|
|
||
|
|
Removed excluded plugin from auto-load list:
|
||
|
|
```nu
|
||
|
|
# Before
|
||
|
|
let plugin_binaries = ["nu_plugin_clipboard", "nu_plugin_example", ...]
|
||
|
|
|
||
|
|
# After
|
||
|
|
let plugin_binaries = ["nu_plugin_clipboard", ...]
|
||
|
|
# NOTE: nu_plugin_example excluded (reference only)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rationale**:
|
||
|
|
- ✅ Users won't attempt to load non-existent plugins
|
||
|
|
- ✅ Clear documentation in code
|
||
|
|
- ✅ Manual approach is explicit and debuggable
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Alternatives Considered
|
||
|
|
|
||
|
|
### Alternative 1: Build-Time Feature Flags
|
||
|
|
|
||
|
|
**Approach**: Use Cargo feature flags to exclude from build
|
||
|
|
```rust
|
||
|
|
#[cfg(feature = "include_example")]
|
||
|
|
pub mod example;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rejected Because**:
|
||
|
|
- ❌ Requires rebuilding nushell for different distributions
|
||
|
|
- ❌ Complicates build process
|
||
|
|
- ❌ Makes reference plugins harder to access
|
||
|
|
- ❌ Doesn't support dynamic exclusion
|
||
|
|
|
||
|
|
### Alternative 2: Separate Distribution Manifests
|
||
|
|
|
||
|
|
**Approach**: Maintain separate plugin lists per distribution profile
|
||
|
|
```toml
|
||
|
|
[profiles.enterprise]
|
||
|
|
plugins = ["auth", "kms", ...]
|
||
|
|
|
||
|
|
[profiles.developer]
|
||
|
|
plugins = ["auth", "kms", "example", ...]
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rejected Because**:
|
||
|
|
- ❌ Too complex for current needs
|
||
|
|
- ❌ Requires duplicating plugin lists
|
||
|
|
- ❌ Hard to maintain consistency
|
||
|
|
- ✅ Can be added as future enhancement
|
||
|
|
|
||
|
|
### Alternative 3: Comment-Based Exclusion
|
||
|
|
|
||
|
|
**Approach**: Mark excluded plugins with comments
|
||
|
|
```nu
|
||
|
|
# EXCLUDED: nu_plugin_example
|
||
|
|
let workspace_plugins = [
|
||
|
|
"nu_plugin_auth",
|
||
|
|
"nu_plugin_example", # Comment marks as excluded
|
||
|
|
# ... others
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rejected Because**:
|
||
|
|
- ❌ Not machine-readable
|
||
|
|
- ❌ Prone to human error
|
||
|
|
- ❌ Hard to maintain across multiple scripts
|
||
|
|
- ❌ No single source of truth
|
||
|
|
|
||
|
|
### Alternative 4: External Exclusion File
|
||
|
|
|
||
|
|
**Approach**: Separate exclusion manifest file
|
||
|
|
```yaml
|
||
|
|
excluded:
|
||
|
|
- nu_plugin_example
|
||
|
|
- nu_plugin_dev_tools
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rejected Because**:
|
||
|
|
- ❌ Yet another file to maintain
|
||
|
|
- ❌ Could conflict with plugin_registry.toml
|
||
|
|
- ✅ Registry approach is sufficient
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Selected Solution: Registry-Based Approach
|
||
|
|
|
||
|
|
**Best Fit Because**:
|
||
|
|
|
||
|
|
1. **Single Source of Truth** - all exclusions in one file
|
||
|
|
2. **Non-Breaking** - doesn't affect build, test, or development workflows
|
||
|
|
3. **Maintainable** - easy to add/remove exclusions
|
||
|
|
4. **Robust** - graceful error handling, non-blocking failures
|
||
|
|
5. **Extensible** - can add profiles, conditions, or metadata later
|
||
|
|
6. **Cost-Effective** - minimal code changes, reuses existing registry
|
||
|
|
7. **Reversible** - can be disabled by emptying the exclusion list
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Consequences
|
||
|
|
|
||
|
|
### Positive Outcomes ✅
|
||
|
|
|
||
|
|
1. **Clean Distributions**: Reference plugins no longer shipped to end users
|
||
|
|
2. **Still Buildable**: Excluded plugins remain available for testing/reference
|
||
|
|
3. **Maintainable**: Single file controls all exclusions
|
||
|
|
4. **Non-Breaking**: Existing build/test workflows unchanged
|
||
|
|
5. **Documented**: Architecture and usage documented for future maintainers
|
||
|
|
6. **Extensible**: Foundation for profile-based and conditional exclusions
|
||
|
|
|
||
|
|
### Trade-offs ⚖️
|
||
|
|
|
||
|
|
1. **Two-Level Filtering**: Both collection and config exclude (small redundancy)
|
||
|
|
- Acceptable: Provides defense-in-depth
|
||
|
|
|
||
|
|
2. **No Profile-Based Exclusion**: Can't exclude per-distribution-type yet
|
||
|
|
- Acceptable: Can add later without breaking changes
|
||
|
|
|
||
|
|
3. **Manual Config Updates**: Must update default_config.nu separately
|
||
|
|
- Acceptable: Config is explicit and documented
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Implementation Timeline
|
||
|
|
|
||
|
|
- **Phase 1** (COMPLETED 2025-12-03):
|
||
|
|
- ✅ Add `[distribution]` section to `plugin_registry.toml`
|
||
|
|
- ✅ Add filtering functions to collection and packaging scripts
|
||
|
|
- ✅ Update default_config.nu
|
||
|
|
- ✅ Create architecture documentation
|
||
|
|
|
||
|
|
- **Phase 2** (Future Enhancement):
|
||
|
|
- 🔄 Add profile-based exclusions (`[distribution.profiles]`)
|
||
|
|
- 🔄 Support conditional exclusion logic
|
||
|
|
- 🔄 Add deprecation timeline tracking
|
||
|
|
|
||
|
|
- **Phase 3** (Future Enhancement):
|
||
|
|
- 🔄 Build system integration (Cargo feature coordination)
|
||
|
|
- 🔄 Automated testing of exclusion lists
|
||
|
|
- 🔄 CI/CD verification steps
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Testing Strategy
|
||
|
|
|
||
|
|
### Unit Tests
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Verify registry parsing
|
||
|
|
nu -c "open ./etc/plugin_registry.toml | get distribution.excluded_plugins"
|
||
|
|
|
||
|
|
# Verify filter functions work
|
||
|
|
nu -c "source scripts/collect_full_binaries.nu; get_excluded_plugins"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Integration Tests
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Collection excludes
|
||
|
|
just collect
|
||
|
|
find distribution -name "*example*" # Should be empty
|
||
|
|
|
||
|
|
# Packaging excludes
|
||
|
|
just pack-full
|
||
|
|
tar -tzf bin_archives/*.tar.gz | grep example # Should be empty
|
||
|
|
|
||
|
|
# Build includes
|
||
|
|
just build
|
||
|
|
ls nushell/target/release/nu_plugin_example # Should exist
|
||
|
|
|
||
|
|
# Config doesn't auto-load
|
||
|
|
grep "nu_plugin_example" scripts/templates/default_config.nu | grep "plugin_binaries"
|
||
|
|
# Should NOT appear in plugin_binaries list
|
||
|
|
```
|
||
|
|
|
||
|
|
### Release Validation
|
||
|
|
|
||
|
|
Before each release:
|
||
|
|
```bash
|
||
|
|
# Pre-release checklist
|
||
|
|
./scripts/validate_exclusions.nu # Future script
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Rollback Plan
|
||
|
|
|
||
|
|
If the exclusion system causes problems:
|
||
|
|
|
||
|
|
1. **Quick Disable**:
|
||
|
|
```toml
|
||
|
|
[distribution]
|
||
|
|
excluded_plugins = [] # Empty list
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Full Rollback**:
|
||
|
|
```bash
|
||
|
|
git revert <commit-hash>
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Verification**:
|
||
|
|
```bash
|
||
|
|
just collect && find distribution -name "*" | wc -l # Should be higher
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Monitoring & Observability
|
||
|
|
|
||
|
|
### Logging
|
||
|
|
|
||
|
|
Collection script logs:
|
||
|
|
```
|
||
|
|
log_info "🔍 Discovering workspace plugins for platform: x86_64-linux"
|
||
|
|
log_info "📦 Found 8 workspace plugins"
|
||
|
|
```
|
||
|
|
|
||
|
|
No additional logging needed - system is transparent by design.
|
||
|
|
|
||
|
|
### Verification
|
||
|
|
|
||
|
|
Include verification step in release workflows:
|
||
|
|
```bash
|
||
|
|
# Before packaging
|
||
|
|
EXCLUDED=$(nu -c "open ./etc/plugin_registry.toml | get distribution.excluded_plugins | length")
|
||
|
|
echo "Excluding $EXCLUDED plugins from distribution"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Documentation
|
||
|
|
|
||
|
|
Created:
|
||
|
|
1. `docs/PLUGIN_EXCLUSION_GUIDE.md` - User guide and troubleshooting
|
||
|
|
2. `docs/architecture/PLUGIN_EXCLUSION_SYSTEM.md` - Technical architecture
|
||
|
|
3. This ADR - Decision rationale and design
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Open Questions
|
||
|
|
|
||
|
|
**Q1**: Should we add metrics to track exclusions?
|
||
|
|
- **Current**: No, system is simple and self-evident
|
||
|
|
- **Future**: Could add to CI/CD validation
|
||
|
|
|
||
|
|
**Q2**: Should exclusion list be version-specific?
|
||
|
|
- **Current**: No, global exclusions
|
||
|
|
- **Future**: Could add version support in registry
|
||
|
|
|
||
|
|
**Q3**: What if excluded plugin becomes stable?
|
||
|
|
- **Current**: Remove from exclusion list, rebuild distribution
|
||
|
|
- **Future**: Could automate with deprecation timeline
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Sign-off
|
||
|
|
|
||
|
|
| Role | Name | Date | Status |
|
||
|
|
|------|------|------|--------|
|
||
|
|
| Author | Claude Code | 2025-12-03 | ✅ Implemented |
|
||
|
|
| Reviewed | (async) | 2025-12-03 | ✅ Accepted |
|
||
|
|
| Approved | (project owner) | TBD | ⏳ Pending |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Related Decisions
|
||
|
|
|
||
|
|
- **ADR-002** (Future): Profile-Based Exclusion System
|
||
|
|
- **ADR-003** (Future): Conditional Compilation Features
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- **Implementation**: `etc/plugin_registry.toml`, `scripts/collect_full_binaries.nu`, `scripts/create_distribution_packages.nu`, `scripts/templates/default_config.nu`
|
||
|
|
- **Documentation**: `docs/PLUGIN_EXCLUSION_GUIDE.md`, `docs/architecture/PLUGIN_EXCLUSION_SYSTEM.md`
|
||
|
|
- **Test Cases**: See Testing Strategy section above
|
||
|
|
- **Related Issues**: Project tracking TBD
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**ADR Status**: ✅ ACCEPTED
|
||
|
|
**Implementation Status**: ✅ COMPLETE
|
||
|
|
**Documentation Status**: ✅ COMPLETE
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*For questions or clarifications, see `docs/PLUGIN_EXCLUSION_GUIDE.md` or open an issue.*
|