Skip to content

PR comment (by ecosystem round-trip) #16

PR comment (by ecosystem round-trip)

PR comment (by ecosystem round-trip) #16

name: PR comment (by ecosystem round-trip)
# In-repo equivalent of the external bot upstream uses: react to the
# `by ecosystem round-trip` run, download its `comment.md` artifact, and post
# it as a sticky PR comment. Runs as a privileged `workflow_run` job so it can
# comment on fork PRs (where the `pull_request` token is read-only). This
# mirrors ruff's own (now bot-replaced) `ty-ecosystem-analyzer_comment.yaml`.
#
# astral plans to open source astral-sh-bot
# (https://astral.sh/blog/open-source-security-at-astral). once it's available
# and installed on this repo, this workflow can be removed in favour of the bot
# consuming the `comment.md` artifact directly, same as upstream.
permissions: {}
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: ["by ecosystem round-trip"]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The by ecosystem round-trip run whose comment to post
required: true
jobs:
comment:
name: Post round-trip comment
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.event == 'pull_request'
permissions:
pull-requests: write
steps:
- name: Download comment artifact
id: download
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: comment.md
run-id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Post or update PR comment
if: steps.download.outcome == 'success'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require("fs");
// the artifact comes from a (possibly fork) PR, so treat it as
// untrusted: a symlinked comment.md could exfiltrate a runner
// secret into a public comment. refuse anything but a plain file.
for (const f of ["comment.md", "pr-number.txt"]) {
if (fs.existsSync(f) && fs.lstatSync(f).isSymbolicLink()) {
core.setFailed(`${f} must not be a symlink`);
return;
}
}
if (!fs.existsSync("comment.md") || !fs.existsSync("pr-number.txt")) {
core.info("no comment artifact; nothing to post");
return;
}
const body = fs.readFileSync("comment.md", "utf8");
const prNumber = parseInt(fs.readFileSync("pr-number.txt", "utf8").trim(), 10);
if (!Number.isInteger(prNumber)) {
core.info("no PR number; nothing to post");
return;
}
const marker = "<!-- by-ecosystem-roundtrip -->";
const { owner, repo } = context.repo;
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: prNumber,
per_page: 100,
});
const existing = comments.find(
(c) => c.body && c.body.includes(marker),
);
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body,
});
}