diff --git a/.github/workflows/mirror-changelog.yml b/.github/workflows/mirror-changelog.yml new file mode 100644 index 0000000..950d847 --- /dev/null +++ b/.github/workflows/mirror-changelog.yml @@ -0,0 +1,134 @@ +# Copyright 2025 Google LLC +# +# Licensed 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. + +name: Mirror Toolbox Changelog + +on: + pull_request_target: + types: [opened, edited] + +jobs: + add-release-notes: + if: github.actor == 'renovate[bot]' && startsWith(github.head_ref, 'renovate/googleapis-genai-toolbox') + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Add Toolbox Release Notes to PR Body + uses: actions/github-script@v6 + env: + REQUIRED_KEYWORDS: 'postgres' + with: + script: | + const requiredKeywordsEnv = process.env.REQUIRED_KEYWORDS; + const requiredKeywords = requiredKeywordsEnv.split(',').map(kw => kw.trim()).filter(kw => kw.length > 0); + + const prBody = context.payload.pull_request.body || ''; + + // Extract the relevant changelog section + const startMarker = 'googleapis/genai-toolbox'; + const endMarker = ''; + const startIndex = prBody.indexOf(startMarker); + const endIndex = prBody.indexOf(endMarker, startIndex); + + if (startIndex === -1 || endIndex === -1) { + console.log('Could not find the release notes section in the PR body. Exiting.'); + return; + } + const releaseNotesSection = prBody.substring(startIndex, endIndex); + + // Parse, Filter, and transform + const prefixesToFilter = ['source/', 'sources/', 'tool/', 'tools/']; + + // Use a map for cleaner type switching + const typeMap = { + '##### ⚠ BREAKING CHANGES': 'feat!', + '##### Features': 'feat', + '##### Bug Fixes': 'fix', + '##### Chores': 'ignore', + '##### Miscellaneous Chores': 'ignore', + '##### Documentation': 'ignore', + }; + + let currentType = 'feat'; // Default + const newChangelog = []; + + for (const line of releaseNotesSection.split('\n')) { + const trimmedLine = line.trim(); + + // Update current type if it's a header + if (typeMap[trimmedLine]) { + currentType = typeMap[trimmedLine]; + continue; + } + + // Skip ignored sections + if (currentType === 'ignore') { + continue; + } + + // Match and extract changelog item + const itemMatch = trimmedLine.match(/^[*-]\s(.*)$/); + if (itemMatch) { + const originalContent = itemMatch[1]; + const lineAsLowerCase = originalContent.toLowerCase(); + + const hasPrefix = prefixesToFilter.some(prefix => lineAsLowerCase.includes(prefix)); + + // Check if the line includes ANY of the required keywords + let hasAnyRequiredKeyword = false; + if (requiredKeywords.length > 0) { + hasAnyRequiredKeyword = requiredKeywords.some(keyword => lineAsLowerCase.includes(keyword)); + } + + // Include if it doesn't have a prefix OR it has any of the required keywords + if (!hasPrefix || hasAnyRequiredKeyword) { + newChangelog.push(`- ${currentType}: ${originalContent}`); + } else { + console.log(`Filtering out: ${originalContent}`); + } + } + } + + if (newChangelog.length === 0) { + console.log('Found no changelog items to add after filtering. Exiting.'); + return; + } + + // Construct the override block + const overrideBlock = [ + '\n\nBEGIN_COMMIT_OVERRIDE', + ...newChangelog, + 'END_COMMIT_OVERRIDE' + ].join('\n'); + + // Update PR body + const baseBody = prBody.split('\n\nBEGIN_COMMIT_OVERRIDE')[0].trim(); + const finalBody = baseBody + overrideBlock; + + if (finalBody === prBody) { + console.log('The generated changelog is identical. No update needed.'); + return; + } + + // Update the PR + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + body: finalBody, + }); + + console.log('Successfully updated the PR body with filtered release notes.'); diff --git a/DEVELOPER.md b/DEVELOPER.md index f8b2b98..b4ba19a 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -98,7 +98,41 @@ The primary maintainers for this repository are defined in the ### Releasing -The release process is automated using `release-please`. +The release process is automated using `release-please`. It consists of an automated changelog preparation step followed by the manual merging of a Release PR. + +#### Automated Changelog Enrichment + +Before a Release PR is even created, a special workflow automatically mirrors +relevant changelogs from the core `googleapis/genai-toolbox` dependency. This +ensures that the release notes for this extension accurately reflect important +upstream changes. + +The process is handled by the [`mirror-changelog.yml`](.github/workflows/mirror-changelog.yml) workflow: + +1. **Trigger:** The workflow runs automatically on pull requests created by + Renovate for `toolbox` version updates. +2. **Parsing:** It reads the detailed release notes that Renovate includes in + the PR body. +3. **Filtering:** These release notes are filtered to include only changes + relevant to this extension. The relevance is determined by a keyword (e.g., + `postgres`), passed as an environment variable in the workflow file. +4. **Changelog Injection:** The script formats the filtered entries as + conventional commits and injects them into the PR body within a + `BEGIN_COMMIT_OVERRIDE` block. +5. **Release Please:** When the main Release PR is created, `release-please` + reads this override block instead of the standard `chore(deps): ...` commit + message, effectively mirroring the filtered upstream changelog into this + project's release notes. + +> **Note for Maintainers:** The filtering script is an automation aid, but it +> may occasionally produce "false positives" (e.g., an internal logging change +> that happens to contain the keyword). Before merging a `toolbox` dependency +> PR, maintainers must **review the generated `BEGIN_COMMIT_OVERRIDE` block** +> and manually delete any lines that are not relevant to the end-users of this +> extension. The curated override block is the final source of truth for the +> release changelog. + +#### Release Process 1. **Release PR:** When commits with conventional commit headers (e.g., `feat:`, `fix:`) are merged into the `main` branch, `release-please` will