Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

288 lines
11 KiB
YAML

name: autofix.atom
on:
workflow_run:
workflows: [ci]
types: [completed]
permissions:
actions: read
contents: write
issues: write
pull-requests: read
concurrency:
group: autofix-${{ github.event.workflow_run.id }}
cancel-in-progress: false
jobs:
autofix:
name: Apply handoff fixes
if: ${{ github.repository == 'nexu-io/open-design' && github.event.workflow_run.event == 'pull_request' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Checkout trusted workflow code
uses: actions/checkout@v6.0.2
with:
repository: ${{ github.repository }}
ref: ${{ github.event.repository.default_branch }}
- name: Check handoff helper
run: python3 .github/scripts/handoff.py self-check
- name: Resolve autofix handoff artifacts
id: artifacts
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
RUN_ID: ${{ github.event.workflow_run.id }}
shell: bash
run: |
set -euo pipefail
pattern="$(python3 .github/scripts/handoff.py artifact-pattern autofix)"
count="$(
gh api "repos/$REPO/actions/runs/$RUN_ID/artifacts" \
--jq '.artifacts[]? | select(.expired == false and (.name | startswith("handoff-autofix-"))) | .name' \
| sed '/^$/d' \
| wc -l \
| tr -d ' '
)"
if [ "$count" = "0" ]; then
echo "found=false" >> "$GITHUB_OUTPUT"
exit 0
fi
{
echo "found=true"
echo "pattern=$pattern"
} >> "$GITHUB_OUTPUT"
- name: Download autofix handoff artifacts
if: ${{ steps.artifacts.outputs.found == 'true' }}
uses: actions/download-artifact@v8
with:
pattern: ${{ steps.artifacts.outputs.pattern }}
run-id: ${{ github.event.workflow_run.id }}
path: ${{ runner.temp }}/handoff-autofix
merge-multiple: false
github-token: ${{ github.token }}
- name: Read autofix handoffs
if: ${{ steps.artifacts.outputs.found == 'true' }}
id: handoffs
shell: bash
run: |
set -euo pipefail
entries="$RUNNER_TEMP/autofix-handoffs.jsonl"
python3 .github/scripts/handoff.py list autofix "$RUNNER_TEMP/handoff-autofix" > "$entries"
if [ ! -s "$entries" ]; then
echo "present=false" >> "$GITHUB_OUTPUT"
exit 0
fi
{
echo "present=true"
echo "entries=$entries"
} >> "$GITHUB_OUTPUT"
- name: Detect bot credentials
if: ${{ steps.handoffs.outputs.present == 'true' }}
id: bot-secrets
env:
BOT_APP_CLIENT_ID: ${{ secrets.BOT_APP_CLIENT_ID }}
BOT_APP_PRIVATE_KEY: ${{ secrets.BOT_APP_PRIVATE_KEY }}
shell: bash
run: |
set -euo pipefail
if [ -n "${BOT_APP_CLIENT_ID}" ] && [ -n "${BOT_APP_PRIVATE_KEY}" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
fi
- name: Generate Open Design bot token
if: ${{ steps.handoffs.outputs.present == 'true' && steps.bot-secrets.outputs.present == 'true' }}
id: bot-token
continue-on-error: true
uses: actions/create-github-app-token@v3.2.0
with:
client-id: ${{ secrets.BOT_APP_CLIENT_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
owner: nexu-io
repositories: open-design
permission-contents: write
- name: Apply handoff fixes
if: ${{ steps.handoffs.outputs.present == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
BOT_TOKEN: ${{ steps.bot-token.outputs.token }}
REPO: ${{ github.repository }}
RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
ENTRIES_PATH: ${{ steps.handoffs.outputs.entries }}
shell: bash
run: |
set -euo pipefail
upsert_nix_fallback_comment() {
local id="$1"
local pr_number="$2"
local patch_path="$3"
local reason="$4"
if [ "$id" != "nix-pnpm-deps" ]; then
echo "No fallback comment handler configured for autofix handoff $id."
return 0
fi
local marker="<!-- nix-hash-refresh -->"
local body_path="$RUNNER_TEMP/nix-hash-refresh-fallback-$id.md"
local payload_file="$RUNNER_TEMP/nix-hash-refresh-fallback-$id.json"
{
printf '%s\n' "$marker"
printf "A generated Nix hash refresh is available for this PR, but the automated same-repo push did not complete: %s\n\n" "$reason"
printf "Apply the diff below with \`git apply\`:\n\n"
printf '```diff\n'
cat "$patch_path"
printf '\n```\n'
} > "$body_path"
jq -n --rawfile body "$body_path" '{body: $body}' > "$payload_file"
local comment_id
comment_id="$(
gh api --paginate "repos/$REPO/issues/$pr_number/comments" \
| jq -r --arg marker "$marker" '.[] | select((.body // "") | contains($marker)) | .id' \
| tail -n 1 || true
)"
if [ -n "$comment_id" ]; then
gh api --method PATCH "repos/$REPO/issues/comments/$comment_id" --input "$payload_file" >/dev/null
echo "Updated Nix hash fallback comment on PR $pr_number."
else
gh api --method POST "repos/$REPO/issues/$pr_number/comments" --input "$payload_file" >/dev/null
echo "Created Nix hash fallback comment on PR $pr_number."
fi
}
clear_nix_fallback_comment() {
local id="$1"
local pr_number="$2"
if [ "$id" != "nix-pnpm-deps" ]; then
return 0
fi
local marker="<!-- nix-hash-refresh -->"
local comment_ids
comment_ids="$(
gh api --paginate "repos/$REPO/issues/$pr_number/comments" \
| jq -r --arg marker "$marker" '.[] | select((.body // "") | contains($marker)) | .id' || true
)"
if [ -z "$comment_ids" ]; then
echo "No stale Nix hash fallback comment found on PR $pr_number."
return 0
fi
while IFS= read -r comment_id; do
if [ -n "$comment_id" ]; then
gh api --method DELETE "repos/$REPO/issues/comments/$comment_id" >/dev/null
echo "Deleted stale Nix hash fallback comment $comment_id on PR $pr_number."
fi
done <<< "$comment_ids"
}
while IFS= read -r entry_json; do
id="$(jq -r '.id' <<< "$entry_json")"
pr_number="$(jq -r '.pr_number' <<< "$entry_json")"
head_sha="$(jq -r '.head_sha' <<< "$entry_json")"
base_sha="$(jq -r '.base_sha' <<< "$entry_json")"
patch_path="$(jq -r '.patch_path' <<< "$entry_json")"
commit_message="$(jq -r '.commit_message' <<< "$entry_json")"
allowed_paths_json="$(jq -c '.allowed_paths' <<< "$entry_json")"
if [ "$head_sha" != "$RUN_HEAD_SHA" ]; then
echo "Skipping autofix handoff $id because artifact head $head_sha does not match workflow_run head $RUN_HEAD_SHA."
continue
fi
pr_json="$(gh api "repos/$REPO/pulls/$pr_number")"
pr_state="$(jq -r '.state' <<< "$pr_json")"
pr_draft="$(jq -r '.draft' <<< "$pr_json")"
head_repo="$(jq -r '.head.repo.full_name // empty' <<< "$pr_json")"
head_ref="$(jq -r '.head.ref // empty' <<< "$pr_json")"
current_head="$(jq -r '.head.sha' <<< "$pr_json")"
current_base="$(jq -r '.base.sha' <<< "$pr_json")"
if [ "$pr_state" != "open" ]; then
echo "Skipping autofix handoff $id because PR $pr_number state is $pr_state."
continue
fi
if [ "$pr_draft" != "false" ]; then
echo "Skipping autofix handoff $id because PR $pr_number is draft."
continue
fi
if [ "$head_repo" != "$REPO" ]; then
echo "Skipping autofix handoff $id because PR $pr_number is from fork $head_repo."
continue
fi
if [ "$current_head" != "$head_sha" ]; then
echo "Skipping stale autofix handoff $id for $head_sha; current PR head is $current_head."
continue
fi
if [ "$current_base" != "$base_sha" ]; then
echo "Skipping stale autofix handoff $id for base $base_sha; current PR base is $current_base."
continue
fi
if [ -z "${BOT_TOKEN:-}" ]; then
echo "Skipping autofix handoff $id because bot token is unavailable."
upsert_nix_fallback_comment "$id" "$pr_number" "$patch_path" "bot credentials were unavailable"
continue
fi
work_dir="$RUNNER_TEMP/autofix-work-$id"
rm -rf "$work_dir"
git clone --no-checkout "https://x-access-token:${BOT_TOKEN}@github.com/${REPO}.git" "$work_dir"
cd "$work_dir"
git fetch origin "+refs/heads/$head_ref:refs/remotes/origin/$head_ref"
git checkout -B "handoff-$id" "refs/remotes/origin/$head_ref"
checked_out="$(git rev-parse HEAD)"
if [ "$checked_out" != "$head_sha" ]; then
echo "Skipping stale autofix handoff $id for $head_sha; fetched $checked_out."
cd "$GITHUB_WORKSPACE"
continue
fi
if ! git apply --check "$patch_path"; then
cd "$GITHUB_WORKSPACE"
upsert_nix_fallback_comment "$id" "$pr_number" "$patch_path" "the generated patch did not apply cleanly"
continue
fi
git apply "$patch_path"
changed_files="$(git diff --name-only | sort)"
allowed_files="$(jq -r '.[]' <<< "$allowed_paths_json" | sort)"
if [ "$changed_files" != "$allowed_files" ]; then
echo "Unexpected files changed after applying autofix handoff $id:" >&2
printf '%s\n' "$changed_files" >&2
echo "Allowed files:" >&2
printf '%s\n' "$allowed_files" >&2
exit 1
fi
if git diff --quiet --exit-code; then
echo "Autofix handoff $id produced no changes."
cd "$GITHUB_WORKSPACE"
continue
fi
git config user.name 'open-design-bot[bot]'
git config user.email '282769551+open-design-bot[bot]@users.noreply.github.com'
jq -r '.[]' <<< "$allowed_paths_json" | xargs git add --
git commit -m "$commit_message"
if ! git push origin "HEAD:refs/heads/$head_ref"; then
cd "$GITHUB_WORKSPACE"
upsert_nix_fallback_comment "$id" "$pr_number" "$patch_path" "the bot push failed"
continue
fi
cd "$GITHUB_WORKSPACE"
clear_nix_fallback_comment "$id" "$pr_number"
echo "Applied autofix handoff $id to PR $pr_number."
done < "$ENTRIES_PATH"