@ckeditor/ckeditor5-dev-release-tools 37.0.1 → 38.0.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.
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
5
+ * For licensing, see LICENSE.md.
6
+ */
7
+
8
+ /* eslint-env node */
9
+
10
+ 'use strict';
11
+
12
+ const chalk = require( 'chalk' );
13
+ const columns = require( 'cli-columns' );
14
+ const { tools } = require( '@ckeditor/ckeditor5-dev-utils' );
15
+ const assertNpmAuthorization = require( '../utils/assertnpmauthorization' );
16
+
17
+ /**
18
+ * Used to switch the tags from `staging` to `latest` for specified array of packages.
19
+ *
20
+ * @param {Object} options
21
+ * @param {String} options.npmOwner User that is authorized to release packages.
22
+ * @param {String} options.version Specifies the version of packages to reassign the tags for.
23
+ * @param {Array.<String>} options.packages Array of packages' names to reassign tags for.
24
+ * @returns {Promise}
25
+ */
26
+ module.exports = async function reassignNpmTags( { npmOwner, version, packages } ) {
27
+ const errors = [];
28
+ const packagesSkipped = [];
29
+ const packagesUpdated = [];
30
+
31
+ await assertNpmAuthorization( npmOwner );
32
+
33
+ const counter = tools.createSpinner( 'Reassigning npm tags...', { total: packages.length } );
34
+ counter.start();
35
+
36
+ for ( const packageName of packages ) {
37
+ const latestVersion = await exec( `npm show ${ packageName }@latest version` )
38
+ .then( version => version.trim() )
39
+ .catch( error => {
40
+ errors.push( trimErrorMessage( error.message ) );
41
+ } );
42
+
43
+ if ( latestVersion === version ) {
44
+ packagesSkipped.push( packageName );
45
+
46
+ continue;
47
+ }
48
+
49
+ await exec( `npm dist-tag add ${ packageName }@${ version } latest` )
50
+ .then( () => {
51
+ packagesUpdated.push( packageName );
52
+ } )
53
+ .catch( error => {
54
+ errors.push( trimErrorMessage( error.message ) );
55
+ } )
56
+ .finally( () => {
57
+ counter.increase();
58
+ } );
59
+ }
60
+
61
+ counter.finish();
62
+
63
+ if ( packagesUpdated.length ) {
64
+ console.log( chalk.bold.green( '✨ Tags updated:' ) );
65
+ console.log( columns( packagesUpdated ) );
66
+ }
67
+
68
+ if ( packagesSkipped.length ) {
69
+ console.log( chalk.bold.yellow( '⬇️ Packages skipped:' ) );
70
+ console.log( columns( packagesSkipped ) );
71
+ }
72
+
73
+ if ( errors.length ) {
74
+ console.log( chalk.bold.red( '🐛 Errors found:' ) );
75
+ errors.forEach( msg => console.log( `* ${ msg }` ) );
76
+ }
77
+ };
78
+
79
+ /**
80
+ * @param {String} message
81
+ * @returns {String}
82
+ */
83
+ function trimErrorMessage( message ) {
84
+ return message.replace( /npm ERR!.*\n/g, '' ).trim();
85
+ }
86
+
87
+ /**
88
+ * @param {String} command
89
+ * @returns {Promise.<String>}
90
+ */
91
+ async function exec( command ) {
92
+ return tools.shExec( command, { verbosity: 'silent', async: true } );
93
+ }
@@ -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
+ };
@@ -7,6 +7,7 @@
7
7
 
8
8
  const fs = require( 'fs' );
9
9
  const path = require( 'path' );
10
+ const { getRepositoryUrl } = require( './transformcommitutils' );
10
11
 
11
12
  const utils = {
12
13
  /**
@@ -61,6 +62,39 @@ const utils = {
61
62
  const changelogFile = path.join( cwd, utils.changelogFile );
62
63
 
63
64
  fs.writeFileSync( changelogFile, content, 'utf-8' );
65
+ },
66
+
67
+ /**
68
+ * @param {Number} length
69
+ * @param {String} [cwd=process.cwd()] Where to look for the changelog file.
70
+ */
71
+ truncateChangelog( length, cwd = process.cwd() ) {
72
+ const changelog = utils.getChangelog( cwd );
73
+
74
+ if ( !changelog ) {
75
+ return;
76
+ }
77
+
78
+ const entryHeader = '## [\\s\\S]+?';
79
+ const entryHeaderRegexp = new RegExp( `\\n(${ entryHeader })(?=\\n${ entryHeader }|$)`, 'g' );
80
+
81
+ const entries = [ ...changelog.matchAll( entryHeaderRegexp ) ]
82
+ .filter( match => match && match[ 1 ] )
83
+ .map( match => match[ 1 ] );
84
+
85
+ if ( !entries.length ) {
86
+ return;
87
+ }
88
+
89
+ const truncatedEntries = entries.slice( 0, length );
90
+
91
+ const changelogFooter = entries.length > truncatedEntries.length ?
92
+ `\n\n---\n\nTo see all releases, visit the [release page](${ getRepositoryUrl( cwd ) }/releases).\n` :
93
+ '\n';
94
+
95
+ const truncatedChangelog = utils.changelogHeader + truncatedEntries.join( '\n' ).trim() + changelogFooter;
96
+
97
+ utils.saveChangelog( truncatedChangelog, cwd );
64
98
  }
65
99
  };
66
100