Skip to content

Commit

Permalink
Merge pull request #965 from ckeditor/ck/16625-upload-error-409-fix
Browse files Browse the repository at this point in the history
Feature (release-tools): Added a new task to verify if packages were published correctly to npm. See ckeditor/ckeditor5#16625.
  • Loading branch information
pomek authored Jul 5, 2024
2 parents e196d78 + 5c1f640 commit 73016ca
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 3 deletions.
2 changes: 2 additions & 0 deletions packages/ckeditor5-dev-release-tools/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const { getChangesForVersion, getChangelog, saveChangelog } = require( './utils/
const executeInParallel = require( './utils/executeinparallel' );
const validateRepositoryToRelease = require( './utils/validaterepositorytorelease' );
const checkVersionAvailability = require( './utils/checkversionavailability' );
const verifyPackagesPublishedCorrectly = require( './tasks/verifypackagespublishedcorrectly' );

module.exports = {
generateChangelogForSinglePackage,
Expand All @@ -54,5 +55,6 @@ module.exports = {
getChangelog,
saveChangelog,
validateRepositoryToRelease,
verifyPackagesPublishedCorrectly,
checkVersionAvailability
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

'use strict';

const upath = require( 'upath' );
const { glob } = require( 'glob' );
const fs = require( 'fs-extra' );
const { checkVersionAvailability } = require( '../utils/checkversionavailability' );

/**
* 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}
*/
module.exports = async function verifyPackagesPublishedCorrectly( options ) {
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' ) );

try {
await checkVersionAvailability( version, packageJson.name );
await fs.remove( packageToVerify );
} catch {
errors.push( packageJson.name );
}
}

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.' );
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@ module.exports = async function publishPackageOnNpmCallback( packagePath, taskOp
const upath = require( 'upath' );
const fs = require( 'fs-extra' );

await tools.shExec( `npm publish --access=public --tag ${ taskOptions.npmTag }`, { cwd: packagePath, async: true, verbosity: 'error' } )
.catch( () => {
const result = await tools.shExec( `npm publish --access=public --tag ${ taskOptions.npmTag }`, {
cwd: packagePath,
async: true,
verbosity: 'error'
} )
.catch( e => {
const packageName = upath.basename( packagePath );

if ( e.toString().includes( 'code E409' ) ) {
return { shouldKeepDirectory: true };
}

throw new Error( `Unable to publish "${ packageName }" package.` );
} );

await fs.remove( packagePath );
if ( !result || !result.shouldKeepDirectory ) {
await fs.remove( packagePath );
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

'use strict';

const expect = require( 'chai' ).expect;
const sinon = require( 'sinon' );
const mockery = require( 'mockery' );

describe( 'dev-release-tools/utils', () => {
describe( 'verifyPackagesPublishedCorrectly()', () => {
let verifyPackagesPublishedCorrectly, sandbox, stubs;

beforeEach( () => {
sandbox = sinon.createSandbox();

stubs = {
fs: {
remove: sandbox.stub().resolves(),
readJson: sandbox.stub().resolves()
},
devUtils: {
checkVersionAvailability: sandbox.stub().resolves()
},
glob: {
glob: sandbox.stub().resolves( [] )
}
};

mockery.enable( {
useCleanCache: true,
warnOnReplace: false,
warnOnUnregistered: false
} );

mockery.registerMock( 'fs-extra', stubs.fs );
mockery.registerMock( '../utils/checkversionavailability', stubs.devUtils );
mockery.registerMock( 'glob', stubs.glob );

verifyPackagesPublishedCorrectly = require( '../../lib/tasks/verifypackagespublishedcorrectly' );
} );

afterEach( () => {
mockery.deregisterAll();
mockery.disable();
sandbox.restore();
} );

it( 'should not verify packages if there are no packages in the release directory', async () => {
stubs.glob.glob.resolves( [] );

const packagesDirectory = '/workspace/ckeditor5/release/npm';
const version = 'latest';
const onSuccess = sandbox.stub();

await verifyPackagesPublishedCorrectly( { packagesDirectory, version, onSuccess } );

expect( onSuccess.firstCall.args[ 0 ] ).to.equal( '✅ No packages found to check for upload error 409.' );
expect( stubs.devUtils.checkVersionAvailability.callCount ).to.equal( 0 );
} );

it( 'should verify packages and remove them from the release directory on "npm show" command success', async () => {
stubs.glob.glob.resolves( [ 'package1', 'package2' ] );
stubs.fs.readJson
.onCall( 0 ).resolves( { name: '@namespace/package1' } )
.onCall( 1 ).resolves( { name: '@namespace/package2' } );

const packagesDirectory = '/workspace/ckeditor5/release/npm';
const version = 'latest';
const onSuccess = sandbox.stub();

await verifyPackagesPublishedCorrectly( { packagesDirectory, version, onSuccess } );

expect( stubs.devUtils.checkVersionAvailability.firstCall.args[ 0 ] ).to.equal( 'latest' );
expect( stubs.devUtils.checkVersionAvailability.firstCall.args[ 1 ] ).to.equal( '@namespace/package1' );
expect( stubs.fs.remove.firstCall.args[ 0 ] ).to.equal( 'package1' );

expect( stubs.devUtils.checkVersionAvailability.secondCall.args[ 0 ] ).to.equal( 'latest' );
expect( stubs.devUtils.checkVersionAvailability.secondCall.args[ 1 ] ).to.equal( '@namespace/package2' );
expect( stubs.fs.remove.secondCall.args[ 0 ] ).to.equal( 'package2' );

expect( onSuccess.firstCall.args[ 0 ] ).to.equal( '✅ All packages that returned 409 were uploaded correctly.' );
} );

it( 'should not remove package from release directory on on error', async () => {
stubs.glob.glob.resolves( [ 'package1', 'package2' ] );
stubs.fs.readJson
.onCall( 0 ).resolves( { name: '@namespace/package1' } )
.onCall( 1 ).resolves( { name: '@namespace/package2' } );
stubs.devUtils.checkVersionAvailability
.onCall( 0 ).rejects()
.onCall( 1 ).resolves();

const packagesDirectory = '/workspace/ckeditor5/release/npm';
const version = 'latest';
const onSuccess = sandbox.stub();

await verifyPackagesPublishedCorrectly( { packagesDirectory, version, onSuccess } )
.then(
() => {
throw new Error( 'this should not be thrown!' );
},
e => {
expect( e.message ).to.equal(
'Packages that were uploaded incorrectly, and need manual verification:\n@namespace/package1'
);
}
);

expect( stubs.fs.remove.callCount ).to.equal( 1 );
expect( stubs.fs.remove.firstCall.args[ 0 ] ).to.equal( 'package2' );
} );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,15 @@ describe( 'dev-release-tools/utils', () => {
}
);
} );

it( 'should not remove a package directory and not throw error when publishing on npm failed with code 409', async () => {
stubs.devUtils.tools.shExec.rejects( new Error( 'code E409' ) );

const packagePath = '/workspace/ckeditor5/packages/ckeditor5-foo';

await publishPackageOnNpmCallback( packagePath, { npmTag: 'nightly' } );

expect( stubs.fs.remove.callCount ).to.equal( 0 );
} );
} );
} );

0 comments on commit 73016ca

Please sign in to comment.