Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/readme-auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: README auto-merge

# Approves and merges the daily PR opened by update-readme.yml's App token.
# Closely modelled on robotiq.github.io's dependabot-auto-merge.yml.
#
# Why this isn't self-approval: prToMain's required-review rule blocks a
# PR's *author* from approving it, not a different actor. The App
# (robotiq-readme-bot[bot]) opens the PR; this workflow's own GITHUB_TOKEN
# (github-actions[bot]) submits the approval. Two distinct actors, so
# GitHub accepts it — the same arrangement that merges Dependabot's PRs on
# robotiq.github.io today.
#
# Why this is safe to merge without further human review: prToMain also
# requires the `test` status check (test.yml) to pass, and `gh pr merge
# --auto` queues the merge without completing it until that check reports
# success. A change that breaks scripts/update-readme.mjs therefore sits as
# an open, failing PR instead of reaching the org's public landing page.
# Nothing here bypasses a rule; it satisfies them.
#
# Why plain `pull_request` and not `pull_request_target`: the dependabot
# equivalent needs pull_request_target only because GitHub hands a
# read-only, secret-less token to `pull_request` runs on Dependabot PRs.
# That restriction doesn't apply here — this PR comes from a branch in this
# repo — so the safer trigger works. Note this job deliberately has NO
# checkout step; don't add one, since approving and merging code that the
# job has also checked out with a write-capable token is the footgun both
# triggers are guarding against.

on:
pull_request:
branches: [main]

permissions:
contents: write
pull-requests: write

jobs:
automerge:
# Both conditions matter: the actor check stops this from approving
# anyone else's PR, and the branch check keeps it to the generated one
# even if the App is ever used for something else.
if: >-
github.actor == 'robotiq-readme-bot[bot]' &&
github.event.pull_request.head.ref == 'auto/update-readme'
runs-on: ubuntu-latest
steps:
- name: Approve
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: gh pr review --approve "$PR_URL"

- name: Enable auto-merge
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: gh pr merge --auto --squash --delete-branch "$PR_URL"
14 changes: 9 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
name: Test

# Runs scripts/update-readme.test.mjs on every PR into main. Intended to be
# added as a required status check on the prToMain ruleset, so a PR that
# breaks scripts/update-readme.mjs (including one opened by
# update-readme.yml's own daily run) can't merge — see the "required check"
# discussion on PR #2.
# Runs scripts/update-readme.test.mjs on every PR into main, as the `test`
# required status check on the prToMain ruleset.
#
# This gates human PRs and the daily automated one alike. The latter only
# works because update-readme.yml opens its PR with a GitHub App token:
# GitHub doesn't trigger workflow runs from events created by GITHUB_TOKEN,
# so a PR opened with that token would never start this workflow and its
# required check would never report — see the note at the top of
# update-readme.yml.

on:
pull_request:
Expand Down
115 changes: 55 additions & 60 deletions .github/workflows/update-readme.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,108 +4,103 @@ name: Update README
# scripts/update-readme.mjs): the repository table (from the GitHub API) and
# the software tools tables (imported from robotiq/robotiq.github.io's
# docs/intro.mdx). Opens a PR only when the regenerated content actually
# differs, then auto-merges it — mirroring
# robotiq.github.io's .github/workflows/dependabot-auto-merge.yml.
# differs; readme-auto-merge.yml then approves and merges it. Fully
# automatic, no human review, no PAT, no bot account, and no ruleset bypass
# — every prToMain rule stays enforced against every actor.
#
# Why this needs a PAT instead of just GITHUB_TOKEN: the prToMain ruleset's
# required-review rule blocks a PR's *author* from approving its own PR. In
# dependabot-auto-merge.yml, Dependabot (a distinct actor) opens the PR and
# GITHUB_TOKEN (github-actions[bot]) approves it — two different actors, so
# it's not self-approval. Here there's no Dependabot; this workflow itself
# generates the content. So the "open PR" step authenticates as
# README_BOT_PAT (a fine-grained PAT scoped to just this repo, Contents +
# Pull requests: write) instead of GITHUB_TOKEN, making its author a
# distinct actor from the github-actions[bot] identity that then approves
# and merges it in the next step.
# Why the PR is opened with a GitHub App token and not GITHUB_TOKEN:
#
# Why this is safe to merge without further human review: the generated
# content is either the GitHub API's own repo descriptions or
# robotiq.github.io's own published docs/intro.mdx — both already public and
# already reviewed upstream. What actually gates the merge is the "Test"
# workflow (.github/workflows/test.yml) as a required status check on the
# prToMain ruleset: `gh pr merge --auto` waits on it, so a change that breaks
# update-readme.mjs sits as an open, failing PR instead of reaching the org's
# public landing page. Without that required check, `--auto` has nothing to
# wait on and errors out instead of merging — see the review on PR #2.
# 1. GitHub does not trigger workflow runs from events created by
# GITHUB_TOKEN. A PR opened with it would never start test.yml, so the
# required `test` check would sit "expected — waiting for status"
# forever and the PR would never become mergeable.
# 2. prToMain requires one approving review, and GitHub blocks a PR's own
# author from approving it. The approval in readme-auto-merge.yml comes
# from GITHUB_TOKEN (github-actions[bot]); that only works if some
# *other* actor opened the PR.
#
# Also requires: `allow_auto_merge` enabled on this repo (Settings → General
# → Pull Requests), and the commit's author email resolving to a real GitHub
# account (below) so prToMain's require_extra_approval_for_unattributed_changes
# rule doesn't kick in and demand a second approval nothing here can produce.
# The `robotiq-readme-bot` App satisfies both: it's a distinct actor from
# github-actions[bot], and it isn't tied to anyone's personal account, so
# there's no rotation/offboarding problem. This is the same two-actor shape
# as robotiq.github.io's dependabot-auto-merge.yml, with the App standing in
# for Dependabot.

on:
schedule:
- cron: '17 6 * * *'
workflow_dispatch: {}

# The App token does all the writing; this job's own GITHUB_TOKEN only needs
# to read the checkout.
permissions:
contents: write
pull-requests: write
contents: read

concurrency:
group: update-readme
cancel-in-progress: false

jobs:
update-readme:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Mint App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- uses: actions/checkout@v4
with:
token: ${{ secrets.README_BOT_PAT }}
# Persist the App token as the credential git push will use, so
# the push (and the PR it produces) is attributed to the App.
token: ${{ steps.app-token.outputs.token }}

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Regenerate README
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
run: node scripts/update-readme.mjs

- name: Open PR if content changed
id: pr
env:
GH_TOKEN: ${{ secrets.README_BOT_PAT }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
if git diff --quiet -- profile/README.md; then
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi

# Fixed name rather than one timestamped per run: a run that lands
# before yesterday's PR merged (still waiting on the required
# check) force-pushes onto the same branch/PR instead of piling up
# a new branch and a new unmergeable PR each day.
# before yesterday's PR merged force-pushes onto the same
# branch/PR instead of piling up a new branch and a new PR each
# day.
BRANCH="auto/update-readme"
git config user.name "robotiq-readme-bot"
# This must resolve to a real GitHub account (github-actions[bot]'s
# own noreply address) or the prToMain ruleset's
# require_extra_approval_for_unattributed_changes rule treats the
# commit as unattributed and demands a second approval this
# workflow has no way to produce.
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

# The App's bot identity, so the commit is attributed to a real
# GitHub account — prToMain sets
# require_extra_approval_for_unattributed_changes, which would
# demand a second approval for commits it can't attribute.
git config user.name "robotiq-readme-bot[bot]"
git config user.email "332551394+robotiq-readme-bot[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add profile/README.md
git commit -m "chore: update README repository table and software tools section"
git push --force origin "$BRANCH"

EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json url --jq '.[0].url')
# `// empty` because --jq '.[0].url' prints the literal string
# "null" on an empty list, which [ -n ] would treat as a hit.
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json url --jq '.[0].url // empty')
if [ -n "$EXISTING_PR" ]; then
PR_URL="$EXISTING_PR"
# Already open: the force-push above fires a `synchronize` event,
# which re-runs readme-auto-merge.yml against it.
echo "Updated existing PR: $EXISTING_PR"
else
PR_URL=$(gh pr create \
gh pr create \
--base main \
--head "$BRANCH" \
--title "chore: update README repository table" \
--body "Automated daily refresh of the repository table (GitHub API) and software tools section (robotiq.github.io docs/intro.mdx) — see \`scripts/update-readme.mjs\`.")
--body "Automated daily refresh of the repository table (GitHub API) and software tools section (robotiq.github.io docs/intro.mdx) — see \`scripts/update-readme.mjs\`."
fi

echo "changed=true" >> "$GITHUB_OUTPUT"
echo "url=$PR_URL" >> "$GITHUB_OUTPUT"

- name: Approve and auto-merge
if: steps.pr.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ steps.pr.outputs.url }}
run: |
gh pr review --approve "$PR_URL"
gh pr merge --auto --squash --delete-branch "$PR_URL"
Loading