From cf3653abff70f3c3116f8270d7c311e8b791742b Mon Sep 17 00:00:00 2001 From: Matteoverzotti Date: Thu, 5 Feb 2026 21:37:30 +0200 Subject: [PATCH 1/2] Added codeowners, pr template, validator --- .github/CODEOWNERS | 1 + .github/PULL_REQUEST_TEMPLATE.md | 14 ++ .github/workflows/pr-checklist-validator.yml | 198 +++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/pr-checklist-validator.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..cc324bb --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @furkando @NGabuaeva @EdiOanceaV2 @sinantalhakosar @rturtu @hikmet-demir @Matteoverzotti @robertbarbu27 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2d04a37 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +## Proposed Change + +_Describe what this pull request does?_ + +## To-Do + +_Add to-do items here_ + +- [ ] Does it need backend deployment? +- [ ] Does it need confirmation on calculations? + +## Screenshot + +_Add screenshots or short video_ diff --git a/.github/workflows/pr-checklist-validator.yml b/.github/workflows/pr-checklist-validator.yml new file mode 100644 index 0000000..e8137d5 --- /dev/null +++ b/.github/workflows/pr-checklist-validator.yml @@ -0,0 +1,198 @@ +name: PR Checklist Validator + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +jobs: + validate-checklist: + runs-on: ubuntu-latest + name: Validate PR Checklist + + steps: + - name: Check PR Checklist + uses: actions/github-script@v7 + with: + script: | + const prBody = context.payload.pull_request.body || ''; + + // Find all checkboxes in the PR body + const checkboxRegex = /- \[([ xX])\]/g; + const checkboxes = [...prBody.matchAll(checkboxRegex)]; + + if (checkboxes.length === 0) { + core.setFailed('āŒ No checkboxes found in PR description. Please use the PR template.'); + return; + } + + // Count checked and unchecked boxes + const totalBoxes = checkboxes.length; + const checkedBoxes = checkboxes.filter(match => + match[1].toLowerCase() === 'x' + ).length; + const uncheckedBoxes = totalBoxes - checkedBoxes; + + // Create a summary + core.summary + .addHeading('PR Checklist Validation Results') + .addRaw(`Total checkboxes: ${totalBoxes}`) + .addRaw(`\nChecked: āœ… ${checkedBoxes}`) + .addRaw(`\nUnchecked: āŒ ${uncheckedBoxes}`) + .write(); + + // Log the results + console.log(`Total checkboxes: ${totalBoxes}`); + console.log(`Checked: ${checkedBoxes}`); + console.log(`Unchecked: ${uncheckedBoxes}`); + + // Collect all errors + const errors = []; + + if (uncheckedBoxes > 0) { + errors.push( + `šŸ“Š Status: ${checkedBoxes}/${totalBoxes} items checked\n` + + `āš ļø Please check all ${uncheckedBoxes} remaining checkbox(es) in the PR description before merging.` + ); + } + + // Fail if there are any errors + if (errors.length > 0) { + core.setFailed( + `āŒ PR validation failed!\n\n` + + errors.join('\n\n') + + `\n\nšŸ’” Tip: Review each section carefully and ensure all requirements are met.` + ); + } else { + console.log('āœ… All checklist items are complete!'); + core.notice('āœ… All checklist items are complete! PR is ready for review.'); + } + + - name: Comment on PR (if incomplete) + if: failure() + uses: actions/github-script@v7 + with: + script: | + const prBody = context.payload.pull_request.body || ''; + const checkboxes = [...prBody.matchAll(/- \[([ xX])\]/g)]; + const totalBoxes = checkboxes.length; + const checkedBoxes = checkboxes.filter(match => + match[1].toLowerCase() === 'x' + ).length; + const uncheckedBoxes = totalBoxes - checkedBoxes; + + // Check if we already commented + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + }); + + const botComment = comments.find(comment => + comment.user && comment.user.type === 'Bot' && + comment.body.includes('PR Checklist Validation') + ); + + const issues = []; + if (totalBoxes === 0) { + issues.push(`- āŒ **Checklist:** No checkboxes found. Please use the PR template.`); + } else if (uncheckedBoxes > 0) { + issues.push(`- āŒ **Checklist:** ${checkedBoxes}/${totalBoxes} items checked (${uncheckedBoxes} remaining)`); + } + + // Build steps dynamically with correct numbering + let stepNum = 1; + const steps = [`${stepNum++}. Review the PR description`]; + + if (totalBoxes === 0) { + steps.push(`${stepNum++}. Use the PR template to add the required checklist`); + } else if (uncheckedBoxes > 0) { + steps.push(`${stepNum++}. Check all applicable boxes (\`- [x]\`)`); + steps.push(`${stepNum++}. If an item doesn't apply, check it and add a note explaining why`); + } + + steps.push(`${stepNum}. Push an update or edit the PR description to re-trigger this check`); + + const commentBody = [ + '## šŸ“‹ PR Checklist Validation Failed', + '', + 'āš ļø This PR cannot be merged until the following issues are resolved:', + '', + ...issues, + '', + '### What to do:', + ...steps, + '', + '---', + '*This check ensures all PR requirements are met before merging. If you believe this check should not apply to your PR, please discuss with the team.*' + ].join('\n'); + + if (botComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: commentBody + }); + } + + - name: Comment on PR (if complete) + if: success() + uses: actions/github-script@v7 + with: + script: | + const prBody = context.payload.pull_request.body || ''; + const checkboxes = [...prBody.matchAll(/- \[([ xX])\]/g)]; + const totalBoxes = checkboxes.length; + const checkedBoxes = checkboxes.filter(match => + match[1].toLowerCase() === 'x' + ).length; + + // Check if we already commented + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + }); + + const botComment = comments.find(comment => + comment.user && comment.user.type === 'Bot' && + comment.body.includes('PR Checklist Validation') + ); + + const commentBody = [ + '## āœ… PR Checklist Validation Passed', + '', + `**Status:** All ${totalBoxes} checklist items completed`, + '', + 'šŸŽ‰ This PR meets all requirements and is ready for review!', + '', + '---', + '*This validation ensures all PR requirements are met before merging.*' + ].join('\n'); + + if (botComment) { + // Update existing comment with success message + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }); + } else { + // Create new success comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: commentBody + }); + } From d3bf946377dbf303b7ca1c18e9f49f457ae5fe55 Mon Sep 17 00:00:00 2001 From: Matteoverzotti Date: Thu, 5 Feb 2026 21:46:26 +0200 Subject: [PATCH 2/2] Change PR template and linear --- .github/PULL_REQUEST_TEMPLATE.md | 18 +++---- .github/workflows/pr-checklist-validator.yml | 54 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2d04a37..4f791dd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,14 +1,10 @@ -## Proposed Change +# Pull Request -_Describe what this pull request does?_ +## Summary +_Explain what this PR does and why it is needed._ -## To-Do +**Linear ticket:** PRO-XXX -_Add to-do items here_ - -- [ ] Does it need backend deployment? -- [ ] Does it need confirmation on calculations? - -## Screenshot - -_Add screenshots or short video_ +## Author Checklist +- [ ] PR tested locally +- [ ] PR tested on preview diff --git a/.github/workflows/pr-checklist-validator.yml b/.github/workflows/pr-checklist-validator.yml index e8137d5..586a2b7 100644 --- a/.github/workflows/pr-checklist-validator.yml +++ b/.github/workflows/pr-checklist-validator.yml @@ -32,18 +32,41 @@ jobs: ).length; const uncheckedBoxes = totalBoxes - checkedBoxes; + // Check for Linear URL/ticket + const linearRegex = /\*\*Linear ticket:\*\*[ \t]*([^\n]+)/i; + const linearMatch = prBody.match(linearRegex); + let linearError = null; + + if (!linearMatch) { + linearError = 'āŒ Linear ticket field not found in PR description'; + } else { + const linearValue = linearMatch[1].trim(); + // Check if it's still the placeholder or empty + if (!linearValue || linearValue === 'PRO-XXX' || linearValue === '') { + linearError = 'āŒ Linear ticket is not filled out (still showing placeholder "PRO-XXX")'; + } else { + // Validate format: should be either a ticket ID (e.g., PRO-123) or a full Linear URL + const validLinearPattern = /^(?:https?:\/\/linear\.app\/[^\s]+|[A-Z]+-\d+)$/; + if (!validLinearPattern.test(linearValue)) { + linearError = `āŒ Linear ticket format is invalid: "${linearValue}" (expected format: PRO-123 or https://linear.app/...)`; + } + } + } + // Create a summary core.summary .addHeading('PR Checklist Validation Results') .addRaw(`Total checkboxes: ${totalBoxes}`) .addRaw(`\nChecked: āœ… ${checkedBoxes}`) .addRaw(`\nUnchecked: āŒ ${uncheckedBoxes}`) + .addRaw(`\nLinear ticket: ${linearError ? 'āŒ Invalid' : 'āœ… Valid'}`) .write(); // Log the results console.log(`Total checkboxes: ${totalBoxes}`); console.log(`Checked: ${checkedBoxes}`); console.log(`Unchecked: ${uncheckedBoxes}`); + console.log(`Linear validation: ${linearError || 'Valid'}`); // Collect all errors const errors = []; @@ -55,6 +78,10 @@ jobs: ); } + if (linearError) { + errors.push(`šŸ”— ${linearError}\nāš ļø Please add a valid Linear ticket (e.g., PRO-123 or full Linear URL).`); + } + // Fail if there are any errors if (errors.length > 0) { core.setFailed( @@ -80,6 +107,25 @@ jobs: ).length; const uncheckedBoxes = totalBoxes - checkedBoxes; + // Check Linear ticket + const linearRegex = /\*\*Linear ticket:\*\*[ \t]*([^\n]+)/i; + const linearMatch = prBody.match(linearRegex); + let linearError = null; + + if (!linearMatch) { + linearError = 'Linear ticket field not found'; + } else { + const linearValue = linearMatch[1].trim(); + if (!linearValue || linearValue === 'PRO-XXX' || linearValue === '') { + linearError = 'Linear ticket is still showing placeholder "PRO-XXX"'; + } else { + const validLinearPattern = /^(?:https?:\/\/linear\.app\/[^\s]+|[A-Z]+-\d+)$/; + if (!validLinearPattern.test(linearValue)) { + linearError = `Linear ticket format is invalid: "${linearValue}"`; + } + } + } + // Check if we already commented const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, @@ -98,6 +144,9 @@ jobs: } else if (uncheckedBoxes > 0) { issues.push(`- āŒ **Checklist:** ${checkedBoxes}/${totalBoxes} items checked (${uncheckedBoxes} remaining)`); } + if (linearError) { + issues.push(`- āŒ **Linear ticket:** ${linearError}`); + } // Build steps dynamically with correct numbering let stepNum = 1; @@ -110,6 +159,10 @@ jobs: steps.push(`${stepNum++}. If an item doesn't apply, check it and add a note explaining why`); } + if (linearError) { + steps.push(`${stepNum++}. Add a valid Linear ticket (format: \`PRO-123\` or \`https://linear.app/...\`)`); + } + steps.push(`${stepNum}. Push an update or edit the PR description to re-trigger this check`); const commentBody = [ @@ -172,6 +225,7 @@ jobs: '## āœ… PR Checklist Validation Passed', '', `**Status:** All ${totalBoxes} checklist items completed`, + '**Linear ticket:** Valid āœ…', '', 'šŸŽ‰ This PR meets all requirements and is ready for review!', '',