From 2adfb13379092d1bac2146889ecc053be9c6e051 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Mon, 6 Jul 2026 08:44:32 +0200 Subject: [PATCH 1/3] Added git branch validation pattern Due to auto release note, branch pattern must be followed. --- .github/workflows/pr-auto-semver.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-auto-semver.yml b/.github/workflows/pr-auto-semver.yml index d3f595e..701c682 100644 --- a/.github/workflows/pr-auto-semver.yml +++ b/.github/workflows/pr-auto-semver.yml @@ -34,7 +34,6 @@ jobs: PR_TITLE: ${{ github.event.pull_request.title }} PR_BODY: ${{ github.event.pull_request.body }} steps: - - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -140,3 +139,26 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + branch-name: + name: Validate branch name pattern + runs-on: ubuntu-latest + steps: + - name: Check branch name pattern + # For the pattern see also ../release-drafter-labeler-config.yml + env: + BRANCH: ${{ github.head_ref }} + run: | + if [[ "${BRANCH}" == "develop" ]]; then + echo "Branch name is 'develop', skipping pattern check" + exit 0 + fi + if [[ "${BRANCH}" == "main" ]]; then + echo "Branch name is 'main', skipping pattern check" + exit 0 + fi + expected_pattern="^(feature|feat|bugfix|bug|hotfix|hot|data|norn|skip-rn)[/_-].+$" + if [[ ! "${BRANCH}" =~ ${expected_pattern} ]]; then + echo "Invalid branch name: ${BRANCH}, expected pattern: ${expected_pattern}" + exit 1 + fi From bbbfa47a1666190d18035b5796701496fef77529 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Thu, 9 Jul 2026 13:08:38 +0200 Subject: [PATCH 2/3] Replace branch name pattern validation with PR label validation The branch name was only used to auto-assign a label for release note categorization, and the branch is deleted once the PR is merged, so its naming is not important in itself. Labels are what actually matter: they categorize release notes and can now also be used to block accidental merges (e.g. via a "DO NOT MERGE" label), so validate labels directly instead of enforcing a branch naming convention. --- .github/workflows/pr-auto-semver.yml | 23 ------ .github/workflows/pr-labels.yml | 78 ++++++++++++++++++++ workflow-templates/pr-labels.properties.json | 8 ++ workflow-templates/pr-labels.yml | 11 +++ 4 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/pr-labels.yml create mode 100644 workflow-templates/pr-labels.properties.json create mode 100644 workflow-templates/pr-labels.yml diff --git a/.github/workflows/pr-auto-semver.yml b/.github/workflows/pr-auto-semver.yml index 701c682..d44b8b6 100644 --- a/.github/workflows/pr-auto-semver.yml +++ b/.github/workflows/pr-auto-semver.yml @@ -139,26 +139,3 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - branch-name: - name: Validate branch name pattern - runs-on: ubuntu-latest - steps: - - name: Check branch name pattern - # For the pattern see also ../release-drafter-labeler-config.yml - env: - BRANCH: ${{ github.head_ref }} - run: | - if [[ "${BRANCH}" == "develop" ]]; then - echo "Branch name is 'develop', skipping pattern check" - exit 0 - fi - if [[ "${BRANCH}" == "main" ]]; then - echo "Branch name is 'main', skipping pattern check" - exit 0 - fi - expected_pattern="^(feature|feat|bugfix|bug|hotfix|hot|data|norn|skip-rn)[/_-].+$" - if [[ ! "${BRANCH}" =~ ${expected_pattern} ]]; then - echo "Invalid branch name: ${BRANCH}, expected pattern: ${expected_pattern}" - exit 1 - fi diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml new file mode 100644 index 0000000..478d3e3 --- /dev/null +++ b/.github/workflows/pr-labels.yml @@ -0,0 +1,78 @@ +name: PR Label Validation Reusable Workflow + +# Validate that the PR has exactly one of the required category labels and none of the +# forbidden labels. This is triggered on label changes so that manually adding or removing +# a label re-runs the validation. + +# NOTES: +# - this workflow only works for github pull_request trigger +# - Github labels are managed by Terraform, see https://github.com/swissgeo/infra-terraform/blob/main/github/modules/issue-labels/main.tf + +# Usage example: +# on: +# pull_request: +# types: +# - labeled +# - unlabeled + +on: + workflow_call: + inputs: + required_labels: + description: 'JSON array of labels; exactly one must be present on the PR' + type: string + default: '["bug", "feature", "new-release", "data-integration"]' + forbidden_labels: + description: 'JSON array of labels that must not be present on the PR' + type: string + default: '["DO NOT MERGE :bomb:", "WIP :construction:"]' + +jobs: + pr-labels: + name: Validate PR labels + runs-on: ubuntu-latest + steps: + - name: Check required and forbidden labels + uses: actions/github-script@v9 + env: + REQUIRED_LABELS: ${{ inputs.required_labels }} + FORBIDDEN_LABELS: ${{ inputs.forbidden_labels }} + with: + script: | + // Every PR is also an issue in GitHub's API, so the issues endpoint returns PR labels too. + const { data: issueLabels } = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const labels = issueLabels.map((label) => label.name); + core.info(`PR labels: ${JSON.stringify(labels)}`); + + const forbiddenLabels = JSON.parse(process.env.FORBIDDEN_LABELS); + for (const label of forbiddenLabels) { + if (labels.includes(label)) { + core.setFailed(`PR has forbidden label: ${label}`); + return; + } + } + + if (labels.includes("skip-relase-note")) { + core.info(`PR has skip-release-note label`); + return; + } + + // Exactly one of these category labels is required (other labels are allowed alongside it) + const requiredLabels = JSON.parse(process.env.REQUIRED_LABELS); + const matched = requiredLabels.filter((label) => labels.includes(label)); + + if (matched.length === 0) { + core.setFailed(`PR must have one of the following labels: ${requiredLabels.join(", ")}`); + return; + } + + if (matched.length > 1) { + core.setFailed(`PR must have only one of the following labels, found: ${matched.join(", ")}`); + return; + } + + core.info(`PR label check passed: ${matched.join(", ")}`); diff --git a/workflow-templates/pr-labels.properties.json b/workflow-templates/pr-labels.properties.json new file mode 100644 index 0000000..ac21e77 --- /dev/null +++ b/workflow-templates/pr-labels.properties.json @@ -0,0 +1,8 @@ +{ + "name": "PR Label Validation Workflow", + "description": "PR label validation workflow template for Swissgeo project. This template validates that a PR has exactly one required category label and no forbidden labels, re-checking whenever a label is added or removed.", + "iconName": "swissgeo-logo", + "categories": [ + "JavaScript", "TypeScript", "Python" + ] +} diff --git a/workflow-templates/pr-labels.yml b/workflow-templates/pr-labels.yml new file mode 100644 index 0000000..d1db0fc --- /dev/null +++ b/workflow-templates/pr-labels.yml @@ -0,0 +1,11 @@ +name: on-pr-label + +on: + pull_request: + types: + - labeled + - unlabeled + +jobs: + pr-labels: + uses: swissgeo/.github/.github/workflows/pr-labels.yml@main From 1d9e4fa7cb4cbe560fc9104ce567572ef2171d00 Mon Sep 17 00:00:00 2001 From: Brice Schaffner Date: Fri, 10 Jul 2026 15:31:45 +0200 Subject: [PATCH 3/3] wip --- .github/workflows/pr-labels.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml index 478d3e3..26f1a6b 100644 --- a/.github/workflows/pr-labels.yml +++ b/.github/workflows/pr-labels.yml @@ -56,7 +56,8 @@ jobs: } } - if (labels.includes("skip-relase-note")) { + // If the skip-release-note label is present, no further check is needed + if (labels.includes("skip-release-note")) { core.info(`PR has skip-release-note label`); return; }