# 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
