Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Welcome first-time contributors
name: Contributor guidance

on:
pull_request_target:
Expand All @@ -7,34 +7,50 @@ on:
permissions: {}

jobs:
first_time_contributor:
name: Is first-time contributor
contributor:
name: Resolve contributor status
# GitHub can report first-time contributors as NONE in the event payload.
# npm-cli-bot and nodejs-github-bot report MEMBER and are filtered here.
if: >-
github.run_attempt == 1 &&
github.repository == 'nodejs/node' &&
github.event.pull_request.user.login != 'dependabot[bot]' &&
(github.event.pull_request.author_association == 'FIRST_TIMER' ||
github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' ||
github.event.pull_request.author_association == 'NONE')
runs-on: ubuntu-slim
permissions:
pull-requests: read
outputs:
eligible: >-
${{
github.event.pull_request.author_association != 'NONE' ||
steps.recheck.outputs.eligible == 'true'
}}
is_first_time: ${{ steps.resolve.outputs.is_first_time }}
should_scan: ${{ steps.resolve.outputs.should_scan }}
steps:
- name: Recheck contributor eligibility
id: recheck
if: github.event.pull_request.author_association == 'NONE'
- name: Check author association
id: resolve
env:
GH_TOKEN: ${{ github.token }}
EVENT_ASSOCIATION: ${{ github.event.pull_request.author_association }}
GH_TOKEN: ${{ secrets.GH_USER_TOKEN }}
NUMBER: ${{ github.event.pull_request.number }}
run: |
echo "Author association from event: $EVENT_ASSOCIATION"

case "$EVENT_ASSOCIATION" in
FIRST_TIMER|FIRST_TIME_CONTRIBUTOR)
echo 'is_first_time=true' >> "$GITHUB_OUTPUT"
echo 'should_scan=true' >> "$GITHUB_OUTPUT"
exit 0
;;
NONE)
;;
*)
echo 'is_first_time=false' >> "$GITHUB_OUTPUT"
echo 'should_scan=false' >> "$GITHUB_OUTPUT"
exit 0
;;
esac

started_at=$SECONDS
for delay in 15 30 60 120; do
# TODO: Remove the retries once privileged API requests are confirmed
# to return the association immediately.
for delay in 0 15 30 60; do
sleep "$delay"
association=$(gh api "/repos/$GITHUB_REPOSITORY/pulls/$NUMBER" \
--jq '.author_association')
Expand All @@ -43,60 +59,57 @@ jobs:

case "$association" in
FIRST_TIMER|FIRST_TIME_CONTRIBUTOR)
echo 'eligible=true' >> "$GITHUB_OUTPUT"
echo 'is_first_time=true' >> "$GITHUB_OUTPUT"
echo 'should_scan=true' >> "$GITHUB_OUTPUT"
exit 0
;;
NONE)
;;
*)
echo 'eligible=false' >> "$GITHUB_OUTPUT"
echo 'is_first_time=false' >> "$GITHUB_OUTPUT"
echo 'should_scan=true' >> "$GITHUB_OUTPUT"
exit 0
;;
esac
done

echo 'eligible=false' >> "$GITHUB_OUTPUT"
echo 'is_first_time=false' >> "$GITHUB_OUTPUT"
echo 'should_scan=false' >> "$GITHUB_OUTPUT"

agentscan:
needs: first_time_contributor
if: needs.first_time_contributor.outputs.eligible == 'true'
guidance:
name: Apply contributor guidance
needs: contributor
if: needs.contributor.outputs.should_scan == 'true'
runs-on: ubuntu-slim
permissions:
contents: read
outputs:
scan_outcome: ${{ steps.scan.outcome }}
classification: ${{ steps.scan.outputs.classification }}
community_flagged: ${{ steps.scan.outputs['community-flagged'] }}
pull-requests: write
steps:
- name: Scan contributor activity
id: scan
id: agentscan
# The welcome should still be posted if this advisory scan fails.
continue-on-error: true
uses: MatteoGabriele/agentscan-action@98202262c925c508d4c1424b1dfbe17ee35b0c02 # v2.4.0
uses: MatteoGabriele/agentscan-action@8112fb79b33fafb8506159df20129a34209ac410 # v2.5.0
with:
github-token: ${{ github.token }}
mode: silent
mode: labels
scan-pull-requests: true
scan-issues: false
auto-close: false
honeypot: false

comment:
needs:
- first_time_contributor
- agentscan
if: needs.first_time_contributor.outputs.eligible == 'true'
runs-on: ubuntu-slim
permissions:
pull-requests: write
steps:
- name: Welcome first-time contributor
- name: Comment with contributor guidance
env:
ADD_CAUTION: >-
${{
steps.agentscan.outcome == 'success' &&
(steps.agentscan.outputs.classification == 'mixed' ||
steps.agentscan.outputs.classification == 'automation' ||
steps.agentscan.outputs['community-flagged'] == 'true')
}}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IS_FIRST_TIME: ${{ needs.contributor.outputs.is_first_time }}
NUMBER: ${{ github.event.pull_request.number }}
AGENTSCAN_OUTCOME: ${{ needs.agentscan.outputs.scan_outcome }}
AGENTSCAN_CLASSIFICATION: ${{ needs.agentscan.outputs.classification }}
AGENTSCAN_COMMUNITY_FLAGGED: ${{ needs.agentscan.outputs.community_flagged }}
WELCOME_MESSAGE: >2-
Welcome to Node.js, and thank you for your first contribution!

Expand Down Expand Up @@ -128,20 +141,15 @@ jobs:
[automation policy](https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md#automation-and-bots)
for additional context.
run: |
add_caution=false
if [[ "$AGENTSCAN_OUTCOME" == "success" ]]; then
case "$AGENTSCAN_CLASSIFICATION" in
mixed|automation)
add_caution=true
;;
esac
if [[ "$AGENTSCAN_COMMUNITY_FLAGGED" == "true" ]]; then
add_caution=true
fi
if [[ "$IS_FIRST_TIME" == "true" && "$ADD_CAUTION" == "true" ]]; then
body="$WELCOME_MESSAGE"$'\n\n'"$CAUTION_MESSAGE"
elif [[ "$IS_FIRST_TIME" == "true" ]]; then
body="$WELCOME_MESSAGE"
elif [[ "$ADD_CAUTION" == "true" ]]; then
body="$CAUTION_MESSAGE"
else
exit 0
fi

if [[ "$add_caution" == "true" ]]; then
printf '%s\n\n%s\n' "$WELCOME_MESSAGE" "$CAUTION_MESSAGE"
else
printf '%s\n' "$WELCOME_MESSAGE"
fi | gh pr comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file -
printf '%s\n' "$body" |
gh pr comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file -