diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md new file mode 100644 index 000000000..5f447b8a5 --- /dev/null +++ b/DEPRECATIONS.md @@ -0,0 +1,11 @@ +# Deprecated APIs + +Modules exported by packages in the repository can be marked as deprecated when an improved alternative appears, our processes require updates, or the proposed API is unsafe. + +## List of deprecated API + +Below, you can find all deprecation codes used in the `ckeditor5-dev-*` packages. + +### DEP0001: `verifyPackagesPublishedCorrectly()` + +Since v45, the `verifyPackagesPublishedCorrectly()` function is no longer available as its responsibility has been merged with `publishPackages()`. diff --git a/packages/ckeditor5-dev-release-tools/lib/index.js b/packages/ckeditor5-dev-release-tools/lib/index.js index d056e3a1e..d547988d8 100644 --- a/packages/ckeditor5-dev-release-tools/lib/index.js +++ b/packages/ckeditor5-dev-release-tools/lib/index.js @@ -29,6 +29,7 @@ export { default as saveChangelog } from './utils/savechangelog.js'; export { default as executeInParallel } from './utils/executeinparallel.js'; export { default as validateRepositoryToRelease } from './utils/validaterepositorytorelease.js'; export { default as checkVersionAvailability } from './utils/checkversionavailability.js'; +export { default as verifyPackagesPublishedCorrectly } from './tasks/verifypackagespublishedcorrectly.js'; export { default as getNpmTagFromVersion } from './utils/getnpmtagfromversion.js'; export { default as isVersionPublishableForTag } from './utils/isversionpublishablefortag.js'; export { default as provideToken } from './utils/providetoken.js'; diff --git a/packages/ckeditor5-dev-release-tools/lib/tasks/verifypackagespublishedcorrectly.js b/packages/ckeditor5-dev-release-tools/lib/tasks/verifypackagespublishedcorrectly.js new file mode 100644 index 000000000..31f27d76f --- /dev/null +++ b/packages/ckeditor5-dev-release-tools/lib/tasks/verifypackagespublishedcorrectly.js @@ -0,0 +1,59 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import upath from 'upath'; +import { glob } from 'glob'; +import fs from 'fs-extra'; +import checkVersionAvailability from '../utils/checkversionavailability.js'; + +/** + * Npm sometimes throws incorrect error 409 while publishing, while the package uploads correctly. + * The purpose of the script is to validate if packages that threw 409 are uploaded correctly to npm. + * + * @param {object} options + * @param {string} options.packagesDirectory Relative path to a location of packages to release. + * @param {string} options.version Version of the current release. + * @param {function} options.onSuccess Callback fired when function is successful. + * @returns {Promise} + */ +export default async function verifyPackagesPublishedCorrectly( options ) { + process.emitWarning( + 'The `verifyPackagesPublishedCorrectly()` function is deprecated and will be removed in the upcoming release (v45). ' + + 'Its responsibility has been merged with `publishPackages()`.', + { + type: 'DeprecationWarning', + code: 'DEP0001', + detail: 'https://github.com/ckeditor/ckeditor5-dev/blob/master/DEPRECATIONS.md#dep0001-verifypackagespublishedcorrectly' + } + ); + + const { packagesDirectory, version, onSuccess } = options; + const packagesToVerify = await glob( upath.join( packagesDirectory, '*' ), { absolute: true } ); + const errors = []; + + if ( !packagesToVerify.length ) { + onSuccess( 'No packages found to check for upload error 409.' ); + + return; + } + + for ( const packageToVerify of packagesToVerify ) { + const packageJson = await fs.readJson( upath.join( packageToVerify, 'package.json' ) ); + + const isPackageVersionAvailable = await checkVersionAvailability( version, packageJson.name ); + + if ( isPackageVersionAvailable ) { + errors.push( packageJson.name ); + } else { + await fs.remove( packageToVerify ); + } + } + + if ( errors.length ) { + throw new Error( 'Packages that were uploaded incorrectly, and need manual verification:\n' + errors.join( '\n' ) ); + } + + onSuccess( 'All packages that returned 409 were uploaded correctly.' ); +} diff --git a/packages/ckeditor5-dev-release-tools/tests/index.js b/packages/ckeditor5-dev-release-tools/tests/index.js index e68cf0267..dd4bb16b2 100644 --- a/packages/ckeditor5-dev-release-tools/tests/index.js +++ b/packages/ckeditor5-dev-release-tools/tests/index.js @@ -30,6 +30,7 @@ import { import executeInParallel from '../lib/utils/executeinparallel.js'; import validateRepositoryToRelease from '../lib/utils/validaterepositorytorelease.js'; import checkVersionAvailability from '../lib/utils/checkversionavailability.js'; +import verifyPackagesPublishedCorrectly from '../lib/tasks/verifypackagespublishedcorrectly.js'; import getNpmTagFromVersion from '../lib/utils/getnpmtagfromversion.js'; import isVersionPublishableForTag from '../lib/utils/isversionpublishablefortag.js'; import provideToken from '../lib/utils/providetoken.js'; @@ -48,6 +49,7 @@ vi.mock( '../lib/tasks/push' ); vi.mock( '../lib/tasks/publishpackages' ); vi.mock( '../lib/tasks/updateversions' ); vi.mock( '../lib/tasks/cleanuppackages' ); +vi.mock( '../lib/tasks/verifypackagespublishedcorrectly' ); vi.mock( '../lib/utils/versions' ); vi.mock( '../lib/utils/getnpmtagfromversion' ); vi.mock( '../lib/utils/changelog' ); @@ -233,6 +235,13 @@ describe( 'dev-release-tools/index', () => { } ); } ); + describe( 'verifyPackagesPublishedCorrectly()', () => { + it( 'should be a function', () => { + expect( verifyPackagesPublishedCorrectly ).to.be.a( 'function' ); + expect( index.verifyPackagesPublishedCorrectly ).to.equal( verifyPackagesPublishedCorrectly ); + } ); + } ); + describe( 'isVersionPublishableForTag()', () => { it( 'should be a function', () => { expect( isVersionPublishableForTag ).to.be.a( 'function' ); diff --git a/packages/ckeditor5-dev-release-tools/tests/tasks/verifypackagespublishedcorrectly.js b/packages/ckeditor5-dev-release-tools/tests/tasks/verifypackagespublishedcorrectly.js new file mode 100644 index 000000000..ce59981c8 --- /dev/null +++ b/packages/ckeditor5-dev-release-tools/tests/tasks/verifypackagespublishedcorrectly.js @@ -0,0 +1,92 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { glob } from 'glob'; +import fs from 'fs-extra'; +import verifyPackagesPublishedCorrectly from '../../lib/tasks/verifypackagespublishedcorrectly.js'; +import checkVersionAvailability from '../../lib/utils/checkversionavailability.js'; + +vi.mock( 'fs-extra' ); +vi.mock( '../../lib/utils/checkversionavailability' ); +vi.mock( 'glob' ); + +describe( 'verifyPackagesPublishedCorrectly()', () => { + beforeEach( () => { + vi.spyOn( process, 'emitWarning' ).mockImplementation( () => { + } ); + vi.mocked( fs ).remove.mockResolvedValue(); + vi.mocked( fs ).readJson.mockResolvedValue(); + vi.mocked( glob ).mockResolvedValue( [] ); + vi.mocked( checkVersionAvailability ).mockResolvedValue(); + } ); + + it( 'should not verify packages if there are no packages in the release directory', async () => { + const packagesDirectory = '/workspace/ckeditor5/release/npm'; + const version = 'latest'; + const onSuccess = vi.fn(); + + await verifyPackagesPublishedCorrectly( { packagesDirectory, version, onSuccess } ); + + expect( onSuccess ).toHaveBeenCalledExactlyOnceWith( 'No packages found to check for upload error 409.' ); + expect( vi.mocked( checkVersionAvailability ) ).not.toHaveBeenCalled(); + } ); + + it( 'should verify packages and remove them from the release directory on if their version are already taken', async () => { + vi.mocked( glob ).mockResolvedValue( [ 'package1', 'package2' ] ); + vi.mocked( fs ).readJson + .mockResolvedValueOnce( { name: '@namespace/package1' } ) + .mockResolvedValueOnce( { name: '@namespace/package2' } ); + + const packagesDirectory = '/workspace/ckeditor5/release/npm'; + const version = 'latest'; + const onSuccess = vi.fn(); + + await verifyPackagesPublishedCorrectly( { packagesDirectory, version, onSuccess } ); + + expect( vi.mocked( checkVersionAvailability ) ).toHaveBeenCalledWith( 'latest', '@namespace/package1' ); + expect( vi.mocked( checkVersionAvailability ) ).toHaveBeenCalledWith( 'latest', '@namespace/package2' ); + expect( vi.mocked( fs ).remove ).toHaveBeenCalledWith( 'package1' ); + expect( vi.mocked( fs ).remove ).toHaveBeenCalledWith( 'package2' ); + + expect( onSuccess ).toHaveBeenCalledExactlyOnceWith( 'All packages that returned 409 were uploaded correctly.' ); + } ); + + it( 'should not remove package from release directory when package is not available on npm', async () => { + vi.mocked( glob ).mockResolvedValue( [ 'package1', 'package2' ] ); + vi.mocked( fs ).readJson + .mockResolvedValueOnce( { name: '@namespace/package1' } ) + .mockResolvedValueOnce( { name: '@namespace/package2' } ); + vi.mocked( checkVersionAvailability ) + .mockResolvedValueOnce( true ) + .mockResolvedValueOnce( false ); + + const packagesDirectory = '/workspace/ckeditor5/release/npm'; + const version = 'latest'; + const onSuccess = vi.fn(); + + await expect( verifyPackagesPublishedCorrectly( { packagesDirectory, version, onSuccess } ) ) + .rejects.toThrow( 'Packages that were uploaded incorrectly, and need manual verification:\n@namespace/package1' ); + + expect( vi.mocked( fs ).remove ).toHaveBeenCalledExactlyOnceWith( 'package2' ); + } ); + + it( 'should print a deprecation warning', async () => { + const packagesDirectory = '/workspace/ckeditor5/release/npm'; + const version = 'latest'; + const onSuccess = vi.fn(); + + await verifyPackagesPublishedCorrectly( { packagesDirectory, version, onSuccess } ); + + expect( vi.mocked( process.emitWarning ) ).toHaveBeenCalledExactlyOnceWith( + expect.any( String ), + expect.objectContaining( { + type: 'DeprecationWarning', + code: 'DEP0001', + detail: expect.stringContaining( 'dep0001' ) + } ) + ); + } ); +} );