180 lines
7.1 KiB
YAML
180 lines
7.1 KiB
YAML
name: Docs Preview
|
|
|
|
# Builds the docs site for a PR and deploys it to Cloudflare Pages.
|
|
#
|
|
# Security: the build executes Python from the PR (mkdocstrings imports
|
|
# src/mcp, `!!python/name:` config directives run, and heads may ship their
|
|
# own build scripts). The build is gated by `authorize` (admin sender for
|
|
# auto-preview, admin/maintainer commenter for /preview-docs) and isolated
|
|
# from Cloudflare secrets — `build` runs PR code with no secrets and hands
|
|
# the static site to `deploy` via an artifact, so PR code never shares a
|
|
# runner with the Cloudflare token. `authorize` and `comment` run
|
|
# .github/scripts/docs_preview.js, checked out from the default branch only.
|
|
#
|
|
# Required configuration:
|
|
# - secrets.CLOUDFLARE_API_TOKEN (scope: Account → Cloudflare Pages → Edit)
|
|
# - secrets.CLOUDFLARE_ACCOUNT_ID
|
|
# - vars.CLOUDFLARE_PAGES_PROJECT (existing Pages project, e.g. mcp-python-sdk-docs)
|
|
|
|
on:
|
|
pull_request_target: # zizmor: ignore[dangerous-triggers] build is permission-gated and secret-isolated; see header comment
|
|
types: [opened, reopened, synchronize]
|
|
paths:
|
|
- docs/**
|
|
- docs_src/**
|
|
- i18n/**
|
|
- mkdocs.yml
|
|
- scripts/docs/**
|
|
- pyproject.toml
|
|
issue_comment:
|
|
types: [created]
|
|
|
|
permissions: {}
|
|
|
|
concurrency:
|
|
# Workflow-level concurrency is evaluated when the run is queued — before any
|
|
# job-level `if:` — so an unrelated PR comment would otherwise cancel an
|
|
# in-flight build. Only runs that actually produce a preview share a group;
|
|
# everything else falls through to a unique run_id group.
|
|
group: >-
|
|
docs-preview-pr-${{
|
|
github.event_name == 'pull_request_target' && github.event.pull_request.number
|
|
|| (github.event.issue.pull_request && startsWith(github.event.comment.body, '/preview-docs') && github.event.issue.number)
|
|
|| github.run_id
|
|
}}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
authorize:
|
|
if: >-
|
|
github.event_name == 'pull_request_target' ||
|
|
(github.event.issue.pull_request && startsWith(github.event.comment.body, '/preview-docs'))
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
outputs:
|
|
authorized: ${{ steps.check.outputs.authorized }}
|
|
pr_number: ${{ steps.check.outputs.pr_number }}
|
|
head_sha: ${{ steps.check.outputs.head_sha }}
|
|
slash_attempt: ${{ steps.check.outputs.slash_attempt }}
|
|
steps:
|
|
- name: Check out the scripts (default branch)
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
sparse-checkout: .github/scripts
|
|
|
|
- name: Determine authorization
|
|
id: check
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
with:
|
|
script: |
|
|
const { authorize } = require('./.github/scripts/docs_preview.js');
|
|
await authorize({ github, context, core });
|
|
|
|
build:
|
|
needs: authorize
|
|
if: needs.authorize.outputs.authorized == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
ref: ${{ needs.authorize.outputs.head_sha }}
|
|
persist-credentials: false
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
with:
|
|
# pull_request_target runs share the base-branch Actions cache; saving
|
|
# a cache populated while untrusted PR code ran would let it poison
|
|
# later trusted workflows. Mirrors publish-pypi.yml.
|
|
enable-cache: false
|
|
version: 0.9.5
|
|
|
|
# pull_request_target runs this workflow file from the base branch, so
|
|
# the whole recipe — dependency sync included — must come from the
|
|
# checkout itself: heads that ship scripts/docs/build.sh (the Zensical
|
|
# toolchain) build with it; older heads, and v1.x heads previewed via
|
|
# /preview-docs, still build with MkDocs. Both arms must write the site
|
|
# to site/. Keep the detection in sync with build_site() in
|
|
# scripts/build-docs.sh.
|
|
- run: |
|
|
if [ -f scripts/docs/build.sh ]; then
|
|
bash scripts/docs/build.sh
|
|
else
|
|
uv sync --frozen --group docs
|
|
# The env var silences mkdocs-material's MkDocs 2.0 warning banner.
|
|
NO_MKDOCS_2_WARNING=1 uv run --frozen --no-sync mkdocs build
|
|
fi
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: site
|
|
path: site/
|
|
retention-days: 1
|
|
# An empty site/ means the build arm broke its output contract; fail
|
|
# here instead of surfacing as a confusing download error in deploy.
|
|
if-no-files-found: error
|
|
|
|
deploy:
|
|
needs: [authorize, build]
|
|
if: needs.authorize.outputs.authorized == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions: {}
|
|
outputs:
|
|
deployment_url: ${{ steps.wrangler.outputs.deployment-url }}
|
|
alias_url: ${{ steps.wrangler.outputs.pages-deployment-alias-url }}
|
|
steps:
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: site
|
|
path: site
|
|
|
|
- name: Deploy to Cloudflare Pages
|
|
id: wrangler
|
|
uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0
|
|
with:
|
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
packageManager: npm
|
|
command: >-
|
|
pages deploy ./site
|
|
--project-name=${{ vars.CLOUDFLARE_PAGES_PROJECT }}
|
|
--branch=pr-${{ needs.authorize.outputs.pr_number }}
|
|
--commit-hash=${{ needs.authorize.outputs.head_sha }}
|
|
--commit-dirty=true
|
|
|
|
comment:
|
|
needs: [authorize, build, deploy]
|
|
if: >-
|
|
always() &&
|
|
needs.deploy.result != 'cancelled' &&
|
|
(needs.authorize.outputs.authorized == 'true' || needs.authorize.outputs.slash_attempt == 'true')
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read # check out the script from the default branch
|
|
pull-requests: write
|
|
steps:
|
|
- name: Check out the scripts (default branch)
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
sparse-checkout: .github/scripts
|
|
|
|
- name: Post or update preview comment
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
env:
|
|
AUTHORIZED: ${{ needs.authorize.outputs.authorized }}
|
|
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
|
|
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
|
|
DEPLOY_RESULT: ${{ needs.deploy.result }}
|
|
DEPLOYMENT_URL: ${{ needs.deploy.outputs.deployment_url }}
|
|
ALIAS_URL: ${{ needs.deploy.outputs.alias_url }}
|
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
with:
|
|
script: |
|
|
const { comment } = require('./.github/scripts/docs_preview.js');
|
|
await comment({ github, context, core });
|