Repository navigation
Generate toolkit docs #264
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: Generate toolkit docs | |
| # Description: Generate toolkit JSON from the public tool catalog, then sync sidebar navigation. | |
| # Flow: | |
| # 1) Generate toolkit JSON into toolkit-docs-generator/data/toolkits | |
| # 2) Sync integrations sidebar _meta.tsx from toolkit-docs-generator/data/toolkits | |
| # 3) Regenerate public/llms.txt so the automation PR is self-contained | |
| # 4) Create or update a PR if changes were produced | |
| # | |
| # Opens the PR with GITHUB_TOKEN. That does not start other workflows, so this | |
| # job generates llms.txt here instead of depending on llmstxt.yml to fire. | |
| on: | |
| repository_dispatch: | |
| types: [porter_deploy_succeeded] | |
| workflow_dispatch: | |
| inputs: | |
| public_catalog_url: | |
| description: "Override public catalog URL (staging only; production is the default)" | |
| required: false | |
| type: string | |
| # 11:00 UTC = 3 AM PST / 4 AM PDT — late enough that DST drift doesn't matter. | |
| schedule: | |
| - cron: "0 11 * * *" | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # Read this run's jobs so the Slack alert can deep-link the failing job log | |
| # rather than the run summary. | |
| actions: read | |
| concurrency: | |
| group: generate-toolkit-docs | |
| cancel-in-progress: true | |
| jobs: | |
| generate: | |
| runs-on: ubuntu-latest | |
| # Opt in to Node 24 for JavaScript actions before GitHub forces the | |
| # switch on 2026-06-02. Harmless today; unblocks the cutover. | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| outputs: | |
| # Distinguishes "generation itself failed" from "a later step failed". | |
| # Without it, a broken notification would make the alert job announce a | |
| # generation failure that never happened. | |
| generation-succeeded: ${{ steps.generate-docs.outputs.succeeded }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate toolkit docs | |
| id: generate-docs | |
| # Invoked by path rather than through `pnpm exec`, which would reset the | |
| # working directory to the repo root and break the relative paths below. | |
| run: | | |
| ../node_modules/.bin/tsx src/cli/index.ts generate \ | |
| --all \ | |
| --skip-unchanged \ | |
| --preserve-last-known-good \ | |
| --verbose \ | |
| --api-source public-catalog \ | |
| --llm-provider anthropic \ | |
| --llm-model "claude-sonnet-4-6" \ | |
| --llm-api-key "$ANTHROPIC_API_KEY" \ | |
| --llm-max-tokens 8192 \ | |
| --llm-editor-provider anthropic \ | |
| --llm-editor-model "claude-sonnet-4-6" \ | |
| --llm-editor-api-key "$ANTHROPIC_API_KEY" \ | |
| --toolkit-concurrency 8 \ | |
| --llm-concurrency 15 \ | |
| --exclude-file ./remove-toolkits.txt \ | |
| --ignore-file ./skip-toolkits.txt \ | |
| --custom-sections ./curation \ | |
| --output data/toolkits | |
| echo "succeeded=true" >>"$GITHUB_OUTPUT" | |
| working-directory: toolkit-docs-generator | |
| env: | |
| # Unset in normal runs: the generator defaults to the production | |
| # experience API. Pass public_catalog_url on workflow_dispatch to | |
| # point a manual run at staging or a local BFF. | |
| PUBLIC_CATALOG_URL: ${{ inputs.public_catalog_url }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| - name: Sync toolkit sidebar navigation | |
| run: pnpm exec tsx toolkit-docs-generator/scripts/sync-toolkit-sidebar.ts --remove-empty-sections=false --verbose | |
| # GITHUB_TOKEN PRs do not start llmstxt.yml, so generate the file here. | |
| # The toolkit generator CLI still does not write llms.txt. | |
| - name: Generate llms.txt | |
| run: pnpm llmstxt | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| - name: Create pull request | |
| id: cpr | |
| uses: peter-evans/create-pull-request@v7 | |
| env: | |
| HUSKY: 0 | |
| with: | |
| token: ${{ github.token }} | |
| commit-message: "[AUTO] Adding MCP Servers docs update" | |
| title: "[AUTO] Adding MCP Servers docs update" | |
| body: | | |
| This PR was generated after a Porter deploy succeeded. | |
| - Trigger: ${{ github.event_name }} | |
| - Deploy env: ${{ github.event.client_payload.env || 'unknown' }} | |
| - Deploy SHA: ${{ github.event.client_payload.deploy_sha || 'unknown' }} | |
| - Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| # Stable branch so later runs update the open auto-PR instead of | |
| # opening a new one each time. create-pull-request force-pushes the | |
| # latest generated docs onto this branch. | |
| branch: automation/toolkit-docs | |
| delete-branch: true | |
| # A token without reviewer permission is a real gap, but it must not fail | |
| # the run: the generated PR is already open and useful without a reviewer | |
| # attached. An annotation keeps it visible instead of silent. | |
| - name: Request team review | |
| if: steps.cpr.outputs.pull-request-number != '' | |
| run: | | |
| if ! gh pr edit ${{ steps.cpr.outputs.pull-request-number }} \ | |
| --add-reviewer ArcadeAI/engineering-tools-and-dx 2>review-error.log; then | |
| echo "::warning::Could not request review on PR #${{ steps.cpr.outputs.pull-request-number }}: $(cat review-error.log)" | |
| fi | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Upload generation report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: failed-tools | |
| path: toolkit-docs-generator-verification/logs/failed-tools.json | |
| if-no-files-found: ignore | |
| # No `continue-on-error` here on purpose. A Slack step that dies quietly | |
| # is how a wrong webhook secret went unnoticed: the alert it was meant to | |
| # deliver is exactly what nobody was watching for. | |
| # | |
| # Gated on the generate step rather than on the default "everything so far | |
| # succeeded". Missing or stale pages are worth reporting even when a later | |
| # step broke — under the default gate a failed sidebar sync or PR creation | |
| # would skip this step, while `generation-succeeded` suppressed the alert | |
| # job below, and nobody heard about the toolkits at all. `!cancelled()` | |
| # rather than `always()` because `cancel-in-progress` cancels superseded | |
| # runs, and those have nothing to report. | |
| - name: Report preserved or omitted toolkits to Slack | |
| if: ${{ !cancelled() && steps.generate-docs.outputs.succeeded == 'true' }} | |
| run: | | |
| # The deep link is a nicety; the alert is the point. Actions runs this | |
| # with `-e -o pipefail`, so a failed `gh api` — or a SIGPIPE from | |
| # trimming its output — would abort the step before curl and lose the | |
| # alert entirely. Select the first match in jq instead of piping to | |
| # head, and fall back to the run URL on any failure. | |
| job_url=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs" \ | |
| --jq '[.jobs[] | select(.name == "generate") | .html_url] | .[0] // empty') || job_url="" | |
| payload=$(../node_modules/.bin/tsx src/cli/index.ts alert \ | |
| --report ../toolkit-docs-generator-verification/logs/failed-tools.json \ | |
| --log-url "${job_url:-$RUN_URL}") | |
| if [ -z "$payload" ]; then | |
| echo "No preserved or omitted toolkits to report." | |
| exit 0 | |
| fi | |
| if [ -z "$SLACK_WEBHOOK_URL" ]; then | |
| echo "::error::SLACK_PROJ_DOCS_WEBHOOK_URL is not configured" | |
| exit 1 | |
| fi | |
| curl --fail-with-body --silent --show-error \ | |
| -X POST \ | |
| -H "Content-Type: application/json" \ | |
| --data "$payload" \ | |
| "$SLACK_WEBHOOK_URL" | |
| working-directory: toolkit-docs-generator | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_PROJ_DOCS_WEBHOOK_URL }} | |
| alert: | |
| name: Alert on failure | |
| # Every red run gets a message. Which message depends on whether generation | |
| # itself broke or something after it did: those need different people to do | |
| # different things, and calling a post-generation failure a "generation | |
| # failure" sends someone hunting for a validation error that doesn't exist. | |
| if: ${{ !cancelled() && needs.generate.result == 'failure' }} | |
| needs: generate | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Notify #proj-docs in Slack | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_PROJ_DOCS_WEBHOOK_URL }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| TRIGGER: ${{ github.event_name }} | |
| COMMIT: ${{ github.sha }} | |
| GENERATION_SUCCEEDED: ${{ needs.generate.outputs.generation-succeeded }} | |
| run: | | |
| if [ -z "$SLACK_WEBHOOK_URL" ]; then | |
| echo "::error::SLACK_PROJ_DOCS_WEBHOOK_URL is not configured" | |
| exit 1 | |
| fi | |
| if [ "$GENERATION_SUCCEEDED" = "true" ]; then | |
| headline=":warning: Toolkit docs generated, but the workflow failed afterward" | |
| detail="Generation succeeded, so the toolkit JSON is fine. A later workflow step failed — inspect the run to determine whether sidebar sync, llms.txt generation, PR creation, artifact upload, or Slack notification needs attention." | |
| else | |
| headline=":rotating_light: Toolkit docs generation failed" | |
| detail="The failed run contains the exact file path and validation error." | |
| fi | |
| payload=$(jq -n \ | |
| --arg headline "$headline" \ | |
| --arg detail "$detail" \ | |
| --arg run_url "$RUN_URL" \ | |
| --arg trigger "$TRIGGER" \ | |
| --arg commit "$COMMIT" \ | |
| '{text: ($headline + "\n\n*Workflow run:* <" + $run_url + "|Open failed run>\n*Trigger:* " + $trigger + "\n*Commit:* " + $commit + "\n\n" + $detail)}') | |
| curl --fail-with-body --silent --show-error \ | |
| -X POST \ | |
| -H "Content-Type: application/json" \ | |
| --data "$payload" \ | |
| "$SLACK_WEBHOOK_URL" |