diff --git a/.github/actions/pr-title-check/README.md b/.github/actions/pr-title-check/README.md index 7d8ca18..305aa5e 100644 --- a/.github/actions/pr-title-check/README.md +++ b/.github/actions/pr-title-check/README.md @@ -1,6 +1,6 @@ # PR Title Check Action -A composite action that validates pull request titles against conventional commit standards. +A composite action that validates pull request titles against conventional commit standards and ensures a Jira ticket is referenced. ## Usage @@ -11,7 +11,14 @@ A composite action that validates pull request titles against conventional commi ## How it Works -This action uses [Slashgear/action-check-pr-title](https://github.com/Slashgear/action-check-pr-title) to validate PR titles against a conventional commit pattern. +This action runs two checks: + +1. **Title format**: Uses [Slashgear/action-check-pr-title](https://github.com/Slashgear/action-check-pr-title) to validate PR titles against a conventional commit pattern. +2. **Jira reference**: Ensures every PR links to a Jira ticket, either via a full URL in the description (`https://montaapp.atlassian.net/browse/PROJECT-123`) or a ticket reference in the title (`[FOO-123]`). + +### Skipping the Jira check + +Include `nojira` or `no-jira` in the branch name, PR title, or description to skip the Jira check. Automated PRs from Renovate and Dependabot are skipped automatically. ## Title Format diff --git a/.github/actions/pr-title-check/action.yaml b/.github/actions/pr-title-check/action.yaml index 85b6eed..e8f5f00 100644 --- a/.github/actions/pr-title-check/action.yaml +++ b/.github/actions/pr-title-check/action.yaml @@ -8,3 +8,48 @@ runs: with: regexp: '^(\[(develop|development|staging)\]\s)?(build|chore|ci|docs|feat|feature|fix|perf|refactor|revert|style|test|release|ignore)(\([\w\- ]+\))?!?: (.+)' helpMessage: "Example: 'feat(app-ui): Add new dashboard component (WEB-123)'" + - name: Check for Jira link in PR description + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const prBody = context.payload.pull_request.body || ''; + const prTitle = context.payload.pull_request.title || ''; + const branchName = context.payload.pull_request.head.ref || ''; + const prAuthor = context.payload.pull_request.user.login || ''; + + // Skip validation for automated PRs (Renovate, Dependabot, etc.) + const automatedBots = ['renovate[bot]', 'dependabot[bot]']; + if (automatedBots.includes(prAuthor)) { + core.info(`✅ Automated PR from ${prAuthor} - skipping Jira link validation`); + return; + } + + // Check if "nojira" or "no-jira" is present in branch name, title, or description (case-insensitive) + const noJiraPattern = /no-?jira/i; + const hasNoJira = noJiraPattern.test(branchName) || + noJiraPattern.test(prTitle) || + noJiraPattern.test(prBody); + + if (hasNoJira) { + core.info('✅ "nojira"/"no-jira" found - skipping Jira link validation'); + return; + } + + // Check for Jira link + const jiraLinkPattern = /https:\/\/montaapp\.atlassian\.net\/browse\/[A-Z]+-\d+/; + const jiraTitleRefPattern = /\[[A-Z]+-\d+\]/; + + if (jiraLinkPattern.test(prBody)) { + const match = prBody.match(jiraLinkPattern); + core.info(`✅ Found Jira ticket: ${match[0]}`); + } else if (jiraTitleRefPattern.test(prTitle)) { + const match = prTitle.match(jiraTitleRefPattern); + core.info(`✅ Found Jira ticket reference in PR title: ${match[0]}`); + } else { + core.setFailed('❌ No Jira ticket link found in PR description.\n\n' + + 'Please add a link to your Jira ticket in the format:\n' + + 'https://montaapp.atlassian.net/browse/PROJECT-123\n\n' + + 'Example: https://montaapp.atlassian.net/browse/CPONETOPS-568\n\n' + + 'Or include a ticket reference in the PR title, e.g. [FOO-123]\n\n' + + 'To skip this check, include "nojira" or "no-jira" in the branch name, PR title, or description.'); + }