Wizard CI #33
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: Wizard CI | |
| permissions: | |
| contents: read | |
| on: | |
| # Scheduled runs (Wed at 11 AM EST / 4 PM UTC) | |
| schedule: | |
| - cron: '0 16 * * 3' | |
| # Manual and programmatic triggers | |
| workflow_dispatch: | |
| inputs: | |
| app: | |
| description: 'App path (e.g., "next-js/15-app-router-saas"), category (e.g., "next-js"), or "all"' | |
| required: false | |
| type: string | |
| default: '' | |
| evaluate: | |
| description: 'Run PR evaluator' | |
| type: boolean | |
| default: true | |
| base_branch: | |
| description: 'Base branch for PR' | |
| type: string | |
| default: 'main' | |
| wizard_ref: | |
| description: 'Wizard repo branch/tag/sha' | |
| type: string | |
| default: 'main' | |
| examples_ref: | |
| description: 'Examples repo branch/tag/sha' | |
| type: string | |
| default: 'main' | |
| posthog_ref: | |
| description: 'PostHog repo branch/tag/sha (for MCP)' | |
| type: string | |
| default: 'master' | |
| posthog_region: | |
| description: 'PostHog region (us or eu)' | |
| required: false | |
| type: choice | |
| options: | |
| - us | |
| - eu | |
| default: 'us' | |
| trigger_id: | |
| description: 'Trigger ID (auto-generated if empty)' | |
| type: string | |
| default: '' | |
| notify_slack: | |
| description: 'Post notifications to Slack' | |
| type: boolean | |
| default: false | |
| # Webhook trigger from external repos (wizard, examples, posthog) | |
| # POST https://api.github.com/repos/{owner}/{repo}/dispatches | |
| # Body: {"event_type": "wizard-ci-trigger", "client_payload": {"app": "all", "source": "wizard"}} | |
| repository_dispatch: | |
| types: [wizard-ci-trigger] | |
| jobs: | |
| # ============================================================================ | |
| # DISCOVER: Find and select apps to run | |
| # ============================================================================ | |
| discover: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| apps: ${{ steps.select-apps.outputs.apps }} | |
| app_count: ${{ steps.select-apps.outputs.app_count }} | |
| first_app: ${{ steps.select-apps.outputs.first_app }} | |
| trigger_id: ${{ steps.generate-id.outputs.trigger_id }} | |
| # Resolved inputs (from workflow_dispatch, repository_dispatch, or schedule) | |
| input_app: ${{ steps.resolve-inputs.outputs.app }} | |
| input_evaluate: ${{ steps.resolve-inputs.outputs.evaluate }} | |
| input_base_branch: ${{ steps.resolve-inputs.outputs.base_branch }} | |
| input_wizard_ref: ${{ steps.resolve-inputs.outputs.wizard_ref }} | |
| input_examples_ref: ${{ steps.resolve-inputs.outputs.examples_ref }} | |
| input_posthog_ref: ${{ steps.resolve-inputs.outputs.posthog_ref }} | |
| input_posthog_region: ${{ steps.resolve-inputs.outputs.posthog_region }} | |
| input_source: ${{ steps.resolve-inputs.outputs.source }} | |
| input_notify_slack: ${{ steps.resolve-inputs.outputs.notify_slack }} | |
| # PR notification outputs (for upstream PR triggers) | |
| input_notify_pr: ${{ steps.resolve-inputs.outputs.notify_pr }} | |
| input_source_repo: ${{ steps.resolve-inputs.outputs.source_repo }} | |
| input_source_pr_number: ${{ steps.resolve-inputs.outputs.source_pr_number }} | |
| input_source_pr_url: ${{ steps.resolve-inputs.outputs.source_pr_url }} | |
| steps: | |
| - name: Resolve inputs | |
| id: resolve-inputs | |
| run: | | |
| # Determine source and resolve inputs based on trigger type | |
| if [ "${{ github.event_name }}" = "schedule" ]; then | |
| # Scheduled run - use defaults for all apps with Slack notifications | |
| APP="all" | |
| EVALUATE="true" | |
| BASE_BRANCH="main" | |
| WIZARD_REF="main" | |
| EXAMPLES_REF="main" | |
| POSTHOG_REF="master" | |
| POSTHOG_REGION="us" | |
| TRIGGER_ID="" | |
| SOURCE="scheduled" | |
| NOTIFY_SLACK="true" | |
| # No PR notification for scheduled runs | |
| NOTIFY_PR="false" | |
| SOURCE_REPO="" | |
| SOURCE_PR_NUMBER="" | |
| SOURCE_PR_URL="" | |
| elif [ "${{ github.event_name }}" = "repository_dispatch" ]; then | |
| # Webhook trigger - use client_payload with defaults | |
| APP="${{ github.event.client_payload.app || 'all' }}" | |
| EVALUATE="${{ github.event.client_payload.evaluate || 'true' }}" | |
| BASE_BRANCH="${{ github.event.client_payload.base_branch || 'main' }}" | |
| WIZARD_REF="${{ github.event.client_payload.wizard_ref || 'main' }}" | |
| EXAMPLES_REF="${{ github.event.client_payload.examples_ref || 'main' }}" | |
| POSTHOG_REF="${{ github.event.client_payload.posthog_ref || 'master' }}" | |
| POSTHOG_REGION="${{ github.event.client_payload.posthog_region || 'us' }}" | |
| TRIGGER_ID="${{ github.event.client_payload.trigger_id || '' }}" | |
| # Map source values to readable labels | |
| RAW_SOURCE="${{ github.event.client_payload.source || 'dispatch' }}" | |
| case "$RAW_SOURCE" in | |
| wizard) SOURCE="wizard repo" ;; | |
| examples) SOURCE="examples repo" ;; | |
| *) SOURCE="$RAW_SOURCE" ;; | |
| esac | |
| NOTIFY_SLACK="${{ github.event.client_payload.notify_slack || 'false' }}" | |
| # PR notification inputs (for upstream PR triggers) | |
| NOTIFY_PR="${{ github.event.client_payload.notify_pr || 'false' }}" | |
| SOURCE_REPO="${{ github.event.client_payload.source_repo || '' }}" | |
| SOURCE_PR_NUMBER="${{ github.event.client_payload.source_pr_number || '' }}" | |
| SOURCE_PR_URL="${{ github.event.client_payload.source_pr_url || '' }}" | |
| else | |
| # workflow_dispatch - use inputs directly | |
| APP="${{ inputs.app || 'all' }}" | |
| EVALUATE="${{ inputs.evaluate }}" | |
| BASE_BRANCH="${{ inputs.base_branch }}" | |
| WIZARD_REF="${{ inputs.wizard_ref }}" | |
| EXAMPLES_REF="${{ inputs.examples_ref }}" | |
| POSTHOG_REF="${{ inputs.posthog_ref }}" | |
| POSTHOG_REGION="${{ inputs.posthog_region }}" | |
| TRIGGER_ID="${{ inputs.trigger_id }}" | |
| SOURCE="manual" | |
| NOTIFY_SLACK="${{ inputs.notify_slack }}" | |
| # No PR notification for manual runs | |
| NOTIFY_PR="false" | |
| SOURCE_REPO="" | |
| SOURCE_PR_NUMBER="" | |
| SOURCE_PR_URL="" | |
| fi | |
| echo "app=$APP" >> $GITHUB_OUTPUT | |
| echo "evaluate=$EVALUATE" >> $GITHUB_OUTPUT | |
| echo "base_branch=$BASE_BRANCH" >> $GITHUB_OUTPUT | |
| echo "wizard_ref=$WIZARD_REF" >> $GITHUB_OUTPUT | |
| echo "examples_ref=$EXAMPLES_REF" >> $GITHUB_OUTPUT | |
| echo "posthog_ref=$POSTHOG_REF" >> $GITHUB_OUTPUT | |
| echo "posthog_region=$POSTHOG_REGION" >> $GITHUB_OUTPUT | |
| echo "trigger_id=$TRIGGER_ID" >> $GITHUB_OUTPUT | |
| echo "source=$SOURCE" >> $GITHUB_OUTPUT | |
| echo "notify_slack=$NOTIFY_SLACK" >> $GITHUB_OUTPUT | |
| echo "notify_pr=$NOTIFY_PR" >> $GITHUB_OUTPUT | |
| echo "source_repo=$SOURCE_REPO" >> $GITHUB_OUTPUT | |
| echo "source_pr_number=$SOURCE_PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "source_pr_url=$SOURCE_PR_URL" >> $GITHUB_OUTPUT | |
| echo "Resolved inputs:" | |
| echo " app: $APP" | |
| echo " evaluate: $EVALUATE" | |
| echo " base_branch: $BASE_BRANCH" | |
| echo " wizard_ref: $WIZARD_REF" | |
| echo " examples_ref: $EXAMPLES_REF" | |
| echo " posthog_ref: $POSTHOG_REF" | |
| echo " posthog_region: $POSTHOG_REGION" | |
| echo " source: $SOURCE" | |
| echo " notify_slack: $NOTIFY_SLACK" | |
| echo " notify_pr: $NOTIFY_PR" | |
| echo " source_repo: $SOURCE_REPO" | |
| echo " source_pr_number: $SOURCE_PR_NUMBER" | |
| echo " source_pr_url: $SOURCE_PR_URL" | |
| - name: Generate trigger ID | |
| id: generate-id | |
| run: | | |
| TRIGGER_ID="${{ steps.resolve-inputs.outputs.trigger_id }}" | |
| if [ -n "$TRIGGER_ID" ]; then | |
| echo "Using provided trigger ID: $TRIGGER_ID" | |
| else | |
| TRIGGER_ID=$(openssl rand -hex 4 | cut -c1-7) | |
| echo "Generated trigger ID: $TRIGGER_ID" | |
| fi | |
| echo "trigger_id=$TRIGGER_ID" >> $GITHUB_OUTPUT | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Find available apps | |
| id: find-apps | |
| run: | | |
| # Find all directories with package.json in apps/ | |
| APPS=$(find apps -name "package.json" -not -path "*/node_modules/*" \ | |
| | sed 's|apps/||' | sed 's|/package.json||' | sort) | |
| # Convert to JSON array | |
| APPS_JSON=$(echo "$APPS" | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| echo "apps=$APPS_JSON" >> $GITHUB_OUTPUT | |
| echo "Available apps:" | |
| echo "$APPS" | |
| - name: Select apps to run | |
| id: select-apps | |
| run: | | |
| INPUT_APP="${{ steps.resolve-inputs.outputs.app }}" | |
| AVAILABLE='${{ steps.find-apps.outputs.apps }}' | |
| if [ -z "$INPUT_APP" ] || [ "$INPUT_APP" = "all" ]; then | |
| # Run all apps | |
| SELECTED="$AVAILABLE" | |
| echo "Running all apps" | |
| else | |
| # Check if it's an exact app match | |
| if echo "$AVAILABLE" | jq -e --arg app "$INPUT_APP" 'index($app) != null' > /dev/null; then | |
| SELECTED="[\"$INPUT_APP\"]" | |
| echo "Running single app: $INPUT_APP" | |
| else | |
| # Check if it's a category/prefix (e.g., "next-js" matches "next-js/15-app-router-todo") | |
| MATCHED=$(echo "$AVAILABLE" | jq -c --arg prefix "$INPUT_APP/" '[.[] | select(startswith($prefix))]') | |
| MATCH_COUNT=$(echo "$MATCHED" | jq 'length') | |
| if [ "$MATCH_COUNT" -gt 0 ]; then | |
| SELECTED="$MATCHED" | |
| echo "Running $MATCH_COUNT app(s) matching prefix: $INPUT_APP" | |
| else | |
| echo "::error::App or category '$INPUT_APP' not found." | |
| echo "Available apps:" | |
| echo "$AVAILABLE" | jq -r '.[]' | |
| echo "" | |
| echo "Available categories:" | |
| echo "$AVAILABLE" | jq -r '.[]' | cut -d'/' -f1 | sort -u | |
| exit 1 | |
| fi | |
| fi | |
| fi | |
| echo "apps=$SELECTED" >> $GITHUB_OUTPUT | |
| APP_COUNT=$(echo "$SELECTED" | jq 'length') | |
| echo "app_count=$APP_COUNT" >> $GITHUB_OUTPUT | |
| FIRST_APP=$(echo "$SELECTED" | jq -r '.[0]') | |
| echo "first_app=$FIRST_APP" >> $GITHUB_OUTPUT | |
| echo "Selected $APP_COUNT app(s) to run" | |
| # ============================================================================ | |
| # NOTIFY-START: Post initial Slack notification (if enabled) | |
| # ============================================================================ | |
| notify-start: | |
| needs: discover | |
| runs-on: ubuntu-latest | |
| if: needs.discover.outputs.input_notify_slack == 'true' | |
| outputs: | |
| thread_ts: ${{ steps.slack.outputs.thread_ts }} | |
| steps: | |
| - name: Post to Slack | |
| id: slack | |
| run: | | |
| RESPONSE=$(curl -s -X POST https://slack.com/api/chat.postMessage \ | |
| -H "Authorization: Bearer ${{ secrets.SLACK_BOT_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "channel": "${{ secrets.SLACK_CHANNEL_ID }}", | |
| "text": "Wizard CI Started - Trigger ID: ${{ needs.discover.outputs.trigger_id }}", | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "fields": [ | |
| {"type": "mrkdwn", "text": "*Trigger ID:* `${{ needs.discover.outputs.trigger_id }}`"}, | |
| {"type": "mrkdwn", "text": "*Apps:* ${{ needs.discover.outputs.app_count }}"}, | |
| {"type": "mrkdwn", "text": "*Source:* ${{ needs.discover.outputs.input_source }}"}, | |
| {"type": "mrkdwn", "text": "*Workflow:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>"} | |
| ] | |
| }, | |
| { | |
| "type": "context", | |
| "elements": [ | |
| {"type": "mrkdwn", "text": "_AlaakuhhZAAM!_ :magic_wand: Creating wizard runs..."} | |
| ] | |
| } | |
| ] | |
| }') | |
| echo "Slack API response: $RESPONSE" | |
| THREAD_TS=$(echo "$RESPONSE" | jq -r '.ts') | |
| if [ "$THREAD_TS" = "null" ] || [ -z "$THREAD_TS" ]; then | |
| echo "::warning::Failed to get Slack thread_ts. Response: $RESPONSE" | |
| echo "thread_ts=" >> $GITHUB_OUTPUT | |
| else | |
| echo "thread_ts=$THREAD_TS" >> $GITHUB_OUTPUT | |
| fi | |
| # ============================================================================ | |
| # NOTIFY-PR-START: Create initial PR comment with pending status (if enabled) | |
| # ============================================================================ | |
| notify-pr-start: | |
| needs: discover | |
| runs-on: ubuntu-latest | |
| if: needs.discover.outputs.input_notify_pr == 'true' && needs.discover.outputs.input_source_pr_number != '' | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.GH_APP_POSTHOG_WIZARD_CI_BOT_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_POSTHOG_WIZARD_CI_BOT_PRIVATE_KEY }} | |
| owner: PostHog | |
| repositories: ${{ needs.discover.outputs.input_source_repo }} | |
| - name: Create initial PR comment | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ steps.app-token.outputs.token }} | |
| script: | | |
| const owner = 'PostHog'; | |
| const repo = '${{ needs.discover.outputs.input_source_repo }}'; | |
| const issueNumber = ${{ needs.discover.outputs.input_source_pr_number }}; | |
| const triggerId = '${{ needs.discover.outputs.trigger_id }}'; | |
| const allApps = ${{ needs.discover.outputs.apps }}; | |
| const workflowUrl = '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'; | |
| const wizardRef = '${{ needs.discover.outputs.input_wizard_ref }}'; | |
| const examplesRef = '${{ needs.discover.outputs.input_examples_ref }}'; | |
| const posthogRef = '${{ needs.discover.outputs.input_posthog_ref }}'; | |
| const prsUrl = workflowUrl.replace('/actions/runs/', '/pulls?q=') + triggerId; | |
| const marker = '<!-- wizard-ci-results:' + triggerId + ' -->'; | |
| const rows = allApps.map(function(a) { | |
| return '| `' + a + '` | ⏳ | pending |'; | |
| }).join('\n'); | |
| const body = [ | |
| marker, | |
| '## 🧙 Wizard CI Results', | |
| '', | |
| '**Trigger ID:** `' + triggerId + '`', | |
| '**Workflow:** [View run](' + workflowUrl + ')', | |
| '', | |
| '| App | Confidence | PR |', | |
| '|-----|------------|-----|', | |
| rows, | |
| '', | |
| '### Configuration', | |
| '', | |
| '| Setting | Value |', | |
| '|---------|-------|', | |
| '| Wizard ref | `' + wizardRef + '` |', | |
| '| Examples ref | `' + examplesRef + '` |', | |
| '| PostHog ref | `' + posthogRef + '` |', | |
| '', | |
| '> Search for trigger ID `' + triggerId + '` in [wizard-workbench PRs](' + prsUrl + ').' | |
| ].join('\n'); | |
| await github.rest.issues.createComment({ | |
| owner: owner, | |
| repo: repo, | |
| issue_number: issueNumber, | |
| body: body | |
| }); | |
| console.log('Created initial PR comment with trigger ID: ' + triggerId); | |
| # ============================================================================ | |
| # WIZARD-CI: Run wizard on each app (matrix strategy) | |
| # ============================================================================ | |
| wizard-ci: | |
| needs: [discover, notify-start, notify-pr-start] | |
| if: always() && needs.discover.result == 'success' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 10 | |
| matrix: | |
| app: ${{ fromJson(needs.discover.outputs.apps) }} | |
| outputs: | |
| pr_url: ${{ steps.run-wizard.outputs.pr_url }} | |
| pr_number: ${{ steps.run-wizard.outputs.pr_number }} | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.GH_APP_POSTHOG_WIZARD_CI_BOT_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_POSTHOG_WIZARD_CI_BOT_PRIVATE_KEY }} | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 9 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Install GitHub CLI | |
| run: | | |
| curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg | |
| echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null | |
| sudo apt-get update | |
| sudo apt-get install -y gh | |
| - name: Clone and build Wizard CLI | |
| run: | | |
| echo "::group::Cloning wizard repo" | |
| git clone --depth 1 --branch ${{ needs.discover.outputs.input_wizard_ref }} \ | |
| https://github.com/PostHog/wizard.git $HOME/wizard | |
| echo "::endgroup::" | |
| echo "::group::Installing wizard dependencies" | |
| cd $HOME/wizard | |
| pnpm install --frozen-lockfile | |
| echo "::endgroup::" | |
| echo "::group::Building wizard" | |
| pnpm build | |
| echo "::endgroup::" | |
| echo "WIZARD_PATH=$HOME/wizard" >> $GITHUB_ENV | |
| - name: Clone and build Examples | |
| run: | | |
| echo "::group::Cloning examples repo" | |
| git clone --depth 1 --branch ${{ needs.discover.outputs.input_examples_ref }} \ | |
| https://github.com/PostHog/examples.git $HOME/examples | |
| echo "::endgroup::" | |
| echo "::group::Installing examples dependencies" | |
| cd $HOME/examples | |
| npm install | |
| echo "::endgroup::" | |
| echo "::group::Building examples MCP resources" | |
| npm run build | |
| echo "::endgroup::" | |
| echo "EXAMPLES_PATH=$HOME/examples" >> $GITHUB_ENV | |
| - name: Clone PostHog monorepo and install MCP | |
| run: | | |
| echo "::group::Cloning PostHog monorepo (sparse checkout)" | |
| git clone --depth 1 --branch ${{ needs.discover.outputs.input_posthog_ref }} \ | |
| --filter=blob:none --sparse \ | |
| https://github.com/PostHog/posthog.git $HOME/posthog | |
| cd $HOME/posthog | |
| git sparse-checkout set services/mcp | |
| echo "::endgroup::" | |
| echo "::group::Installing MCP dependencies" | |
| cd $HOME/posthog/services/mcp/typescript | |
| # Use --ignore-workspace to avoid resolving all 45 workspace packages | |
| pnpm install --ignore-workspace --no-frozen-lockfile | |
| echo "::endgroup::" | |
| echo "MCP_PATH=$HOME/posthog/services/mcp" >> $GITHUB_ENV | |
| - name: Configure Git | |
| run: | | |
| git config user.name "wizard-ci[bot]" | |
| git config user.email "wizard-ci[bot]@users.noreply.github.com" | |
| # Switch remote to HTTPS for token-based auth | |
| git remote set-url origin "https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}.git" | |
| - name: Run Wizard CI | |
| id: run-wizard | |
| run: | | |
| # Start examples dev server in background (serves resources at localhost:8765) | |
| cd $EXAMPLES_PATH | |
| npm run dev & | |
| EXAMPLES_PID=$! | |
| # Wait for examples server to be ready | |
| echo "Waiting for examples server to start..." | |
| for i in {1..30}; do | |
| if curl -s http://localhost:8765/examples-mcp-resources.zip > /dev/null 2>&1; then | |
| echo "Examples server is ready" | |
| break | |
| fi | |
| sleep 1 | |
| done | |
| # Start MCP server in background (consumes from local examples server) | |
| cd $MCP_PATH/typescript | |
| pnpm dev:local-resources & | |
| MCP_PID=$! | |
| # Wait for MCP server to be ready | |
| echo "Waiting for MCP server to start..." | |
| for i in {1..30}; do | |
| if curl -s http://localhost:8787/mcp > /dev/null 2>&1; then | |
| echo "MCP server is ready" | |
| break | |
| fi | |
| sleep 1 | |
| done | |
| # Run wizard-ci (always runs in CI mode internally) | |
| cd $GITHUB_WORKSPACE | |
| CMD="pnpm wizard-ci --app ${{ matrix.app }} --base ${{ needs.discover.outputs.input_base_branch }}" | |
| if [ -n "${{ needs.discover.outputs.trigger_id }}" ]; then | |
| CMD="$CMD --trigger-id ${{ needs.discover.outputs.trigger_id }}" | |
| fi | |
| if [ "${{ needs.discover.outputs.input_evaluate }}" = "true" ]; then | |
| CMD="$CMD --evaluate" | |
| fi | |
| $CMD 2>&1 | tee wizard-output.log | |
| WIZARD_EXIT=$? | |
| # Save resources zips from running server (for artifact upload) | |
| curl -s http://localhost:8765/examples-mcp-resources.zip -o examples-mcp-resources.zip || true | |
| curl -s http://localhost:8765/skills-mcp-resources.zip -o skills-mcp-resources.zip || true | |
| # Stop servers | |
| kill $MCP_PID 2>/dev/null || true | |
| kill $EXAMPLES_PID 2>/dev/null || true | |
| # Extract PR URL | |
| PR_URL=$(grep -oP 'PR: \K(https://github.com/[^\s]+)' wizard-output.log || true) | |
| if [ -n "$PR_URL" ]; then | |
| echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT | |
| PR_NUM=$(echo $PR_URL | grep -oP '/pull/\K\d+') | |
| echo "pr_number=$PR_NUM" >> $GITHUB_OUTPUT | |
| fi | |
| # Extract confidence score from evaluator output (e.g., "### Confidence score: 4/5") | |
| CONFIDENCE=$(grep -o 'Confidence score: [0-9]/[0-9]' wizard-output.log | head -1 | sed 's/Confidence score: //' || echo "") | |
| echo "confidence_score=$CONFIDENCE" >> $GITHUB_OUTPUT | |
| echo "Extracted confidence score: '$CONFIDENCE'" | |
| exit $WIZARD_EXIT | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| # PostHog credentials | |
| POSTHOG_REGION: ${{ needs.discover.outputs.input_posthog_region }} | |
| POSTHOG_PERSONAL_API_KEY: ${{ secrets.GH_APP_POSTHOG_WIZARD_CI_BOT_POSTHOG_PERSONAL_KEY }} | |
| # Dependency paths | |
| WIZARD_PATH: ${{ env.WIZARD_PATH }} | |
| EXAMPLES_PATH: ${{ env.EXAMPLES_PATH }} | |
| MCP_PATH: ${{ env.MCP_PATH }} | |
| # Dependency refs (for PR body) | |
| WIZARD_REF: ${{ needs.discover.outputs.input_wizard_ref }} | |
| EXAMPLES_REF: ${{ needs.discover.outputs.input_examples_ref }} | |
| POSTHOG_REF: ${{ needs.discover.outputs.input_posthog_ref }} | |
| # Source info (for PR body) | |
| CI_SOURCE: ${{ needs.discover.outputs.input_source }} | |
| - name: Prepare artifact name | |
| # Not used for anything rn | |
| if: always() | |
| id: artifact-name | |
| run: | | |
| # Replace slashes with dashes for valid artifact name | |
| SAFE_NAME="${{ matrix.app }}" | |
| SAFE_NAME="${SAFE_NAME//\//-}" | |
| echo "safe_name=$SAFE_NAME" >> $GITHUB_OUTPUT | |
| - name: Upload examples resources | |
| # Only upload once (from the first matrix job) since the zip is identical across all jobs | |
| if: always() && !env.ACT && matrix.app == needs.discover.outputs.first_app | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: examples-resources-${{ github.run_id }} | |
| path: examples-mcp-resources.zip | |
| if-no-files-found: warn | |
| - name: Upload skills resources | |
| # Only upload once (from the first matrix job) since the zip is identical across all jobs | |
| if: always() && !env.ACT && matrix.app == needs.discover.outputs.first_app | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: skills-resources-${{ github.run_id }} | |
| path: skills-mcp-resources.zip | |
| if-no-files-found: warn | |
| - name: Label and close PR | |
| if: steps.run-wizard.outputs.pr_number | |
| run: | | |
| gh pr edit ${{ steps.run-wizard.outputs.pr_number }} --add-label "CI/CD" | |
| gh pr close ${{ steps.run-wizard.outputs.pr_number }} | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "## Wizard CI Results: ${{ matrix.app }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Trigger ID:** \`${{ needs.discover.outputs.trigger_id }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "**App:** ${{ matrix.app }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Evaluate:** ${{ needs.discover.outputs.input_evaluate }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Dependency Versions" >> $GITHUB_STEP_SUMMARY | |
| echo "- **wizard:** ${{ needs.discover.outputs.input_wizard_ref }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **examples:** ${{ needs.discover.outputs.input_examples_ref }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **mcp (posthog):** ${{ needs.discover.outputs.input_posthog_ref }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -n "${{ steps.run-wizard.outputs.pr_url }}" ]; then | |
| echo "**PR:** ${{ steps.run-wizard.outputs.pr_url }}" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| - name: Get job ID | |
| if: always() && (needs.notify-start.outputs.thread_ts != '' || needs.discover.outputs.input_notify_pr == 'true') | |
| id: job-id | |
| run: | | |
| # Fetch the current job ID from the GitHub API | |
| JOB_ID=$(curl -s -H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \ | |
| "${{ github.api_url }}/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs" \ | |
| | jq -r '.jobs[] | select(.name | contains("${{ matrix.app }}")) | .id' | head -1) | |
| echo "job_id=$JOB_ID" >> $GITHUB_OUTPUT | |
| echo "Job ID: $JOB_ID" | |
| - name: Notify Slack | |
| if: always() && needs.notify-start.outputs.thread_ts != '' | |
| run: | | |
| CONFIDENCE="${{ steps.run-wizard.outputs.confidence_score }}" | |
| if [ -z "$CONFIDENCE" ]; then | |
| CONFIDENCE="N/A" | |
| fi | |
| # Build job-specific logs URL | |
| JOB_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ steps.job-id.outputs.job_id }}" | |
| if [ -n "${{ steps.run-wizard.outputs.pr_url }}" ]; then | |
| PR_FIELD="<${{ steps.run-wizard.outputs.pr_url }}|#${{ steps.run-wizard.outputs.pr_number }}> (<$JOB_URL|logs>)" | |
| else | |
| PR_FIELD="Failed (<$JOB_URL|logs>)" | |
| fi | |
| curl -s -X POST https://slack.com/api/chat.postMessage \ | |
| -H "Authorization: Bearer ${{ secrets.SLACK_BOT_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"channel\": \"${{ secrets.SLACK_CHANNEL_ID }}\", | |
| \"thread_ts\": \"${{ needs.notify-start.outputs.thread_ts }}\", | |
| \"text\": \"${{ matrix.app }}\", | |
| \"blocks\": [ | |
| { | |
| \"type\": \"section\", | |
| \"fields\": [ | |
| {\"type\": \"mrkdwn\", \"text\": \"*App:* ${{ matrix.app }}\"}, | |
| {\"type\": \"mrkdwn\", \"text\": \"*Confidence:* $CONFIDENCE\"}, | |
| {\"type\": \"mrkdwn\", \"text\": \"*PR:* $PR_FIELD\"} | |
| ] | |
| } | |
| ] | |
| }" | |
| - name: Generate token for source repo | |
| if: always() && needs.discover.outputs.input_notify_pr == 'true' && needs.discover.outputs.input_source_pr_number != '' | |
| id: source-repo-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.GH_APP_POSTHOG_WIZARD_CI_BOT_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_POSTHOG_WIZARD_CI_BOT_PRIVATE_KEY }} | |
| owner: PostHog | |
| repositories: ${{ needs.discover.outputs.input_source_repo }} | |
| - name: Update source PR comment | |
| if: always() && needs.discover.outputs.input_notify_pr == 'true' && needs.discover.outputs.input_source_pr_number != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ steps.source-repo-token.outputs.token }} | |
| script: | | |
| const owner = 'PostHog'; | |
| const repo = '${{ needs.discover.outputs.input_source_repo }}'; | |
| const issueNumber = ${{ needs.discover.outputs.input_source_pr_number }}; | |
| const triggerId = '${{ needs.discover.outputs.trigger_id }}'; | |
| const app = '${{ matrix.app }}'; | |
| const confidence = '${{ steps.run-wizard.outputs.confidence_score }}' || 'N/A'; | |
| const prUrl = '${{ steps.run-wizard.outputs.pr_url }}'; | |
| const prNumber = '${{ steps.run-wizard.outputs.pr_number }}'; | |
| const jobUrl = '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ steps.job-id.outputs.job_id }}'; | |
| // Build this job's result row | |
| const prField = prUrl ? '[#' + prNumber + '](' + prUrl + ') ([logs](' + jobUrl + '))' : 'Failed ([logs](' + jobUrl + '))'; | |
| const thisRow = '| `' + app + '` | ' + confidence + ' | ' + prField + ' |'; | |
| // Marker to identify our comment (created by notify-pr-start job) | |
| const marker = '<!-- wizard-ci-results:' + triggerId + ' -->'; | |
| // Find existing comment | |
| const comments = await github.rest.issues.listComments({ | |
| owner: owner, | |
| repo: repo, | |
| issue_number: issueNumber, | |
| per_page: 100 | |
| }); | |
| const existingComment = comments.data.find(function(c) { return c.body.includes(marker); }); | |
| if (!existingComment) { | |
| console.log('Could not find results comment with marker: ' + marker); | |
| return; | |
| } | |
| // Update existing comment - replace this app's row | |
| var body = existingComment.body; | |
| const escapedApp = app.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| const rowPattern = new RegExp('\\| `' + escapedApp + '` \\|[^\\n]*\\|', 'g'); | |
| if (body.match(rowPattern)) { | |
| body = body.replace(rowPattern, thisRow); | |
| } | |
| await github.rest.issues.updateComment({ | |
| owner: owner, | |
| repo: repo, | |
| comment_id: existingComment.id, | |
| body: body | |
| }); | |
| console.log('Updated comment for ' + app); | |
| # ============================================================================ | |
| # SUMMARY: Final summary of all triggered workflows | |
| # ============================================================================ | |
| summary: | |
| needs: [discover, notify-start, notify-pr-start, wizard-ci] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Final Summary | |
| run: | | |
| echo "## Wizard CI Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Trigger ID:** \`${{ needs.discover.outputs.trigger_id }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "**Source:** ${{ needs.discover.outputs.input_source }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Total apps:** ${{ needs.discover.outputs.app_count }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Apps" >> $GITHUB_STEP_SUMMARY | |
| echo '${{ needs.discover.outputs.apps }}' | jq -r '.[] | "- " + .' >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Configuration" >> $GITHUB_STEP_SUMMARY | |
| echo "| Setting | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|---------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Trigger ID | \`${{ needs.discover.outputs.trigger_id }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Source | ${{ needs.discover.outputs.input_source }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Evaluate | ${{ needs.discover.outputs.input_evaluate }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Base Branch | ${{ needs.discover.outputs.input_base_branch }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| PostHog Region | ${{ needs.discover.outputs.input_posthog_region }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Wizard Ref | ${{ needs.discover.outputs.input_wizard_ref }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Examples Ref | ${{ needs.discover.outputs.input_examples_ref }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| PostHog Ref | ${{ needs.discover.outputs.input_posthog_ref }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "> Search for trigger ID \`${{ needs.discover.outputs.trigger_id }}\` to find all related PRs." >> $GITHUB_STEP_SUMMARY | |
| - name: Update Slack message | |
| if: needs.notify-start.outputs.thread_ts != '' | |
| run: | | |
| curl -s -X POST https://slack.com/api/chat.update \ | |
| -H "Authorization: Bearer ${{ secrets.SLACK_BOT_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"channel\": \"${{ secrets.SLACK_CHANNEL_ID }}\", | |
| \"ts\": \"${{ needs.notify-start.outputs.thread_ts }}\", | |
| \"text\": \"Wizard CI Completed - Trigger ID: ${{ needs.discover.outputs.trigger_id }}\", | |
| \"blocks\": [ | |
| { | |
| \"type\": \"section\", | |
| \"fields\": [ | |
| {\"type\": \"mrkdwn\", \"text\": \"*Trigger ID:* \`${{ needs.discover.outputs.trigger_id }}\`\"}, | |
| {\"type\": \"mrkdwn\", \"text\": \"*Apps:* ${{ needs.discover.outputs.app_count }}\"}, | |
| {\"type\": \"mrkdwn\", \"text\": \"*Source:* ${{ needs.discover.outputs.input_source }}\"}, | |
| {\"type\": \"mrkdwn\", \"text\": \"*Workflow:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>\"} | |
| ] | |
| }, | |
| { | |
| \"type\": \"context\", | |
| \"elements\": [ | |
| {\"type\": \"mrkdwn\", \"text\": \"_AlaakuhhZAAM!_ :magic_wand: <${{ github.server_url }}/${{ github.repository }}/pulls?q=sort%3Aupdated-desc+is%3Apr+${{ needs.discover.outputs.trigger_id }}|See all created wizard runs>\"} | |
| ] | |
| } | |
| ] | |
| }" | |