@ckeditor/ckeditor5-dev-release-tools 37.0.1 → 38.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +22 -10
- package/lib/tasks/cleanuppackages.js +175 -0
- package/lib/tasks/commitandtag.js +28 -0
- package/lib/tasks/creategithubrelease.js +104 -0
- package/lib/tasks/preparerepository.js +149 -0
- package/lib/tasks/publishpackages.js +69 -0
- package/lib/tasks/push.js +29 -0
- package/lib/tasks/reassignnpmtags.js +93 -0
- package/lib/tasks/updatedependencies.js +84 -0
- package/lib/tasks/updateversions.js +117 -0
- package/lib/utils/assertfilestopublish.js +88 -0
- package/lib/utils/assertnpmauthorization.js +26 -0
- package/lib/utils/assertnpmtag.js +60 -0
- package/lib/utils/assertpackages.js +34 -0
- package/lib/utils/executeinparallel.js +167 -0
- package/lib/utils/getpackagejson.js +8 -4
- package/lib/utils/parallelworker.js +25 -0
- package/lib/utils/publishpackagesonnpm.js +35 -0
- package/lib/utils/{validatepackagetorelease.js → validaterepositorytorelease.js} +17 -12
- package/package.json +7 -14
- package/lib/tasks/bumpversions.js +0 -354
- package/lib/tasks/preparepackages.js +0 -257
- package/lib/tasks/releasesubrepositories.js +0 -1001
- package/lib/tasks/updateckeditor5dependencies.js +0 -392
- package/lib/utils/creategithubrelease.js +0 -37
- package/lib/utils/executeonpackages.js +0 -26
- package/lib/utils/getpackagestorelease.js +0 -152
- package/lib/utils/updatedependenciesversions.js +0 -32
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require( 'fs-extra' );
|
|
9
|
+
const { glob } = require( 'glob' );
|
|
10
|
+
const upath = require( 'upath' );
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The purpose of this script is to update all eligible dependencies to a version specified in the `options.version`. The following packages
|
|
14
|
+
* are taken into consideration:
|
|
15
|
+
*
|
|
16
|
+
* - The root package located in `options.cwd` path.
|
|
17
|
+
* - All packages located in the `options.packagesDirectory` path relative to `options.cwd`.
|
|
18
|
+
*
|
|
19
|
+
* The eligible dependencies are distinguished by the return value from the `options.shouldUpdateVersionCallback` function. Only if this
|
|
20
|
+
* callback returns a truthy value for a given dependency, its version will be updated.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} options
|
|
23
|
+
* @param {String} options.version Target version or a range version to which all eligible dependencies will be updated.
|
|
24
|
+
* Examples: `1.0.0`, `^1.0.0`, etc.
|
|
25
|
+
* @param {Function} options.shouldUpdateVersionCallback Callback function that decides whether to update a version for a dependency.
|
|
26
|
+
* It receives a package name as an argument and should return a boolean value.
|
|
27
|
+
* @param {String} [options.packagesDirectory] Relative path to a location of packages to update their dependencies. If not specified,
|
|
28
|
+
* only the root package is checked.
|
|
29
|
+
* @param {String} [options.cwd=process.cwd()] Current working directory from which all paths will be resolved.
|
|
30
|
+
* @returns {Promise}
|
|
31
|
+
*/
|
|
32
|
+
module.exports = async function updateDependencies( options ) {
|
|
33
|
+
const {
|
|
34
|
+
version,
|
|
35
|
+
packagesDirectory,
|
|
36
|
+
shouldUpdateVersionCallback,
|
|
37
|
+
cwd = process.cwd()
|
|
38
|
+
} = options;
|
|
39
|
+
|
|
40
|
+
const globOptions = {
|
|
41
|
+
cwd,
|
|
42
|
+
nodir: true,
|
|
43
|
+
absolute: true
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const globPatterns = [ 'package.json' ];
|
|
47
|
+
|
|
48
|
+
if ( packagesDirectory ) {
|
|
49
|
+
const packagesDirectoryPattern = upath.join( packagesDirectory, '*', 'package.json' );
|
|
50
|
+
|
|
51
|
+
globPatterns.push( packagesDirectoryPattern );
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const pkgJsonPaths = await glob( globPatterns, globOptions );
|
|
55
|
+
|
|
56
|
+
for ( const pkgJsonPath of pkgJsonPaths ) {
|
|
57
|
+
const pkgJson = await fs.readJson( pkgJsonPath );
|
|
58
|
+
|
|
59
|
+
updateVersion( version, shouldUpdateVersionCallback, pkgJson.dependencies );
|
|
60
|
+
updateVersion( version, shouldUpdateVersionCallback, pkgJson.devDependencies );
|
|
61
|
+
updateVersion( version, shouldUpdateVersionCallback, pkgJson.peerDependencies );
|
|
62
|
+
|
|
63
|
+
await fs.writeJson( pkgJsonPath, pkgJson, { spaces: 2 } );
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Updates the version for each eligible dependency.
|
|
69
|
+
*
|
|
70
|
+
* @param {String} version
|
|
71
|
+
* @param {Function} callback
|
|
72
|
+
* @param {Object} [dependencies]
|
|
73
|
+
*/
|
|
74
|
+
function updateVersion( version, callback, dependencies ) {
|
|
75
|
+
if ( !dependencies ) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for ( const packageName of Object.keys( dependencies ) ) {
|
|
80
|
+
if ( callback( packageName ) ) {
|
|
81
|
+
dependencies[ packageName ] = version;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { glob } = require( 'glob' );
|
|
9
|
+
const fs = require( 'fs-extra' );
|
|
10
|
+
const semver = require( 'semver' );
|
|
11
|
+
const { normalizeTrim, toUnix, dirname, join } = require( 'upath' );
|
|
12
|
+
const { tools } = require( '@ckeditor/ckeditor5-dev-utils' );
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The purpose of the script is to update the version of a root package found in the current working
|
|
16
|
+
* directory and packages if the `options.packagesDirectory` path is provided.
|
|
17
|
+
*
|
|
18
|
+
* Before updating phase, the specified version must meet the following conditions:
|
|
19
|
+
*
|
|
20
|
+
* * It must not be in use in the npm registry.
|
|
21
|
+
* * It must be higher than the version specified in the root package (found in the `cwd` directory).
|
|
22
|
+
* Exception: passing a version starting with the `0.0.0-nightly` string. It is used for publishing
|
|
23
|
+
* a nightly release.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} options
|
|
26
|
+
* @param {String} options.version Version to store in a `package.json` file under the `version` key.
|
|
27
|
+
* @param {String} [options.packagesDirectory] Relative path to a location of packages to update. If not specified,
|
|
28
|
+
* only the root package is checked.
|
|
29
|
+
* @param {String} [options.cwd=process.cwd()] Current working directory from which all paths will be resolved.
|
|
30
|
+
* @returns {Promise}
|
|
31
|
+
*/
|
|
32
|
+
module.exports = async function updateVersions( { packagesDirectory, version, cwd = process.cwd() } ) {
|
|
33
|
+
const normalizedCwd = toUnix( cwd );
|
|
34
|
+
const normalizedPackagesDir = packagesDirectory ? normalizeTrim( packagesDirectory ) : null;
|
|
35
|
+
|
|
36
|
+
const globPatterns = getGlobPatterns( normalizedPackagesDir );
|
|
37
|
+
const pkgJsonPaths = await glob( globPatterns, { cwd: normalizedCwd, absolute: true, nodir: true } );
|
|
38
|
+
const randomPackagePath = getRandomPackagePath( pkgJsonPaths, normalizedPackagesDir );
|
|
39
|
+
|
|
40
|
+
const rootPackageJson = join( normalizedCwd, 'package.json' );
|
|
41
|
+
const randomPackageJson = join( randomPackagePath, 'package.json' );
|
|
42
|
+
|
|
43
|
+
checkIfVersionIsValid( version, ( await fs.readJson( rootPackageJson ) ).version );
|
|
44
|
+
await checkVersionAvailability( version, ( await fs.readJson( randomPackageJson ) ).name );
|
|
45
|
+
|
|
46
|
+
for ( const pkgJsonPath of pkgJsonPaths ) {
|
|
47
|
+
const pkgJson = await fs.readJson( pkgJsonPath );
|
|
48
|
+
|
|
49
|
+
pkgJson.version = version;
|
|
50
|
+
await fs.writeJson( pkgJsonPath, pkgJson, { spaces: 2 } );
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {String|null} packagesDirectory
|
|
56
|
+
* @returns {Array.<String>}
|
|
57
|
+
*/
|
|
58
|
+
function getGlobPatterns( packagesDirectory ) {
|
|
59
|
+
const patterns = [ 'package.json' ];
|
|
60
|
+
|
|
61
|
+
if ( packagesDirectory ) {
|
|
62
|
+
patterns.push( packagesDirectory + '/*/package.json' );
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return patterns;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {Array.<String>} pkgJsonPaths
|
|
70
|
+
* @param {String|null} packagesDirectory
|
|
71
|
+
* @returns {Object}
|
|
72
|
+
*/
|
|
73
|
+
function getRandomPackagePath( pkgJsonPaths, packagesDirectory ) {
|
|
74
|
+
const randomPkgJsonPaths = packagesDirectory ?
|
|
75
|
+
pkgJsonPaths.filter( packagePath => packagePath.includes( packagesDirectory ) ) :
|
|
76
|
+
pkgJsonPaths;
|
|
77
|
+
const randomPkgJsonPath = randomPkgJsonPaths[ Math.floor( Math.random() * randomPkgJsonPaths.length ) ];
|
|
78
|
+
|
|
79
|
+
return dirname( randomPkgJsonPath );
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Checks if the specified version is greater than the current one.
|
|
84
|
+
*
|
|
85
|
+
* A nightly version is always considered as valid.
|
|
86
|
+
*
|
|
87
|
+
* @param {String} newVersion
|
|
88
|
+
* @param {String} currentVersion
|
|
89
|
+
*/
|
|
90
|
+
function checkIfVersionIsValid( newVersion, currentVersion ) {
|
|
91
|
+
if ( newVersion.startsWith( '0.0.0-nightly' ) ) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if ( !semver.gt( newVersion, currentVersion ) ) {
|
|
96
|
+
throw new Error( `Provided version ${ newVersion } must be greater than ${ currentVersion } or match pattern 0.0.0-nightly.` );
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Checks if the provided version is not used in npm and there will be no errors when calling publish.
|
|
102
|
+
*
|
|
103
|
+
* @param {String} version
|
|
104
|
+
* @param {String} packageName
|
|
105
|
+
* @returns {Promise}
|
|
106
|
+
*/
|
|
107
|
+
async function checkVersionAvailability( version, packageName ) {
|
|
108
|
+
return tools.shExec( `npm show ${ packageName }@${ version } version`, { verbosity: 'silent', async: true } )
|
|
109
|
+
.then( () => {
|
|
110
|
+
throw new Error( `Provided version ${ version } is already used in npm by ${ packageName }.` );
|
|
111
|
+
} )
|
|
112
|
+
.catch( err => {
|
|
113
|
+
if ( !err.toString().includes( 'is not in this registry' ) ) {
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
} );
|
|
117
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require( 'fs-extra' );
|
|
9
|
+
const upath = require( 'upath' );
|
|
10
|
+
const { glob } = require( 'glob' );
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if all files expected to be released actually exist in the package directory. Verification takes place for all packages.
|
|
14
|
+
*
|
|
15
|
+
* @param {String} packagePaths
|
|
16
|
+
* @param {Object.<String, Array.<String>>|null} optionalEntries
|
|
17
|
+
* @returns {Promise}
|
|
18
|
+
*/
|
|
19
|
+
module.exports = async function assertFilesToPublish( packagePaths, optionalEntries ) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
|
|
22
|
+
for ( const packagePath of packagePaths ) {
|
|
23
|
+
const requiredEntries = [];
|
|
24
|
+
const packageJsonPath = upath.join( packagePath, 'package.json' );
|
|
25
|
+
const packageJson = await fs.readJson( packageJsonPath );
|
|
26
|
+
|
|
27
|
+
if ( packageJson.main ) {
|
|
28
|
+
requiredEntries.push( packageJson.main );
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if ( packageJson.types ) {
|
|
32
|
+
requiredEntries.push( packageJson.types );
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if ( packageJson.files ) {
|
|
36
|
+
requiredEntries.push( ...getRequiredEntries( packageJson.files, packageJson.name, optionalEntries ) );
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const unmatchedEntries = [];
|
|
40
|
+
|
|
41
|
+
for ( const requiredEntry of requiredEntries ) {
|
|
42
|
+
// To match a directory or a file using a single `requiredEntry` pattern.
|
|
43
|
+
const foundFiles = await glob( [ requiredEntry, requiredEntry + '/**' ], {
|
|
44
|
+
cwd: packagePath,
|
|
45
|
+
dot: true,
|
|
46
|
+
nodir: true
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
if ( foundFiles.length === 0 ) {
|
|
50
|
+
unmatchedEntries.push( requiredEntry );
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if ( unmatchedEntries.length ) {
|
|
55
|
+
errors.push( `Missing files in "${ packageJson.name }" package for entries: "${ unmatchedEntries.join( '", "' ) }"` );
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if ( errors.length ) {
|
|
60
|
+
throw new Error( errors.join( '\n' ) );
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Filters out the optional entries from the `files` field and returns only the required ones.
|
|
66
|
+
*
|
|
67
|
+
* @param {Array.<String>} entries
|
|
68
|
+
* @param {String} packageName
|
|
69
|
+
* @param {Object.<String, Array.<String>>|null} optionalEntries
|
|
70
|
+
* @returns {Array.<String>}
|
|
71
|
+
*/
|
|
72
|
+
function getRequiredEntries( entries, packageName, optionalEntries ) {
|
|
73
|
+
if ( !optionalEntries ) {
|
|
74
|
+
return entries;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return entries.filter( entry => {
|
|
78
|
+
if ( optionalEntries[ packageName ] ) {
|
|
79
|
+
return !optionalEntries[ packageName ].includes( entry );
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if ( optionalEntries.default ) {
|
|
83
|
+
return !optionalEntries.default.includes( entry );
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return true;
|
|
87
|
+
} );
|
|
88
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { tools } = require( '@ckeditor/ckeditor5-dev-utils' );
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Checks whether a user is logged to npm as the provided account name.
|
|
12
|
+
*
|
|
13
|
+
* @param {String} npmOwner Expected npm account name that should be logged into npm.
|
|
14
|
+
* @returns {Promise}
|
|
15
|
+
*/
|
|
16
|
+
module.exports = async function assertNpmAuthorization( npmOwner ) {
|
|
17
|
+
return tools.shExec( 'npm whoami', { verbosity: 'error', async: true } )
|
|
18
|
+
.then( npmCurrentUser => {
|
|
19
|
+
if ( npmOwner !== npmCurrentUser.trim() ) {
|
|
20
|
+
return Promise.reject();
|
|
21
|
+
}
|
|
22
|
+
} )
|
|
23
|
+
.catch( () => {
|
|
24
|
+
throw new Error( `You must be logged to npm as "${ npmOwner }" to execute this release step.` );
|
|
25
|
+
} );
|
|
26
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require( 'fs-extra' );
|
|
9
|
+
const upath = require( 'upath' );
|
|
10
|
+
const semver = require( 'semver' );
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if the npm tag matches the tag calculated from the package version. Verification takes place for all packages.
|
|
14
|
+
*
|
|
15
|
+
* @param {Array.<String>} packagePaths
|
|
16
|
+
* @param {String} npmTag
|
|
17
|
+
* @returns {Promise}
|
|
18
|
+
*/
|
|
19
|
+
module.exports = async function assertNpmTag( packagePaths, npmTag ) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
|
|
22
|
+
for ( const packagePath of packagePaths ) {
|
|
23
|
+
const packageJsonPath = upath.join( packagePath, 'package.json' );
|
|
24
|
+
const packageJson = await fs.readJson( packageJsonPath );
|
|
25
|
+
const versionTag = getVersionTag( packageJson.version );
|
|
26
|
+
|
|
27
|
+
if ( versionTag === npmTag ) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if ( versionTag === 'latest' && npmTag === 'staging' ) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
errors.push( `The version tag "${ versionTag }" from "${ packageJson.name }" package does not match the npm tag "${ npmTag }".` );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if ( errors.length ) {
|
|
39
|
+
throw new Error( errors.join( '\n' ) );
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns the version tag for the package.
|
|
45
|
+
*
|
|
46
|
+
* For the official release, returns the "latest" tag. For a non-official release (pre-release), returns the version tag extracted from
|
|
47
|
+
* the package version.
|
|
48
|
+
*
|
|
49
|
+
* @param {String} version
|
|
50
|
+
* @returns {String}
|
|
51
|
+
*/
|
|
52
|
+
function getVersionTag( version ) {
|
|
53
|
+
const [ versionTag ] = semver.prerelease( version ) || [ 'latest' ];
|
|
54
|
+
|
|
55
|
+
if ( versionTag.startsWith( 'nightly' ) ) {
|
|
56
|
+
return 'nightly';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return versionTag;
|
|
60
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require( 'fs-extra' );
|
|
9
|
+
const upath = require( 'upath' );
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Checks if all packages in the provided directories contain the `package.json` file.
|
|
13
|
+
*
|
|
14
|
+
* @param {Array.<String>} packagePaths
|
|
15
|
+
* @returns {Promise}
|
|
16
|
+
*/
|
|
17
|
+
module.exports = async function assertPackages( packagePaths ) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
|
|
20
|
+
for ( const packagePath of packagePaths ) {
|
|
21
|
+
const packageName = upath.basename( packagePath );
|
|
22
|
+
const packageJsonPath = upath.join( packagePath, 'package.json' );
|
|
23
|
+
|
|
24
|
+
if ( await fs.pathExists( packageJsonPath ) ) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
errors.push( `The "package.json" file is missing in the "${ packageName }" package.` );
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if ( errors.length ) {
|
|
32
|
+
throw new Error( errors.join( '\n' ) );
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* eslint-env node */
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const crypto = require( 'crypto' );
|
|
11
|
+
const upath = require( 'upath' );
|
|
12
|
+
const fs = require( 'fs/promises' );
|
|
13
|
+
const { Worker } = require( 'worker_threads' );
|
|
14
|
+
const { glob } = require( 'glob' );
|
|
15
|
+
|
|
16
|
+
const WORKER_SCRIPT = upath.join( __dirname, 'parallelworker.js' );
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* This util allows executing a specified task in parallel using Workers. It can be helpful when executing a not resource-consuming
|
|
20
|
+
* task in all packages specified in a path.
|
|
21
|
+
*
|
|
22
|
+
* If the callback loads dependencies, they must be specified directly in the function due to the worker's limitations.
|
|
23
|
+
* Functions cannot be passed to workers. Hence, we store the callback as a Node.js file loaded by workers.
|
|
24
|
+
*
|
|
25
|
+
* @see https://nodejs.org/api/worker_threads.html
|
|
26
|
+
* @param {Object} options
|
|
27
|
+
* @param {String} options.packagesDirectory Relative path to a location of packages to execute a task.
|
|
28
|
+
* @param {Function} options.taskToExecute A callback that is executed on all found packages.
|
|
29
|
+
* It receives an absolute path to a package as an argument. It can be synchronous or may return a promise.
|
|
30
|
+
* @param {AbortSignal} options.signal Signal to abort the asynchronous process.
|
|
31
|
+
* @param {ListrTaskObject} options.listrTask An instance of `ListrTask`.
|
|
32
|
+
* @param {Function} [options.packagesDirectoryFilter] A function that is executed for each found package directory to filter out those
|
|
33
|
+
* that do not require executing a task. It should return a truthy value to keep the package and a falsy value to skip the package from
|
|
34
|
+
* processing.
|
|
35
|
+
* @param {String} [options.cwd=process.cwd()] Current working directory from which all paths will be resolved.
|
|
36
|
+
* @param {Number} [options.concurrency=require( 'os' ).cpus().length / 2] Number of CPUs that will execute the task.
|
|
37
|
+
* @returns {Promise}
|
|
38
|
+
*/
|
|
39
|
+
module.exports = async function executeInParallel( options ) {
|
|
40
|
+
const {
|
|
41
|
+
packagesDirectory,
|
|
42
|
+
signal,
|
|
43
|
+
taskToExecute,
|
|
44
|
+
listrTask,
|
|
45
|
+
packagesDirectoryFilter = null,
|
|
46
|
+
cwd = process.cwd(),
|
|
47
|
+
concurrency = require( 'os' ).cpus().length / 2
|
|
48
|
+
} = options;
|
|
49
|
+
|
|
50
|
+
const normalizedCwd = upath.toUnix( cwd );
|
|
51
|
+
const packages = await glob( `${ packagesDirectory }/*/`, {
|
|
52
|
+
cwd: normalizedCwd,
|
|
53
|
+
absolute: true
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
const packagesToProcess = packagesDirectoryFilter ?
|
|
57
|
+
packages.filter( packagesDirectoryFilter ) :
|
|
58
|
+
packages;
|
|
59
|
+
|
|
60
|
+
const packagesInThreads = getPackagesGroupedByThreads( packagesToProcess, concurrency );
|
|
61
|
+
|
|
62
|
+
const callbackModule = upath.join( cwd, crypto.randomUUID() + '.js' );
|
|
63
|
+
await fs.writeFile( callbackModule, `'use strict';\nmodule.exports = ${ taskToExecute };`, 'utf-8' );
|
|
64
|
+
|
|
65
|
+
const onPackageDone = progressFactory( listrTask, packagesToProcess.length );
|
|
66
|
+
|
|
67
|
+
const workers = packagesInThreads.map( packages => {
|
|
68
|
+
return createWorker( {
|
|
69
|
+
signal,
|
|
70
|
+
onPackageDone,
|
|
71
|
+
workerData: { packages, callbackModule }
|
|
72
|
+
} );
|
|
73
|
+
} );
|
|
74
|
+
|
|
75
|
+
return Promise.all( workers )
|
|
76
|
+
.catch( err => {
|
|
77
|
+
// `err` can be `undefined` if a process was aborted.
|
|
78
|
+
if ( !err ) {
|
|
79
|
+
return Promise.resolve();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return Promise.reject( err );
|
|
83
|
+
} )
|
|
84
|
+
.finally( async () => {
|
|
85
|
+
await fs.unlink( callbackModule );
|
|
86
|
+
} );
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {ListrTaskObject} listrTask
|
|
91
|
+
* @param {Number} total
|
|
92
|
+
* @returns {Function}
|
|
93
|
+
*/
|
|
94
|
+
function progressFactory( listrTask, total ) {
|
|
95
|
+
let done = 0;
|
|
96
|
+
|
|
97
|
+
return () => {
|
|
98
|
+
done += 1;
|
|
99
|
+
listrTask.output = `Status: ${ done }/${ total }.`;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {Object} options
|
|
105
|
+
* @param {AbortSignal} options.signal
|
|
106
|
+
* @param {Function} options.onPackageDone
|
|
107
|
+
* @param {Object} options.workerData
|
|
108
|
+
* @returns {Promise}
|
|
109
|
+
*/
|
|
110
|
+
function createWorker( { signal, onPackageDone, workerData } ) {
|
|
111
|
+
return new Promise( ( resolve, reject ) => {
|
|
112
|
+
const worker = new Worker( WORKER_SCRIPT, { workerData } );
|
|
113
|
+
|
|
114
|
+
signal.addEventListener( 'abort', () => {
|
|
115
|
+
worker.terminate();
|
|
116
|
+
}, { once: true } );
|
|
117
|
+
|
|
118
|
+
worker.on( 'error', err => {
|
|
119
|
+
reject( err );
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
worker.on( 'message', msg => {
|
|
123
|
+
if ( msg === 'done:package' ) {
|
|
124
|
+
onPackageDone();
|
|
125
|
+
}
|
|
126
|
+
} );
|
|
127
|
+
|
|
128
|
+
worker.on( 'exit', code => {
|
|
129
|
+
return code ? reject() : resolve();
|
|
130
|
+
} );
|
|
131
|
+
|
|
132
|
+
return worker;
|
|
133
|
+
} );
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Split the collection of packages into smaller chunks to process a task using threads.
|
|
138
|
+
*
|
|
139
|
+
* To avoid having packages with a common prefix in a single thread, use a loop for attaching packages to threads.
|
|
140
|
+
*
|
|
141
|
+
* @param {Array.<String>} packages An array of absolute paths to packages.
|
|
142
|
+
* @param {Number} concurrency A number of threads.
|
|
143
|
+
* @returns {Array.<Array.<String>>}
|
|
144
|
+
*/
|
|
145
|
+
function getPackagesGroupedByThreads( packages, concurrency ) {
|
|
146
|
+
return packages.reduce( ( collection, packageItem, index ) => {
|
|
147
|
+
const arrayIndex = index % concurrency;
|
|
148
|
+
|
|
149
|
+
if ( !collection[ arrayIndex ] ) {
|
|
150
|
+
collection.push( [] );
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
collection[ arrayIndex ].push( packageItem );
|
|
154
|
+
|
|
155
|
+
return collection;
|
|
156
|
+
}, [] );
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @typedef {Object} ListrTaskObject
|
|
161
|
+
*
|
|
162
|
+
* @see https://listr2.kilic.dev/api/classes/ListrTaskObject.html
|
|
163
|
+
*
|
|
164
|
+
* @property {String} title Title of the task.
|
|
165
|
+
*
|
|
166
|
+
* @property {String} output Update the current output of the task.
|
|
167
|
+
*/
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
const fs = require( 'fs' );
|
|
9
|
-
const
|
|
9
|
+
const upath = require( 'upath' );
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Returns object from `package.json`.
|
|
@@ -18,7 +18,11 @@ const path = require( 'path' );
|
|
|
18
18
|
* @returns {Object}
|
|
19
19
|
*/
|
|
20
20
|
module.exports = function getPackageJson( cwd = process.cwd() ) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
21
|
+
let pkgJsonPath = cwd;
|
|
22
|
+
|
|
23
|
+
if ( !pkgJsonPath.endsWith( 'package.json' ) ) {
|
|
24
|
+
pkgJsonPath = upath.join( cwd, 'package.json' );
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return JSON.parse( fs.readFileSync( pkgJsonPath, 'utf-8' ) );
|
|
24
28
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
// This file is covered by the "executeInParallel() - integration" test cases.
|
|
9
|
+
|
|
10
|
+
// Required due to top-level await.
|
|
11
|
+
( async () => {
|
|
12
|
+
/**
|
|
13
|
+
* @param {String} callbackModule
|
|
14
|
+
* @param {Array.<String>} packages
|
|
15
|
+
*/
|
|
16
|
+
const { parentPort, workerData } = require( 'worker_threads' );
|
|
17
|
+
const callback = require( workerData.callbackModule );
|
|
18
|
+
|
|
19
|
+
for ( const packagePath of workerData.packages ) {
|
|
20
|
+
await callback( packagePath );
|
|
21
|
+
|
|
22
|
+
// To increase the status log.
|
|
23
|
+
parentPort.postMessage( 'done:package' );
|
|
24
|
+
}
|
|
25
|
+
} )();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require( 'fs-extra' );
|
|
9
|
+
const upath = require( 'upath' );
|
|
10
|
+
const { tools } = require( '@ckeditor/ckeditor5-dev-utils' );
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Calls the npm command to publish all packages. When a package is successfully published, it is removed from the filesystem.
|
|
14
|
+
*
|
|
15
|
+
* @param {Array.<String>} packagePaths
|
|
16
|
+
* @param {String} npmTag
|
|
17
|
+
* @param {ListrTaskObject} listrTask
|
|
18
|
+
* @returns {Promise}
|
|
19
|
+
*/
|
|
20
|
+
module.exports = async function publishPackagesOnNpm( packagePaths, npmTag, listrTask ) {
|
|
21
|
+
let index = 0;
|
|
22
|
+
|
|
23
|
+
for ( const packagePath of packagePaths ) {
|
|
24
|
+
listrTask.output = `Status: ${ ++index }/${ packagePaths.length }. Processing the "${ upath.basename( packagePath ) }" directory.`;
|
|
25
|
+
|
|
26
|
+
await tools.shExec( `npm publish --access=public --tag ${ npmTag }`, { cwd: packagePath, async: true, verbosity: 'error' } )
|
|
27
|
+
.catch( () => {
|
|
28
|
+
const packageName = upath.basename( packagePath );
|
|
29
|
+
|
|
30
|
+
throw new Error( `Unable to publish "${ packageName }" package.` );
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
await fs.remove( packagePath );
|
|
34
|
+
}
|
|
35
|
+
};
|