diff --git a/packages/ckeditor5-dev-release-tools/lib/index.js b/packages/ckeditor5-dev-release-tools/lib/index.js index 9d44650e4..c7ff8eab0 100644 --- a/packages/ckeditor5-dev-release-tools/lib/index.js +++ b/packages/ckeditor5-dev-release-tools/lib/index.js @@ -22,6 +22,7 @@ export { getNextNightly, getNextInternal, getCurrent, + getDateIdentifier, getLastTagFromGit } from './utils/versions.js'; export { default as getChangesForVersion } from './utils/getchangesforversion.js'; diff --git a/packages/ckeditor5-dev-release-tools/lib/utils/versions.js b/packages/ckeditor5-dev-release-tools/lib/utils/versions.js index 8fc3b42ea..21d58fe53 100644 --- a/packages/ckeditor5-dev-release-tools/lib/utils/versions.js +++ b/packages/ckeditor5-dev-release-tools/lib/utils/versions.js @@ -29,8 +29,15 @@ export function getLastFromChangelog( cwd = process.cwd() ) { /** * Returns the current (latest) pre-release version that matches the provided release identifier. + * It takes into account and distinguishes pre-release tags with different names but starting with the same base name. * If the package does not have any pre-releases with the provided identifier yet, `null` is returned. * + * Examples: + * * "0.0.0-nightly" - Matches the last "nightly" version regardless of the publication date. + * It does not match other nightly tags that start with the same "nightly" base name, e.g. "0.0.0-nightly-next-YYYYMMDD.X". + * * "0.0.0-nightly-20230615" - Matches the last "nightly" version from the 2023-06-15 day. + * * "42.0.0-alpha" - Matches the last "alpha" version for the 42.0.0 version. + * * @param {ReleaseIdentifier} releaseIdentifier * @param {string} [cwd=process.cwd()] * @returns {Promise.} @@ -41,7 +48,13 @@ export function getLastPreRelease( releaseIdentifier, cwd = process.cwd() ) { return packument( packageName ) .then( result => { const lastVersion = Object.keys( result.versions ) - .filter( version => version.startsWith( releaseIdentifier ) ) + .filter( version => { + const optionalDateIdentifier = '(-[0-9]{8})?'; + const optionalSequenceNumber = '(\\.[0-9]+)?'; + const versionRegExp = new RegExp( `^${ releaseIdentifier }${ optionalDateIdentifier }${ optionalSequenceNumber }$` ); + + return versionRegExp.test( version ); + } ) .sort( ( a, b ) => a.localeCompare( b, undefined, { numeric: true } ) ) .pop(); @@ -138,9 +151,11 @@ export function getCurrent( cwd = process.cwd() ) { } /** + * Returns current date in the "YYYYMMDD" format. + * * @returns {string} */ -function getDateIdentifier() { +export function getDateIdentifier() { const today = new Date(); const year = today.getFullYear().toString(); const month = ( today.getMonth() + 1 ).toString().padStart( 2, '0' ); @@ -152,9 +167,4 @@ function getDateIdentifier() { /** * @typedef {string} ReleaseIdentifier The pre-release identifier without the last dynamic part (the pre-release sequential number). * It consists of the core base version (".."), a hyphen ("-"), and a pre-release identifier name (e.g. "alpha"). - * - * Examples: - * * "0.0.0-nightly" - matches the last nightly version regardless of the publication date. - * * "0.0.0-nightly-20230615" - matches the last nightly version from the 2023-06-15 day. - * * "42.0.0-alpha" - matches the last alpha version for the 42.0.0 version. */ diff --git a/packages/ckeditor5-dev-release-tools/tests/index.js b/packages/ckeditor5-dev-release-tools/tests/index.js index e01b67467..5e687401f 100644 --- a/packages/ckeditor5-dev-release-tools/tests/index.js +++ b/packages/ckeditor5-dev-release-tools/tests/index.js @@ -26,6 +26,7 @@ import { getNextNightly, getNextInternal, getCurrent, + getDateIdentifier, getLastTagFromGit } from '../lib/utils/versions.js'; import executeInParallel from '../lib/utils/executeinparallel.js'; @@ -150,6 +151,13 @@ describe( 'dev-release-tools/index', () => { } ); } ); + describe( 'getDateIdentifier()', () => { + it( 'should be a function', () => { + expect( getDateIdentifier ).to.be.a( 'function' ); + expect( index.getDateIdentifier ).to.equal( getDateIdentifier ); + } ); + } ); + describe( 'getLastPreRelease()', () => { it( 'should be a function', () => { expect( getLastPreRelease ).to.be.a( 'function' ); diff --git a/packages/ckeditor5-dev-release-tools/tests/utils/versions.js b/packages/ckeditor5-dev-release-tools/tests/utils/versions.js index a925d8ab1..f676d4225 100644 --- a/packages/ckeditor5-dev-release-tools/tests/utils/versions.js +++ b/packages/ckeditor5-dev-release-tools/tests/utils/versions.js @@ -17,7 +17,8 @@ import { getNextNightly, getNextInternal, getLastTagFromGit, - getCurrent + getCurrent, + getDateIdentifier } from '../../lib/utils/versions.js'; vi.mock( '@ckeditor/ckeditor5-dev-utils' ); @@ -145,6 +146,23 @@ describe( 'versions', () => { } ); } ); + it( 'returns null if pre-release version matches the release identifier only partially', () => { + vi.mocked( packument ).mockResolvedValue( { + name: 'ckeditor5', + versions: { + '0.0.0-nightly-20230615.0': {}, + '0.0.0-nightly-20230615.1': {}, + '0.0.0-nightly-20230615.2': {}, + '0.0.0-nightly-next-20230615.3': {} + } + } ); + + return getLastPreRelease( '0.0.0-nightly-2023' ) + .then( result => { + expect( result ).to.equal( null ); + } ); + } ); + it( 'returns last pre-release version matching the release identifier', () => { vi.mocked( packument ).mockResolvedValue( { name: 'ckeditor5', @@ -245,6 +263,111 @@ describe( 'versions', () => { expect( result ).to.equal( '0.0.0-nightly-20230615.2' ); } ); } ); + + it( 'returns last version from exactly the "nightly" tag when multiple nightly tags exist', () => { + vi.mocked( packument ).mockResolvedValue( { + name: 'ckeditor5', + versions: { + '0.0.0-nightly-20230615.0': {}, + '0.0.0-nightly-20230615.1': {}, + '0.0.0-nightly-20230615.2': {}, + '0.0.0-nightly-next-20230615.3': {} + } + } ); + + return getLastPreRelease( '0.0.0-nightly' ) + .then( result => { + expect( result ).to.equal( '0.0.0-nightly-20230615.2' ); + } ); + } ); + + it( 'returns last version from exactly the "nightly-next" tag when multiple nightly tags exist', () => { + vi.mocked( packument ).mockResolvedValue( { + name: 'ckeditor5', + versions: { + '0.0.0-nightly-next-20230615.0': {}, + '0.0.0-nightly-next-20230615.1': {}, + '0.0.0-nightly-next-20230615.2': {}, + '0.0.0-nightly-20230615.3': {} + } + } ); + + return getLastPreRelease( '0.0.0-nightly-next' ) + .then( result => { + expect( result ).to.equal( '0.0.0-nightly-next-20230615.2' ); + } ); + } ); + + it( 'returns last version from exactly the "nightly" tag when multiple nightly tags exist from a specific day', () => { + vi.mocked( packument ).mockResolvedValue( { + name: 'ckeditor5', + versions: { + '0.0.0-nightly-20230615.0': {}, + '0.0.0-nightly-20230615.1': {}, + '0.0.0-nightly-20230615.2': {}, + '0.0.0-nightly-next-20230615.3': {} + } + } ); + + return getLastPreRelease( '0.0.0-nightly-20230615' ) + .then( result => { + expect( result ).to.equal( '0.0.0-nightly-20230615.2' ); + } ); + } ); + + it( 'returns last version from exactly the "nightly-next" tag when multiple nightly tags exist from a specific day', () => { + vi.mocked( packument ).mockResolvedValue( { + name: 'ckeditor5', + versions: { + '0.0.0-nightly-next-20230615.0': {}, + '0.0.0-nightly-next-20230615.1': {}, + '0.0.0-nightly-next-20230615.2': {}, + '0.0.0-nightly-20230615.3': {} + } + } ); + + return getLastPreRelease( '0.0.0-nightly-next-20230615' ) + .then( result => { + expect( result ).to.equal( '0.0.0-nightly-next-20230615.2' ); + } ); + } ); + + it( 'returns last pre-release version matching the release identifier exactly', () => { + vi.mocked( packument ).mockResolvedValue( { + name: 'ckeditor5', + versions: { + '0.0.0-nightly-20230615.0': {}, + '37.0.0-alpha.1': {}, + '37.0.0-alpha.2': {}, + '41.0.0': {}, + '37.0.0-alpha.10': {}, + '37.0.0-alpha.11': {} + } + } ); + + return getLastPreRelease( '37.0.0-alpha.10' ) + .then( result => { + expect( result ).to.equal( '37.0.0-alpha.10' ); + } ); + } ); + + it( 'returns last nightly version matching the release identifier exactly', () => { + vi.mocked( packument ).mockResolvedValue( { + name: 'ckeditor5', + versions: { + '0.0.0-nightly-20230615.0': {}, + '0.0.0-nightly-20230615.1': {}, + '0.0.0-nightly-20230615.2': {}, + '0.0.0-nightly-next-20230615.1': {}, + '0.0.0-nightly-next-20230615.2': {} + } + } ); + + return getLastPreRelease( '0.0.0-nightly-20230615.1' ) + .then( result => { + expect( result ).to.equal( '0.0.0-nightly-20230615.1' ); + } ); + } ); } ); describe( 'getLastNightly()', () => { @@ -252,7 +375,7 @@ describe( 'versions', () => { vi.mocked( getPackageJson ).mockReturnValue( { name: 'ckeditor5' } ); } ); - it( 'returns last nightly pre-release version', () => { + it( 'returns last pre-release version from exactly the "nightly" tag', () => { vi.mocked( packument ).mockResolvedValue( { name: 'ckeditor5', versions: { @@ -261,6 +384,7 @@ describe( 'versions', () => { '0.0.0-nightly-20230614.1': {}, '0.0.0-nightly-20230614.2': {}, '0.0.0-nightly-20230615.0': {}, + '0.0.0-nightly-next-20230616.0': {}, '37.0.0-alpha.0': {}, '42.0.0': {} } @@ -310,11 +434,12 @@ describe( 'versions', () => { } ); } ); - it( 'returns nightly version with incremented id if older nightly version was already published', () => { + it( 'returns version with incremented id from exactly the "nightly" tag if older version was already published', () => { vi.mocked( packument ).mockResolvedValue( { name: 'ckeditor5', versions: { '0.0.0-nightly-20230615.5': {}, + '0.0.0-nightly-next-20230616.0': {}, '37.0.0-alpha.0': {}, '42.0.0': {} } @@ -325,6 +450,23 @@ describe( 'versions', () => { expect( result ).to.equal( '0.0.0-nightly-20230615.6' ); } ); } ); + + it( 'returns version with incremented id from exactly the "nightly-next" tag if older version was already published', () => { + vi.mocked( packument ).mockResolvedValue( { + name: 'ckeditor5', + versions: { + '0.0.0-nightly-next-20230615.5': {}, + '0.0.0-nightly-20230616.0': {}, + '37.0.0-alpha.0': {}, + '42.0.0': {} + } + } ); + + return getNextPreRelease( '0.0.0-nightly-next' ) + .then( result => { + expect( result ).to.equal( '0.0.0-nightly-next-20230615.6' ); + } ); + } ); } ); describe( 'getNextNightly()', () => { @@ -339,11 +481,12 @@ describe( 'versions', () => { vi.useRealTimers(); } ); - it( 'asks for a last nightly pre-release version', () => { + it( 'returns next pre-release version from exactly the "nightly" tag', () => { vi.mocked( packument ).mockResolvedValue( { name: 'ckeditor5', versions: { '0.0.0-nightly-20230615.0': {}, + '0.0.0-nightly-next-20230615.5': {}, '37.0.0-alpha.0': {}, '42.0.0': {} } @@ -368,7 +511,7 @@ describe( 'versions', () => { vi.useRealTimers(); } ); - it( 'asks for a last internal pre-release version', () => { + it( 'returns next internal pre-release version', () => { vi.mocked( packument ).mockResolvedValue( { name: 'ckeditor5', versions: { @@ -385,6 +528,21 @@ describe( 'versions', () => { } ); } ); + describe( 'getDateIdentifier()', () => { + beforeEach( () => { + vi.useFakeTimers(); + vi.setSystemTime( new Date( '2023-06-15 12:00:00' ) ); + } ); + + afterEach( () => { + vi.useRealTimers(); + } ); + + it( 'returns current date in the YYYYMMDD format', () => { + expect( getDateIdentifier() ).to.equal( '20230615' ); + } ); + } ); + describe( 'getLastTagFromGit()', () => { it( 'returns last tag if exists', () => { vi.mocked( tools.shExec ).mockReturnValue( 'v1.0.0' );