> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploystack.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin System

> Complete guide to the DeployStack frontend plugin architecture for extending functionality with custom components, routes, and state management.

DeployStack's frontend features a powerful plugin architecture that enables extending the application with additional functionality, UI components, routes, and state management. This modular approach allows for clean separation of concerns and extensible development.

## Architecture Overview

The plugin system is designed with flexibility and maintainability in mind:

* **Modular Extension**: Add new UI components at designated extension points
* **Route Registration**: Register new routes in the Vue Router
* **State Management**: Add new Pinia stores for plugin-specific state
* **Lifecycle Management**: Initialize and cleanup plugins properly
* **Type Safety**: Full TypeScript support for plugin development

## Plugin Structure

A standard plugin follows this directory structure:

```bash theme={null}
your-plugin/
├── index.ts              # Main plugin entry point (required)
├── components/           # Plugin-specific components
│   ├── PluginComponent.vue
│   └── PluginCard.vue
├── views/               # Plugin-specific views/pages
│   ├── PluginPage.vue
│   └── PluginSettings.vue
├── store.ts             # Plugin-specific Pinia store (optional)
├── composables/         # Plugin-specific composables (optional)
│   └── usePluginFeature.ts
├── types.ts             # Plugin-specific types (optional)
└── README.md            # Plugin documentation
```

## Plugin Interface

Every plugin must implement the `Plugin` interface:

```typescript theme={null}
interface Plugin {
  meta: PluginMeta
  initialize(app: App, router: Router, pinia: Pinia): Promise<void> | void
  cleanup?(): Promise<void> | void
}

interface PluginMeta {
  id: string           // Unique plugin identifier
  name: string         // Human-readable plugin name
  version: string      // Plugin version (semver)
  description: string  // Plugin description
  author?: string      // Plugin author (optional)
}
```

## Creating Your First Plugin

### 1. Basic Plugin Structure

Create a new directory for your plugin:

```bash theme={null}
mkdir -p src/plugins/my-custom-plugin
cd src/plugins/my-custom-plugin
```

### 2. Create a Component

Start with a simple Vue component:

```vue theme={null}
<!-- src/plugins/my-custom-plugin/components/CustomWidget.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { BarChart, TrendingUp, Server } from 'lucide-vue-next'

const data = ref({
  totalItems: 0,
  activeItems: 0,
  totalRequests: 0
})

const isLoading = ref(true)

async function fetchData() {
  isLoading.value = true
  try {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000))
    data.value = {
      totalItems: 12,
      activeItems: 8,
      totalRequests: 1542
    }
  } catch (error) {
    console.error('Failed to fetch data:', error)
  } finally {
    isLoading.value = false
  }
}

onMounted(() => {
  fetchData()
})
</script>

<template>
  <div class="custom-widget p-6 bg-white rounded-lg shadow-md border">
    <div class="flex items-center justify-between mb-4">
      <h3 class="text-lg font-semibold text-gray-900 flex items-center">
        <BarChart class="h-5 w-5 mr-2 text-indigo-600" />
        Custom Plugin Widget
      </h3>
      <button 
        @click="fetchData" 
        class="text-sm text-indigo-600 hover:text-indigo-800"
        :disabled="isLoading"
      >
        {{ isLoading ? 'Loading...' : 'Refresh' }}
      </button>
    </div>
    
    <div v-if="isLoading" class="animate-pulse">
      <div class="grid grid-cols-3 gap-4">
        <div v-for="i in 3" :key="i" class="h-16 bg-gray-200 rounded"></div>
      </div>
    </div>
    
    <div v-else class="grid grid-cols-3 gap-4">
      <div class="text-center p-4 bg-blue-50 rounded-lg">
        <Server class="h-6 w-6 mx-auto mb-2 text-blue-600" />
        <div class="text-2xl font-bold text-blue-700">{{ data.totalItems }}</div>
        <div class="text-sm text-blue-600">Total Items</div>
      </div>
      
      <div class="text-center p-4 bg-green-50 rounded-lg">
        <TrendingUp class="h-6 w-6 mx-auto mb-2 text-green-600" />
        <div class="text-2xl font-bold text-green-700">{{ data.activeItems }}</div>
        <div class="text-sm text-green-600">Active Items</div>
      </div>
      
      <div class="text-center p-4 bg-purple-50 rounded-lg">
        <BarChart class="h-6 w-6 mx-auto mb-2 text-purple-600" />
        <div class="text-2xl font-bold text-purple-700">{{ data.totalRequests.toLocaleString() }}</div>
        <div class="text-sm text-purple-600">Total Requests</div>
      </div>
    </div>
  </div>
</template>
```

### 3. Implement the Plugin

Create the main plugin file:

```typescript theme={null}
// src/plugins/my-custom-plugin/index.ts
import type { Plugin } from '@/plugin-system/types'
import type { App } from 'vue'
import type { Router } from 'vue-router'
import type { Pinia } from 'pinia'
import { registerExtensionPoint } from '@/plugin-system/extension-points'
import CustomWidget from './components/CustomWidget.vue'
import CustomPage from './views/CustomPage.vue'

class MyCustomPlugin implements Plugin {
  meta = {
    id: 'my-custom-plugin',
    name: 'My Custom Plugin',
    version: '1.0.0',
    description: 'Provides custom functionality and widgets for the application',
    author: 'Your Name'
  }
  
  initialize(app: App, router: Router, pinia: Pinia) {
    console.log('Initializing My Custom Plugin...')
    
    // Register the widget at an extension point
    registerExtensionPoint('dashboard-widgets', CustomWidget, this.meta.id, {
      order: 10, // Show early in the dashboard
      props: {
        refreshInterval: 30000 // Refresh every 30 seconds
      }
    })
    
    // Register a dedicated page route
    router.addRoute({
      path: '/custom',
      name: 'Custom',
      component: CustomPage,
      meta: {
        title: 'Custom Plugin',
        requiresAuth: true
      }
    })
    
    console.log('My Custom Plugin initialized successfully')
  }
  
  cleanup() {
    console.log('Cleaning up My Custom Plugin...')
    // Extension points are automatically cleaned up by the plugin manager
  }
}

export default MyCustomPlugin
```

### 4. Create a Dedicated Page

Create a full page view for your plugin:

```vue theme={null}
<!-- src/plugins/my-custom-plugin/views/CustomPage.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { BarChart, Settings } from 'lucide-vue-next'
import CustomWidget from '../components/CustomWidget.vue'

const pageData = ref({
  title: 'Custom Plugin Dashboard',
  description: 'This page is provided by the custom plugin'
})
</script>

<template>
  <div class="custom-page max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
    <div class="mb-8">
      <h1 class="text-3xl font-bold text-gray-900 flex items-center">
        <BarChart class="h-8 w-8 mr-3 text-indigo-600" />
        {{ pageData.title }}
      </h1>
      <p class="mt-2 text-gray-600">
        {{ pageData.description }}
      </p>
    </div>
    
    <!-- Include the widget component -->
    <div class="mb-8">
      <CustomWidget />
    </div>
    
    <!-- Additional page content -->
    <div class="bg-white p-6 rounded-lg shadow-md border">
      <h2 class="text-xl font-semibold mb-4 flex items-center">
        <Settings class="h-6 w-6 mr-2 text-gray-600" />
        Plugin Settings
      </h2>
      <p class="text-gray-600">
        This is where you could add plugin-specific settings and configuration options.
      </p>
    </div>
  </div>
</template>
```

### 5. Add Plugin State Management

Create a Pinia store for your plugin:

```typescript theme={null}
// src/plugins/my-custom-plugin/store.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export interface CustomData {
  totalItems: number
  activeItems: number
  totalRequests: number
}

export const useCustomStore = defineStore('my-custom-plugin', () => {
  // State
  const data = ref<CustomData>({
    totalItems: 0,
    activeItems: 0,
    totalRequests: 0
  })
  
  const isLoading = ref(false)
  const lastUpdated = ref<Date | null>(null)
  
  // Getters
  const activePercentage = computed(() => {
    if (data.value.totalItems === 0) return 0
    return Math.round((data.value.activeItems / data.value.totalItems) * 100)
  })
  
  const isHealthy = computed(() => activePercentage.value > 80)
  
  // Actions
  async function fetchData() {
    isLoading.value = true
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 1000))
      
      data.value = {
        totalItems: Math.floor(Math.random() * 20) + 10,
        activeItems: Math.floor(Math.random() * 15) + 5,
        totalRequests: Math.floor(Math.random() * 5000) + 1000
      }
      
      lastUpdated.value = new Date()
    } catch (error) {
      console.error('Failed to fetch data:', error)
      throw error
    } finally {
      isLoading.value = false
    }
  }
  
  return {
    // State
    data,
    isLoading,
    lastUpdated,
    
    // Getters
    activePercentage,
    isHealthy,
    
    // Actions
    fetchData
  }
})
```

## Extension Points

Extension points are designated areas in your application where plugins can inject components.

### Using Extension Points in Your App

Add extension points to your main application components:

```vue theme={null}
<!-- In your Dashboard.vue or other main components -->
<template>
  <div class="dashboard">
    <h1>Dashboard</h1>
    
    <!-- Extension point for dashboard widgets -->
    <div class="widgets-grid">
      <ExtensionPoint pointId="dashboard-widgets" />
    </div>
    
    <!-- Extension point for sidebar items -->
    <aside class="sidebar">
      <ExtensionPoint pointId="sidebar-items" />
    </aside>
    
    <!-- Extension point for action buttons -->
    <div class="actions">
      <ExtensionPoint pointId="action-buttons" />
    </div>
  </div>
</template>
```

### Registering Components at Extension Points

In your plugin's initialize method:

```typescript theme={null}
// Register a single component
registerExtensionPoint(
  'dashboard-widgets',    // Extension point ID
  CustomWidget,          // Vue component
  this.meta.id,          // Plugin ID
  {
    order: 10,           // Display order (optional)
    props: {             // Props to pass to component (optional)
      refreshInterval: 30000
    }
  }
)

// Register multiple components
registerExtensionPoint('action-buttons', RefreshButton, this.meta.id, { order: 1 })
registerExtensionPoint('action-buttons', ExportButton, this.meta.id, { order: 2 })
```

### Conditional Rendering

Show specific plugin components based on conditions:

```vue theme={null}
<template>
  <!-- Show only specific plugin -->
  <ExtensionPoint pointId="dashboard-widgets" pluginName="my-custom-plugin" />
  
  <!-- Show all plugins at this extension point -->
  <ExtensionPoint pointId="dashboard-widgets" />
</template>
```

## Registering Plugins

Add your plugin to the plugin loader:

```typescript theme={null}
// src/plugins/index.ts
import type { Plugin } from '../plugin-system/types'
import HelloWorldPlugin from './hello-world'
import MyCustomPlugin from './my-custom-plugin'

export async function loadPlugins(): Promise<Plugin[]> {
  return [
    new HelloWorldPlugin(),
    new MyCustomPlugin(),
    // Add more plugins here
  ]
}
```

## Plugin Development Best Practices

### 1. Plugin Naming and Structure

```typescript theme={null}
// Good plugin naming
class ServerMonitoringPlugin implements Plugin {
  meta = {
    id: 'server-monitoring',               // kebab-case
    name: 'Server Monitoring',             // Human readable
    version: '1.2.3',                     // Semantic versioning
    description: 'Real-time server monitoring', // Clear description
    author: 'Your Team'
  }
}

// Avoid generic names
// ❌ class UtilsPlugin
// ❌ class MyPlugin
// ❌ class Plugin1
```

### 2. Component Naming Convention

```vue theme={null}
<!-- Good: Prefix with plugin name -->
<!-- MonitoringWidget.vue -->
<!-- MonitoringDashboard.vue -->
<!-- MonitoringChart.vue -->

<!-- Avoid generic names that might conflict -->
<!-- ❌ Widget.vue -->
<!-- ❌ Dashboard.vue -->
<!-- ❌ Chart.vue -->
```

### 3. Error Handling

```typescript theme={null}
class RobustPlugin implements Plugin {
  initialize(app: App, router: Router, pinia: Pinia) {
    try {
      // Plugin initialization logic
      this.setupComponents()
      this.registerRoutes(router)
      this.initializeStore(pinia)
      
      console.log(`${this.meta.name} initialized successfully`)
    } catch (error) {
      console.error(`Failed to initialize ${this.meta.name}:`, error)
      
      // Graceful degradation - don't throw unless critical
      this.handleInitializationError(error)
    }
  }
  
  private handleInitializationError(error: Error) {
    // Log detailed error information
    console.error('Plugin initialization error details:', {
      pluginId: this.meta.id,
      version: this.meta.version,
      error: error.message,
      stack: error.stack
    })
  }
}
```

### 4. Resource Cleanup

```typescript theme={null}
class CleanPlugin implements Plugin {
  private intervals: number[] = []
  private eventListeners: Array<{ element: EventTarget, type: string, listener: EventListener }> = []
  
  initialize(app: App, router: Router, pinia: Pinia) {
    // Set up intervals
    const intervalId = setInterval(() => {
      this.refreshData()
    }, 30000)
    this.intervals.push(intervalId)
    
    // Set up event listeners
    const listener = (event: Event) => this.handleEvent(event)
    document.addEventListener('visibilitychange', listener)
    this.eventListeners.push({
      element: document,
      type: 'visibilitychange',
      listener
    })
  }
  
  cleanup() {
    // Clean up intervals
    this.intervals.forEach(id => clearInterval(id))
    this.intervals = []
    
    // Clean up event listeners
    this.eventListeners.forEach(({ element, type, listener }) => {
      element.removeEventListener(type, listener)
    })
    this.eventListeners = []
    
    console.log(`${this.meta.name} cleaned up successfully`)
  }
}
```

### 5. Type Safety

```typescript theme={null}
// Define clear interfaces for your plugin data
interface PluginData {
  totalItems: number
  activeItems: number
  status: 'active' | 'inactive' | 'error'
}

interface PluginConfig {
  refreshInterval?: number
  enableNotifications?: boolean
  apiEndpoint?: string
}

// Use proper typing in components
interface Props {
  data: PluginData
  config?: PluginConfig
  onRefresh?: () => void
}

const props = withDefaults(defineProps<Props>(), {
  config: () => ({}),
  onRefresh: () => {}
})
```

## Plugin Composables

Create reusable composition functions:

```typescript theme={null}
// src/plugins/my-custom-plugin/composables/useCustomFeature.ts
import { ref, onMounted, onUnmounted } from 'vue'
import { useCustomStore } from '../store'

export function useCustomFeature(refreshInterval = 30000) {
  const store = useCustomStore()
  const isAutoRefreshEnabled = ref(true)
  let intervalId: number | null = null
  
  function startAutoRefresh() {
    if (intervalId) clearInterval(intervalId)
    
    intervalId = setInterval(() => {
      if (isAutoRefreshEnabled.value) {
        store.fetchData()
      }
    }, refreshInterval)
  }
  
  function stopAutoRefresh() {
    if (intervalId) {
      clearInterval(intervalId)
      intervalId = null
    }
  }
  
  function toggleAutoRefresh() {
    isAutoRefreshEnabled.value = !isAutoRefreshEnabled.value
    if (isAutoRefreshEnabled.value) {
      startAutoRefresh()
    } else {
      stopAutoRefresh()
    }
  }
  
  onMounted(() => {
    store.fetchData()
    startAutoRefresh()
  })
  
  onUnmounted(() => {
    stopAutoRefresh()
  })
  
  return {
    data: store.data,
    isLoading: store.isLoading,
    activePercentage: store.activePercentage,
    isHealthy: store.isHealthy,
    isAutoRefreshEnabled,
    refreshData: store.fetchData,
    toggleAutoRefresh
  }
}
```

## Testing Plugins

### Unit Testing Plugin Components

```typescript theme={null}
// tests/plugins/my-custom-plugin/CustomWidget.test.ts
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import CustomWidget from '@/plugins/my-custom-plugin/components/CustomWidget.vue'

describe('CustomWidget', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })
  
  it('should render widget correctly', async () => {
    const wrapper = mount(CustomWidget)
    
    // Wait for component to load
    await wrapper.vm.$nextTick()
    
    expect(wrapper.find('.custom-widget').exists()).toBe(true)
    expect(wrapper.text()).toContain('Custom Plugin Widget')
  })
  
  it('should show loading state initially', () => {
    const wrapper = mount(CustomWidget)
    expect(wrapper.find('.animate-pulse').exists()).toBe(true)
  })
  
  it('should handle refresh button click', async () => {
    const wrapper = mount(CustomWidget)
    const refreshButton = wrapper.find('button')
    
    await refreshButton.trigger('click')
    expect(wrapper.vm.isLoading).toBe(true)
  })
})
```

### Integration Testing

```typescript theme={null}
// tests/plugins/my-custom-plugin/integration.test.ts
import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
import { createPinia } from 'pinia'
import MyCustomPlugin from '@/plugins/my-custom-plugin'
import { PluginManager } from '@/plugin-system/plugin-manager'

describe('My Custom Plugin Integration', () => {
  let pluginManager: PluginManager
  let router: any
  let pinia: any
  
  beforeEach(() => {
    router = createRouter({
      history: createWebHistory(),
      routes: []
    })
    pinia = createPinia()
    pluginManager = new PluginManager()
  })
  
  it('should initialize plugin successfully', async () => {
    const plugin = new MyCustomPlugin()
    
    await expect(
      plugin.initialize(null as any, router, pinia)
    ).resolves.not.toThrow()
    
    // Check if routes were added
    const routes = router.getRoutes()
    expect(routes.some((route: any) => route.name === 'Custom')).toBe(true)
  })
})
```

## Troubleshooting

### Common Issues

#### Plugin Not Loading

```typescript theme={null}
// Debug plugin loading
export async function loadPlugins(): Promise<Plugin[]> {
  const plugins: Plugin[] = []
  
  try {
    const HelloWorldPlugin = (await import('./hello-world')).default
    plugins.push(new HelloWorldPlugin())
    console.log('✅ HelloWorldPlugin loaded')
  } catch (error) {
    console.error('❌ Failed to load HelloWorldPlugin:', error)
  }
  
  try {
    const CustomPlugin = (await import('./my-custom-plugin')).default
    plugins.push(new CustomPlugin())
    console.log('✅ CustomPlugin loaded')
  } catch (error) {
    console.error('❌ Failed to load CustomPlugin:', error)
  }
  
  return plugins
}
```

#### Extension Points Not Rendering

1. **Check Extension Point Placement**: Ensure `<ExtensionPoint pointId="your-point-id" />` is placed in your application views
2. **Verify Point ID**: Make sure the extension point ID matches between registration and usage
3. **Component Import**: Check that components are correctly imported and registered
4. **Console Errors**: Look for JavaScript errors that might prevent component rendering

#### Plugin State Issues

```typescript theme={null}
// Debug plugin state
class DebuggablePlugin implements Plugin {
  initialize(app: App, router: Router, pinia: Pinia) {
    // Add global debug method
    app.config.globalProperties.$debugPlugin = (pluginId: string) => {
      console.log('Plugin Debug Info:', {
        id: this.meta.id,
        routes: router.getRoutes().filter(r => r.meta?.pluginId === pluginId),
        stores: pinia._s,
        extensionPoints: this.getRegisteredExtensionPoints()
      })
    }
  }
  
  private getRegisteredExtensionPoints() {
    // Return extension points registered by this plugin
    return []
  }
}
```

### Performance Debugging

```typescript theme={null}
// Performance monitoring for plugins
class PerformanceMonitoredPlugin implements Plugin {
  initialize(app: App, router: Router, pinia: Pinia) {
    const startTime = performance.now()
    
    try {
      this.doInitialization()
      
      const endTime = performance.now()
      console.log(`${this.meta.id} initialization took ${endTime - startTime}ms`)
    } catch (error) {
      const endTime = performance.now()
      console.error(`${this.meta.id} failed after ${endTime - startTime}ms:`, error)
      throw error
    }
  }
  
  private doInitialization() {
    // Your plugin initialization logic
  }
}
```

This plugin system documentation provides everything needed to create maintainable and well-tested plugins for the DeployStack frontend. The modular architecture ensures that functionality can be extended cleanly while maintaining the core application's stability and performance.
