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
66 changes: 66 additions & 0 deletions .github/scripts/validate-pr-title.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const nlp = require('compromise')
const title = process.env.PR_TITLE || ''

let isValidTitle = true

function logSuccess(message) {
console.log(`✅ ${message}`)
}

function logFailure(message) {
isValidTitle = false
console.error(`❌ ${message}`)
}

function capitalized(string) {
if (!string) return '';
return string[0].toUpperCase() + string.substring(1);
}

// Rule 1: PR title must not be empty
if (title) {
logSuccess(`PR title is not empty`)
} else {
logFailure(`PR title must not be empty`)
}

// Rule 2: PR title must be 72 characters or less
if (title.length <= 72) {
logSuccess(`PR title is ${title.length} characters`)
} else {
logFailure(`PR title must be 72 characters or less (currently ${title.length} characters)`)
}

// Rule 3: PR title must begin with a capital letter
if (/^[A-Z]/.test(title)) {
logSuccess(`PR title begins with a capital letter`)
} else {
logFailure('PR title must begin with a capital letter')
}

// Rule 4: PR title must end with a letter or number
if (/[A-Za-z0-9]$/.test(title)) {
logSuccess(`PR title ends with a letter or number`)
} else {
logFailure('PR title must end with a letter or number')
}

// Rule 5: PR title must be written in the imperative
const firstWord = title.split(' ')[0]
const firstWordLowercased = firstWord.toLowerCase()
const firstWordCapitalized = capitalized(firstWord)
const firstWordAsImperativeVerb = nlp(firstWord).verbs().toInfinitive().out('text')
const firstWordAsImperativeVerbLowercased = firstWordAsImperativeVerb.toLowerCase()
const firstWordAsImperativeVerbCapitalized = capitalized(firstWordAsImperativeVerb)

if (firstWordLowercased === firstWordAsImperativeVerbLowercased) {
logSuccess(`PR title is written in the imperative`)
} else if (firstWordAsImperativeVerb) {
logFailure(`PR title must be written in the imperative ("${firstWordAsImperativeVerbCapitalized}" instead of "${firstWordCapitalized}")`)
} else {
logFailure(`PR title must begin with a verb and be written in the imperative`)
}

if (!isValidTitle) {
process.exit(1)
}
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
branches:
- main
pull_request:
types: [opened, reopened, synchronize]
branches:
- '*'

Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/pr-labels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: PR Labels

on:
pull_request:
types: [opened, labeled, unlabeled, reopened, synchronize]

permissions:
contents: read
pull-requests: read

jobs:
check-for-required-labels:
name: Check For Required Labels
runs-on: ubuntu-latest

steps:
- name: Checkout source code
uses: actions/checkout@v4

- name: Validate PR has required labels
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
REPO=${{ github.repository }}

REQUIRED_LABELS=("bug" "ci/cd" "documentation" "enhancement" "formatting" "refactoring" "testing")
LABELS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '.labels[].name')

echo "PR labels:"
echo "$LABELS"

for required in "${REQUIRED_LABELS[@]}"; do
if echo "$LABELS" | grep -q "^$required$"; then
echo "✅ Found required label: $required"
exit 0
fi
done

echo "❌ PR is missing a required label."
echo "At least one of the following labels is required:"
printf '%s\n' "${REQUIRED_LABELS[@]}"
exit 1
shell: bash
30 changes: 30 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: PR Title

on:
pull_request:
types: [opened, edited, reopened, synchronize]

permissions:
contents: read
pull-requests: read

jobs:
validate-pr-title:
name: Validate PR Title
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: latest

- name: Install dependencies
run: npm install compromise

- name: Validate PR title
run: node .github/scripts/validate-pr-title.js
env:
PR_TITLE: ${{ github.event.pull_request.title }}