nushell-plugins/docs/architecture/ADR-001-PLUGIN_EXCLUSION_SYSTEM.md
Jesús Pérez 4b92aa764a
Some checks failed
Build and Test / Validate Setup (push) Has been cancelled
Build and Test / Build (darwin-amd64) (push) Has been cancelled
Build and Test / Build (darwin-arm64) (push) Has been cancelled
Build and Test / Build (linux-amd64) (push) Has been cancelled
Build and Test / Build (windows-amd64) (push) Has been cancelled
Build and Test / Build (linux-arm64) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Package Results (push) Has been cancelled
Build and Test / Quality Gate (push) Has been cancelled
implements a production-ready bootstrap installer with comprehensive error handling, version-agnostic archive extraction, and clear user messaging. All improvements follow DRY principles using symlink-based architecture for single-source-of-truth maintenance
2025-12-11 22:04:54 +00:00

11 KiB

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

[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:

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:

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:

# 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

#[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

[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

# 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

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

# 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

# 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:

# Pre-release checklist
./scripts/validate_exclusions.nu  # Future script

Rollback Plan

If the exclusion system causes problems:

  1. Quick Disable:

    [distribution]
    excluded_plugins = []  # Empty list
    
  2. Full Rollback:

    git revert <commit-hash>
    
  3. Verification:

    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:

# 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

  • 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.