-
Notifications
You must be signed in to change notification settings - Fork 9.7k
287 lines (260 loc) · 11.3 KB
/
Copy pathautofix.atom.yml
File metadata and controls
287 lines (260 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
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"