test: Proper workflow validation (Step 1: test → dev) #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ───────────────────────────────────────────────────────────────── | |
| # PR into Dev Workflow | |
| # ───────────────────────────────────────────────────────────────── | |
| # Validates feature/fix/hotfix pull requests before merging to dev. | |
| # | |
| # Validations: | |
| # - Source branch must be feature/*, fix/*, or hotfix/* | |
| # - PR title must follow conventional commit format | |
| # - At least one issue must be linked | |
| # - All quality gates must pass | |
| # - Fork PRs run read-only checks | |
| # | |
| # Author: Alireza Rezvani | |
| # Date: 2025-11-06 | |
| # ───────────────────────────────────────────────────────────────── | |
| name: PR into Dev | |
| on: | |
| pull_request: | |
| types: | |
| - opened | |
| - synchronize | |
| - ready_for_review | |
| branches: | |
| - dev | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: read | |
| statuses: write | |
| # Cancel in-progress runs for the same PR | |
| concurrency: | |
| group: pr-dev-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ───────────────────────────────────────────────────────────────── | |
| # Fork Safety Check | |
| # ───────────────────────────────────────────────────────────────── | |
| fork-check: | |
| name: Check Fork Status | |
| runs-on: ubuntu-latest | |
| outputs: | |
| is-fork: ${{ steps.fork-safety.outputs.is-fork }} | |
| should-skip-writes: ${{ steps.fork-safety.outputs.should-skip-writes }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Check if PR is from fork | |
| id: fork-safety | |
| uses: ./.github/actions/fork-safety | |
| - name: Log fork status | |
| run: | | |
| if [[ "${{ steps.fork-safety.outputs.is-fork }}" == "true" ]]; then | |
| echo "⚠️ This PR is from a fork - write operations will be skipped for security" | |
| else | |
| echo "✅ This PR is from the same repository" | |
| fi | |
| # ───────────────────────────────────────────────────────────────── | |
| # Branch Name Validation | |
| # ───────────────────────────────────────────────────────────────── | |
| validate-branch: | |
| name: Validate Branch Name | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Validate source branch name | |
| run: | | |
| BRANCH_NAME="${{ github.head_ref }}" | |
| echo "🔍 Validating branch name: $BRANCH_NAME" | |
| # Check if branch follows naming convention | |
| if [[ ! "$BRANCH_NAME" =~ ^(feature|fix|hotfix)/ ]]; then | |
| echo "❌ Invalid branch name: $BRANCH_NAME" | |
| echo "" | |
| echo "Branch must start with one of:" | |
| echo " - feature/ (for new features)" | |
| echo " - fix/ (for bug fixes)" | |
| echo " - hotfix/ (for critical fixes)" | |
| echo "" | |
| echo "Example: feature/issue-123-add-user-auth" | |
| exit 1 | |
| fi | |
| echo "✅ Branch name is valid: $BRANCH_NAME" | |
| # ───────────────────────────────────────────────────────────────── | |
| # PR Title Validation (Conventional Commits) | |
| # ───────────────────────────────────────────────────────────────── | |
| validate-pr-title: | |
| name: Validate PR Title | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Validate conventional commit format | |
| uses: amannn/action-semantic-pull-request@v6 | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| with: | |
| types: | | |
| feat | |
| fix | |
| docs | |
| style | |
| refactor | |
| perf | |
| test | |
| build | |
| ci | |
| chore | |
| revert | |
| requireScope: false | |
| subjectPattern: ^[A-Z].+$ | |
| subjectPatternError: | | |
| The subject "{subject}" found in the pull request title "{title}" | |
| didn't match the configured pattern. Please ensure that the subject | |
| starts with an uppercase character. | |
| - name: Add comment on invalid title | |
| if: failure() | |
| uses: actions/github-script@v8 | |
| with: | |
| github-token: ${{ github.token }} | |
| script: | | |
| const prNumber = context.payload.pull_request.number; | |
| const comment = `## ❌ Invalid PR Title | |
| Your PR title doesn't follow the conventional commit format. | |
| **Required format:** \`<type>(<scope>): <subject>\` | |
| **Valid types:** | |
| - \`feat\`: New feature | |
| - \`fix\`: Bug fix | |
| - \`docs\`: Documentation changes | |
| - \`style\`: Code style changes (formatting, etc.) | |
| - \`refactor\`: Code refactoring | |
| - \`perf\`: Performance improvements | |
| - \`test\`: Adding or updating tests | |
| - \`build\`: Build system changes | |
| - \`ci\`: CI/CD changes | |
| - \`chore\`: Other changes | |
| - \`revert\`: Revert a previous commit | |
| **Examples:** | |
| - \`feat(auth): Add user authentication\` | |
| - \`fix(api): Resolve null pointer exception\` | |
| - \`docs: Update README with setup instructions\` | |
| **Subject rules:** | |
| - Start with uppercase letter | |
| - Be concise and descriptive | |
| - Use imperative mood ("Add" not "Added") | |
| Please update your PR title and I'll re-check automatically.`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: comment | |
| }); | |
| # ───────────────────────────────────────────────────────────────── | |
| # Linked Issue Validation | |
| # ───────────────────────────────────────────────────────────────── | |
| validate-linked-issue: | |
| name: Validate Linked Issue | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check for linked issues | |
| id: check-linked-issue | |
| uses: actions/github-script@v8 | |
| with: | |
| github-token: ${{ github.token }} | |
| script: | | |
| const prNumber = context.payload.pull_request.number; | |
| const prBody = context.payload.pull_request.body || ''; | |
| // Regex to find linked issues: Closes #123, Fixes #456, Relates to #789 | |
| const issueRegex = /(close[sd]?|fix(e[sd])?|resolve[sd]?|relates?\s+to)\s+#(\d+)/gi; | |
| const matches = [...prBody.matchAll(issueRegex)]; | |
| if (matches.length === 0) { | |
| core.setFailed('No linked issues found in PR description'); | |
| core.setOutput('has-linked-issue', 'false'); | |
| return; | |
| } | |
| const issueNumbers = matches.map(m => m[3]); | |
| console.log(`✅ Found ${matches.length} linked issue(s): #${issueNumbers.join(', #')}`); | |
| core.setOutput('has-linked-issue', 'true'); | |
| core.setOutput('issue-numbers', issueNumbers.join(',')); | |
| - name: Add comment on missing linked issue | |
| if: failure() | |
| uses: actions/github-script@v8 | |
| with: | |
| github-token: ${{ github.token }} | |
| script: | | |
| const prNumber = context.payload.pull_request.number; | |
| const comment = `## ❌ No Linked Issue Found | |
| This PR must be linked to at least one issue for automated tracking. | |
| **How to link an issue:** | |
| Add one of the following keywords to your PR description: | |
| - \`Closes #123\` - Closes the issue when PR is merged | |
| - \`Fixes #456\` - Closes the issue when PR is merged | |
| - \`Resolves #789\` - Closes the issue when PR is merged | |
| - \`Relates to #101\` - References the issue without closing | |
| **Example:** | |
| \`\`\`markdown | |
| ## Summary | |
| This PR adds user authentication. | |
| Closes #123 | |
| Relates to #124 | |
| \`\`\` | |
| **Why this is required:** | |
| - Enables automated status tracking on the project board | |
| - Links code changes to requirements | |
| - Maintains project traceability | |
| Please update your PR description with a linked issue.`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: comment | |
| }); | |
| # ───────────────────────────────────────────────────────────────── | |
| # Rate Limit Check (Before Running Quality Checks) | |
| # ───────────────────────────────────────────────────────────────── | |
| rate-limit-check: | |
| name: Check API Rate Limit | |
| runs-on: ubuntu-latest | |
| needs: | |
| - fork-check | |
| if: needs.fork-check.outputs.should-skip-writes != 'true' | |
| outputs: | |
| can-proceed: ${{ steps.rate-limit.outputs.can-proceed }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Check rate limit | |
| id: rate-limit | |
| uses: ./.github/actions/rate-limit-check | |
| with: | |
| minimum-remaining: 50 | |
| github-token: ${{ github.token }} | |
| - name: Log rate limit status | |
| run: | | |
| if [[ "${{ steps.rate-limit.outputs.can-proceed }}" == "false" ]]; then | |
| echo "⚠️ Rate limit too low: ${{ steps.rate-limit.outputs.remaining }} remaining" | |
| echo "Waiting until: ${{ steps.rate-limit.outputs.reset-time }}" | |
| exit 1 | |
| fi | |
| echo "✅ Rate limit OK: ${{ steps.rate-limit.outputs.remaining }} calls remaining" | |
| # ───────────────────────────────────────────────────────────────── | |
| # Quality Gates (Reusable Workflow) | |
| # ───────────────────────────────────────────────────────────────── | |
| quality-checks: | |
| name: Run Quality Checks | |
| needs: | |
| - validate-branch | |
| - validate-pr-title | |
| - validate-linked-issue | |
| - rate-limit-check | |
| if: | | |
| always() && | |
| needs.validate-branch.result == 'success' && | |
| needs.validate-pr-title.result == 'success' && | |
| needs.validate-linked-issue.result == 'success' && | |
| needs.rate-limit-check.result == 'success' | |
| uses: ./.github/workflows/reusable-pr-checks.yml | |
| with: | |
| mobile_check: false | |
| integration_tests: false | |
| node_version: '20' | |
| pnpm_version: '9' | |
| # ───────────────────────────────────────────────────────────────── | |
| # Final Status Check | |
| # ───────────────────────────────────────────────────────────────── | |
| final-status: | |
| name: Final PR Status | |
| runs-on: ubuntu-latest | |
| needs: | |
| - fork-check | |
| - validate-branch | |
| - validate-pr-title | |
| - validate-linked-issue | |
| - quality-checks | |
| if: always() | |
| steps: | |
| - name: Generate final summary | |
| run: | | |
| echo "# 🎯 PR Validation Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Fork status | |
| if [[ "${{ needs.fork-check.outputs.is-fork }}" == "true" ]]; then | |
| echo "⚠️ **Fork PR**: This PR is from a fork. Write operations were skipped for security." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "## ✅ Validation Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY | |
| # Branch name | |
| if [[ "${{ needs.validate-branch.result }}" == "success" ]]; then | |
| echo "| Branch Name | ✅ Valid |" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "| Branch Name | ❌ Invalid |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # PR title | |
| if [[ "${{ needs.validate-pr-title.result }}" == "success" ]]; then | |
| echo "| PR Title (Conventional) | ✅ Valid |" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "| PR Title (Conventional) | ❌ Invalid |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # Linked issue | |
| if [[ "${{ needs.validate-linked-issue.result }}" == "success" ]]; then | |
| echo "| Linked Issue | ✅ Found |" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "| Linked Issue | ❌ Missing |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # Quality checks | |
| if [[ "${{ needs.quality-checks.result }}" == "success" ]]; then | |
| echo "| Quality Checks | ✅ Passed |" >> $GITHUB_STEP_SUMMARY | |
| elif [[ "${{ needs.quality-checks.result }}" == "skipped" ]]; then | |
| echo "| Quality Checks | ⏭️ Skipped (validation failed) |" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "| Quality Checks | ❌ Failed |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # Overall status | |
| if [[ "${{ needs.validate-branch.result }}" == "failure" ]] || \ | |
| [[ "${{ needs.validate-pr-title.result }}" == "failure" ]] || \ | |
| [[ "${{ needs.validate-linked-issue.result }}" == "failure" ]] || \ | |
| [[ "${{ needs.quality-checks.result }}" == "failure" ]]; then | |
| echo "## ❌ PR Validation Failed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Please fix the issues above before this PR can be merged." >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| else | |
| echo "## ✅ PR Ready for Review" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "All validations passed! This PR is ready for code review and merge." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "---" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "_PR validation completed at $(date -u '+%Y-%m-%d %H:%M:%S UTC')_" >> $GITHUB_STEP_SUMMARY |