-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
189 lines (175 loc) · 10.8 KB
/
Copy pathpublish-output-diffs.yaml
File metadata and controls
189 lines (175 loc) · 10.8 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
name: Publish generated output diff
on:
workflow_run:
workflows:
- Generated output diff
types:
- completed
permissions:
actions: read
contents: read
pull-requests: write
jobs:
publish:
name: Publish report and update PR comment
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-22.04
steps:
- name: Download comparison data
uses: actions/download-artifact@v4
with:
name: generated-output-diff
path: comparison
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Check out trusted report renderer
uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
path: trusted-source
- name: Validate metadata and result
id: metadata
env:
EVENT_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
EVENT_HEAD_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }}
EVENT_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
EVENT_PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
GH_TOKEN: ${{ github.token }}
REPOSITORY: ${{ github.repository }}
run: |
PR_NUMBER=$(node -e 'const m = require("./comparison/metadata.json"); if (!Number.isSafeInteger(m.prNumber) || m.prNumber <= 0) process.exit(1); process.stdout.write(String(m.prNumber))')
gh api "repos/$REPOSITORY/pulls/$PR_NUMBER" > /tmp/pull-request.json
if [[ -z "$EVENT_PR_NUMBER" ]]; then
HEAD_OWNER=${EVENT_HEAD_REPOSITORY%%/*}
gh api --method GET "repos/$REPOSITORY/pulls" -f state=open -f "head=$HEAD_OWNER:$EVENT_HEAD_BRANCH" > /tmp/source-pulls.json
else
printf '[]\n' > /tmp/source-pulls.json
fi
node - <<'NODE'
const fs = require("node:fs");
const metadata = JSON.parse(fs.readFileSync("comparison/metadata.json", "utf8"));
const result = JSON.parse(fs.readFileSync("comparison/result.json", "utf8"));
const pullRequest = JSON.parse(fs.readFileSync("/tmp/pull-request.json", "utf8"));
const sourcePulls = JSON.parse(fs.readFileSync("/tmp/source-pulls.json", "utf8"));
const sha = /^[0-9a-f]{40,64}$/;
if (!Number.isSafeInteger(metadata.prNumber) || metadata.prNumber <= 0) throw new Error("Invalid PR number");
if (process.env.EVENT_PR_NUMBER !== "" && String(metadata.prNumber) !== process.env.EVENT_PR_NUMBER) throw new Error("Artifact PR does not match workflow event");
if (process.env.EVENT_PR_NUMBER === "" && (sourcePulls.length !== 1 || sourcePulls[0].number !== metadata.prNumber)) throw new Error("Could not uniquely associate fork workflow with artifact PR");
if (metadata.headSha !== process.env.EVENT_HEAD_SHA) throw new Error("Artifact head does not match workflow event");
if (pullRequest.number !== metadata.prNumber || pullRequest.base.repo.full_name.toLowerCase() !== process.env.REPOSITORY.toLowerCase() || pullRequest.head.repo.full_name.toLowerCase() !== process.env.EVENT_HEAD_REPOSITORY.toLowerCase() || pullRequest.head.ref !== process.env.EVENT_HEAD_BRANCH) throw new Error("Artifact PR does not match workflow source");
for (const key of ["baseSha", "prSha", "headSha"]) {
if (!sha.test(metadata[key])) throw new Error(`Invalid ${key}`);
}
if (typeof metadata.supported !== "boolean" || typeof result.hasDifferences !== "boolean") throw new Error("Invalid comparison state");
const summary = result.summary;
for (const key of ["files", "modified", "added", "deleted", "changedLines", "insertions", "deletions"]) {
if (!Number.isSafeInteger(summary[key]) || summary[key] < 0) throw new Error(`Invalid summary field ${key}`);
}
const prUrl = `https://github.com/${process.env.REPOSITORY}/pull/${metadata.prNumber}`;
const values = {
base_sha: metadata.baseSha,
changed_lines: summary.changedLines,
deleted: summary.deleted,
deletions: summary.deletions,
files: summary.files,
has_differences: result.hasDifferences,
head_sha: metadata.headSha,
insertions: summary.insertions,
modified: summary.modified,
new_files: summary.added,
pr_number: metadata.prNumber,
pr_sha: metadata.prSha,
pr_url: prUrl,
supported: metadata.supported,
};
const output = Object.entries(values).map(([key, value]) => `${key}=${value}`).join("\n") + "\n";
fs.appendFileSync(process.env.GITHUB_OUTPUT, output);
NODE
- name: Set up Node
if: steps.metadata.outputs.supported == 'true' && steps.metadata.outputs.has_differences == 'true'
uses: actions/setup-node@v4
with:
node-version-file: trusted-source/.nvmrc
cache: npm
cache-dependency-path: trusted-source/package-lock.json
- name: Install trusted report dependencies
if: steps.metadata.outputs.supported == 'true' && steps.metadata.outputs.has_differences == 'true'
working-directory: trusted-source
run: npm ci --ignore-scripts
- name: Render report
if: steps.metadata.outputs.supported == 'true' && steps.metadata.outputs.has_differences == 'true'
run: |
mkdir -p report
trusted-source/node_modules/.bin/tsx trusted-source/script/output-diff.ts render \
comparison/result.json comparison/output.diff report/index.html \
'${{ steps.metadata.outputs.pr_url }}' \
'${{ steps.metadata.outputs.base_sha }}' \
'${{ steps.metadata.outputs.pr_sha }}' \
'${{ steps.metadata.outputs.head_sha }}'
cp comparison/output.diff report/output.diff
- name: Publish immutable report to hostr
if: steps.metadata.outputs.supported == 'true' && steps.metadata.outputs.has_differences == 'true'
env:
HOSTR_TOKEN: ${{ secrets.HOSTR_TOKEN }}
REPORT_PATH: quicktype/output-diffs/pr-${{ steps.metadata.outputs.pr_number }}/${{ steps.metadata.outputs.pr_sha }}/${{ steps.metadata.outputs.head_sha }}
run: |
test -n "$HOSTR_TOKEN"
upload_immutable() {
local file=$1 content_type=$2 url=$3 status
status=$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' "$url")
case "$status" in
200) echo "Already published: $url" ;;
404)
curl --fail-with-body --retry 3 -X POST \
-H "Authorization: Bearer $HOSTR_TOKEN" \
-H "Content-Type: $content_type" \
--data-binary "@$file" "$url"
;;
*) echo "Unexpected hostr response $status for $url" >&2; return 1 ;;
esac
}
upload_immutable report/index.html 'text/html; charset=utf-8' \
"https://hostr.flingit.run/s/$REPORT_PATH/index.html"
upload_immutable report/output.diff 'text/x-diff; charset=utf-8' \
"https://hostr.flingit.run/s/$REPORT_PATH/output.diff"
- name: Check whether this is still the current PR head
if: steps.metadata.outputs.supported == 'true'
id: current
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ steps.metadata.outputs.pr_number }}
run: |
CURRENT_HEAD=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER" --jq .head.sha)
if [[ "$CURRENT_HEAD" == "${{ steps.metadata.outputs.head_sha }}" ]]; then
echo "is_current=true" >> "$GITHUB_OUTPUT"
else
echo "is_current=false" >> "$GITHUB_OUTPUT"
echo "The PR advanced to $CURRENT_HEAD; not posting a comment for this stale comparison." >> "$GITHUB_STEP_SUMMARY"
fi
- name: Create output-diff comment
if: steps.metadata.outputs.supported == 'true' && steps.current.outputs.is_current == 'true'
env:
GH_TOKEN: ${{ github.token }}
HAS_DIFFERENCES: ${{ steps.metadata.outputs.has_differences }}
PR_NUMBER: ${{ steps.metadata.outputs.pr_number }}
REPORT_URL: https://hostr.flingit.run/s/quicktype/output-diffs/pr-${{ steps.metadata.outputs.pr_number }}/${{ steps.metadata.outputs.pr_sha }}/${{ steps.metadata.outputs.head_sha }}/index.html
run: |
MARKER='<!-- quicktype-generated-output-diff -->'
if [[ "$HAS_DIFFERENCES" == true ]]; then
BODY=$(cat <<EOF
$MARKER
### Generated-output differences
**${{ steps.metadata.outputs.files }} files differ** — ${{ steps.metadata.outputs.modified }} modified, ${{ steps.metadata.outputs.new_files }} new, ${{ steps.metadata.outputs.deleted }} deleted
**${{ steps.metadata.outputs.changed_lines }} changed lines** — +${{ steps.metadata.outputs.insertions }} / −${{ steps.metadata.outputs.deletions }}
[Open the generated-output report →]($REPORT_URL)
EOF
)
else
BODY=$(cat <<EOF
$MARKER
### No generated-output differences
✅ This PR does not change generated outputs.
EOF
)
fi
gh api --method POST "repos/${{ github.repository }}/issues/$PR_NUMBER/comments" -f body="$BODY" >/dev/null