Skip to content

Commit 22cdcc4

Browse files
cristianocclaude
andcommitted
Link a semantic diff page on each pull request
When a pull request is opened, reopened or pushed, the Semantic Diff workflow renders its commits with scripts/commitRecords.js, uploads the page to the CDN at semantic-diff/pr-<N>/index.html, and keeps one comment on the pull request with its link. The link carries the rendered head commit as a query string. Closing the pull request deletes the page. A pull request from a branch of this repository runs on pull_request, with its own workflow and renderer, from its first push and on any base branch. A pull request from a fork runs on pull_request_target. Security: pull_request_target takes the workflow from the default branch and runs it with the repository's secrets, so for forks the job runs only the default branch's code. It checks out the base commit without persisted credentials, fetches refs/pull/<N>/head as git objects, and renders them with the base branch's renderer, which reads git data and escapes all text it writes into the page. Each pull request runs on one of the two events: the jobs compare the event with the head repository, and the concurrency group names the event. Review: Why the CDN and not GitHub Pages? Pages here is one deployment from master (the dev playground) that each deploy replaces whole; the CDN already holds the playground's per-pull-request bundles under dev-playground-bundles/pr-<N>/. Area: ci docs Changelog: none Output: unchanged Ran: Semantic Diff run on #8870 (render ok; upload needs --s3-no-check-bucket, as the playground upload uses); dry run of the render step in a fresh blob:none clone of rescript-lang/rescript against refs/pull/8862/head (page written, both checks pass); YAML parse (ok, both triggers and job conditions read back); upload, comment and cleanup steps run only on GitHub PR: #8870 Signed-off-by: Cristiano Calcagno <cristianoc@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent df9c4e0 commit 22cdcc4

2 files changed

Lines changed: 107 additions & 0 deletions

File tree

‎.claude/skills/records/SKILL.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,6 @@ node scripts/commitRecords.js --fragment --out part.html <rev>...
133133
the description of the work. A pull request description is a rendering of
134134
its range.
135135
- Before review or merge: render `master..<branch>` and hand over the page.
136+
- On GitHub, `.github/workflows/semantic_diff.yml` renders each pull
137+
request's range when it is opened or pushed, and comments the page's link
138+
(`Semantic diff: https://cdn.rescript-lang.org/semantic-diff/pr-<N>/…`).
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
name: Semantic Diff
2+
3+
# Renders the commit records of each pull request (scripts/commitRecords.js)
4+
# as a review page on the CDN and links it from the pull request.
5+
#
6+
# A pull request from a branch of this repository runs on pull_request, with
7+
# its own workflow and renderer. A pull request from a fork runs on
8+
# pull_request_target, which GitHub takes from the default branch and runs
9+
# with the repository's secrets: it executes only the default branch's code,
10+
# and the fork's commits are fetched as git data and never checked out.
11+
12+
on:
13+
pull_request:
14+
types: [opened, synchronize, reopened, closed]
15+
pull_request_target:
16+
types: [opened, synchronize, reopened, closed]
17+
18+
concurrency:
19+
group: semantic-diff-${{ github.event_name }}-${{ github.event.pull_request.number }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
render:
24+
if: >-
25+
github.event.action != 'closed' &&
26+
github.actor != 'dependabot[bot]' &&
27+
(github.event_name == 'pull_request') ==
28+
(github.event.pull_request.head.repo.full_name == github.repository)
29+
runs-on: ubuntu-24.04
30+
permissions:
31+
contents: read
32+
pull-requests: write
33+
env:
34+
PR_NUMBER: ${{ github.event.pull_request.number }}
35+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
36+
steps:
37+
- name: Checkout renderer
38+
uses: actions/checkout@v7
39+
with:
40+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.event.pull_request.base.sha }}
41+
fetch-depth: 0
42+
filter: blob:none
43+
persist-credentials: false
44+
45+
- name: Render semantic diff
46+
id: render
47+
run: |
48+
git fetch --no-tags --filter=blob:none origin "refs/pull/${PR_NUMBER}/head:refs/heads/pr-${PR_NUMBER}"
49+
base=$(git merge-base "$BASE_SHA" "pr-${PR_NUMBER}")
50+
mkdir -p semantic-diff
51+
node scripts/commitRecords.js --out semantic-diff/index.html "${base}..pr-${PR_NUMBER}"
52+
echo "head=$(git rev-parse --short=9 "pr-${PR_NUMBER}")" >> "$GITHUB_OUTPUT"
53+
54+
- name: Setup Rclone
55+
uses: cometkim/rclone-actions/setup-rclone@main
56+
57+
- name: Configure Rclone remote
58+
uses: cometkim/rclone-actions/configure-remote/s3-provider@main
59+
with:
60+
name: rescript
61+
provider: Cloudflare
62+
endpoint: https://${{ vars.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
63+
access-key-id: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
64+
secret-access-key: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
65+
acl: private
66+
67+
- name: Upload semantic diff
68+
run: rclone copyto --s3-no-check-bucket semantic-diff/index.html "rescript:cdn-assets/semantic-diff/pr-${PR_NUMBER}/index.html"
69+
70+
# The query string names the rendered commit, so each push gets a URL
71+
# that no cache has seen.
72+
- name: Comment semantic diff URL
73+
uses: thollander/actions-comment-pull-request@v3
74+
with:
75+
pr-number: ${{ github.event.pull_request.number }}
76+
comment-tag: semantic-diff
77+
message: |
78+
Semantic diff: https://cdn.rescript-lang.org/semantic-diff/pr-${{ github.event.pull_request.number }}/index.html?v=${{ steps.render.outputs.head }}
79+
80+
cleanup:
81+
if: >-
82+
github.event.action == 'closed' &&
83+
github.actor != 'dependabot[bot]' &&
84+
(github.event_name == 'pull_request') ==
85+
(github.event.pull_request.head.repo.full_name == github.repository)
86+
runs-on: ubuntu-24.04
87+
permissions: {}
88+
steps:
89+
- name: Setup Rclone
90+
uses: cometkim/rclone-actions/setup-rclone@main
91+
92+
- name: Configure Rclone remote
93+
uses: cometkim/rclone-actions/configure-remote/s3-provider@main
94+
with:
95+
name: rescript
96+
provider: Cloudflare
97+
endpoint: https://${{ vars.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
98+
access-key-id: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
99+
secret-access-key: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
100+
acl: private
101+
102+
- name: Delete semantic diff
103+
continue-on-error: true
104+
run: rclone purge --s3-no-check-bucket "rescript:cdn-assets/semantic-diff/pr-${{ github.event.pull_request.number }}"

0 commit comments

Comments
 (0)