Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ on:
jobs:
self-test:
runs-on: ubuntu-latest
env:
RENDERDIFF_COMMENT_DRY_RUN: "1"
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
Expand Down Expand Up @@ -42,6 +44,7 @@ jobs:
threshold: "0.999"
out: differ-out
artifact-name: differ
comment: "true"

- id: accept
uses: ./
Expand Down Expand Up @@ -76,3 +79,5 @@ jobs:
test "$DOWN" = 2
node -e 'const r = require("./differ-out/results.json").results; for (const p of ["/color", "/tint"]) if (!(r.find((x) => x.url === p)?.score < 0.999)) throw p'
grep -q '"/color"' accept-out/renderdiff.accepted.json
grep -q '<!-- renderdiff:differ -->' "$RUNNER_TEMP/renderdiff-comment-differ.md"
grep -q '/color' "$RUNNER_TEMP/renderdiff-comment-differ.md"
51 changes: 50 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ inputs:
artifact-name:
description: Name of the uploaded artifact with the report, results.json and crops.
default: renderdiff
comment:
description: "true posts the result as one PR comment and updates it on later runs. Needs pull-requests: write."
default: "false"

outputs:
exit-code:
Expand Down Expand Up @@ -151,20 +154,66 @@ runs:
echo "### renderdiff: $verdict"
echo
echo "\`$LEFT\` (your change) vs \`$RIGHT\` (reference). Report, results.json and crops are in the \`$ARTIFACT\` artifact."
} > "$RUNNER_TEMP/renderdiff-$ARTIFACT.md"
{
cat "$RUNNER_TEMP/renderdiff-$ARTIFACT.md"
echo
echo '```text'
head -c 900000 "$log"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

- if: always() && steps.run.outcome == 'success'
- id: upload
if: always() && steps.run.outcome == 'success'
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-name }}
path: ${{ inputs.out }}
if-no-files-found: ignore
retention-days: 14

- if: always() && inputs.comment == 'true' && steps.run.outcome == 'success'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
ARTIFACT: ${{ inputs.artifact-name }}
ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }}
MARKER: <!-- renderdiff:${{ inputs.artifact-name }} -->
run: |
log="$RUNNER_TEMP/renderdiff-$ARTIFACT.txt"
body="$RUNNER_TEMP/renderdiff-comment-$ARTIFACT.md"
run="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
{
echo "$MARKER"
cat "$RUNNER_TEMP/renderdiff-$ARTIFACT.md"
echo
echo "[This run]($run)${ARTIFACT_URL:+ · [artifact]($ARTIFACT_URL)}"
echo
echo '```text'
# Cut lines are dropped whole so a split UTF-8 char can't glue onto the closing fence.
if [ "$(wc -c < "$log")" -gt 60000 ]; then head -c 60000 "$log" | sed '$d'; echo '…'; else cat "$log"; [ -z "$(tail -c1 "$log")" ] || echo; fi
echo '```'
[ "$(wc -c < "$log")" -le 60000 ] || echo "Output truncated; the full output is in the job summary of [this run]($run)."
} > "$body"

pr=$PR
if [ -z "$pr" ] && [ "$GITHUB_EVENT_NAME" = deployment_status ]; then
pr=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" --jq 'map(select(.state == "open"))[0].number // empty') || pr=
fi
if [ -z "$pr" ]; then
echo "::notice::renderdiff: no open pull request for this run, comment skipped"
exit 0
fi

if ids=$(gh api --paginate "repos/$GITHUB_REPOSITORY/issues/$pr/comments" --jq '.[] | select(.user.login == "github-actions[bot]" and (.body | startswith(env.MARKER))) | .id'); then
id=${ids%%$'\n'*}
if [ -n "$id" ]; then req=(-X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$id"); else req=(-X POST "repos/$GITHUB_REPOSITORY/issues/$pr/comments"); fi
if [ -n "${RENDERDIFF_COMMENT_DRY_RUN:-}" ]; then echo "dry run: gh api ${req[*]}"; exit 0; fi
gh api "${req[@]}" -F body=@"$body" > /dev/null && exit 0
fi
echo "::warning::renderdiff: could not comment on PR #$pr (a fork or Dependabot PR gets a read-only token); the result is in the job summary"

- if: steps.run.outputs.exit-code != '0'
shell: bash
env:
Expand Down
18 changes: 18 additions & 0 deletions docs/github-action.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ exits non-zero.
| `wait-timeout` | Whole seconds to wait for `left` to answer (any status under 500). Default 300. Past it, `exit-code` is 2. |
| `out` | Output directory, over the config's `outDir`. Default `renderdiff-out`. |
| `artifact-name` | Default `renderdiff`. |
| `comment` | `"true"` posts the result as one PR comment and edits it on every later run. One comment per `artifact-name`, so two runs keep their own. |

To run the action twice in one job, give each run its own `out` and `artifact-name`; in a
matrix, put the matrix value in `artifact-name`. The action sets up Node 24, which later
Expand Down Expand Up @@ -90,6 +91,23 @@ jobs:

The action waits for `left` to answer, so the server can still be starting.

### Comment on the pull request

With `comment: "true"` the job needs write access to pull requests:

```yaml
permissions:
contents: read
pull-requests: write
```

It works on `pull_request`, and on `deployment_status` by finding the open PR for the
deployed commit. A fork's PR and Dependabot's get a read-only token, so the step warns and
the result stays in the job summary. Don't switch to `pull_request_target` to get write
access: it runs the PR's code with the base repo's token and secrets. The step needs `gh`
on the runner (preinstalled on GitHub-hosted ones). The comment is cut at 60 000 bytes,
with a link to the full job summary.

### Dependency upgrades from Renovate or Dependabot

A dependency bump should change nothing. That's the default without `threshold`: any
Expand Down
Loading