This is an automated email from the ASF dual-hosted git repository.
mridulpathak pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-plugins.git
The following commit(s) were added to refs/heads/trunk by this push:
new 423db2355 Label pull requests from their title prefix for the release
notes
423db2355 is described below
commit 423db23558ea91638b61044d15caa1f50ff29a2d
Author: Mridul Pathak <[email protected]>
AuthorDate: Wed Oct 28 13:18:24 2026 +0530
Label pull requests from their title prefix for the release notes
This adds the PR Labels workflow and the release notes configuration of the
framework, so that plugin pull requests get their type label from the title
prefix, and release_branch when they target a release branch, like framework
pull requests do, and so that GitHub-generated release notes can group them.
Both files are copies of the framework trunk files.
---
.github/release.yml | 62 +++++++++++++
.github/workflows/pr-type-label.yml | 167 ++++++++++++++++++++++++++++++++++++
2 files changed, 229 insertions(+)
diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 000000000..b826a5c85
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,62 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Configuration of the release notes GitHub generates for a release.
+# GitHub reads this file from the tag being released, so it has to be on the
+# release branch before the tag is cut.
+# The type labels are set from the PR title prefix by pr-type-label.yml.
+# The cve and security labels are set by hand, cve only once the CVE is
+# announced, after which the release notes can be generated again.
+# The api-change label is set by hand on a pull request that changes or removes
+# a public API, so that upgraders find it.
+# A pull request is listed in the first category whose labels match, so the
+# security and API categories come before the type categories.
+# See
https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
+
+changelog:
+ exclude:
+ labels:
+ - dependencies
+ authors:
+ - dependabot[bot]
+ categories:
+ - title: Security fixes
+ labels:
+ - cve
+ - title: Security hardening
+ labels:
+ - security
+ - title: API changes
+ labels:
+ - api-change
+ - title: Bug fixes
+ labels:
+ - bug
+ - title: Improvements
+ labels:
+ - enhancement
+ - title: New features
+ labels:
+ - feature
+ - title: Documentation
+ labels:
+ - documentation
+ - title: Other changes
+ labels:
+ - "*"
diff --git a/.github/workflows/pr-type-label.yml
b/.github/workflows/pr-type-label.yml
new file mode 100644
index 000000000..ee4ad39fa
--- /dev/null
+++ b/.github/workflows/pr-type-label.yml
@@ -0,0 +1,167 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Sets the type label (bug, enhancement, feature, documentation) of a pull
+# request from the prefix of its title, so that the release notes GitHub
+# generates (see .github/release.yml) can group pull requests by type.
+# Dependency updates titled "Bump ..." get the dependencies label, which
+# leaves them out of the release notes like the Dependabot pull requests.
+# Pull requests to a release branch get the release_branch label.
+# It never checks out nor runs code of the pull request.
+# Run it manually (workflow_dispatch) to label pull requests already merged.
+
+name: "PR Labels"
+
+on:
+ pull_request_target:
+ types: [ opened, edited, reopened ]
+ branches: [ trunk, release* ]
+ workflow_dispatch:
+ inputs:
+ since:
+ description: "Label pull requests merged on or after this date
(YYYY-MM-DD)"
+ required: true
+ dry_run:
+ description: "Only log the labels, without changing the pull requests"
+ type: boolean
+ default: true
+
+permissions: {}
+
+# Only the runs that label share the group of their pull request, so that a
body-only edit, whose
+# job is skipped, can't cancel the still queued run of the opening of the pull
request.
+concurrency:
+ group: >-
+ pr-type-label-${{ github.event_name == 'pull_request_target' &&
+ (github.event.action != 'edited' || github.event.changes.title ||
github.event.changes.base) &&
+ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ label:
+ if: >-
+ github.event_name == 'workflow_dispatch' ||
+ (github.event.pull_request.user.login != 'dependabot[bot]' &&
+ (github.event.action != 'edited' || github.event.changes.title ||
github.event.changes.base))
+ runs-on: ubuntu-slim
+ permissions:
+ pull-requests: write
+ steps:
+ - name: Set the type label from the title prefix
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
v9.0.0
+ with:
+ script: |
+ const TYPE_LABELS = ['bug', 'enhancement', 'feature',
'documentation'];
+ const RELEASE_BRANCH_LABEL = 'release_branch';
+ const PREFIXES = [
+ [/^(fixed|fix|fixing)(?=[\s:])/i, 'bug'],
+ [/^(improved|completed)(?=[\s:])/i, 'enhancement'],
+ [/^implemented(?=[\s:])/i, 'feature'],
+ [/^documented(?=[\s:])/i, 'documentation'],
+ [/^bump(?=[\s:])/i, 'dependencies'],
+ ];
+ // e.g. "Backport PR #1360: 'Fixed: ...'", "Backport pr 1157
(Improved: ...)", "Backported: ..."
+ const BACKPORT =
/^backport(?:ed)?\b\s*(?:pr\b)?\s*#?\s*(\d*)\s*[:\-–]?\s*['"(]?\s*/i;
+ const { owner, repo } = context.repo;
+
+ function labelFromPrefix(title) {
+ const match = PREFIXES.find(([prefix]) => prefix.test(title));
+ return match ? match[1] : null;
+ }
+
+ async function typeLabel(pr) {
+ const title = (pr.title || '').trim();
+ const label = labelFromPrefix(title);
+ if (label) {
+ return label;
+ }
+ const preamble = title.match(BACKPORT);
+ if (!preamble && !/\bbackport/i.test(pr.body || '')) {
+ return null;
+ }
+ const stripped = title.slice(preamble ? preamble[0].length : 0);
+ const strippedLabel = labelFromPrefix(stripped);
+ if (strippedLabel) {
+ return strippedLabel;
+ }
+ // fall back to the title of the backported pull request
+ const ref = (preamble && preamble[1] ? preamble : null) ||
title.match(/#\s*(\d+)/)
+ || title.match(/\bpr\s*(\d+)/i) || (pr.body ||
'').match(/#(\d+)/);
+ if (!ref || Number(ref[1]) === pr.number) {
+ return null;
+ }
+ try {
+ const { data: original } = await github.rest.pulls.get({
owner, repo, pull_number: Number(ref[1]) });
+ return labelFromPrefix((original.title || '').trim());
+ } catch (error) {
+ core.info(`#${pr.number}: cannot read #${ref[1]}:
${error.message}`);
+ return null;
+ }
+ }
+
+ async function setLabels(pr, dryRun) {
+ const label = await typeLabel(pr);
+ const current = pr.labels.map(l => l.name);
+ const remove = label ? current.filter(name =>
TYPE_LABELS.includes(name) && name !== label) : [];
+ const add = label && !current.includes(label) ? [label] : [];
+ const onReleaseBranch = pr.base.ref.startsWith('release');
+ if (onReleaseBranch && !current.includes(RELEASE_BRANCH_LABEL)) {
+ add.push(RELEASE_BRANCH_LABEL);
+ } else if (pr.base.ref === pr.base.repo.default_branch &&
current.includes(RELEASE_BRANCH_LABEL)) {
+ remove.push(RELEASE_BRANCH_LABEL);
+ }
+ core.info(`#${pr.number} [${label || 'no
type'}]${remove.map(name => ` -${name}`).join('')}${add.map(name => `
+${name}`).join('')} ${pr.title}`);
+ if (dryRun) {
+ return;
+ }
+ for (const name of remove) {
+ await github.rest.issues.removeLabel({ owner, repo,
issue_number: pr.number, name });
+ }
+ if (add.length) {
+ await github.rest.issues.addLabels({ owner, repo,
issue_number: pr.number, labels: add });
+ }
+ }
+
+ if (context.eventName === 'pull_request_target') {
+ // read the pull request again, a previous run may have changed
its labels
+ const { data: pr } = await github.rest.pulls.get({ owner, repo,
pull_number: context.payload.pull_request.number });
+ await setLabels(pr, false);
+ return;
+ }
+
+ const { since } = context.payload.inputs;
+ const dryRun = context.payload.inputs.dry_run !== false &&
context.payload.inputs.dry_run !== 'false';
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(since)) {
+ core.setFailed(`since must be a date (YYYY-MM-DD), got:
${since}`);
+ return;
+ }
+ const sinceTime = Date.parse(`${since}T00:00:00Z`);
+ const merged = await github.paginate(github.rest.pulls.list,
+ { owner, repo, state: 'closed', sort: 'updated', direction:
'desc', per_page: 100 },
+ (response, done) => {
+ if (response.data.some(pr => Date.parse(pr.updated_at) <
sinceTime)) {
+ done();
+ }
+ return response.data.filter(pr => pr.merged_at &&
Date.parse(pr.merged_at) >= sinceTime
+ && pr.user.login !== 'dependabot[bot]');
+ });
+ core.info(`${merged.length} pull requests merged since
${since}${dryRun ? ' (dry run)' : ''}`);
+ for (const pr of merged) {
+ await setLabels(pr, dryRun);
+ }