diff --git a/.github/workflows/greet-new-users.yml b/.github/workflows/greet-new-users.yml index 949a388b..356faaa4 100644 --- a/.github/workflows/greet-new-users.yml +++ b/.github/workflows/greet-new-users.yml @@ -6,21 +6,33 @@ on: pull_request_target: types: [ opened ] +# SECURITY: pull_request_target runs with a write token in the base-repo +# context. This workflow must never check out or execute anything from the +# PR head, and untrusted fields (titles, bodies, logins) must never be +# `${{ }}`-interpolated into the script source — read them via +# `context.payload` inside the script instead. +permissions: + issues: write + pull-requests: write + jobs: greeting: runs-on: ubuntu-latest - timeout-minutes: 1 + timeout-minutes: 2 steps: - - uses: actions/first-interaction@v3 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - issue_message: | + # Inline replacement for actions/first-interaction, which greets every + # PR from any author who never opened an issue in this repo (including + # bots like pre-commit.ci and dependabot). + # See: https://github.com/actions/first-interaction/issues/369 + - uses: actions/github-script@v9 + env: + ISSUE_MESSAGE: | **Thank you for submitting your first issue with us!** 🎉 Our response times may vary, but we'll get back to you as soon as we can! Welcome aboard! 🚀 - pr_message: | + PR_MESSAGE: | **Thank you for submitting your first pull request with us!** 🎉 Our response times may vary, but we'll get back to you as soon as we can! @@ -28,3 +40,64 @@ jobs: To help us help you, please make sure you have ticked all the boxes in the pull request template. Welcome aboard! 🚀 + with: + script: | + try { + const isIssue = context.eventName === 'issues' + const item = isIssue ? context.payload.issue : context.payload.pull_request + const author = item.user + + // Never greet bots (pre-commit.ci, dependabot, github-actions, Copilot, ...) + if (author.type === 'Bot') { + return core.info(`Skipping: ${author.login} is a bot`) + } + + // Repo-affiliated authors are never "new users". This is only a + // shortcut: a missing/unreliable value falls through to the real + // check below, so it can never cause a wrong greeting. + if (['OWNER', 'MEMBER', 'COLLABORATOR'].includes(item.author_association)) { + return core.info(`Skipping: ${author.login} is ${item.author_association}`) + } + + let isFirst = true + if (isIssue) { + // Everything this author created (the endpoint returns PRs too, + // so keep only true issues that predate the current one). + const created = await github.paginate(github.rest.issues.listForRepo, { + ...context.repo, + creator: author.login, + state: 'all', + per_page: 100, + }) + isFirst = !created.some((i) => !i.pull_request && i.number < item.number) + } else { + // pulls.list cannot filter by author, so scan all PRs and stop + // as soon as an older PR by this author shows up. + await github.paginate( + github.rest.pulls.list, + { ...context.repo, state: 'all', per_page: 100 }, + (response, done) => { + if (response.data.some((p) => p.user?.login === author.login && p.number < item.number)) { + isFirst = false + done() + } + return [] + }, + ) + } + + if (!isFirst) { + return core.info(`Skipping: not ${author.login}'s first ${isIssue ? 'issue' : 'pull request'}`) + } + + core.info(`Greeting ${author.login} on their first ${isIssue ? 'issue' : 'pull request'}`) + await github.rest.issues.createComment({ + ...context.repo, + issue_number: item.number, + body: isIssue ? process.env.ISSUE_MESSAGE : process.env.PR_MESSAGE, + }) + } catch (error) { + // The greeting is cosmetic: a red X on a newcomer's first PR is + // worse than a missing welcome, so warn instead of failing. + core.warning(`Skipping greeting: ${error.message}`) + } diff --git a/docs/reference/changelog.md b/docs/reference/changelog.md index c08ef295..c23ca199 100644 --- a/docs/reference/changelog.md +++ b/docs/reference/changelog.md @@ -37,6 +37,7 @@ Unreleased changes - Use the official `astral-sh/setup-uv` action to install and cache `uv` in CI ({gh-pr}`386`) - Let `uv` manage the Python interpreter and virtual environment in CI ({gh-pr}`393`) - Remove the stale `requirements/*.txt` glob from the CI cache key, left over from the migration to PEP 735 dependency groups ({gh-pr}`394`) +- Replace the broken `actions/first-interaction` action with an inline `actions/github-script` step, so that first-time greetings are no longer posted on every PR opened by bots like pre-commit.ci and dependabot ({gh-pr}`398`) ---