diff --git a/.github/config/labeler.yml b/.github/config/labeler.yml index b5a17a5c4f..2efa435e4a 100644 --- a/.github/config/labeler.yml +++ b/.github/config/labeler.yml @@ -1,10 +1,4 @@ # see https://github.com/actions/labeler?tab=readme-ov-file#match-object to configure correctly -feature: -- head-branch: 'feature/*' -fix: -- head-branch: 'fix/*' -chore: -- head-branch: 'chore/*' dependencies: - any: - head-branch: 'dependencies/*' diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 0f08ba17be..06785945db 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -13,7 +13,104 @@ permissions: issues: write jobs: - branch-name-labeler: + conventional-commit-labeler: + name: Label PR based on Conventional Commit Specification + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + env: + TYPE_TO_LABEL: | + { + "feat":"kind/enhancement", + "fix":"fix", + "chore":"chore", + "docs":"kind/documentation", + "test":"kind/test", + "perf":"kind/performance" + } + SCOPE_TO_LABEL: | + { + "deps":"dependencies" + } + BREAKING_CHANGE_LABEL: "breaking" + with: + script: | + console.log("Verify that the PR title follows the Conventional Commit format"); + + // Parse mappings from environment variables + const typeToLabel = JSON.parse(process.env.TYPE_TO_LABEL); + const scopeToLabel = JSON.parse(process.env.SCOPE_TO_LABEL); + console.log("Type-to-Label Mapping:", typeToLabel); + console.log("Scope-to-Label Mapping:", scopeToLabel); + + // Dynamically generate allowed types + const allowedTypes = Object.keys(typeToLabel).join('|'); + console.log(`Allowed Types: ${allowedTypes}`); + + const prTitle = context.payload.pull_request.title; + console.log(`PR Title: ${prTitle}`); + + // We know this regex looks scary, but it's just to match the Conventional Commit format + // It parses out a Title into several named regex groups, which we can use to extract various semantic patterns: + // - type: The type of change (feat, fix, etc.) + // - scope: The scope of the change (optional and set in brackets) + // - breaking: A flag to indicate a breaking change (!) + // - subject: The subject of the change + // Example: feat(scope)!: add new feature + // ^^^^ ^^^^^ ^ ^^^^^^^^^^^^^^^ + // type scope subject + const regex = new RegExp( + `^(((Initial commit)|(Merge [^\\r\\n]+(\\s)[^\\r\\n]+((\\s)((\\s)[^\\r\\n]+)+)*(\\s)?)|^((?${allowedTypes})(\\((?[\\w\\-]+)\\))?(?!?): (?[^\\r\\n]+((\\s)((\\s)[^\\r\\n]+)+)*))(\\s)?)$)` + ); + console.log(`Regex: ${regex}`); + + const match = prTitle.match(regex); + console.log(`Match: ${match != null}`); + + if (match && match.groups) { + const { type, scope, breaking } = match.groups; + + // Initialize labels array + const labels = []; + + if (breaking) { + console.log("Adding breaking change label"); + labels.push(process.env.BREAKING_CHANGE_LABEL); + } + + // Add type-based label + if (type && typeToLabel[type]) { + labels.push(typeToLabel[type]); + } else { + console.log(`No label found for type: ${type}`); + } + + // Add scope-based label if scope exists + if (scope && scopeToLabel[scope]) { + labels.push(scopeToLabel[scope]); + } else if (scope) { + console.log(`No label found for scope: ${scope}`); + } + + if (labels.length > 0) { + console.log(`Adding labels: ${labels}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: labels, + }); + } else { + console.log("No labels to add."); + } + } else { + console.log("Invalid PR title format. Make sure you named the PR after the specification at https://www.conventionalcommits.org/en/v1.0.0/#specification. Exiting..."); + process.exit(1); + } + labeler: name: Label PR based on Config permissions: contents: read @@ -54,7 +151,7 @@ jobs: # github_api_url: 'api.github.com' # files_to_ignore: '' verify-labels: - needs: [branch-name-labeler, size-labeler] + needs: [labeler, size-labeler, conventional-commit-labeler] name: verify labels runs-on: ubuntu-latest steps: @@ -62,15 +159,4 @@ jobs: uses: docker://agilepathway/pull-request-label-checker:latest with: one_of: chore,fix,bugfix,bug,kind/bug,feature,enhancement,kind/enhancement,dependencies - repo_token: ${{ secrets.GITHUB_TOKEN }} - semantic-pr-title: - name: ensure pr conforms to semantic commit style - runs-on: ubuntu-latest - steps: - # This enforces Semantic Pull Request titles: - # see https://github.com/amannn/action-semantic-pull-request?tab=readme-ov-file#examples for examples - # see https://www.conventionalcommits.org/en/v1.0.0/#specification for the full specification - # We want this because we use squashing so all squashed commits should conform to our commit style - - uses: amannn/action-semantic-pull-request@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + repo_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file