diff --git a/docs/customizing.md b/docs/customizing.md index 98c796789..9df888c5c 100644 --- a/docs/customizing.md +++ b/docs/customizing.md @@ -116,6 +116,35 @@ If you wish to avoid that, consider using `component-no-space: true`/`--componen | `${version}` | The version of the component being released | | `${branch?}` | The target branch of the pull request. If you have multiple release branches, this helps identify which release branch we are working on | +#### Group Pull Request Title + +For grouped/merged pull requests (when using plugins like `linked-versions` or when `separate-pull-requests` is `false`), you can use the `group-pull-request-title-pattern` option instead: + +```json +{ + "group-pull-request-title-pattern": "chore${scope}: release ${component} ${version}" +} +``` + +This pattern uses the same placeholders as `pull-request-title-pattern`. When used with the `linked-versions` plugin, the `${component}` placeholder will be replaced with the plugin's `groupName`. + +For example, with the configuration: + +```json +{ + "group-pull-request-title-pattern": "chore${scope}: release ${component} ${version}", + "plugins": [ + { + "type": "linked-versions", + "groupName": "my-sdk", + "components": ["pkgA", "pkgB"] + } + ] +} +``` + +The resulting pull request title will be: `chore(main): release my-sdk v1.2.3` + ### Pull Request Header If you would like to customize the pull request header, you can use the diff --git a/docs/manifest-releaser.md b/docs/manifest-releaser.md index 9e19e1d33..7aec73621 100644 --- a/docs/manifest-releaser.md +++ b/docs/manifest-releaser.md @@ -235,6 +235,8 @@ defaults (those are documented in comments) // Template values (i.e. ${scope}, ${component} and ${version}) are inherited // from the root path's (i.e. '.') package, if present // absence defaults to "chore: release ${branch}" + // Note: When used with the `linked-versions` plugin, ${component} will be + // replaced with the plugin's groupName. "group-pull-request-title-pattern": "chore: release ${branch}", // When searching for the latest release SHAs, only consider the last N releases. @@ -568,7 +570,49 @@ Note: when combining the `linked-versions` plugin with a `workspace` plugin, you will need to tell the `workspace` plugin to skip its own internal merge. See #1457 for context. -Example: +#### Pull Request Title Configuration + +By default, the `linked-versions` plugin generates pull request titles with +the format `chore(scope): release libraries`. You can customize +this by using the `group-pull-request-title-pattern` option: + +```json +{ + "release-type": "rust", + "group-pull-request-title-pattern": "chore${scope}: release ${component} ${version}", + "packages": { + "packages/rustA": { + "component": "pkgA" + }, + "packages/rustB": { + "component": "pkgB" + } + }, + "plugins": [ + { + "type": "cargo-workspace", + "merge": false + }, + { + "type": "linked-versions", + "groupName": "my-sdk", + "components": [ + "pkgA", "pkgB" + ] + } + ] +} +``` + +With this configuration, the pull request title will be +`chore(main): release my-sdk v1.2.3` instead of the default +`chore(main): release my-sdk libraries`. + +The `${component}` placeholder in the pattern will be replaced with the +`groupName` from the plugin configuration. Other available placeholders are +`${scope}`, `${version}`, and `${branch}`. + +Example without configuration (backward compatibility): ```json { @@ -597,6 +641,8 @@ Example: } ``` +This will use the default title: `chore(main): release my group libraries`. + ### sentence-case Capitalize the leading word in a commit message, taking into account common exceptions, e.g., gRPC. diff --git a/src/factories/plugin-factory.ts b/src/factories/plugin-factory.ts index 5f2756e51..6e9013a32 100644 --- a/src/factories/plugin-factory.ts +++ b/src/factories/plugin-factory.ts @@ -48,6 +48,9 @@ export interface PluginFactoryOptions { updateAllPackages?: boolean; considerAllArtifacts?: boolean; + // linked-versions options + groupPullRequestTitlePattern?: string; + logger?: Logger; } diff --git a/src/manifest.ts b/src/manifest.ts index 576ad8ce4..589e3ce73 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -401,6 +401,7 @@ export class Manifest { repositoryConfig: this.repositoryConfig, manifestPath: this.manifestPath, separatePullRequests: this.separatePullRequests, + groupPullRequestTitlePattern: this.groupPullRequestTitlePattern, }) ); this.pullRequestOverflowHandler = new FilePullRequestOverflowHandler( diff --git a/src/plugins/linked-versions.ts b/src/plugins/linked-versions.ts index d13ed51ac..5da92659a 100644 --- a/src/plugins/linked-versions.ts +++ b/src/plugins/linked-versions.ts @@ -26,6 +26,7 @@ import {BranchName} from '../util/branch-name'; interface LinkedVersionsPluginOptions { merge?: boolean; + groupPullRequestTitlePattern?: string; logger?: Logger; } @@ -39,6 +40,7 @@ export class LinkedVersions extends ManifestPlugin { readonly groupName: string; readonly components: Set; readonly merge: boolean; + private groupPullRequestTitlePattern?: string; constructor( github: GitHub, @@ -52,6 +54,7 @@ export class LinkedVersions extends ManifestPlugin { this.groupName = groupName; this.components = new Set(components); this.merge = options.merge ?? true; + this.groupPullRequestTitlePattern = options.groupPullRequestTitlePattern; } /** @@ -173,12 +176,23 @@ export class LinkedVersions extends ManifestPlugin { // delegate to the merge plugin and add merged pull request if (inScopeCandidates.length > 0) { + // Use configured pattern if available, otherwise default to "libraries" for backward compatibility + let pullRequestTitlePattern = this.groupPullRequestTitlePattern + ? this.groupPullRequestTitlePattern + : `chore\${scope}: release ${this.groupName} libraries`; + + // Replace ${component} placeholder with the actual group name + pullRequestTitlePattern = pullRequestTitlePattern.replace( + '${component}', + this.groupName + ); + const merge = new Merge( this.github, this.targetBranch, this.repositoryConfig, { - pullRequestTitlePattern: `chore\${scope}: release ${this.groupName} \${version}`, + pullRequestTitlePattern, forceMerge: true, headBranchName: BranchName.ofGroupTargetBranch( this.groupName, diff --git a/test/plugins/linked-versions.ts b/test/plugins/linked-versions.ts index 16f46c846..87b6d697a 100644 --- a/test/plugins/linked-versions.ts +++ b/test/plugins/linked-versions.ts @@ -346,4 +346,147 @@ describe('LinkedVersions plugin', () => { expect(groupPullRequest1.headRefName).to.not.include(' '); expect(groupPullRequest2.headRefName).to.not.include(' '); }); + + it('should use "libraries" in title by default for backward compatibility', async () => { + const manifest = new Manifest( + github, + 'target-branch', + { + 'path/a': { + releaseType: 'simple', + component: 'pkg1', + }, + 'path/b': { + releaseType: 'simple', + component: 'pkg2', + }, + 'path/c': { + releaseType: 'simple', + component: 'pkg3', + }, + }, + { + 'path/a': Version.parse('1.0.0'), + 'path/b': Version.parse('0.2.3'), + 'path/c': Version.parse('0.2.3'), + }, + { + separatePullRequests: true, + plugins: [ + { + type: 'linked-versions', + groupName: 'my-group', + components: ['pkg2', 'pkg3'], + }, + ], + } + ); + const pullRequests = await manifest.buildPullRequests(); + expect(pullRequests).lengthOf(2); + const groupedPullRequest = pullRequests.find(pr => + pr.title.toString().includes('my-group') + ); + expect(groupedPullRequest).to.not.be.undefined; + expect(groupedPullRequest!.title.toString()).to.include('libraries'); + expect(groupedPullRequest!.title.toString()).to.equal( + 'chore(target-branch): release my-group libraries' + ); + }); + + it('should respect groupPullRequestTitlePattern with version', async () => { + const manifest = new Manifest( + github, + 'target-branch', + { + 'path/a': { + releaseType: 'simple', + component: 'pkg1', + }, + 'path/b': { + releaseType: 'simple', + component: 'pkg2', + }, + 'path/c': { + releaseType: 'simple', + component: 'pkg3', + }, + }, + { + 'path/a': Version.parse('1.0.0'), + 'path/b': Version.parse('0.2.3'), + 'path/c': Version.parse('0.2.3'), + }, + { + separatePullRequests: true, + groupPullRequestTitlePattern: + 'chore${scope}: release ${component} ${version}', + plugins: [ + { + type: 'linked-versions', + groupName: 'my-sdk', + components: ['pkg2', 'pkg3'], + }, + ], + } + ); + const pullRequests = await manifest.buildPullRequests(); + expect(pullRequests).lengthOf(2); + // Find the grouped PR (pkg2+pkg3) - it should have multiple release data entries + const groupedPullRequest = pullRequests.find( + pr => pr.body.releaseData.length > 1 + ); + expect(groupedPullRequest).to.not.be.undefined; + expect(groupedPullRequest!.title.toString()).to.not.include('libraries'); + expect(groupedPullRequest!.title.toString()).to.include('0.2.4'); + expect(groupedPullRequest!.title.toString()).to.equal( + 'chore(target-branch): release my-sdk 0.2.4' + ); + }); + + it('should respect custom groupPullRequestTitlePattern', async () => { + const manifest = new Manifest( + github, + 'target-branch', + { + 'path/a': { + releaseType: 'simple', + component: 'pkg1', + }, + 'path/b': { + releaseType: 'simple', + component: 'pkg2', + }, + 'path/c': { + releaseType: 'simple', + component: 'pkg3', + }, + }, + { + 'path/a': Version.parse('1.0.0'), + 'path/b': Version.parse('0.2.3'), + 'path/c': Version.parse('0.2.3'), + }, + { + separatePullRequests: true, + groupPullRequestTitlePattern: 'feat${scope}: ${component} v${version}', + plugins: [ + { + type: 'linked-versions', + groupName: 'core-libs', + components: ['pkg2', 'pkg3'], + }, + ], + } + ); + const pullRequests = await manifest.buildPullRequests(); + expect(pullRequests).lengthOf(2); + // Find the grouped PR (pkg2+pkg3) - it should have multiple release data entries + const groupedPullRequest = pullRequests.find( + pr => pr.body.releaseData.length > 1 + ); + expect(groupedPullRequest).to.not.be.undefined; + expect(groupedPullRequest!.title.toString()).to.equal( + 'feat(target-branch): core-libs v0.2.4' + ); + }); });