open-design/.github/workflows/nix-hash-autofix.yml
Marc Chan d5659d82d4
chore(nix): streamline pnpm deps hash maintenance (#2919)
* chore(nix): streamline pnpm deps hash maintenance

Generated-By: looper 0.9.0 (runner=worker, agent=opencode)

* fix(ci): satisfy actionlint in nix hash autofix

Generated-By: looper 0.9.0 (runner=fixer, agent=opencode)

* fix(ci): allow nix hash autofix on fork PRs

Generated-By: looper 0.9.0 (runner=fixer, agent=opencode)

* fix(ci): follow up nix hash review

Generated-By: looper 0.9.0 (runner=fixer, agent=opencode)

* fix(ci): tolerate nix hash bot token failures

Generated-By: looper 0.9.0 (runner=fixer, agent=opencode)
2026-05-26 07:35:38 +00:00

205 lines
9 KiB
YAML

name: nix-hash-autofix
on:
workflow_run:
workflows: [ci]
types: [completed]
permissions:
actions: read
contents: read
pull-requests: write
jobs:
autofix:
if: ${{ github.repository == 'nexu-io/open-design' && github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
steps:
- name: Check for Nix hash refresh artifact
id: artifact
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
RUN_ID: ${{ github.event.workflow_run.id }}
shell: bash
run: |
set -euo pipefail
artifact_id="$(gh api "repos/$REPO/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[] | select(.name == "nix-hash-refresh") | .id' | head -n 1 || true)"
if [ -z "$artifact_id" ]; then
echo "present=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "present=true" >> "$GITHUB_OUTPUT"
echo "artifact_id=$artifact_id" >> "$GITHUB_OUTPUT"
- name: Download Nix hash refresh artifact
if: ${{ steps.artifact.outputs.present == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
ARTIFACT_ID: ${{ steps.artifact.outputs.artifact_id }}
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/nix-hash-refresh"
gh api \
-H 'Accept: application/vnd.github+json' \
"repos/$REPO/actions/artifacts/$ARTIFACT_ID/zip" > "$RUNNER_TEMP/nix-hash-refresh.zip"
unzip -q "$RUNNER_TEMP/nix-hash-refresh.zip" -d "$RUNNER_TEMP/nix-hash-refresh"
- name: Read PR and patch metadata
if: ${{ steps.artifact.outputs.present == 'true' }}
id: meta
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
WORKFLOW_PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number || '' }}
WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
shell: bash
run: |
set -euo pipefail
status="$(python3 -c 'import json, sys; data = json.load(open(sys.argv[1], "r", encoding="utf-8")); print(data.get("status", "missing"))' "$RUNNER_TEMP/nix-hash-refresh/metadata.json")"
artifact_head_sha="$(python3 -c 'import json, sys; data = json.load(open(sys.argv[1], "r", encoding="utf-8")); print(data.get("headSha", ""))' "$RUNNER_TEMP/nix-hash-refresh/metadata.json")"
pr_number="$WORKFLOW_PR_NUMBER"
if [ -z "$pr_number" ]; then
pr_number="$(gh api "repos/$REPO/commits/$WORKFLOW_HEAD_SHA/pulls" --jq '.[0].number // empty')"
fi
if ! [[ "$pr_number" =~ ^[0-9]+$ ]]; then
echo "Unable to derive PR number for workflow head $WORKFLOW_HEAD_SHA." >&2
exit 1
fi
pr_json="$(gh api "repos/$REPO/pulls/$pr_number")"
head_repo="$(printf '%s' "$pr_json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["head"]["repo"]["full_name"])')"
head_ref="$(printf '%s' "$pr_json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["head"]["ref"])')"
head_sha="$(printf '%s' "$pr_json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["head"]["sha"])')"
{
echo "status=$status"
echo "artifact_head_sha=$artifact_head_sha"
echo "pr_number=$pr_number"
echo "head_repo=$head_repo"
echo "head_ref=$head_ref"
echo "head_sha=$head_sha"
} >> "$GITHUB_OUTPUT"
if [ -n "$artifact_head_sha" ] && [ "$artifact_head_sha" = "$head_sha" ]; then
echo "head_sha_matches=true" >> "$GITHUB_OUTPUT"
else
echo "head_sha_matches=false" >> "$GITHUB_OUTPUT"
fi
if [ "$head_repo" = "$REPO" ] && [ "$status" = "patch-generated" ] && [ "$artifact_head_sha" = "$head_sha" ]; then
echo "can_autopush=true" >> "$GITHUB_OUTPUT"
else
echo "can_autopush=false" >> "$GITHUB_OUTPUT"
fi
- name: Detect bot credentials
if: ${{ steps.meta.outputs.can_autopush == 'true' }}
id: bot-secrets
env:
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
BOT_APP_PRIVATE_KEY: ${{ secrets.BOT_APP_PRIVATE_KEY }}
shell: bash
run: |
set -euo pipefail
if [ -n "${BOT_APP_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.meta.outputs.can_autopush == 'true' && steps.bot-secrets.outputs.present == 'true' }}
id: bot-token
continue-on-error: true
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
owner: nexu-io
repositories: open-design
permission-contents: write
- name: Checkout PR branch for auto-apply
if: ${{ steps.meta.outputs.can_autopush == 'true' && steps.bot-token.outcome == 'success' }}
uses: actions/checkout@v6.0.2
with:
repository: ${{ steps.meta.outputs.head_repo }}
ref: ${{ steps.meta.outputs.head_ref }}
token: ${{ steps.bot-token.outputs.token }}
- name: Apply generated hash patch
if: ${{ steps.meta.outputs.can_autopush == 'true' && steps.bot-token.outcome == 'success' }}
id: apply
shell: bash
run: |
set -euo pipefail
patch_path="$RUNNER_TEMP/nix-hash-refresh/nix-pnpm-deps.patch"
if [ ! -f "$patch_path" ]; then
echo "applied=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git apply --check "$patch_path"
git apply "$patch_path"
changed_files="$(git diff --name-only)"
if [ "$changed_files" != "nix/pnpm-deps.nix" ]; then
echo "Unexpected files changed after applying hash patch:" >&2
printf '%s\n' "$changed_files" >&2
exit 1
fi
if git diff --quiet --exit-code; then
echo "applied=false" >> "$GITHUB_OUTPUT"
else
echo "applied=true" >> "$GITHUB_OUTPUT"
fi
- name: Commit and push generated hash refresh
if: ${{ steps.meta.outputs.can_autopush == 'true' && steps.bot-token.outcome == 'success' && steps.apply.outputs.applied == 'true' }}
shell: bash
run: |
set -euo pipefail
git config user.name 'open-design-bot[bot]'
git config user.email '282769551+open-design-bot[bot]@users.noreply.github.com'
git add nix/pnpm-deps.nix
git commit -m 'chore(nix): refresh pnpm deps hash'
git push origin "HEAD:${{ steps.meta.outputs.head_ref }}"
- name: Upsert fork/manual patch comment
if: ${{ steps.artifact.outputs.present == 'true' && steps.meta.outputs.head_sha_matches == 'true' && (steps.meta.outputs.can_autopush != 'true' || steps.bot-token.outcome != 'success' || steps.apply.outputs.applied != 'true') }}
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ steps.meta.outputs.pr_number }}
HASH_REFRESH_STATUS: ${{ steps.meta.outputs.status }}
shell: bash
run: |
set -euo pipefail
body_file="$RUNNER_TEMP/nix-hash-refresh-comment.md"
patch_file="$RUNNER_TEMP/nix-hash-refresh/nix-pnpm-deps.patch"
marker='<!-- nix-hash-refresh -->'
{
printf '%s\n' "$marker"
if [ "${HASH_REFRESH_STATUS}" = 'patch-generated' ]; then
printf "A generated Nix hash refresh is available for this PR.\n\n"
printf "Apply the patch from the \`nix-hash-refresh\` artifact attached to the failed \`ci\` run, or paste the diff below into \`git apply\`:\n\n"
printf '```diff\n'
if [ -f "$patch_file" ]; then
cat "$patch_file"
else
printf '(artifact did not include nix-pnpm-deps.patch)\n'
fi
printf '\n```\n'
else
printf "The failed \`ci\` run attempted to refresh \`nix/pnpm-deps.nix\`, but it could not produce a hash-only patch.\n\n"
printf "Download the \`nix-hash-refresh\` artifact from that run and inspect \`update.log\` for details.\n"
fi
} > "$body_file"
comment_id="$(gh api --paginate "repos/$REPO/issues/$PR_NUMBER/comments" --jq ".[] | select(.body | contains(\"$marker\")) | .id" | tail -n 1 || true)"
if [ -n "$comment_id" ]; then
gh api --method PATCH "repos/$REPO/issues/comments/$comment_id" --field body="$(cat "$body_file")" >/dev/null
else
gh api --method POST "repos/$REPO/issues/$PR_NUMBER/comments" --field body="$(cat "$body_file")" >/dev/null
fi