Skip to content

Content pipelines: Update content #21

Content pipelines: Update content

Content pipelines: Update content #21

name: 'Content pipelines: Update content'
# **What it does**: On a schedule, runs the content pipeline update script for each
# configured entry. The script clones each source repo, detects changes, and
# runs a Copilot agent to update content articles. The workflow handles
# branching, committing, and opening PRs.
# **Why we have it**: Keeps reference documentation in sync with upstream source
# docs without storing copies of those source docs in this repository.
# **Who does it impact**: Docs content writers, docs engineering.
#
# To add a new entry, add it to src/content-pipelines/config.yml and to the matrix
# `include` list below (only `id` is needed). The update logic lives in
# src/content-pipelines/scripts/update.ts, which reads config.yml for all other
# values. Run locally: npx tsx src/content-pipelines/scripts/update.ts --help
on:
schedule:
- cron: '20 16 * * 1-5' # Mon-Fri at 16:20 UTC
workflow_dispatch:
permissions:
contents: write
pull-requests: write
env:
HUSKY: 0
jobs:
update:
if: github.repository == 'github/docs-internal'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# Each entry only needs `id`. Everything else (source-repo,
# source-path, target-articles, etc.) is read from
# src/content-pipelines/config.yml by the update script.
- id: copilot-cli
# - id: mcp-server
steps:
- name: Checkout docs-internal
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: ./.github/actions/node-npm-setup
- name: Install Copilot CLI
run: npm install -g @github/copilot@prerelease
- name: Derive branch name
id: branch
run: echo "update_branch=docs/content-pipeline-${{ matrix.id }}-update" >> "$GITHUB_OUTPUT"
- name: Check for existing PR
id: check-pr
env:
GH_TOKEN: ${{ github.token }}
UPDATE_BRANCH: ${{ steps.branch.outputs.update_branch }}
run: |
PR_NUMBER=$(gh pr list --head "$UPDATE_BRANCH" --state open --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
- name: Setup branch
id: setup-branch
env:
UPDATE_BRANCH: ${{ steps.branch.outputs.update_branch }}
PR_NUMBER: ${{ steps.check-pr.outputs.pr_number }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git ls-remote --exit-code --heads origin "$UPDATE_BRANCH" > /dev/null 2>&1; then
git fetch --unshallow origin "$UPDATE_BRANCH" main 2>/dev/null || git fetch origin "$UPDATE_BRANCH" main
git checkout "$UPDATE_BRANCH"
git merge origin/main --no-edit || {
echo "Merge conflict with main — resetting branch to main"
git merge --abort 2>/dev/null || true
git checkout -f main
git branch -D "$UPDATE_BRANCH"
if [ -z "$PR_NUMBER" ]; then
git push origin --delete "$UPDATE_BRANCH" || true
else
echo "Skipping remote branch delete — PR #$PR_NUMBER is open"
echo "force_push=true" >> "$GITHUB_OUTPUT"
fi
git checkout -b "$UPDATE_BRANCH"
}
else
git checkout -b "$UPDATE_BRANCH"
fi
- name: Run content pipeline update script
env:
GH_TOKEN: ${{ secrets.DOCS_BOT_PAT_BASE }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COPILOT_GITHUB_TOKEN: ${{ secrets.DOCS_BOT_PAT_COPILOT }}
run: npx tsx src/content-pipelines/scripts/update.ts --id "${{ matrix.id }}"
- name: Commit changes
id: commit
env:
ID: ${{ matrix.id }}
run: |
git add content/ data/
if git diff --cached --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No documentation changes to commit"
else
git add "src/content-pipelines/state/${ID}.sha"
git commit -m "docs: update ${ID} content from source docs" \
-m "Updated by the content-pipeline-update agent (${ID}) via GitHub Actions." \
-m "Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Push changes
if: steps.commit.outputs.has_changes == 'true'
env:
UPDATE_BRANCH: ${{ steps.branch.outputs.update_branch }}
FORCE_PUSH: ${{ steps.setup-branch.outputs.force_push }}
run: |
if [ "$FORCE_PUSH" = "true" ]; then
echo "Force-pushing to align branch after merge conflict reset"
git push --force-with-lease origin "$UPDATE_BRANCH"
else
git push origin "$UPDATE_BRANCH"
fi
- name: Read source repo info from config
id: source-info
env:
PIPELINE_ID: ${{ matrix.id }}
run: |
SOURCE_REPO=$(yq -r ".[\"${PIPELINE_ID}\"].\"source-repo\"" src/content-pipelines/config.yml)
SOURCE_PATH=$(yq -r ".[\"${PIPELINE_ID}\"].\"source-path\"" src/content-pipelines/config.yml)
echo "source_repo=$SOURCE_REPO" >> "$GITHUB_OUTPUT"
echo "source_path=$SOURCE_PATH" >> "$GITHUB_OUTPUT"
- name: Create or update PR
if: steps.commit.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ secrets.DOCS_BOT_PAT_BASE }}
UPDATE_BRANCH: ${{ steps.branch.outputs.update_branch }}
PIPELINE_ID: ${{ matrix.id }}
SOURCE_REPO: ${{ steps.source-info.outputs.source_repo }}
SOURCE_PATH: ${{ steps.source-info.outputs.source_path }}
run: |
PR_NUMBER="${{ steps.check-pr.outputs.pr_number }}"
PR_TITLE="docs: update ${PIPELINE_ID} content from source docs"
SOURCE_LINK="See the [upstream repo](https://github.com/${SOURCE_REPO}/tree/main/${SOURCE_PATH}) for changes that triggered this update."
if [ -n "$PR_NUMBER" ]; then
echo "PR #$PR_NUMBER already exists"
echo "Ensuring PR #$PR_NUMBER is marked ready for review"
gh pr ready "$PR_NUMBER" || echo "Unable to mark PR #$PR_NUMBER as ready (it may already be ready)"
else
echo "Creating new PR"
PR_BODY="_GitHub Copilot generated this pull request._"$'\n\n'
PR_BODY+="> [!NOTE]"$'\n'
PR_BODY+="> This PR is **automatically generated** by the [content pipeline update workflow](${{ github.server_url }}/${{ github.repository }}/actions/workflows/content-pipelines.yml). Each run adds a new commit with any documentation changes detected."$'\n\n'
PR_BODY+="## What this does"$'\n\n'
PR_BODY+="Runs the \`content-pipeline-update\` agent (${PIPELINE_ID}) against the latest source docs and updates official articles under \`content/\` that have fallen out of sync."$'\n\n'
PR_BODY+="## Source changes"$'\n\n'
PR_BODY+="${SOURCE_LINK}"$'\n\n'
PR_BODY+="## Review"$'\n\n'
PR_BODY+="* Review each commit for accuracy — the agent uses AI, so spot-check important changes"$'\n'
PR_BODY+="* To adjust agent behavior, see [Modifying results](${{ github.server_url }}/${{ github.repository }}/blob/main/src/content-pipelines/README.md#modifying-results)"$'\n'
PR_BODY+="* Once satisfied, merge to keep docs up to date"$'\n'
PR_BODY+="* A new PR will be created on the next run if there are further changes"
gh pr create \
--title "$PR_TITLE" \
--body "$PR_BODY" \
--base main \
--head "$UPDATE_BRANCH" \
--label "workflow-generated,content-pipeline-update,ready-for-doc-review,skip FR board"
fi
- uses: ./.github/actions/slack-alert
if: ${{ failure() && github.event_name != 'workflow_dispatch' }}
with:
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
- uses: ./.github/actions/create-workflow-failure-issue
if: ${{ failure() && github.event_name != 'workflow_dispatch' }}
with:
token: ${{ secrets.DOCS_BOT_PAT_BASE }}