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
2 changes: 2 additions & 0 deletions .github/workflows/cla.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ jobs:
if: ${{ (startsWith(github.head_ref, 'dependabot') || startsWith(github.head_ref, 'renovate')) == false }}
env:
PR_DESCRIPTION: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./scripts/check_cla.py
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,4 @@ dist
tsconfig.tsbuildinfo
convex-local-backend*
.mypy_cache
__pycache__/
89 changes: 79 additions & 10 deletions scripts/check_cla.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,86 @@
import os
import re
import sys
import urllib.error
import urllib.request

CLA_TEXT = "By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice."
ORGANIZATION = "get-convex"

try:
PR_DESCRIPTION = os.environ["PR_DESCRIPTION"]
except KeyError:
print("There was no pull request description given")
sys.exit(1)
def is_org_member(username, github_token):
"""
Check if a user is a member of the get-convex GitHub organization.
Returns True if the user is a confirmed member.
Returns False if the user is not a member (404 response).
Returns None if the check fails (network error, API error, etc.).
"""
try:
# GitHub API endpoint to check organization membership
url = f"https://api.github.com/orgs/{ORGANIZATION}/members/{username}"

headers = {
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28"
}

if github_token:
headers["Authorization"] = f"Bearer {github_token}"

request = urllib.request.Request(url, headers=headers)

# Try to fetch the membership status
# A 204 response means the user is a confirmed member
# A 404 response means the user is not a member (or membership is private)
# Other errors indicate API failure and we return None to show a warning
try:
with urllib.request.urlopen(request) as response:
if response.status == 204:
return True
return None
except urllib.error.HTTPError as e:
if e.code == 404:
# User is not a member or membership is private
return False
# For other HTTP errors, return None to indicate failure
return None

except Exception as e:
print(f"⚠️ Warning: Failed to check organization membership: {e}")
return None

if not re.search(re.escape(CLA_TEXT), PR_DESCRIPTION, re.MULTILINE):
print(
"Pull request description does not include the required CLA text. Please add the following text to your PR description:\n\n" + CLA_TEXT
)
sys.exit(1)
def main():
# Get PR description
try:
PR_DESCRIPTION = os.environ["PR_DESCRIPTION"]
except KeyError:
print("❌ Error: There was no pull request description given")
sys.exit(1)

# Get PR author username - this should always be set
PR_AUTHOR = os.environ.get("PR_AUTHOR")
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")

if not PR_AUTHOR:
print("❌ Error: PR_AUTHOR environment variable not set")
sys.exit(1)

# Check if the author is a member of get-convex organization
member_status = is_org_member(PR_AUTHOR, GITHUB_TOKEN)

if member_status is True:
print(f"✅ Skipping CLA check: {PR_AUTHOR} is a member of the {ORGANIZATION} organization (Convex employee/contractor)")
sys.exit(0)
elif member_status is None:
print(f"⚠️ Warning: Could not verify organization membership for {PR_AUTHOR}. Proceeding with CLA check.")

# Proceed with standard CLA check
if not re.search(re.escape(CLA_TEXT), PR_DESCRIPTION, re.MULTILINE):
print(
"❌ Pull request description does not include the required CLA text. Please add the following text to your PR description:\n\n" + CLA_TEXT
)
sys.exit(1)

print("✅ CLA text found in PR description")

if __name__ == "__main__":
main()
Loading