Skip to content

fuxingloh/multi-labeler

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

Multi Labeler

codecov CodeFactor Release License MIT

Multi labeler for title, body, comments, commit messages, branch, base branch, author or files. Optionally, generate a status check based on the labels.

Who is using fuxingloh/multi-labeler?

Features

  • Single compiled javascript file, extremely fast. Use fewer credits!
  • Append based multi-labeler, using .github/labeler.yml as config.
  • Automatically fail if labeler.yml is malformed, type-checked.
  • Set label to sync for conditional labeling, removed if condition failed.
  • Regex Matcher:
    • PR/Issue title
    • PR/Issue body
    • PR/Issue comments
    • PR commit messages
    • PR branch name
    • PR base (target) branch name
  • File Matcher:
    • Files count
    • Files any glob match
    • Files all glob match
  • Author Matcher
  • Generate status checks:
    • Any label match
    • All label match

Usage

.github/workflow/labeler.yml

on:
  pull_request_target:
  # for OSS with public contributions (forked PR)

  pull_request:
  # Useful for triaging code review, and generate compliance status check.
  # Semantic release? Done.
  # Make a file change in a mono repo. Tag the mono repo getting changed to generate better release!

  issues:
  # Useful for triaging error!
  # '- [x] Is this a bug?' = 'bug' label!

  issue_comment:
  # To pickup comment body in pr or issue and generate a label.
  # Imagine someone comment 'Me too, I get TimeoutException from ...' in comment body.
  # Generate a 'bug/timeout' label for better triaging!

permissions:
  # Setting up permissions in the workflow to limit the scope of what it can do. Optional!
  contents: read # the config file
  issues: write # for labeling issues (on: issues)
  pull-requests: write # for labeling pull requests (on: pull_request_target or on: pull_request)
  statuses: write # to generate status
  checks: write # to generate status

jobs:
  labeler:
    name: Labeler
    runs-on: ubuntu-latest
    steps:
      # follows semantic versioning. Lock to different version: v1, v1.5, v1.5.0 or use a commit hash.
      - uses: fuxingloh/multi-labeler@v4 # v4
        with:
          github-token: ${{secrets.GITHUB_TOKEN}} # optional, default to '${{ github.token }}'
          config-path: .github/labeler.yml # optional, default to '.github/labeler.yml'
          config-repo: my-org/my-repo # optional, default to '${{ github.repository }}'

.github/labeler.yml

# .github/labeler.yml

version: v1

labels:
  - label: 'feat'
    sync: true # remove label if match failed, default: false (pull_request/issue only)
    matcher:
      # Matcher will match on any 8 matchers
      title: '^feat:.*'
      body: '/feat'
      comment: '/feat'
      branch: '^feat/.*'
      baseBranch: '^feat/.*'
      commits: '^feat:.*'
      author:
        - github-actions
        - fuxingloh
      files:
        any: ['app/*']
        all: ['!app/config/**']
        count:
          gte: 1
          lte: 1000

# Optional, if you want labels to generate a success/failure status check
checks:
  - context: 'Status Check'
    url: 'https://go.to/detail'
    description:
      success: 'Ready for review & merge.'
      failure: 'Missing labels for release.'
    labels:
      any:
        - any
        - have
      all:
        - all
        - must
        - have

Examples

Semantic Pull Request

.github/workflow/pr-triage.yml

on:
  pull_request:
    types: [opened, edited, synchronize, ready_for_review]
    branches: [master, main]

jobs:
  labeler:
    name: Labeler
    runs-on: ubuntu-latest
    steps:
      - uses: fuxingloh/multi-labeler@v4

.github/labeler.yml

version: v1

labels:
  - label: 'feat'
    matcher:
      title: '^feat: .*'
      commits: '^feat: .*'

  - label: 'fix'
    matcher:
      title: '^fix: .*'
      commits: '^fix: .*'

  - label: 'chore'
    matcher:
      title: '^chore: .*'
      commits: '^chore: .*'

  - label: 'docs'
    matcher:
      title: '^docs: .*'
      commits: '^docs: .*'

checks:
  - context: 'Semantic Pull Request'
    url: 'https://github.com/fuxingloh/multi-labeler/blob/main/.github/labeler.yml'
    description:
      success: Ready for review & merge.
      failure: Missing semantic label for merge.
    labels:
      any:
        - feat
        - fix
        - chore
        - docs
PR Triage

.github/workflow/pr-triage.yml

on:
  pull_request:
    types: [opened, edited, synchronize, ready_for_review]
    branches: [master, main]

jobs:
  labeler:
    name: Labeler
    runs-on: ubuntu-latest
    steps:
      - uses: fuxingloh/multi-labeler@v4

.github/labeler.yml

version: v1

labels:
  - label: 'feat'
    matcher:
      title: '^feat:.*'
      branch: '^feat/.*'
      commits: '^feat:.*'

  - label: 'fix'
    matcher:
      title: '^fix:.*'
      branch: '^fix/.*'
      commits: '^fix:.*'

  - label: 'release'
    matcher:
      baseBranch: '^release/.*'
Issue Triage

.github/workflow/issue-triage.yml

on:
  issues:
    types: [opened, edited]

jobs:
  labeler:
    name: Labeler
    runs-on: ubuntu-latest
    steps:
      - uses: fuxingloh/multi-labeler@v4

.github/labeler.yml

version: v1

labels:
  - label: 'bug'
    matcher:
      body: "(\\n|.)*- \\[x\\] bug(\\n|.)*"
Comment Triage

.github/workflow/comment-slash.yml

on:
  issue_comment:
    types: [created, edited]

jobs:
  labeler:
    name: Labeler
    runs-on: ubuntu-latest
    steps:
      - uses: fuxingloh/multi-labeler@v4

.github/labeler.yml

version: v1

labels:
  - label: 'coverage'
    matcher:
      comment: "# \\[Codecov\\] .*"

  - label: 'stale'
    matcher:
      comment: '/stale'

Configuration

Once you’ve added fuxingloh/multi-labeler to your repository, it must be enabled by adding a .github/labeler.yml configuration file to the repository. If you want to use a configuration file shared across multiple repositories, you can set theconfig-repo input to point to a different repository. However, make sure to set a github-token that has permissions to access the provided repository, as the default GITHUB_TOKEN only has access to the repository the action is running in.

Matchers

RegEx matcher requires backslash '' to be double slashed '\'. Hence, to match brackets '()' you need a regex of '\(\)'. See #103

PR/Issue Title: RegEx

version: v1

labels:
  - label: 'feat'
    matcher:
      title: '^feat:.*'

PR/Issue Body: RegEx

version: v1

labels:
  - label: 'bug'
    matcher:
      # e.g. '- [x] bug'
      body: "(\\n|.)*- \\[x\\] bug(\\n|.)*"

PR/Issue Comment: RegEx

version: v1

labels:
  - label: 'stale'
    matcher:
      comment: '/stale'

PR Branch: RegEx

version: v1

labels:
  - label: 'feat'
    matcher:
      branch: '^feat/.*'

PR Base Branch: RegEx

version: v1

labels:
  - label: 'release'
    matcher:
      baseBranch: '^release/.*'

PR Commits: RegEx

Check all commits and find any match, max of 250 commits only.

version: v1

labels:
  - label: 'feat'
    matcher:
      commits: '^feat: .*'

PR/Issue Author

Check for pr or issue author match.

version: v1

labels:
  - label: 'single'
    matcher:
      author: 'fuxingloh'
  - label: 'any'
    matcher:
      author:
        - adam
        - claire

PR Files: Glob Matcher

Maximum of 3000 files only. If you use this to audit changes, take note of the 3000 files limitation. Matchers within files are 'and condition'; all must match.

PR Files Basic

version: v1

labels:
  - label: 'github'
    sync: true
    matcher:
      # This is shorthand for any: [".github/**"]
      files: '.github/**'

  - label: 'security'
    sync: true
    matcher:
      # This is shorthand for any: ["web/security/**", "security/**"]
      files: ['web/security/**', 'security/**']

PR Files Count

version: v1

labels:
  - label: 'size: s'
    sync: true
    matcher:
      files:
        count:
          gte: 1
          lte: 4

  - label: 'size: m'
    sync: true
    matcher:
      files:
        count:
          gte: 5
          lte: 10

  - label: 'size: l'
    sync: true
    matcher:
      files:
        count:
          gte: 11

PR Files Any & All

version: v1

labels:
  - label: 'ci'
    sync: true
    matcher:
      files:
        any: ['.github/workflow/**', '.circleci/**']
        all: ['!app/**']

  - label: 'attention'
    sync: true
    matcher:
      files:
        any: ['app/**']
        count:
          neq: 1

PR Status Checks

PR Check any

version: v1

checks:
  - context: 'Release Drafter'
    url: 'https://go.to/detail'
    description:
      success: 'Ready for review & merge.'
      failure: 'Missing labels for release.'
    labels:
      any:
        - feat
        - fix
        - chore
        - docs

PR Check any + all

version: v1

checks:
  - context: 'Merge check'
    description: 'Labels for merge.'
    labels:
      any: ['reviewed', 'size:s']
      all: ['app']

PR Check none

version: v1

checks:
  - context: 'Merge check'
    description: "Disable merging when 'DO NOT MERGE' label is set"
    labels:
      none: ['DO NOT MERGE']

Why?

There are so many labelers why create another? 😧

  1. I want a lightweight labeler written in TypeScript so that it doesn't have to build a docker image every time it runs.
  2. I want a simple match first append based multi-labeler without it being a turing complete solution.
  3. I want to write my rules with .github/labeler.yml for a single source of label truth.
  4. I don't want it to do anything else, labels only.
    1. Assume you are using GitHub branch protection (labels only).
    2. I want to run this in PR triage before everything else (labels only).
    3. Chain this action with another action; this should just be for (labels only).