Marketplace Deploy (JeremyDev87/codingbuddy) #42
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: marketplace-deploy | |
| on: | |
| push: | |
| branches: | |
| - master | |
| paths: | |
| - '.claude-plugin/**' | |
| - 'packages/claude-code-plugin/.claude-plugin/**' | |
| - '.github/workflows/marketplace.yml' | |
| - '.github/scripts/marketplace-utils.sh' | |
| - '.github/templates/marketplace-index.html' | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| # Workflow-level condition for defense-in-depth | |
| # Prevents execution on forked repositories | |
| run-name: Marketplace Deploy (${{ github.repository }}) | |
| permissions: | |
| contents: read | |
| pages: write | |
| # Required for GitHub's OIDC-based Pages authentication | |
| id-token: write | |
| concurrency: | |
| group: 'pages' | |
| cancel-in-progress: false | |
| # Shared environment variables to avoid hardcoded paths (DRY) | |
| # Note: Repository name 'JeremyDev87/codingbuddy' is hardcoded in job conditions | |
| # because GitHub Actions env vars are not available in job-level 'if' expressions | |
| env: | |
| MARKETPLACE_JSON: .claude-plugin/marketplace.json | |
| SITE_DIR: _site | |
| jobs: | |
| validate: | |
| # Primary repository restriction - prevents forks from deploying | |
| if: github.repository == 'JeremyDev87/codingbuddy' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Validate marketplace.json | |
| run: | | |
| set -euo pipefail | |
| echo "Validating marketplace.json..." | |
| # Check if file exists | |
| if [ ! -f "$MARKETPLACE_JSON" ]; then | |
| echo "Error: $MARKETPLACE_JSON not found" | |
| exit 1 | |
| fi | |
| # Validate JSON syntax | |
| if ! jq empty "$MARKETPLACE_JSON" 2>/dev/null; then | |
| echo "Error: Invalid JSON syntax in $MARKETPLACE_JSON" | |
| exit 1 | |
| fi | |
| # Validate required fields | |
| NAME=$(jq -r '.name' "$MARKETPLACE_JSON") | |
| if [ "$NAME" == "null" ] || [ -z "$NAME" ]; then | |
| echo "Error: 'name' field is required" | |
| exit 1 | |
| fi | |
| PLUGINS=$(jq -r '.plugins' "$MARKETPLACE_JSON") | |
| if [ "$PLUGINS" == "null" ]; then | |
| echo "Error: 'plugins' array is required" | |
| exit 1 | |
| fi | |
| # Source shared utility functions | |
| source .github/scripts/marketplace-utils.sh | |
| # Validate each plugin | |
| PLUGIN_COUNT=$(get_plugin_count) | |
| echo "Found $PLUGIN_COUNT plugin(s)" | |
| for ((i=0; i<PLUGIN_COUNT; i++)); do | |
| if ! validate_plugin "$i"; then | |
| exit 1 | |
| fi | |
| PLUGIN_NAME=$(get_plugin_name "$i") | |
| echo "✓ Plugin '$PLUGIN_NAME' validated" | |
| done | |
| echo "" | |
| echo "✅ Marketplace validation passed!" | |
| build: | |
| # Defense-in-depth: Additional repository check | |
| if: github.repository == 'JeremyDev87/codingbuddy' | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup Pages | |
| uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 | |
| with: | |
| enablement: true | |
| - name: Prepare marketplace directory | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$SITE_DIR/.claude-plugin" | |
| # Copy marketplace.json | |
| cp "$MARKETPLACE_JSON" "$SITE_DIR/.claude-plugin/" | |
| # Source shared utility functions | |
| source .github/scripts/marketplace-utils.sh | |
| # Copy each plugin's .claude-plugin directory | |
| PLUGIN_COUNT=$(get_plugin_count) | |
| for ((i=0; i<PLUGIN_COUNT; i++)); do | |
| PLUGIN_NAME=$(get_plugin_name "$i") | |
| PLUGIN_SOURCE=$(get_plugin_source "$i") | |
| # Create plugin directory structure | |
| mkdir -p "$SITE_DIR/$PLUGIN_SOURCE/.claude-plugin" | |
| # Copy plugin files | |
| cp -r "$PLUGIN_SOURCE/.claude-plugin/"* "$SITE_DIR/$PLUGIN_SOURCE/.claude-plugin/" | |
| echo "✓ Copied plugin '$PLUGIN_NAME' from '$PLUGIN_SOURCE'" | |
| done | |
| # Copy index.html for human visitors | |
| cp .github/templates/marketplace-index.html "$SITE_DIR/index.html" | |
| echo "" | |
| echo "📦 Marketplace build complete!" | |
| echo "Contents of $SITE_DIR:" | |
| find "$SITE_DIR" -type f | |
| - name: Upload artifact | |
| uses: actions/upload-pages-artifact@0ca16172ca884f0a37117fed41734f29784cc980 # main (include-hidden-files support) | |
| with: | |
| path: '_site' | |
| include-hidden-files: true | |
| deploy: | |
| # Defense-in-depth: Additional repository check | |
| if: github.repository == 'JeremyDev87/codingbuddy' | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| outputs: | |
| page_url: ${{ steps.deployment.outputs.page_url }} | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 | |
| - name: Output deployment info | |
| env: | |
| PAGE_URL: ${{ steps.deployment.outputs.page_url }} | |
| run: | | |
| set -euo pipefail | |
| echo "🎉 Marketplace deployed!" | |
| echo "" | |
| echo "Landing page URL: $PAGE_URL" | |
| echo "" | |
| echo "To use this marketplace:" | |
| echo " claude marketplace add JeremyDev87/codingbuddy" | |
| echo " claude plugin install codingbuddy@jeremydev87" | |
| verify: | |
| # Post-deploy verification — catches silent deployment failures | |
| if: github.repository == 'JeremyDev87/codingbuddy' | |
| needs: deploy | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Verify deployed URLs | |
| env: | |
| BASE_URL: ${{ needs.deploy.outputs.page_url }} | |
| MARKETPLACE_JSON: .claude-plugin/marketplace.json | |
| run: | | |
| set -euo pipefail | |
| # Ensure BASE_URL ends with / | |
| BASE_URL="${BASE_URL%/}/" | |
| echo "Waiting 30s for CDN propagation..." | |
| sleep 30 | |
| # Source shared utility functions | |
| source .github/scripts/marketplace-utils.sh | |
| FAILED=0 | |
| verify_url() { | |
| local url=$1 | |
| local description=$2 | |
| local status | |
| status=$(curl -sI -o /dev/null -w '%{http_code}' --retry 3 --retry-delay 5 "$url") | |
| if [ "$status" != "200" ]; then | |
| echo "::error::$description returned HTTP $status (expected 200): $url" | |
| FAILED=1 | |
| else | |
| echo "✅ $description accessible (HTTP $status)" | |
| fi | |
| } | |
| # Verify landing page | |
| verify_url "${BASE_URL}" "Landing page" | |
| # Verify marketplace.json | |
| verify_url "${BASE_URL}.claude-plugin/marketplace.json" "marketplace.json" | |
| # Verify each plugin's plugin.json | |
| PLUGIN_COUNT=$(get_plugin_count) | |
| echo "Verifying $PLUGIN_COUNT plugin(s)..." | |
| for ((i=0; i<PLUGIN_COUNT; i++)); do | |
| PLUGIN_NAME=$(get_plugin_name "$i") | |
| PLUGIN_SOURCE=$(get_plugin_source "$i") | |
| # Remove leading ./ from source path | |
| PLUGIN_PATH="${PLUGIN_SOURCE#./}" | |
| verify_url "${BASE_URL}${PLUGIN_PATH}/.claude-plugin/plugin.json" "Plugin '$PLUGIN_NAME' plugin.json" | |
| done | |
| if [ "$FAILED" -ne 0 ]; then | |
| echo "" | |
| echo "❌ URL verification failed!" | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "✅ All deployed URLs verified successfully!" |