Files
leo-claude-mktplace/.claude/skills/claude-plugin-developer/references/ci-cd-integration.md

17 KiB

CI/CD Integration Guide

Complete guide for automating plugin testing, validation, and deployment.

GitHub Actions

Basic Validation Workflow

# .github/workflows/validate-plugin.yml
name: Validate Plugin

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  validate:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Validate plugin manifest
        run: |
          python scripts/validate_manifest.py .claude-plugin/plugin.json
      
      - name: Check JSON syntax
        run: |
          find . -name "*.json" -exec jq . {} \; > /dev/null
      
      - name: Test commands
        run: |
          python scripts/test_commands.py .
      
      - name: Check file permissions
        run: |
          # Ensure hook scripts are executable
          find hooks -name "*.sh" -type f -exec test -x {} \; || exit 1

Advanced Testing Workflow

# .github/workflows/test-plugin.yml
name: Test Plugin

on:
  push:
    branches: [main, develop]
  pull_request:

env:
  PLUGIN_NAME: my-plugin

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Lint Markdown
        uses: DavidAnson/markdownlint-cli2-action@v14
        with:
          globs: |
            **/*.md
            !node_modules
      
      - name: Spell Check
        uses: streetsidesoftware/cspell-action@v5
        with:
          files: |
            **/*.md
            **/*.json
  
  test-scripts:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11']
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-cov
      
      - name: Run Python tests
        run: |
          pytest tests/ --cov=scripts --cov-report=xml
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
  
  test-hooks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Test shell scripts
        run: |
          # Install shellcheck
          sudo apt-get update && sudo apt-get install -y shellcheck
          
          # Check all shell scripts
          find . -name "*.sh" -exec shellcheck {} \;
      
      - name: Test hook execution
        run: |
          # Simulate hook environment
          export CHANGED_FILE="test.py"
          export FILE_EXTENSION="py"
          
          # Test each hook
          for hook in hooks/*.sh; do
            if [ -x "$hook" ]; then
              echo "Testing $hook..."
              timeout 10s "$hook" || echo "Hook $hook failed or timed out"
            fi
          done
  
  integration:
    runs-on: ubuntu-latest
    needs: [lint, test-scripts, test-hooks]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Claude CLI (mock)
        run: |
          # In real scenario, install actual Claude CLI
          echo "Installing Claude CLI..."
      
      - name: Test plugin installation
        run: |
          # Mock test - in reality would use Claude CLI
          echo "Testing plugin installation..."
          
      - name: Test command execution
        run: |
          # Mock test - in reality would test commands
          echo "Testing command execution..."

Release Workflow

# .github/workflows/release.yml
name: Release Plugin

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Validate version tag
        run: |
          # Extract version from tag
          VERSION=${GITHUB_REF#refs/tags/v}
          
          # Check manifest version matches
          MANIFEST_VERSION=$(jq -r .version .claude-plugin/plugin.json)
          
          if [ "$VERSION" != "$MANIFEST_VERSION" ]; then
            echo "Tag version ($VERSION) doesn't match manifest ($MANIFEST_VERSION)"
            exit 1
          fi
      
      - name: Create changelog
        id: changelog
        run: |
          # Generate changelog from commits
          git log --pretty=format:"- %s" $(git describe --tags --abbrev=0 HEAD^)..HEAD > CHANGELOG.md
          
      - name: Update marketplace
        run: |
          # Update marketplace.json if it exists
          if [ -f "../marketplace/.claude-plugin/marketplace.json" ]; then
            # Update plugin version in marketplace
            jq --arg v "$VERSION" \
              '.plugins[] | select(.name == "${{ env.PLUGIN_NAME }}").version = $v' \
              ../marketplace/.claude-plugin/marketplace.json > tmp.json
            mv tmp.json ../marketplace/.claude-plugin/marketplace.json
          fi
      
      - name: Create Release
        uses: softprops/action-gh-release@v1
        with:
          body_path: CHANGELOG.md
          files: |
            .claude-plugin/plugin.json
            README.md

GitLab CI

Basic Pipeline

# .gitlab-ci.yml
stages:
  - validate
  - test
  - deploy

variables:
  PLUGIN_NAME: "my-plugin"

validate:manifest:
  stage: validate
  image: python:3.11
  script:
    - python scripts/validate_manifest.py .claude-plugin/plugin.json
  only:
    - merge_requests
    - main

validate:json:
  stage: validate
  image: stedolan/jq
  script:
    - find . -name "*.json" -exec jq . {} \;
  only:
    - merge_requests
    - main

test:commands:
  stage: test
  image: python:3.11
  script:
    - pip install -r requirements.txt
    - python scripts/test_commands.py .
  artifacts:
    reports:
      junit: test-results.xml

test:scripts:
  stage: test
  image: python:3.11
  script:
    - pip install pytest pytest-cov
    - pytest tests/ --junitxml=test-results.xml
  coverage: '/TOTAL.*\s+(\d+%)$/'

deploy:marketplace:
  stage: deploy
  image: alpine/git
  script:
    - |
      # Update marketplace repository
      git clone $MARKETPLACE_REPO marketplace
      cd marketplace
      
      # Update plugin entry
      # ... update logic ...
      
      git add .
      git commit -m "Update $PLUGIN_NAME to $CI_COMMIT_TAG"
      git push origin main
  only:
    - tags

Advanced GitLab Pipeline

# .gitlab-ci.yml
include:
  - template: Security/SAST.gitlab-ci.yml
  - template: Code-Quality.gitlab-ci.yml

stages:
  - build
  - test
  - security
  - deploy
  - cleanup

.plugin_template:
  image: node:18
  before_script:
    - npm ci --cache .npm --prefer-offline
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - .npm/
      - node_modules/

build:plugin:
  extends: .plugin_template
  stage: build
  script:
    - npm run build
    - tar -czf plugin.tar.gz .
  artifacts:
    paths:
      - plugin.tar.gz
    expire_in: 1 week

test:unit:
  extends: .plugin_template
  stage: test
  script:
    - npm test -- --coverage
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

test:integration:
  stage: test
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t $PLUGIN_NAME:test .
    - docker run --rm $PLUGIN_NAME:test npm run test:integration

security:dependencies:
  stage: security
  image: node:18
  script:
    - npm audit --production
  allow_failure: true

deploy:staging:
  stage: deploy
  script:
    - echo "Deploying to staging marketplace..."
  environment:
    name: staging
    url: https://staging.marketplace.example.com
  only:
    - develop

deploy:production:
  stage: deploy
  script:
    - echo "Deploying to production marketplace..."
  environment:
    name: production
    url: https://marketplace.example.com
  only:
    - tags
  when: manual

Jenkins Pipeline

Jenkinsfile

// Jenkinsfile
pipeline {
    agent any
    
    environment {
        PLUGIN_NAME = 'my-plugin'
        PYTHON = 'python3'
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Validate') {
            parallel {
                stage('Manifest') {
                    steps {
                        sh '${PYTHON} scripts/validate_manifest.py .claude-plugin/plugin.json'
                    }
                }
                
                stage('JSON Files') {
                    steps {
                        sh 'find . -name "*.json" -exec jq . {} \\;'
                    }
                }
                
                stage('Markdown') {
                    steps {
                        sh 'npx markdownlint-cli2 "**/*.md"'
                    }
                }
            }
        }
        
        stage('Test') {
            steps {
                sh '''
                    ${PYTHON} -m pip install -r requirements.txt
                    ${PYTHON} -m pytest tests/ --junitxml=test-results.xml
                '''
            }
            
            post {
                always {
                    junit 'test-results.xml'
                }
            }
        }
        
        stage('Security Scan') {
            steps {
                sh '''
                    # Run security scans
                    ${PYTHON} -m pip install safety
                    safety check
                    
                    # Check for secrets
                    npx @secretlint/quick-start "**/*"
                '''
            }
        }
        
        stage('Build') {
            when {
                tag pattern: "v\\d+\\.\\d+\\.\\d+", comparator: "REGEXP"
            }
            steps {
                sh '''
                    # Package plugin
                    tar -czf ${PLUGIN_NAME}-${TAG_NAME}.tar.gz .
                '''
                
                archiveArtifacts artifacts: '*.tar.gz'
            }
        }
        
        stage('Deploy') {
            when {
                tag pattern: "v\\d+\\.\\d+\\.\\d+", comparator: "REGEXP"
            }
            steps {
                input message: 'Deploy to marketplace?'
                
                sh '''
                    # Deploy to marketplace
                    echo "Deploying version ${TAG_NAME}"
                '''
            }
        }
    }
    
    post {
        success {
            slackSend(
                color: 'good',
                message: "Plugin ${PLUGIN_NAME} build successful: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
            )
        }
        
        failure {
            slackSend(
                color: 'danger',
                message: "Plugin ${PLUGIN_NAME} build failed: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
            )
        }
    }
}

CircleCI Configuration

.circleci/config.yml

version: 2.1

orbs:
  python: circleci/python@2.1.1
  node: circleci/node@5.1.0

executors:
  plugin-executor:
    docker:
      - image: cimg/python:3.11-node
    working_directory: ~/plugin

jobs:
  validate:
    executor: plugin-executor
    steps:
      - checkout
      - run:
          name: Validate Manifest
          command: python scripts/validate_manifest.py .claude-plugin/plugin.json
      - run:
          name: Check JSON
          command: |
            sudo apt-get update && sudo apt-get install -y jq
            find . -name "*.json" -exec jq . {} \;
  
  test:
    executor: plugin-executor
    steps:
      - checkout
      - python/install-packages:
          pkg-manager: pip
          pip-dependency-file: requirements.txt
      - run:
          name: Run Tests
          command: |
            pytest tests/ --junitxml=test-results/junit.xml
      - store_test_results:
          path: test-results
      - store_artifacts:
          path: test-results
  
  security:
    executor: plugin-executor
    steps:
      - checkout
      - run:
          name: Security Scan
          command: |
            pip install safety
            safety check
  
  deploy:
    executor: plugin-executor
    steps:
      - checkout
      - run:
          name: Deploy to Marketplace
          command: |
            echo "Deploying to marketplace..."

workflows:
  plugin-pipeline:
    jobs:
      - validate
      - test:
          requires:
            - validate
      - security:
          requires:
            - validate
      - deploy:
          requires:
            - test
            - security
          filters:
            tags:
              only: /^v.*/
            branches:
              ignore: /.*/

Testing Strategies

Unit Tests

# tests/test_commands.py

import pytest
from pathlib import Path
import json

class TestCommands:
    def test_all_commands_have_metadata(self):
        """Ensure all command files have proper metadata."""
        commands_dir = Path("commands")
        for md_file in commands_dir.rglob("*.md"):
            with open(md_file) as f:
                content = f.read()
            
            assert content.startswith("---"), f"{md_file} missing frontmatter"
            assert "_type: command" in content, f"{md_file} missing _type"
            assert "_command:" in content, f"{md_file} missing _command"
            assert "_description:" in content, f"{md_file} missing _description"
    
    def test_manifest_valid(self):
        """Test plugin manifest is valid."""
        with open(".claude-plugin/plugin.json") as f:
            manifest = json.load(f)
        
        assert "name" in manifest
        assert "version" in manifest
        assert "description" in manifest
        assert "author" in manifest

Integration Tests

# tests/test_integration.py

import subprocess
import tempfile
import shutil
from pathlib import Path

def test_plugin_installation():
    """Test plugin can be installed."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Copy plugin to temp directory
        plugin_dir = Path(tmpdir) / "test-plugin"
        shutil.copytree(".", plugin_dir)
        
        # Mock installation test
        result = subprocess.run(
            ["python", "scripts/validate_manifest.py", 
             str(plugin_dir / ".claude-plugin/plugin.json")],
            capture_output=True
        )
        
        assert result.returncode == 0

Deployment Strategies

Blue-Green Deployment

# Deploy to staging first, then swap
deploy:staging:
  stage: deploy
  script:
    - deploy_to_marketplace staging
    - run_smoke_tests staging
  environment:
    name: staging

deploy:production:
  stage: deploy
  script:
    - swap_marketplace_slots staging production
  when: manual
  only:
    - tags

Canary Releases

deploy:canary:
  stage: deploy
  script:
    - |
      # Deploy to 10% of users
      update_marketplace_config canary 0.1
      
      # Monitor for 1 hour
      sleep 3600
      
      # Check metrics
      if check_canary_metrics; then
        update_marketplace_config canary 1.0
      else
        rollback_canary
      fi

Rollback Strategy

#!/bin/bash
# scripts/rollback.sh

PLUGIN_NAME="my-plugin"
PREVIOUS_VERSION=$(git describe --tags --abbrev=0 HEAD^)

echo "Rolling back $PLUGIN_NAME to $PREVIOUS_VERSION"

# Restore previous version
git checkout "v$PREVIOUS_VERSION"

# Update marketplace
update_marketplace_version "$PLUGIN_NAME" "$PREVIOUS_VERSION"

# Notify team
send_notification "Rolled back $PLUGIN_NAME to $PREVIOUS_VERSION"

Monitoring & Alerts

Health Checks

# monitoring/health_check.yml
checks:
  - name: plugin_availability
    url: https://marketplace.example.com/api/plugins/my-plugin
    interval: 5m
    
  - name: command_execution
    command: claude /my-plugin health-check
    interval: 10m
    
  - name: version_check
    script: |
      CURRENT=$(claude plugin list | grep my-plugin | awk '{print $2}')
      EXPECTED=$(cat .claude-plugin/plugin.json | jq -r .version)
      [ "$CURRENT" = "$EXPECTED" ]
    interval: 1h

Metrics Collection

# scripts/collect_metrics.py

import json
import requests
from datetime import datetime

def collect_plugin_metrics():
    metrics = {
        "timestamp": datetime.now().isoformat(),
        "plugin": "my-plugin",
        "installations": get_installation_count(),
        "daily_active_users": get_dau(),
        "command_usage": get_command_stats(),
        "error_rate": get_error_rate()
    }
    
    # Send to monitoring service
    requests.post(
        "https://metrics.example.com/api/plugins",
        json=metrics
    )

Best Practices

  1. Version Everything

    • Tag releases with semantic versions
    • Keep changelog updated
    • Version lock dependencies
  2. Automate Testing

    • Run tests on every commit
    • Block merges without passing tests
    • Include security scanning
  3. Progressive Rollout

    • Deploy to staging first
    • Use canary releases for major changes
    • Have rollback plan ready
  4. Monitor Everything

    • Track installation success rate
    • Monitor command execution time
    • Alert on error spikes
  5. Documentation

    • Update docs with releases
    • Include migration guides
    • Document breaking changes