Skip to content

Wizard CI

Wizard CI #33

Workflow file for this run

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>\"}
]
}
]
}"