Skip to content

Commit d274421

Browse files
authored
feat: allow configuring separate-pull-requests per component (#1412)
* test: add failing test for separating individual components in manifest * feat: allow configuring separate-pull-requests per component * test: add failing test for includeComponentInTag * fix: use the component in the branch name * declare generic types
1 parent ad7e63c commit d274421

4 files changed

Lines changed: 229 additions & 33 deletions

File tree

src/manifest.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export interface ReleaserConfig {
7676
includeVInTag?: boolean;
7777
pullRequestTitlePattern?: string;
7878
tagSeparator?: string;
79+
separatePullRequests?: boolean;
7980

8081
// Changelog options
8182
changelogSections?: ChangelogSection[];
@@ -120,6 +121,7 @@ interface ReleaserConfigJson {
120121
'changelog-type'?: ChangelogNotesType;
121122
'changelog-host'?: string;
122123
'pull-request-title-pattern'?: string;
124+
'separate-pull-requests'?: boolean;
123125
'tag-separator'?: string;
124126
'extra-files'?: string[];
125127
'version-file'?: string;
@@ -177,7 +179,6 @@ export interface ManifestConfig extends ReleaserConfigJson {
177179
'last-release-sha'?: string;
178180
'always-link-local'?: boolean;
179181
plugins?: PluginType[];
180-
'separate-pull-requests'?: boolean;
181182
'group-pull-request-title-pattern'?: string;
182183
'release-search-depth'?: number;
183184
'commit-search-depth'?: number;
@@ -1140,6 +1141,7 @@ function extractReleaserConfig(
11401141
changelogType: config['changelog-type'],
11411142
pullRequestTitlePattern: config['pull-request-title-pattern'],
11421143
tagSeparator: config['tag-separator'],
1144+
separatePullRequests: config['separate-pull-requests'],
11431145
};
11441146
}
11451147

@@ -1386,6 +1388,8 @@ function mergeReleaserConfig(
13861388
pullRequestTitlePattern:
13871389
pathConfig.pullRequestTitlePattern ??
13881390
defaultConfig.pullRequestTitlePattern,
1391+
separatePullRequests:
1392+
pathConfig.separatePullRequests ?? defaultConfig.separatePullRequests,
13891393
};
13901394
}
13911395

src/plugins/merge.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,25 @@ export class Merge extends ManifestPlugin {
5555
}
5656
logger.info(`Merging ${candidates.length} pull requests`);
5757

58+
const [inScopeCandidates, outOfScopeCandidates] = candidates.reduce<
59+
Array<Array<CandidateReleasePullRequest>>
60+
>(
61+
(collection, candidate) => {
62+
if (candidate.config.separatePullRequests) {
63+
collection[1].push(candidate);
64+
} else {
65+
collection[0].push(candidate);
66+
}
67+
return collection;
68+
},
69+
[[], []]
70+
);
71+
5872
const releaseData: ReleaseData[] = [];
5973
const labels = new Set<string>();
6074
let rawUpdates: Update[] = [];
6175
let rootRelease: CandidateReleasePullRequest | null = null;
62-
for (const candidate of candidates) {
76+
for (const candidate of inScopeCandidates) {
6377
const pullRequest = candidate.pullRequest;
6478
rawUpdates = rawUpdates.concat(...pullRequest.updates);
6579
for (const label of pullRequest.labels) {
@@ -99,6 +113,7 @@ export class Merge extends ManifestPlugin {
99113
releaseType,
100114
},
101115
},
116+
...outOfScopeCandidates,
102117
];
103118
}
104119
}

src/strategies/base.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ export abstract class BaseStrategy implements Strategy {
152152
);
153153
}
154154

155+
protected async getBranchComponent(): Promise<string | undefined> {
156+
return this.component || (await this.getDefaultComponent());
157+
}
158+
155159
async getPackageName(): Promise<string | undefined> {
156160
return this.packageName ?? (await this.getDefaultPackageName());
157161
}
@@ -265,8 +269,9 @@ export abstract class BaseStrategy implements Strategy {
265269
newVersion,
266270
this.pullRequestTitlePattern
267271
);
268-
const branchName = component
269-
? BranchName.ofComponentTargetBranch(component, this.targetBranch)
272+
const branchComponent = await this.getBranchComponent();
273+
const branchName = branchComponent
274+
? BranchName.ofComponentTargetBranch(branchComponent, this.targetBranch)
270275
: BranchName.ofTargetBranch(this.targetBranch);
271276
const releaseNotesBody = await this.buildReleaseNotes(
272277
conventionalCommits,

test/manifest.ts

Lines changed: 201 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,38 @@ describe('Manifest', () => {
554554
expect(manifest.releaseSearchDepth).to.eql(10);
555555
expect(manifest.commitSearchDepth).to.eql(50);
556556
});
557+
558+
it('should read changelog host from manifest', async () => {
559+
const getFileContentsStub = sandbox.stub(
560+
github,
561+
'getFileContentsOnBranch'
562+
);
563+
getFileContentsStub
564+
.withArgs('release-please-config.json', 'main')
565+
.resolves(
566+
buildGitHubFileContent(
567+
fixturesPath,
568+
'manifest/config/changelog-host.json'
569+
)
570+
)
571+
.withArgs('.release-please-manifest.json', 'main')
572+
.resolves(
573+
buildGitHubFileContent(
574+
fixturesPath,
575+
'manifest/versions/versions.json'
576+
)
577+
);
578+
const manifest = await Manifest.fromManifest(
579+
github,
580+
github.repository.defaultBranch
581+
);
582+
expect(manifest.repositoryConfig['.'].changelogHost).to.eql(
583+
'https://example.com'
584+
);
585+
expect(
586+
manifest.repositoryConfig['packages/bot-config-utils'].changelogHost
587+
).to.eql('https://override.example.com');
588+
});
557589
});
558590

559591
describe('fromConfig', () => {
@@ -2725,36 +2757,176 @@ describe('Manifest', () => {
27252757
);
27262758
});
27272759

2728-
it('should read changelog host from manifest', async () => {
2729-
const getFileContentsStub = sandbox.stub(
2730-
github,
2731-
'getFileContentsOnBranch'
2732-
);
2733-
getFileContentsStub
2734-
.withArgs('release-please-config.json', 'main')
2735-
.resolves(
2736-
buildGitHubFileContent(
2737-
fixturesPath,
2738-
'manifest/config/changelog-host.json'
2739-
)
2740-
)
2741-
.withArgs('.release-please-manifest.json', 'main')
2742-
.resolves(
2743-
buildGitHubFileContent(
2744-
fixturesPath,
2745-
'manifest/versions/versions.json'
2746-
)
2760+
describe('with multiple components', () => {
2761+
beforeEach(() => {
2762+
mockReleases(sandbox, github, []);
2763+
mockTags(sandbox, github, [
2764+
{
2765+
name: 'b-v1.0.0',
2766+
sha: 'abc123',
2767+
},
2768+
{
2769+
name: 'c-v2.0.0',
2770+
sha: 'abc123',
2771+
},
2772+
{
2773+
name: 'd-v3.0.0',
2774+
sha: 'abc123',
2775+
},
2776+
]);
2777+
mockCommits(sandbox, github, [
2778+
{
2779+
sha: 'def456',
2780+
message: 'fix: some bugfix',
2781+
files: ['pkg/b/foo.txt', 'pkg/c/foo.txt', 'pkg/d/foo.txt'],
2782+
},
2783+
{
2784+
sha: 'abc123',
2785+
message: 'chore: release main',
2786+
files: [],
2787+
pullRequest: {
2788+
headBranchName: 'release-please/branches/main/components/pkg1',
2789+
baseBranchName: 'main',
2790+
number: 123,
2791+
title: 'chore: release main',
2792+
body: '',
2793+
labels: [],
2794+
files: [],
2795+
sha: 'abc123',
2796+
},
2797+
},
2798+
]);
2799+
const getFileContentsStub = sandbox.stub(
2800+
github,
2801+
'getFileContentsOnBranch'
27472802
);
2748-
const manifest = await Manifest.fromManifest(
2749-
github,
2750-
github.repository.defaultBranch
2751-
);
2752-
expect(manifest.repositoryConfig['.'].changelogHost).to.eql(
2753-
'https://example.com'
2754-
);
2755-
expect(
2756-
manifest.repositoryConfig['packages/bot-config-utils'].changelogHost
2757-
).to.eql('https://override.example.com');
2803+
getFileContentsStub
2804+
.withArgs('package.json', 'main')
2805+
.resolves(
2806+
buildGitHubFileContent(
2807+
fixturesPath,
2808+
'manifest/repo/node/pkg1/package.json'
2809+
)
2810+
);
2811+
});
2812+
2813+
it('should allow configuring separate pull requests', async () => {
2814+
const manifest = new Manifest(
2815+
github,
2816+
'main',
2817+
{
2818+
'pkg/b': {
2819+
releaseType: 'simple',
2820+
component: 'b',
2821+
},
2822+
'pkg/c': {
2823+
releaseType: 'simple',
2824+
component: 'c',
2825+
},
2826+
'pkg/d': {
2827+
releaseType: 'simple',
2828+
component: 'd',
2829+
},
2830+
},
2831+
{
2832+
'pkg/b': Version.parse('1.0.0'),
2833+
'pkg/c': Version.parse('2.0.0'),
2834+
'pkg/d': Version.parse('3.0.0'),
2835+
},
2836+
{
2837+
separatePullRequests: true,
2838+
}
2839+
);
2840+
const pullRequests = await manifest.buildPullRequests();
2841+
expect(pullRequests).lengthOf(3);
2842+
const pullRequestB = pullRequests[0];
2843+
expect(pullRequestB.headRefName).to.eql(
2844+
'release-please--branches--main--components--b'
2845+
);
2846+
const pullRequestC = pullRequests[1];
2847+
expect(pullRequestC.headRefName).to.eql(
2848+
'release-please--branches--main--components--c'
2849+
);
2850+
const pullRequestD = pullRequests[2];
2851+
expect(pullRequestD.headRefName).to.eql(
2852+
'release-please--branches--main--components--d'
2853+
);
2854+
});
2855+
2856+
it('should allow configuring individual separate pull requests', async () => {
2857+
const manifest = new Manifest(
2858+
github,
2859+
'main',
2860+
{
2861+
'pkg/b': {
2862+
releaseType: 'simple',
2863+
component: 'b',
2864+
},
2865+
'pkg/c': {
2866+
releaseType: 'simple',
2867+
component: 'c',
2868+
},
2869+
'pkg/d': {
2870+
releaseType: 'simple',
2871+
component: 'd',
2872+
separatePullRequests: true,
2873+
},
2874+
},
2875+
{
2876+
'pkg/b': Version.parse('1.0.0'),
2877+
'pkg/c': Version.parse('2.0.0'),
2878+
'pkg/d': Version.parse('3.0.0'),
2879+
}
2880+
);
2881+
const pullRequests = await manifest.buildPullRequests();
2882+
expect(pullRequests).lengthOf(2);
2883+
const pullRequest = pullRequests[0];
2884+
expect(pullRequest.headRefName).to.eql(
2885+
'release-please--branches--main'
2886+
);
2887+
const mainPullRequest = pullRequests[1];
2888+
expect(mainPullRequest.headRefName).to.eql(
2889+
'release-please--branches--main--components--d'
2890+
);
2891+
});
2892+
2893+
it('should allow configuring individual separate pull requests with includeComponentInTag = false', async () => {
2894+
const manifest = new Manifest(
2895+
github,
2896+
'main',
2897+
{
2898+
'pkg/b': {
2899+
releaseType: 'simple',
2900+
component: 'b',
2901+
},
2902+
'pkg/c': {
2903+
releaseType: 'simple',
2904+
component: 'c',
2905+
},
2906+
'pkg/d': {
2907+
releaseType: 'simple',
2908+
component: 'd',
2909+
separatePullRequests: true,
2910+
includeComponentInTag: false,
2911+
},
2912+
},
2913+
{
2914+
'pkg/b': Version.parse('1.0.0'),
2915+
'pkg/c': Version.parse('2.0.0'),
2916+
'pkg/d': Version.parse('3.0.0'),
2917+
}
2918+
);
2919+
const pullRequests = await manifest.buildPullRequests();
2920+
expect(pullRequests).lengthOf(2);
2921+
const pullRequest = pullRequests[0];
2922+
expect(pullRequest.headRefName).to.eql(
2923+
'release-please--branches--main'
2924+
);
2925+
const mainPullRequest = pullRequests[1];
2926+
expect(mainPullRequest.headRefName).to.eql(
2927+
'release-please--branches--main--components--d'
2928+
);
2929+
});
27582930
});
27592931
});
27602932

0 commit comments

Comments
 (0)