mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
`actions/checkout` configures git's http.extraheader to authenticate
as GITHUB_TOKEN, which update.yml scopes to `contents: read`. The
subsequent `git push --force` in open_pr.sh therefore tripped 403
("Permission to infinito-nexus/core.git denied to github-actions[bot]")
even though the token-minting step had already produced an App
installation token with `contents: write` in GH_TOKEN.
Drop checkout's extraheader and rewrite origin's URL to embed
GH_TOKEN as x-access-token before pushing, so the App's permissions
actually reach the wire.
Also pin two actionlint `-ignore` patterns in scripts/lint/action.sh
to silence false positives for the `client-id` input on
actions/create-github-app-token@v3 (the action's action.yml carries
the input and marks `app-id` deprecated; actionlint 1.7.12's bundled
schema lags behind the upstream release).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
130 lines
3.8 KiB
Bash
Executable File
130 lines
3.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
: "${GH_TOKEN:?Missing GH_TOKEN}"
|
|
: "${UPDATE_BRANCH_PREFIX:?Missing UPDATE_BRANCH_PREFIX}"
|
|
: "${UPDATE_COMMIT_MESSAGE:?Missing UPDATE_COMMIT_MESSAGE}"
|
|
: "${UPDATE_PR_TITLE:?Missing UPDATE_PR_TITLE}"
|
|
: "${UPDATE_PR_BODY:?Missing UPDATE_PR_BODY}"
|
|
|
|
UPDATE_BASE_BRANCH="${UPDATE_BASE_BRANCH:-master}"
|
|
UPDATE_BRANCH_SUFFIX="${UPDATE_BRANCH_SUFFIX:-$(date +%Y%m%d)}"
|
|
BRANCH="${UPDATE_BRANCH_PREFIX}-${UPDATE_BRANCH_SUFFIX}"
|
|
|
|
if ! command -v gh >/dev/null 2>&1; then
|
|
echo "ERROR: gh CLI not found." >&2
|
|
exit 1
|
|
fi
|
|
|
|
REPO="$(git remote get-url origin | sed 's|.*github\.com[:/]||' | sed 's|\.git$||')"
|
|
OWNER="${REPO%/*}"
|
|
|
|
if [[ -n "${APP_SLUG:-}" ]]; then
|
|
BOT_LOGIN="${APP_SLUG}[bot]"
|
|
BOT_USER_ID="$(gh api "/users/${BOT_LOGIN}" --jq .id)"
|
|
git config user.name "${BOT_LOGIN}"
|
|
git config user.email "${BOT_USER_ID}+${BOT_LOGIN}@users.noreply.github.com"
|
|
else
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
fi
|
|
git checkout -B "${BRANCH}"
|
|
|
|
if (($# > 0)); then
|
|
git add -- "$@"
|
|
else
|
|
git add -A
|
|
fi
|
|
|
|
if git diff --cached --quiet; then
|
|
echo "No staged update changes found."
|
|
exit 0
|
|
fi
|
|
|
|
git commit -m "${UPDATE_COMMIT_MESSAGE}"
|
|
|
|
LOCAL_TREE="$(git log -1 --format=%T HEAD)"
|
|
echo "Local tree: ${LOCAL_TREE}"
|
|
|
|
mapfile -t OPEN_PRS < <(
|
|
gh pr list \
|
|
--repo "${REPO}" \
|
|
--state open \
|
|
--base "${UPDATE_BASE_BRANCH}" \
|
|
--limit 100 \
|
|
--json number,headRefName \
|
|
--jq ".[] | select(.headRefName != \"${BRANCH}\") | \"\(.number)\t\(.headRefName)\""
|
|
)
|
|
|
|
DUPLICATE_PR=""
|
|
for entry in "${OPEN_PRS[@]}"; do
|
|
[[ -z "${entry}" ]] && continue
|
|
pr_num="${entry%%$'\t'*}"
|
|
pr_branch="${entry##*$'\t'}"
|
|
pr_head="$(gh pr view "${pr_num}" --repo "${REPO}" --json headRefOid --jq .headRefOid 2>/dev/null || true)"
|
|
if [[ -z "${pr_head}" ]] || ! git fetch --quiet --depth 1 origin "${pr_head}" 2>/dev/null; then
|
|
echo " open PR #${pr_num} (${pr_branch}): head unfetchable; treating as non-duplicate"
|
|
continue
|
|
fi
|
|
pr_tree="$(git log -1 --format=%T "${pr_head}" 2>/dev/null || true)"
|
|
echo " open PR #${pr_num} (${pr_branch}): tree ${pr_tree}"
|
|
if [[ -n "${pr_tree}" && "${pr_tree}" == "${LOCAL_TREE}" ]]; then
|
|
DUPLICATE_PR="${pr_num}"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ -n "${DUPLICATE_PR}" ]]; then
|
|
echo "Open PR #${DUPLICATE_PR} already carries this exact tree. Skipping push and PR creation."
|
|
exit 0
|
|
fi
|
|
|
|
git config --local --unset-all "http.https://github.com/.extraheader" 2>/dev/null || true
|
|
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git"
|
|
git push --force origin "${BRANCH}"
|
|
|
|
PR_NUMBER="$(
|
|
gh pr list \
|
|
--repo "${REPO}" \
|
|
--head "${BRANCH}" \
|
|
--base "${UPDATE_BASE_BRANCH}" \
|
|
--json number \
|
|
--jq '.[0].number // empty'
|
|
)"
|
|
|
|
if [[ -n "${PR_NUMBER}" ]]; then
|
|
echo "Updating existing PR #${PR_NUMBER} for ${BRANCH}"
|
|
gh pr edit "${PR_NUMBER}" \
|
|
--repo "${REPO}" \
|
|
--title "${UPDATE_PR_TITLE}" \
|
|
--body "${UPDATE_PR_BODY}"
|
|
else
|
|
echo "Creating PR for ${BRANCH}"
|
|
PR_URL="$(
|
|
gh pr create \
|
|
--repo "${REPO}" \
|
|
--title "${UPDATE_PR_TITLE}" \
|
|
--body "${UPDATE_PR_BODY}" \
|
|
--base "${UPDATE_BASE_BRANCH}" \
|
|
--head "${OWNER}:${BRANCH}"
|
|
)"
|
|
PR_NUMBER="${PR_URL##*/}"
|
|
echo "Created PR #${PR_NUMBER}: ${PR_URL}"
|
|
fi
|
|
|
|
# Close every other open PR in the same update class as superseded. A
|
|
# PR is in the same class when its branch starts with the configured
|
|
# UPDATE_BRANCH_PREFIX, so e.g. an image-versions run only retires
|
|
# stale image-versions branches and never touches repository-refs,
|
|
# skills, or human-authored PRs.
|
|
for entry in "${OPEN_PRS[@]}"; do
|
|
[[ -z "${entry}" ]] && continue
|
|
pr_branch="${entry##*$'\t'}"
|
|
[[ "${pr_branch}" == "${UPDATE_BRANCH_PREFIX}-"* ]] || continue
|
|
pr_num="${entry%%$'\t'*}"
|
|
echo "Closing superseded PR #${pr_num} (${pr_branch})"
|
|
gh pr close "${pr_num}" \
|
|
--repo "${REPO}" \
|
|
--comment "Superseded by #${PR_NUMBER}."
|
|
done
|