initial project setup: added plugin skill
This commit is contained in:
@@ -0,0 +1,765 @@
|
||||
# CI/CD Integration Guide
|
||||
|
||||
Complete guide for automating plugin testing, validation, and deployment.
|
||||
|
||||
## GitHub Actions
|
||||
|
||||
### Basic Validation Workflow
|
||||
```yaml
|
||||
# .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
|
||||
```yaml
|
||||
# .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
|
||||
```yaml
|
||||
# .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
|
||||
```yaml
|
||||
# .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
|
||||
```yaml
|
||||
# .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
|
||||
```groovy
|
||||
// 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
|
||||
```yaml
|
||||
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
|
||||
```python
|
||||
# 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
|
||||
```python
|
||||
# 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
|
||||
```yaml
|
||||
# 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
|
||||
```yaml
|
||||
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
|
||||
```bash
|
||||
#!/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
|
||||
```yaml
|
||||
# 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
|
||||
```python
|
||||
# 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
|
||||
Reference in New Issue
Block a user