github-actions

How to create GitHub Actions Workflow that works when code in any folder has been changed


I have a repository that contains many folders:

/folder_a
/folder_b
...
/folder_z

I want to create a GitHub Action workflow that is triggered whenever a PR is closed in which code within one of those folders has changed, in order to create a release that is tagged with the name of changed folder + a version number.

Example: if contents in folder_a has been changed in a PR, then when it gets merged it should create a release named folder_a_1.0.0 with the folder name as prefix.

I could create a workflow for each of the folders, but since there are many folders this approach wouldn't be very maintainable (example below with a re-usable release.yml workflow that contains the logic for creating the release and takes a prefix as input):

name: Release folder_a

on:
  pull_request:
    branches:
      - master
    paths:
      - "folder_a/**"

permissions:
  contents: write

jobs:
  release:
    uses: ./.github/workflows/release.yml
    secrets: inherit
    with:
      prefix: folder_a

Is there a way to use a single workflow to do the same job?


Solution

  • Your workflow needs an extra job that would feed names of all folders that changed into the release job. Because your reusable workflow ./.github/workflows/release.yml expects to be invoked with a single folder at a time, you need a matrix that would run your reusable workflow as many times as the number of changed folders:

    name: Release folder_a
    
    on:
      pull_request:
        branches:
          - master
        paths:
          - "folder_*/**"
    
    permissions:
      contents: write
    
    jobs:
    
      collect:
        runs-on:  ubuntu-latest
        outputs:
          outcomes: ${{ steps.collect.outputs.folders }}
        steps:
          - uses: actions/checkout@v4
          - id: collect
            run: |
              git diff --name-only ${{ github.base_ref }} ${{ github.head_ref }} | sed 's|/.*||' | uniq | jq -R -s -c 'split("\n")' > folders
              echo folders=$(cat folders) >> $GITHUB_OUTPUT
    
      release:
        needs: collect
        runs-on:  ubuntu-latest
        strategy:
          fail-fast: false
          matrix:
            folder: ${{ fromJSON(needs.collect.outputs.folders) }}
        uses: ./.github/workflows/release.yml
        secrets: inherit
        with:
          prefix: ${{ matrix.folder }}
    

    Here git diff create a list of all files that changed on the PR relative to the base branch:

    git diff --name-only ${{ github.base_ref }} ${{ github.head_ref }} 
    
    folder_a/file_1
    folder_a/file_2
    folder_a/file_3
    folder_b/file_1
    folder_b/file_2
    

    then sed removes everything after the "/" leaving just the folder name while uniq removes duplicates:

    sed 's|/.*||' | uniq 
    
    folder_a
    folder_b
    

    then jq slurps (-s) this output as raw text (-R) into a JSON list and outputs this list as a compact, one line JSON (-c):

    jq -R -s -c 'split("\n")'
    
    ["folder_a","folder_b"]
    

    This list is used as a folder matrix in the release job that invokes reusable workflow once per folder. Note, that the prefix input will not have a trailing "/"