# 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 { 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 ``` 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.*