diff --git a/src/strategies/java.ts b/src/strategies/java.ts index 1b7f14923..cb79be267 100644 --- a/src/strategies/java.ts +++ b/src/strategies/java.ts @@ -104,7 +104,7 @@ export class Java extends BaseStrategy { ): Promise { const component = await this.getComponent(); const newVersion = latestRelease - ? await this.snapshotVersioning.bump(latestRelease.tag.version, []) + ? this.snapshotVersioning.bump(latestRelease.tag.version, []) : this.initialReleaseVersion(); const versionsMap = await this.buildVersionsMap([]); for (const [component, version] of versionsMap.entries()) { diff --git a/src/versioning-strategies/java-add-snapshot.ts b/src/versioning-strategies/java-add-snapshot.ts index 2437d24a7..2be8e68c0 100644 --- a/src/versioning-strategies/java-add-snapshot.ts +++ b/src/versioning-strategies/java-add-snapshot.ts @@ -34,7 +34,12 @@ class AddSnapshotVersionUpdate implements VersionUpdater { this.strategy = strategy; } bump(version: Version): Version { - const nextPatch = this.strategy.bump(version, [fakeCommit]); + // If the released version is a release candidate, we omit the fake commit approach and simply + // bump -rc(n) to -rc(n+1). + const [didBumpRc, bumpedRcVersion] = this.bumpReleaseCandidate(version); + const nextPatch = didBumpRc + ? bumpedRcVersion + : this.strategy.bump(version, [fakeCommit]); return new Version( nextPatch.major, nextPatch.minor, @@ -43,6 +48,26 @@ class AddSnapshotVersionUpdate implements VersionUpdater { nextPatch.build ); } + bumpReleaseCandidate(version: Version): [boolean, Version] { + const rcRegex = /rc(?\d+)/; + if (!version.preRelease?.match(rcRegex)) { + return [false, version]; + } + let preRelease = version.preRelease; + const match = preRelease.match(rcRegex)!; + const newRcNumber = parseInt(match.groups!.rcNumber) + 1; + preRelease = preRelease.replace(rcRegex, `rc${newRcNumber}`); + return [ + true, + new Version( + version.major, + version.minor, + version.patch, + preRelease, + version.build + ), + ]; + } } /** diff --git a/src/versioning-strategies/java-snapshot.ts b/src/versioning-strategies/java-snapshot.ts index 898d6c78b..504a06cd9 100644 --- a/src/versioning-strategies/java-snapshot.ts +++ b/src/versioning-strategies/java-snapshot.ts @@ -34,16 +34,17 @@ class RemoveSnapshotVersionUpdate implements VersionUpdater { this.parent = parent; } bump(version: Version): Version { + let preRelease = version.preRelease; if (this.parent) { version = this.parent.bump(version); + // reset the release candidate number after a bump + preRelease = version.preRelease?.replace(/rc\d+/, 'rc1'); } return new Version( version.major, version.minor, version.patch, - version.preRelease - ? version.preRelease.replace(/-?SNAPSHOT/, '') - : undefined, + preRelease?.replace(/-?SNAPSHOT/, ''), version.build ); } @@ -63,17 +64,32 @@ export class JavaSnapshot implements VersioningStrategy { version: Version, commits: ConventionalCommit[] ): VersionUpdater { + // Determine the release type from the parent strategy based on the commits. const parentBump = this.strategy.determineReleaseType(version, commits); + + // If the current version is a snapshot, we need to handle it specially. if (version.preRelease?.match(/-?SNAPSHOT/)) { + // To check if the only change is the snapshot removal, we simulate a patch bump. + // We create a fake commit that would cause a patch bump and see what version the parent strategy would return. const patchBumpVersion = this.strategy .determineReleaseType(version, [fakeCommit]) .bump(version); + + // We then get the version that the parent strategy would return with the actual commits. const parentBumpVersion = parentBump.bump(version); + + // If the parent bump version is the same as the patch bump version, + // it means that the commits only triggered a patch bump. + // In this case, we only need to remove the "-SNAPSHOT" from the version. if (patchBumpVersion.toString() === parentBumpVersion.toString()) { return new RemoveSnapshotVersionUpdate(); } + // If the parent bump version is different from the patch bump version, + // it means that the commits triggered a minor or major bump. + // In this case, we need to both apply the parent bump and remove the "-SNAPSHOT". return new RemoveSnapshotVersionUpdate(parentBump); } + // If the current version is not a snapshot, we just return the parent bump. return parentBump; } diff --git a/test/strategies/java.ts b/test/strategies/java.ts index 63245b504..f86fc2ceb 100644 --- a/test/strategies/java.ts +++ b/test/strategies/java.ts @@ -133,6 +133,34 @@ describe('Java', () => { assertNoHasUpdate(release!.updates, 'CHANGELOG.md'); }); + it('returns an rc number bump snapshot PR', async () => { + const strategy = new Java({ + targetBranch: 'main', + github, + }); + + const latestRelease = { + tag: new TagName(Version.parse('2.3.3-rc1')), + sha: 'abc123', + notes: 'some notes', + }; + const release = await strategy.buildReleasePullRequest( + COMMITS_NO_SNAPSHOT, + latestRelease, + false, + DEFAULT_LABELS + ); + + expect(release?.version?.toString()).to.eql('2.3.3-rc2-SNAPSHOT'); + expect(release?.title.toString()).to.eql( + 'chore(main): release 2.3.3-rc2-SNAPSHOT' + ); + expect(release?.headRefName).to.eql('release-please--branches--main'); + expect(release?.draft).to.eql(false); + expect(release?.labels).to.eql(DEFAULT_SNAPSHOT_LABELS); + assertNoHasUpdate(release!.updates, 'CHANGELOG.md'); + }); + it('skips a snapshot bump PR', async () => { const strategy = new Java({ targetBranch: 'main', diff --git a/test/versioning-strategies/java.ts b/test/versioning-strategies/java.ts index 969214298..4619cca31 100644 --- a/test/versioning-strategies/java.ts +++ b/test/versioning-strategies/java.ts @@ -138,9 +138,34 @@ describe('JavaVersioningStrategy', () => { new DefaultVersioningStrategy({bumpMinorPreMajor: true}) ); const oldVersion = Version.parse('0.1.2-SNAPSHOT'); - const newVersion = await strategy.bump(oldVersion, breakingCommits); + const newVersion = strategy.bump(oldVersion, breakingCommits); expect(newVersion.toString()).to.equal('0.2.0'); }); + + describe('with release candidate qualifier', () => { + it('can bump a major', async () => { + const strategy = new JavaSnapshot(new DefaultVersioningStrategy({})); + const oldVersion = Version.parse('1.2.0-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, breakingCommits); + expect(newVersion.toString()).to.equal('2.0.0-rc1'); + }); + + it('can bump a major on pre major for breaking change', async () => { + const strategy = new JavaSnapshot(new DefaultVersioningStrategy({})); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, breakingCommits); + expect(newVersion.toString()).to.equal('1.0.0-rc1'); + }); + + it('can bump a minor pre major for breaking change', async () => { + const strategy = new JavaSnapshot( + new DefaultVersioningStrategy({bumpMinorPreMajor: true}) + ); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, breakingCommits); + expect(newVersion.toString()).to.equal('0.2.0-rc1'); + }); + }); }); describe('with a feature', () => { @@ -166,6 +191,30 @@ describe('JavaVersioningStrategy', () => { const newVersion = await strategy.bump(oldVersion, featureCommits); expect(newVersion.toString()).to.equal('0.1.2'); }); + describe('with release candidate qualifier', () => { + it('can bump a minor', async () => { + const strategy = new JavaSnapshot(new DefaultVersioningStrategy({})); + const oldVersion = Version.parse('1.2.3-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, featureCommits); + expect(newVersion.toString()).to.equal('1.3.0-rc1'); + }); + it('can bump a minor pre-major', async () => { + const strategy = new JavaSnapshot(new DefaultVersioningStrategy({})); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, featureCommits); + expect(newVersion.toString()).to.equal('0.2.0-rc1'); + }); + it('can bump a patch pre-major', async () => { + const strategy = new JavaSnapshot( + new DefaultVersioningStrategy({ + bumpPatchForMinorPreMajor: true, + }) + ); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, featureCommits); + expect(newVersion.toString()).to.equal('0.1.2-rc2'); + }); + }); }); describe('with a fix', () => { @@ -175,6 +224,12 @@ describe('JavaVersioningStrategy', () => { const newVersion = await strategy.bump(oldVersion, fixCommits); expect(newVersion.toString()).to.equal('1.2.3'); }); + it('can bump a patch with release candidate qualifier', async () => { + const strategy = new JavaSnapshot(new DefaultVersioningStrategy({})); + const oldVersion = Version.parse('1.2.3-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, fixCommits); + expect(newVersion.toString()).to.equal('1.2.3-rc2'); + }); }); }); @@ -291,6 +346,30 @@ describe('JavaVersioningStrategy', () => { const newVersion = await strategy.bump(oldVersion, breakingCommits); expect(newVersion.toString()).to.equal('0.1.2'); }); + describe('with release candidate qualifier', () => { + it('can bump a major', async () => { + const strategy = new JavaSnapshot(new AlwaysBumpPatch({})); + const oldVersion = Version.parse('1.2.3-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, breakingCommits); + expect(newVersion.toString()).to.equal('1.2.3-rc2'); + }); + + it('can bump a major on pre major for breaking change', async () => { + const strategy = new JavaSnapshot(new AlwaysBumpPatch({})); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, breakingCommits); + expect(newVersion.toString()).to.equal('0.1.2-rc2'); + }); + + it('can bump a minor pre major for breaking change', async () => { + const strategy = new JavaSnapshot( + new AlwaysBumpPatch({bumpMinorPreMajor: true}) + ); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, breakingCommits); + expect(newVersion.toString()).to.equal('0.1.2-rc2'); + }); + }); }); describe('with a feature', () => { @@ -316,13 +395,37 @@ describe('JavaVersioningStrategy', () => { const newVersion = await strategy.bump(oldVersion, featureCommits); expect(newVersion.toString()).to.equal('0.1.2'); }); + describe('with release candidate qualifier', () => { + it('can bump a minor', async () => { + const strategy = new JavaSnapshot(new AlwaysBumpPatch({})); + const oldVersion = Version.parse('1.2.3-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, featureCommits); + expect(newVersion.toString()).to.equal('1.2.3-rc2'); + }); + + it('can bump a minor on pre major for breaking change', async () => { + const strategy = new JavaSnapshot(new AlwaysBumpPatch({})); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, featureCommits); + expect(newVersion.toString()).to.equal('0.1.2-rc2'); + }); + + it('can bump a minor pre major for breaking change', async () => { + const strategy = new JavaSnapshot( + new AlwaysBumpPatch({bumpMinorPreMajor: true}) + ); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, featureCommits); + expect(newVersion.toString()).to.equal('0.1.2-rc2'); + }); + }); }); describe('with a fix', () => { it('can bump a patch', async () => { const strategy = new JavaSnapshot(new AlwaysBumpPatch({})); const oldVersion = Version.parse('1.2.3-SNAPSHOT'); - const newVersion = await strategy.bump(oldVersion, fixCommits); + const newVersion = strategy.bump(oldVersion, fixCommits); expect(newVersion.toString()).to.equal('1.2.3'); }); @@ -341,6 +444,30 @@ describe('JavaVersioningStrategy', () => { const newVersion = await strategy.bump(oldVersion, fixCommits); expect(newVersion.toString()).to.equal('0.1.2'); }); + describe('with release candidate qualifier', () => { + it('can bump a patch', async () => { + const strategy = new JavaSnapshot(new AlwaysBumpPatch({})); + const oldVersion = Version.parse('1.2.3-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, fixCommits); + expect(newVersion.toString()).to.equal('1.2.3-rc2'); + }); + + it('can bump a patch on pre major for breaking change', async () => { + const strategy = new JavaSnapshot(new AlwaysBumpPatch({})); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, fixCommits); + expect(newVersion.toString()).to.equal('0.1.2-rc2'); + }); + + it('can bump a patch pre major for breaking change', async () => { + const strategy = new JavaSnapshot( + new AlwaysBumpPatch({bumpMinorPreMajor: true}) + ); + const oldVersion = Version.parse('0.1.2-rc2-SNAPSHOT'); + const newVersion = strategy.bump(oldVersion, fixCommits); + expect(newVersion.toString()).to.equal('0.1.2-rc2'); + }); + }); }); }); });