Skip to content

Git-hub Actions (feat) #65

Git-hub Actions (feat)

Git-hub Actions (feat) #65

name: PR Title Checker
on:
pull_request:
types: [opened, edited, reopened, synchronize]
jobs:
validate-title:
runs-on: ubuntu-latest
steps:
- name: Check PR Title Format
uses: actions/github-script@v6
with:
script: |
const title = context.payload.pull_request.title.trim();
// Define valid patterns
const validPatterns = [
// Pattern 1: (type) O3-XXXX: Description
/^\((docs|test|chore|fix|feat|refactor)\)\s+(O3-\d+):\s+.+$/,
// Pattern 2: (type) Description (no ticket)
/^\((docs|test|chore|fix|feat|refactor)\)\s+[^ ].+$/,
// Pattern 3: (BREAKING) O3-XXXX: Description
/^\(BREAKING\)\s+(O3-\d+):\s+.+$/,
// Pattern 4: BREAKING: Description (no ticket)
/^BREAKING:\s+[^ ].+$/
];
// Check if title matches any valid pattern
const isValid = validPatterns.some(pattern => pattern.test(title));
if (!isValid) {
let errorMessage = `🚫 Invalid PR title format. Strict requirements:\n\n`;
// Check for common mistakes
if (/^BREAKING\s/i.test(title)) {
errorMessage += `• For BREAKING changes with ticket numbers, use "(BREAKING) O3-1234: Description"\n`;
errorMessage += `• For BREAKING changes without ticket numbers, use "BREAKING: Description"\n`;
} else if (/^\([^)]+\)\s+O3-\d+[^:]/.test(title)) {
errorMessage += `• Ticket reference must be followed by a colon (O3-1234: Description)\n`;
} else if (/^[^(](docs|test|chore|fix|feat|refactor)/i.test(title)) {
errorMessage += `• Type prefix must be in parentheses (e.g., "(feat)")\n`;
} else if (/^\([^)]+\):/.test(title)) {
errorMessage += `• No colon should appear immediately after parenthesized type\n`;
} else {
errorMessage += `• Must follow one of these exact patterns:\n\n`;
errorMessage += `With ticket reference:\n`;
errorMessage += `- "(feat) O3-1234: Description"\n`;
errorMessage += `- "(BREAKING) O3-1234: Description"\n\n`;
errorMessage += `Without ticket reference:\n`;
errorMessage += `- "(feat) Description"\n`;
errorMessage += `- "BREAKING: Description"\n\n`;
}
errorMessage += `🔍 Found these issues in your title: "${title}"\n\n`;
errorMessage += `💡 Examples of valid titles:\n`;
errorMessage += `• "(fix) O3-4567: Resolve header alignment issue"\n`;
errorMessage += `• "(chore) Update dependency packages"\n`;
errorMessage += `• "(BREAKING) O3-3191: Move LeftNav into primary navigation app"\n`;
errorMessage += `• "BREAKING: Remove deprecated API endpoints"`;
core.setFailed(errorMessage);
}
// Additional strict validation checks
// 1. Validate BREAKING change format
if (/BREAKING/i.test(title)) {
if (title.includes('O3-')) {
if (!/^\(BREAKING\)\s+O3-\d+:\s+.+$/.test(title)) {
core.setFailed(`For BREAKING changes with tickets:\n` +
`• Must use parentheses: "(BREAKING) O3-1234: Description"\n` +
`• Your title: "${title}"`);
}
} else {
if (!/^BREAKING:\s+[^ ].+$/.test(title)) {
core.setFailed(`For BREAKING changes without tickets:\n` +
`• Must use colon: "BREAKING: Description"\n` +
`• Must NOT use parentheses\n` +
`• Your title: "${title}"`);
}
}
}
// 2. Check ticket reference format
const ticketMatch = title.match(/(o3|03|O3)-\d+/);
if (ticketMatch) {
const ticketRef = ticketMatch[0];
if (ticketRef.startsWith('o3')) {
core.setFailed(`Invalid ticket reference:\n` +
`• Must use uppercase O (O3-1234)\n` +
`• Found lowercase: "${ticketRef}"`);
} else if (ticketRef.startsWith('03')) {
core.setFailed(`Invalid ticket reference:\n` +
`• Must use letter O (O3-1234)\n` +
`• Found zero: "${ticketRef}"`);
} else if (!/:/.test(title.split(ticketRef)[1])) {
core.setFailed(`Missing colon after ticket reference:\n` +
`• Must include colon: "O3-1234: Description"\n` +
`• Your title: "${title}"`);
}
}