From 8cd3491874fcb44bcaad4ba4999402deed181d24 Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:44:06 -0400 Subject: [PATCH 1/3] feat(yoshi-java-monorepo): update library version in librarian.yaml (#2750) --- src/strategies/java-yoshi-mono-repo.ts | 18 ++++ src/updaters/java/librarian-yaml.ts | 99 ++++++++++++++++++++ test/strategies/java-yoshi-mono-repo.ts | 9 ++ test/updaters/librarian-yaml.ts | 118 ++++++++++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 src/updaters/java/librarian-yaml.ts create mode 100644 test/updaters/librarian-yaml.ts diff --git a/src/strategies/java-yoshi-mono-repo.ts b/src/strategies/java-yoshi-mono-repo.ts index ef952f4ef..07c1c7f0e 100644 --- a/src/strategies/java-yoshi-mono-repo.ts +++ b/src/strategies/java-yoshi-mono-repo.ts @@ -30,6 +30,7 @@ import { import {ConventionalCommit} from '../commit'; import {Java, JavaBuildUpdatesOption} from './java'; import {JavaUpdate} from '../updaters/java/java-update'; +import {LibrarianYamlUpdater} from '../updaters/java/librarian-yaml'; import {filterCommits} from '../util/filter-commits'; export class JavaYoshiMonoRepo extends Java { @@ -135,6 +136,11 @@ export class JavaYoshiMonoRepo extends Java { this.targetBranch, this.path ); + const librarianFilesSearch = this.github.findFilesByFilenameAndRef( + 'librarian.yaml', + this.targetBranch, + this.path + ); const pomFiles = await pomFilesSearch; pomFiles.forEach(path => { @@ -201,6 +207,18 @@ export class JavaYoshiMonoRepo extends Java { }); }); + const librarianFiles = await librarianFilesSearch; + librarianFiles.forEach(path => { + updates.push({ + path: this.addPath(path), + createIfMissing: false, + updater: new LibrarianYamlUpdater({ + version, + versionsMap, + }), + }); + }); + this.extraFiles.forEach(extraFile => { if (typeof extraFile === 'object') { return; diff --git a/src/updaters/java/librarian-yaml.ts b/src/updaters/java/librarian-yaml.ts new file mode 100644 index 000000000..38eb92323 --- /dev/null +++ b/src/updaters/java/librarian-yaml.ts @@ -0,0 +1,99 @@ +// Copyright 2026 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. + +import {DefaultUpdater} from '../default'; +import * as yaml from 'yaml'; +import {logger as defaultLogger, Logger} from '../../util/logger'; + +export interface JavaModule { + distribution_name_override: string; + [key: string]: any; +} + +export interface LibrarianLibrary { + name: string; + version: string; + java: JavaModule; + [key: string]: any; +} + +export interface LibrarianYamlSchema { + libraries: LibrarianLibrary[]; + [key: string]: any; +} + +/** + * Updates a librarian.yaml file. + */ +export class LibrarianYamlUpdater extends DefaultUpdater { + specialArtifacts: ReadonlyMap = new Map([ + ['google-cloud-java', 'google-cloud-java'], + ]); + /** + * Given initial file contents, return updated contents. + * @param {string} content The initial content + * @returns {string} The updated content + */ + updateContent(content: string, logger: Logger = defaultLogger): string { + if (!this.versionsMap) { + logger.warn('missing versions map'); + return content; + } + + // Use yaml package to make sure librarian.yaml is not reformatted because + // we use different tool to format librarian.yaml. + const doc = yaml.parseDocument(content); + if (!doc || doc.errors.length > 0) { + logger.warn('Invalid yaml, cannot be parsed'); + return content; + } + + const libraries = doc.get('libraries'); + if (!libraries || !yaml.isSeq(libraries)) { + return content; + } + + let modified = false; + for (const library of libraries.items) { + if (!yaml.isMap(library)) continue; + + const artifactID = this.findArtifactID( + library.toJSON() as LibrarianLibrary + ); + if (this.versionsMap.has(artifactID)) { + const newVersion = this.versionsMap.get(artifactID); + if (newVersion && library.get('version') !== newVersion.toString()) { + library.set('version', newVersion.toString()); + modified = true; + } + } + } + + if (modified) { + return doc.toString({lineWidth: 0}); + } + return content; + } + + findArtifactID(library: LibrarianLibrary): string { + const artifact = this.specialArtifacts.get(library.name); + if (artifact) { + return artifact; + } + if (library.java && library.java.distribution_name_override) { + return library.java.distribution_name_override.split(':')[1]; + } + return `google-cloud-${library.name}`; + } +} diff --git a/test/strategies/java-yoshi-mono-repo.ts b/test/strategies/java-yoshi-mono-repo.ts index 98bed4db7..978f3f33f 100644 --- a/test/strategies/java-yoshi-mono-repo.ts +++ b/test/strategies/java-yoshi-mono-repo.ts @@ -27,6 +27,7 @@ import {TagName} from '../../src/util/tag-name'; import {Version} from '../../src/version'; import {Changelog} from '../../src/updaters/changelog'; import {JavaUpdate} from '../../src/updaters/java/java-update'; +import {LibrarianYamlUpdater} from '../../src/updaters/java/librarian-yaml'; import {VersionsManifest} from '../../src/updaters/java/versions-manifest'; import {CompositeUpdater} from '../../src/updaters/composite'; @@ -261,6 +262,9 @@ describe('JavaYoshiMonoRepo', () => { findFilesStub .withArgs('Version.java', 'main', '.') .resolves(['path1/Version.java']); + findFilesStub + .withArgs('librarian.yaml', 'main', '.') + .resolves(['path1/librarian.yaml']); const getFileContentsStub = sandbox.stub( github, 'getFileContentsOnBranch' @@ -289,6 +293,7 @@ describe('JavaYoshiMonoRepo', () => { assertHasUpdate(updates, 'path1/README.md', JavaUpdate); assertHasUpdate(updates, 'path2/README.md', JavaUpdate); assertHasUpdate(updates, 'path1/Version.java', JavaUpdate); + assertHasUpdate(updates, 'path1/librarian.yaml', LibrarianYamlUpdater); }); it('finds and updates extra files', async () => { @@ -340,6 +345,9 @@ describe('JavaYoshiMonoRepo', () => { findFilesStub .withArgs('Version.java', 'main', '.') .resolves(['path1/Version.java']); + findFilesStub + .withArgs('librarian.yaml', 'main', '.') + .resolves(['path1/librarian.yaml']); const getFileContentsStub = sandbox.stub( github, 'getFileContentsOnBranch' @@ -370,6 +378,7 @@ describe('JavaYoshiMonoRepo', () => { assertHasUpdate(updates, 'path1/README.md', JavaUpdate); assertHasUpdate(updates, 'path2/README.md', JavaUpdate); assertHasUpdate(updates, 'path1/Version.java', JavaUpdate); + assertHasUpdate(updates, 'path1/librarian.yaml', LibrarianYamlUpdater); }); it('updates changelog.json', async () => { diff --git a/test/updaters/librarian-yaml.ts b/test/updaters/librarian-yaml.ts new file mode 100644 index 000000000..3de383bd0 --- /dev/null +++ b/test/updaters/librarian-yaml.ts @@ -0,0 +1,118 @@ +// Copyright 2026 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. + +import {describe, it} from 'mocha'; +import {expect} from 'chai'; +import {LibrarianYamlUpdater} from '../../src/updaters/java/librarian-yaml'; +import {Version} from '../../src/version'; + +const oldContent = `language: java +sources: + googleapis: + commit: cd090841ab172574e740c214c99df00aef9c0dee + sha256: 08e4b7744dc23b6e3320a3f1d05db9f40853aaf1089d06bfb8d79044b7a66f21 +default: + java: + libraries_bom_version: 26.79.0 +libraries: + - name: google-cloud-java + version: 1.84.0 + skip_generate: true + - name: shopping-css + version: 0.58.0 + apis: + - path: google/shopping/css/v1 + java: + api_description_override: The CSS API is used to manage your CSS and control your CSS Products portfolio + non_cloud_api: true + distribution_name_override: com.google.shopping:google-shopping-css + name_pretty_override: CSS API + java_apis: + - additional_protos: + - google/cloud/common_resources.proto + path: google/shopping/css/v1 + product_documentation_override: https://developers.google.com/comparison-shopping-services/api + - name: secretmanager + version: 2.1.0 + java: + api_description_override: allows you to encrypt, store, manage, and audit infrastructure and application-level secrets. +`; + +const updatedContent = `language: java +sources: + googleapis: + commit: cd090841ab172574e740c214c99df00aef9c0dee + sha256: 08e4b7744dc23b6e3320a3f1d05db9f40853aaf1089d06bfb8d79044b7a66f21 +default: + java: + libraries_bom_version: 26.79.0 +libraries: + - name: google-cloud-java + version: 1.85.0 + skip_generate: true + - name: shopping-css + version: 0.59.0 + apis: + - path: google/shopping/css/v1 + java: + api_description_override: The CSS API is used to manage your CSS and control your CSS Products portfolio + non_cloud_api: true + distribution_name_override: com.google.shopping:google-shopping-css + name_pretty_override: CSS API + java_apis: + - additional_protos: + - google/cloud/common_resources.proto + path: google/shopping/css/v1 + product_documentation_override: https://developers.google.com/comparison-shopping-services/api + - name: secretmanager + version: 2.2.0 + java: + api_description_override: allows you to encrypt, store, manage, and audit infrastructure and application-level secrets. +`; + +describe('LibrarianYamlUpdater', () => { + it('updates librarian.yaml version based on versionsMap', () => { + const versionsMap = new Map(); + versionsMap.set('google-shopping-css', Version.parse('0.59.0')); + versionsMap.set('google-cloud-secretmanager', Version.parse('2.2.0')); + versionsMap.set('google-cloud-java', Version.parse('1.85.0')); + + const updater = new LibrarianYamlUpdater({ + version: Version.parse('1.0.0'), // Unused + versionsMap, + }); + const newContent = updater.updateContent(oldContent); + // Compare the content to verify librarian.yaml is not reformatted. + expect(newContent).to.eq(updatedContent); + }); + + it('returns original content if versionsMap is missing', () => { + const updater = new LibrarianYamlUpdater({ + version: Version.parse('1.0.0'), + }); + const newContent = updater.updateContent(oldContent); + expect(newContent).to.equal(oldContent); + }); + + it('returns original content if no libraries match versionsMap', () => { + const versionsMap = new Map(); + versionsMap.set('non-existent', Version.parse('1.0.0')); + const updater = new LibrarianYamlUpdater({ + version: Version.parse('1.0.0'), + versionsMap, + }); + const newContent = updater.updateContent(oldContent); + expect(newContent).to.equal(oldContent); + }); +}); From e53fa6d9b6c0ab4896168b95ea3eab56221ccf55 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Mon, 13 Apr 2026 13:50:43 -0700 Subject: [PATCH 2/3] fix: use GitHub API for updating files (#2751) --- src/local-github.ts | 159 ++++++++++++++++---------------------------- 1 file changed, 58 insertions(+), 101 deletions(-) diff --git a/src/local-github.ts b/src/local-github.ts index 501751412..0ee726d35 100644 --- a/src/local-github.ts +++ b/src/local-github.ts @@ -18,6 +18,7 @@ import * as os from 'os'; import * as child_process from 'child_process'; import * as util from 'util'; import * as readline from 'readline'; +import {createPullRequest as suggesterCreatePullRequest} from 'code-suggester'; const execFile = util.promisify(child_process.execFile); const mkdtemp = fs.promises.mkdtemp; @@ -719,69 +720,6 @@ export class LocalGitHub implements Scm { } } - private async applyEditsAndPush( - branch: string, - targetBranch: string, - message: string, - changes: ScmChangeSet - ): Promise { - this.logger.debug(`Applying edits and pushing to ${branch}`); - - // Checkout/Reset PR branch - await execFile('git', ['fetch', 'origin', '--', targetBranch], { - cwd: this.cloneDir, - }); - await execFile( - 'git', - ['checkout', '-B', branch, `origin/${targetBranch}`], - { - cwd: this.cloneDir, - } - ); - - // Write file edits - for (const [filePath, fileUpdate] of changes.entries()) { - const fullPath = path.join(this.cloneDir, filePath); - await fs.promises.mkdir(path.dirname(fullPath), {recursive: true}); - if (fileUpdate.content !== null) { - await fs.promises.writeFile(fullPath, fileUpdate.content); - } else { - await fs.promises.unlink(fullPath).catch(() => {}); - } - if (fileUpdate.mode) { - await fs.promises.chmod(fullPath, parseInt(fileUpdate.mode, 8)); - } - } - - // Commit changes - const msgFile = path.join( - os.tmpdir(), - `release-please-commit-msg-${process.pid}-${Date.now()}` - ); - await fs.promises.writeFile(msgFile, message); - await execFile('git', ['add', '.'], {cwd: this.cloneDir}); - - try { - await execFile('git', ['commit', '--no-verify', '-F', msgFile], { - cwd: this.cloneDir, - }); - } catch (err) { - const error = err as {stdout?: string; stderr?: string}; - if (error.stdout && error.stdout.includes('nothing to commit')) { - this.logger.debug('Nothing to commit'); - } else { - throw err; - } - } finally { - await fs.promises.unlink(msgFile).catch(() => {}); - } - - // Push transit - await execFile('git', ['push', '--no-verify', '-f', 'origin', branch], { - cwd: this.cloneDir, - }); - } - /** * Open a pull request * @@ -800,18 +738,39 @@ export class LocalGitHub implements Scm { options?: ScmCreatePullRequestOptions ): Promise { const changes = await this.buildChangeSet(updates, targetBranch); - await this.applyEditsAndPush( - pullRequest.headBranchName, - targetBranch, - message, - changes - ); - this.logger.info('Creating pull request via GitHub API...'); - return await this.gitHubApi.createPullRequest( - pullRequest, - targetBranch, - options + const prNumber = await suggesterCreatePullRequest( + this.gitHubApi.octokit, + changes, + { + upstreamOwner: this.repository.owner, + upstreamRepo: this.repository.repo, + title: pullRequest.title, + branch: pullRequest.headBranchName, + description: pullRequest.body, + primary: targetBranch, + force: true, + fork: !!options?.fork, + message, + logger: this.logger, + draft: !!options?.draft, + labels: pullRequest.labels, + } ); + if (prNumber === 0) { + this.logger.warn( + 'no code changes detected, skipping pull request creation' + ); + return { + headBranchName: pullRequest.headBranchName, + baseBranchName: targetBranch, + number: 0, + title: pullRequest.title, + body: pullRequest.body, + labels: pullRequest.labels, + files: [], + }; + } + return await this.getPullRequest(prNumber); } /** @@ -836,12 +795,7 @@ export class LocalGitHub implements Scm { targetBranch ); const message = pullRequest.title.toString(); - await this.applyEditsAndPush( - pullRequest.headRefName, - targetBranch, - message, - changes - ); + const title = pullRequest.title.toString(); const body = ( options?.pullRequestOverflowHandler ? await options.pullRequestOverflowHandler.handleOverflow(pullRequest) @@ -849,27 +803,30 @@ export class LocalGitHub implements Scm { ) .toString() .slice(0, MAX_ISSUE_BODY_SIZE); - const pullResponseData = ( - await this.gitHubApi.octokit.pulls.update({ - owner: this.repository.owner, - repo: this.repository.repo, - pull_number: number, - title: pullRequest.title.toString(), - body, - state: 'open', - }) - ).data; - return { - headBranchName: pullResponseData.head.ref, - baseBranchName: pullResponseData.base.ref, - number: pullResponseData.number, - title: pullResponseData.title, - body: pullResponseData.body || '', - files: [], - labels: pullResponseData.labels - .map((label: any) => label.name) - .filter((name: any) => !!name) as string[], - }; + + const prNumber = await suggesterCreatePullRequest( + this.gitHubApi.octokit, + changes, + { + upstreamOwner: this.repository.owner, + upstreamRepo: this.repository.repo, + title, + branch: pullRequest.headRefName, + description: body, + primary: targetBranch, + force: true, + fork: options?.fork === false ? false : true, + message, + logger: this.logger, + draft: pullRequest.draft, + } + ); + if (prNumber !== number) { + this.logger.warn( + `updated code for ${prNumber}, but update requested for ${number}` + ); + } + return this.gitHubApi.updatePullRequest(number, title, body); } async getPullRequest(number: number): Promise { From 712fcf01effd08d7b0e7b1fd3861f2cb388bc8d1 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:59:15 -0700 Subject: [PATCH 3/3] chore(main): release 17.6.0 (#2752) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- src/index.ts | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d5b3a88ef..35d87d129 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "17.5.2" + ".": "17.6.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 956301da5..9ec6ce78a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://www.npmjs.com/package/release-please?activeTab=versions +## [17.6.0](https://github.com/googleapis/release-please/compare/v17.5.2...v17.6.0) (2026-04-13) + + +### Features + +* **yoshi-java-monorepo:** update library version in librarian.yaml ([#2750](https://github.com/googleapis/release-please/issues/2750)) ([8cd3491](https://github.com/googleapis/release-please/commit/8cd3491874fcb44bcaad4ba4999402deed181d24)) + + +### Bug Fixes + +* use GitHub API for updating files ([#2751](https://github.com/googleapis/release-please/issues/2751)) ([e53fa6d](https://github.com/googleapis/release-please/commit/e53fa6d9b6c0ab4896168b95ea3eab56221ccf55)) + ## [17.5.2](https://github.com/googleapis/release-please/compare/v17.5.1...v17.5.2) (2026-04-10) diff --git a/package-lock.json b/package-lock.json index 0ccd67e1a..889abc819 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "release-please", - "version": "17.5.2", + "version": "17.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "release-please", - "version": "17.5.2", + "version": "17.6.0", "license": "Apache-2.0", "dependencies": { "@conventional-commits/parser": "^0.4.1", diff --git a/package.json b/package.json index 4dca84d57..661e8e4ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "release-please", - "version": "17.5.2", + "version": "17.6.0", "description": "generate release PRs based on the conventionalcommits.org spec", "main": "./build/src/index.js", "bin": "./build/src/bin/release-please.js", diff --git a/src/index.ts b/src/index.ts index 2eb3ccaec..b87305679 100644 --- a/src/index.ts +++ b/src/index.ts @@ -63,5 +63,5 @@ export const configSchema = require('../../schemas/config.json'); export const manifestSchema = require('../../schemas/manifest.json'); // x-release-please-start-version -export const VERSION = '17.5.2'; +export const VERSION = '17.6.0'; // x-release-please-end