Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changelog/20260518161120_ci_4458.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
type: Fix
scope:
- ckeditor5-dev-changelog
---

Fixed the suggested pre-release version when starting a pre-release cycle from a stable version. The default now reflects the underlying change type β€” a major bump only when breaking changes are present, a minor bump for features, and a patch bump otherwise β€” instead of always defaulting to a major bump.
14 changes: 10 additions & 4 deletions packages/ckeditor5-dev-changelog/src/utils/determinenextversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { styleText } from 'node:util';
import { type ReleaseType } from 'semver';
import semver, { type ReleaseType } from 'semver';
import { provideNewVersion } from './providenewversion.js';
import { logInfo } from './loginfo.js';
import { detectReleaseChannel } from './detectreleasechannel.js';
Expand Down Expand Up @@ -60,14 +60,20 @@ export async function determineNextVersion( options: DetermineNextVersionOptions

let bumpType: ReleaseType = 'patch';

if ( releaseType === 'prerelease' || releaseType === 'prerelease-promote' ) {
bumpType = 'prerelease';
} else if ( sections.major.entries.length || sections.breaking.entries.length ) {
if ( sections.major.entries.length || sections.breaking.entries.length ) {
bumpType = 'major';
} else if ( sections.minor.entries.length || sections.feature.entries.length ) {
bumpType = 'minor';
}

if ( releaseType === 'prerelease' || releaseType === 'prerelease-promote' ) {
// When already on a prerelease channel, continuing or promoting uses the plain `prerelease` bump.
// Initiating a prerelease from a stable version uses `premajor` / `preminor` / `prepatch` based on commits.
bumpType = semver.prerelease( currentVersion ) ?
'prerelease' :
`pre${ bumpType }` as ReleaseType;
Comment thread
przemyslaw-zan marked this conversation as resolved.
Outdated
}

const areErrorsPresent = !!sections.invalid.entries.length;
const areWarningsPresent = Object.values( sections ).some( section =>
section.entries.some( entry => entry.data.validations && entry.data.validations.length > 0 )
Expand Down
18 changes: 9 additions & 9 deletions packages/ckeditor5-dev-changelog/src/utils/providenewversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,13 @@ function createVersionQuestion( options: Options ) {
function getSuggestedVersion( bumpType: ReleaseType, version: string, releaseChannel: ReleaseChannel ) {
if ( bumpType === 'prerelease' && releaseChannel !== 'latest' ) {
return semver.inc( version, bumpType, releaseChannel );
} else if ( bumpType === 'prerelease' && releaseChannel === 'latest' ) {
// Using 'premajor` and `alpha` channel for a case, when introducing a prerelease for the next major.
// E.g. 1.0.0 -> 2.0.0-alpha.0.
}

return semver.inc( version, 'premajor', 'alpha' );
} else {
return semver.inc( version, bumpType );
if ( bumpType === 'premajor' || bumpType === 'preminor' || bumpType === 'prepatch' ) {
return semver.inc( version, bumpType, 'alpha' );
}

return semver.inc( version, bumpType );
}

function getChoices( {
Expand All @@ -134,10 +133,11 @@ function getChoices( {
} ) {
const proposedVersions: Array<string> = [];
const preReleaseChannels = [ 'alpha', 'beta', 'rc' ];
const preInitialBumpTypes: Array<ReleaseType> = [ 'premajor', 'preminor', 'prepatch' ];
const validPromotionChannels = preReleaseChannels.filter( ( value, index, array ) => index >= array.indexOf( releaseChannel ) );

// 6.0.0 => Latest (stable) release (7.0.0 | 6.1.0 | 6.0.1)
if ( bumpType !== 'prerelease' && releaseType === 'latest' && releaseChannel === 'latest' ) {
if ( releaseType === 'latest' && releaseChannel === 'latest' ) {
proposedVersions.push(
semver.inc( version, 'major' )!,
semver.inc( version, 'minor' )!,
Expand All @@ -146,7 +146,7 @@ function getChoices( {
}

// 6.0.0 => Pre-release (7.0.0-alpha.0 | 6.1.0-alpha.0 | 6.0.1-alpha.0)
if ( bumpType === 'prerelease' && releaseType === 'prerelease' && releaseChannel === 'latest' ) {
if ( preInitialBumpTypes.includes( bumpType ) && releaseType === 'prerelease' && releaseChannel === 'latest' ) {
proposedVersions.push(
semver.inc( version, 'premajor', 'alpha' )!,
semver.inc( version, 'preminor', 'alpha' )!,
Expand All @@ -155,7 +155,7 @@ function getChoices( {
}

// 6.0.0-alpha.0 => Latest (stable) release (6.0.0)
if ( bumpType !== 'prerelease' && releaseType === 'latest' && preReleaseChannels.includes( releaseChannel ) ) {
if ( releaseType === 'latest' && preReleaseChannels.includes( releaseChannel ) ) {
proposedVersions.push(
semver.inc( version, 'release' )!
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ describe( 'determineNextVersion()', () => {
expect( mockedDetectReleaseChannel ).toHaveBeenCalledWith( '1.0.0', false );
} );

it( 'should return a prerelease bump version when releaseType is prerelease', async () => {
mockedProvideNewVersion.mockResolvedValueOnce( '1.0.0-alpha.1' );
it( 'should return a premajor bump when initiating a prerelease from stable with breaking changes', async () => {
mockedProvideNewVersion.mockResolvedValueOnce( '2.0.0-alpha.0' );
options.releaseType = 'prerelease';
options.sections = createSectionsWithEntries( {
major: { entries: [ createEntry( 'Major breaking change' ) ], title: 'Major Breaking Changes' },
Expand All @@ -230,16 +230,66 @@ describe( 'determineNextVersion()', () => {

const result = await determineNextVersion( options );

expect( result ).toBe( '1.0.0-alpha.1' );
expect( result ).toBe( '2.0.0-alpha.0' );

expect( mockedProvideNewVersion ).toHaveBeenCalledWith( expect.objectContaining( {
bumpType: 'prerelease'
bumpType: 'premajor'
} ) );
expect( mockedDetectReleaseChannel ).toHaveBeenCalledWith( '1.0.0', false );
} );

it( 'should return a preminor bump when initiating a prerelease from stable with only feature entries', async () => {
mockedProvideNewVersion.mockResolvedValueOnce( '1.1.0-alpha.0' );
options.releaseType = 'prerelease';
options.sections = createSectionsWithEntries( {
feature: { entries: [ createEntry( 'New feature' ) ], title: 'Features' }
} );

const result = await determineNextVersion( options );

expect( result ).toBe( '1.1.0-alpha.0' );
expect( mockedProvideNewVersion ).toHaveBeenCalledWith( expect.objectContaining( {
bumpType: 'preminor'
} ) );
} );

it( 'should return a prepatch bump when initiating a prerelease from stable with no breaking/feature entries', async () => {
mockedProvideNewVersion.mockResolvedValueOnce( '1.0.1-alpha.0' );
options.releaseType = 'prerelease';
options.sections = createSectionsWithEntries( {
fix: { entries: [ createEntry( 'A fix' ) ], title: 'Bug fixes' }
} );

const result = await determineNextVersion( options );

expect( result ).toBe( '1.0.1-alpha.0' );
expect( mockedProvideNewVersion ).toHaveBeenCalledWith( expect.objectContaining( {
bumpType: 'prepatch'
} ) );
} );

it( 'should return a prerelease bump when continuing a prerelease channel', async () => {
mockedDetectReleaseChannel.mockReturnValue( 'alpha' );
mockedProvideNewVersion.mockResolvedValueOnce( '1.0.0-alpha.1' );
options.currentVersion = '1.0.0-alpha.0';
options.releaseType = 'prerelease';
options.sections = createSectionsWithEntries( {
major: { entries: [ createEntry( 'Major breaking change' ) ], title: 'Major Breaking Changes' }
} );

const result = await determineNextVersion( options );

expect( result ).toBe( '1.0.0-alpha.1' );
expect( mockedProvideNewVersion ).toHaveBeenCalledWith( expect.objectContaining( {
bumpType: 'prerelease'
} ) );
expect( mockedDetectReleaseChannel ).toHaveBeenCalledWith( '1.0.0-alpha.0', false );
} );

it( 'should call detectReleaseChannel with promotePrerelease=true when releaseType is prerelease-promote', async () => {
mockedDetectReleaseChannel.mockReturnValue( 'beta' );
mockedProvideNewVersion.mockResolvedValueOnce( '1.0.0-beta.0' );
options.currentVersion = '1.0.0-alpha.0';
options.releaseType = 'prerelease-promote';
options.sections = createSectionsWithEntries( {
major: { entries: [ createEntry( 'Major breaking change' ) ], title: 'Major Breaking Changes' }
Expand All @@ -251,7 +301,7 @@ describe( 'determineNextVersion()', () => {
expect( mockedProvideNewVersion ).toHaveBeenCalledWith( expect.objectContaining( {
bumpType: 'prerelease'
} ) );
expect( mockedDetectReleaseChannel ).toHaveBeenCalledWith( '1.0.0', true );
expect( mockedDetectReleaseChannel ).toHaveBeenCalledWith( '1.0.0-alpha.0', true );
} );

it( 'should set displayValidationWarning to true when invalid entries are present', async () => {
Expand Down
42 changes: 40 additions & 2 deletions packages/ckeditor5-dev-changelog/tests/utils/providenewversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ describe( 'provideNewVersion()', () => {
] );
} );

it( 'should suggest premajor alpha for prerelease bump type on latest channel', async () => {
it( 'should suggest premajor alpha for premajor bump type on latest channel', async () => {
await provideNewVersion( {
...defaultOptions,
bumpType: 'prerelease',
bumpType: 'premajor',
releaseType: 'prerelease',
releaseChannel: 'latest',
version: '1.0.0'
Expand All @@ -165,6 +165,44 @@ describe( 'provideNewVersion()', () => {
] );
} );

it( 'should suggest preminor alpha for preminor bump type on latest channel', async () => {
await provideNewVersion( {
...defaultOptions,
bumpType: 'preminor',
releaseType: 'prerelease',
releaseChannel: 'latest',
version: '1.0.0'
} );

const promptCall = vi.mocked( inquirer.prompt ).mock.calls[ 0 ]?.[ 0 ] as any;
expect( promptCall[ 0 ].default ).toBe( '1.1.0-alpha.0' );
expect( promptCall[ 0 ].choices ).toEqual( [
{ name: '2.0.0-alpha.0', value: '2.0.0-alpha.0' },
{ name: '1.1.0-alpha.0', value: '1.1.0-alpha.0' },
{ name: '1.0.1-alpha.0', value: '1.0.1-alpha.0' },
{ name: 'Custom...', value: 'custom' }
] );
} );

it( 'should suggest prepatch alpha for prepatch bump type on latest channel', async () => {
await provideNewVersion( {
...defaultOptions,
bumpType: 'prepatch',
releaseType: 'prerelease',
releaseChannel: 'latest',
version: '1.0.0'
} );

const promptCall = vi.mocked( inquirer.prompt ).mock.calls[ 0 ]?.[ 0 ] as any;
expect( promptCall[ 0 ].default ).toBe( '1.0.1-alpha.0' );
expect( promptCall[ 0 ].choices ).toEqual( [
{ name: '2.0.0-alpha.0', value: '2.0.0-alpha.0' },
{ name: '1.1.0-alpha.0', value: '1.1.0-alpha.0' },
{ name: '1.0.1-alpha.0', value: '1.0.1-alpha.0' },
{ name: 'Custom...', value: 'custom' }
] );
} );

it( 'should suggest stable version when bumping patch from alpha to latest', async () => {
await provideNewVersion( {
...defaultOptions,
Expand Down