Skip to content

Commit 62e84fc

Browse files
committed
build: add workflow to encourage more challenging contributions
--- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: na - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: missing_dependencies - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- type: pre_push_report description: Results of running various checks prior to pushing changes. report: - task: run_javascript_examples status: na - task: run_c_examples status: na - task: run_cpp_examples status: na - task: run_javascript_readme_examples status: na - task: run_c_benchmarks status: na - task: run_cpp_benchmarks status: na - task: run_fortran_benchmarks status: na - task: run_javascript_benchmarks status: na - task: run_julia_benchmarks status: na - task: run_python_benchmarks status: na - task: run_r_benchmarks status: na - task: run_javascript_tests status: na --- --- type: pre_push_report description: Results of running various checks prior to pushing changes. report: - task: run_javascript_examples status: na - task: run_c_examples status: na - task: run_cpp_examples status: na - task: run_javascript_readme_examples status: na - task: run_c_benchmarks status: na - task: run_cpp_benchmarks status: na - task: run_fortran_benchmarks status: na - task: run_javascript_benchmarks status: na - task: run_julia_benchmarks status: na - task: run_python_benchmarks status: na - task: run_r_benchmarks status: na - task: run_javascript_tests status: na --- --- type: pre_push_report description: Results of running various checks prior to pushing changes. report: - task: run_javascript_examples status: na - task: run_c_examples status: na - task: run_cpp_examples status: na - task: run_javascript_readme_examples status: na - task: run_c_benchmarks status: na - task: run_cpp_benchmarks status: na - task: run_fortran_benchmarks status: na - task: run_javascript_benchmarks status: na - task: run_julia_benchmarks status: na - task: run_python_benchmarks status: na - task: run_r_benchmarks status: na - task: run_javascript_tests status: na ---
1 parent 635e38f commit 62e84fc

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/usr/bin/env bash
2+
#
3+
# @license Apache-2.0
4+
#
5+
# Copyright (c) 2025 The Stdlib Authors.
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
19+
# Script to prevent contributors from opening too many "Good First PR"s.
20+
#
21+
# Usage: rate_limit_contributions <pr_number>
22+
#
23+
# Arguments:
24+
#
25+
# pr_number Pull request number.
26+
#
27+
# Environment variables:
28+
#
29+
# GITHUB_TOKEN GitHub token for authentication.
30+
31+
# Ensure that the exit status of pipelines is non-zero in the event that at least one of the commands in a pipeline fails:
32+
set -o pipefail
33+
34+
35+
# VARIABLES #
36+
37+
# Resolve the pull request number:
38+
pr_number="$1"
39+
40+
# Threshold of open "Good First PR"s above which to post comment:
41+
max_good_first_prs=5
42+
43+
# GitHub API base URL:
44+
github_api_url="https://api.github.com"
45+
46+
# Repository owner and name:
47+
repo_owner="stdlib-js"
48+
repo_name="stdlib"
49+
50+
51+
# FUNCTIONS #
52+
53+
# Error handler.
54+
#
55+
# $1 - error status
56+
on_error() {
57+
echo 'ERROR: An error was encountered during execution.' >&2
58+
exit "$1"
59+
}
60+
61+
# Prints a success message.
62+
print_success() {
63+
echo 'Success!' >&2
64+
}
65+
66+
# Performs a GitHub API request.
67+
#
68+
# $1 - HTTP method (GET or POST)
69+
# $2 - API endpoint
70+
# $3 - data for POST requests
71+
github_api() {
72+
local method="$1"
73+
local endpoint="$2"
74+
local data="$3"
75+
76+
# Initialize an array to hold curl headers:
77+
local headers=()
78+
79+
# If GITHUB_TOKEN is set, add the Authorization header:
80+
if [ -n "${GITHUB_TOKEN}" ]; then
81+
headers+=("-H" "Authorization: token ${GITHUB_TOKEN}")
82+
fi
83+
84+
# Determine the HTTP method and construct the curl command accordingly...
85+
case "${method}" in
86+
GET)
87+
curl -s "${headers[@]}" "${github_api_url}${endpoint}"
88+
;;
89+
POST)
90+
# For POST requests, always set the Content-Type header:
91+
headers+=("-H" "Content-Type: application/json")
92+
93+
# If data is provided, include it in the request:
94+
if [ -n "${data}" ]; then
95+
curl -s -X POST "${headers[@]}" -d "${data}" "${github_api_url}${endpoint}"
96+
else
97+
# Handle cases where POST data is required but not provided:
98+
echo "ERROR: POST request requires data."
99+
on_error 1
100+
fi
101+
;;
102+
*)
103+
echo "ERROR: Invalid HTTP method: ${method}."
104+
on_error 1
105+
;;
106+
esac
107+
}
108+
109+
# Main execution sequence.
110+
main() {
111+
local num_good_first_prs
112+
local good_first_pr
113+
local pr_details
114+
local pr_author
115+
local user_prs
116+
117+
if [ -z "${pr_number}" ]; then
118+
echo "ERROR: Pull request number is required." >&2
119+
on_error 1
120+
fi
121+
122+
# Fetch pull request details:
123+
pr_details=$(github_api "GET" "/repos/${repo_owner}/${repo_name}/pulls/${pr_number}")
124+
good_first_pr=$(echo "${pr_details}" | jq -r '.labels | any(.name == "Good First PR")')
125+
126+
pr_author=$(echo "${pr_details}" | jq -r '.user.login')
127+
128+
# Fetch other PRs of the same user:
129+
user_prs=$(github_api "GET" "/search/issues?q=state%3Aopen+author%3A${pr_author}+type%3Apr")
130+
131+
# Count number of PRs labeled "Good First PR":
132+
num_good_first_prs=$(echo "${user_prs}" | jq -r '.items | map( select( .labels | any( .name == "Good First PR" ))) | length')
133+
134+
if [ "${num_good_first_prs}" -gt "${max_good_first_prs}" ]; then
135+
# Post a comment on the PR:
136+
comment="Hey, we noticed that you've been opening a number of PRs addressing good first issues.
137+
138+
Thanks for your interest and enthusiasm.
139+
140+
Now that you've made a few contributions, we suggest no longer working on good first issues and instead working on more involved tasks.
141+
142+
Not only does this ensure that other new contributors can work on things, but it ensures you can spend your time on more challenging problems."
143+
144+
if ! github_api "POST" "/repos/${repo_owner}/${repo_name}/issues/${pr_number}/comments" "{\"body\":$(echo "$comment" | jq -R -s -c .)}"; then
145+
echo "Failed to post comment on PR."
146+
on_error 1
147+
fi
148+
149+
exit 1
150+
else
151+
echo "Number of PRs below threshold."
152+
print_success
153+
exit 0
154+
fi
155+
}
156+
157+
main
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#/
2+
# @license Apache-2.0
3+
#
4+
# Copyright (c) 2025 The Stdlib Authors.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#/
18+
19+
# Workflow name:
20+
name: too_many_good_first_prs
21+
22+
# Workflow triggers:
23+
on:
24+
pull_request_target:
25+
types:
26+
- opened
27+
28+
# Workflow jobs:
29+
jobs:
30+
31+
# Define a job which encourages contributors opening too many "Good First PRs" to tackle more challenging problems:
32+
check_num_good_first_prs:
33+
34+
# Define job name:
35+
name: 'Check if contributor has opened too many "Good First PRs"'
36+
37+
# Only run this job if the pull request did not have label `automated-pr`:
38+
if: contains(github.event.pull_request.labels.*.name, 'automated-pr') == false
39+
40+
# Define job permissions:
41+
permissions:
42+
contents: read
43+
pull-requests: write
44+
45+
# Define the type of virtual host machine:
46+
runs-on: ubuntu-latest
47+
48+
# Define the sequence of job steps:
49+
steps:
50+
# Checkout the repository:
51+
- name: 'Checkout repository'
52+
# Pin action to full length commit SHA
53+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
54+
with:
55+
# Specify whether to remove untracked files before checking out the repository:
56+
clean: true
57+
58+
# Limit clone depth to the most recent commit:
59+
fetch-depth: 1
60+
61+
# Specify whether to download Git-LFS files:
62+
lfs: false
63+
timeout-minutes: 10
64+
65+
# Prevent contributors from opening too many "Good First PR"s:
66+
- name: 'Prevent contributors from opening too many "Good First PR"s'
67+
env:
68+
PR_NUMBER: ${{ github.event.pull_request.number }}
69+
run: |
70+
. "$GITHUB_WORKSPACE/.github/workflows/scripts/rate_limit_contributions" $PR_NUMBER

0 commit comments

Comments
 (0)