@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,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 path = require( 'path' );
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
- return JSON.parse(
22
- fs.readFileSync( path.join( cwd, 'package.json' ), 'utf-8' )
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
+ };
@@ -9,23 +9,28 @@ const { tools } = require( '@ckeditor/ckeditor5-dev-utils' );
9
9
 
10
10
  /**
11
11
  * @param {Object} options
12
- * @param {String} options.version Version of the current release.
12
+ * @param {String|null} options.version Version of the current release.
13
13
  * @param {String} options.changes Changelog entries for the current release.
14
14
  * @param {Boolean} [options.ignoreBranchCheck=false] If set on true, branch checking will be skipped.
15
15
  * @param {String} [options.branch='master'] A name of the branch that should be used for releasing packages.
16
- * @returns {Array.<String>}
16
+ * @returns {Promise.<Array.<String>>}
17
17
  */
18
- module.exports = function validatePackageToRelease( options ) {
18
+ module.exports = async function validateRepositoryToRelease( options ) {
19
+ const {
20
+ version,
21
+ changes,
22
+ ignoreBranchCheck = false,
23
+ branch = 'master'
24
+ } = options;
19
25
  const errors = [];
20
- const branch = options.branch || 'master';
21
26
 
22
27
  // Check whether the repository is ready for the release.
23
- const status = exec( 'git status -sb', { verbosity: 'error' } ).trim();
28
+ const status = ( await exec( 'git status -sb' ) ).trim();
24
29
 
25
- if ( !options.ignoreBranchCheck ) {
30
+ if ( !ignoreBranchCheck ) {
26
31
  // Check whether current branch is "master".
27
- if ( !status.startsWith( `## ${ branch }...origin/${ branch }` ) ) {
28
- errors.push( `Not on ${ branch } branch.` );
32
+ if ( !status.startsWith( `## ${ branch }` ) ) {
33
+ errors.push( `Not on the "#${ branch }" branch.` );
29
34
  }
30
35
  }
31
36
 
@@ -42,13 +47,13 @@ module.exports = function validatePackageToRelease( options ) {
42
47
  }
43
48
 
44
49
  // Check whether the changelog entries are correct.
45
- if ( !options.changes ) {
46
- errors.push( `Cannot find changelog entries for version "${ options.version }".` );
50
+ if ( !changes ) {
51
+ errors.push( `Cannot find changelog entries for version "${ version }".` );
47
52
  }
48
53
 
49
54
  return errors;
50
55
 
51
- function exec( command ) {
52
- return tools.shExec( command, { verbosity: 'error' } );
56
+ async function exec( command ) {
57
+ return tools.shExec( command, { verbosity: 'error', async: true } );
53
58
  }
54
59
  };
package/package.json CHANGED
@@ -1,38 +1,31 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-dev-release-tools",
3
- "version": "37.0.1",
3
+ "version": "38.0.0",
4
4
  "description": "Tools used for releasing CKEditor 5 and related packages.",
5
5
  "keywords": [],
6
6
  "main": "lib/index.js",
7
7
  "dependencies": {
8
- "@ckeditor/ckeditor5-dev-utils": "^37.0.1",
8
+ "@ckeditor/ckeditor5-dev-utils": "^38.0.0",
9
9
  "@octokit/rest": "^17.9.2",
10
10
  "chalk": "^4.0.0",
11
11
  "cli-table": "^0.3.1",
12
+ "cli-columns": "^4.0.0",
12
13
  "compare-func": "^2.0.0",
13
14
  "concat-stream": "^2.0.0",
14
15
  "conventional-changelog-writer": "^4.0.16",
15
16
  "conventional-commits-filter": "^2.0.6",
16
17
  "conventional-commits-parser": "^3.1.0",
17
- "fs-extra": "^9.1.0",
18
18
  "diff": "^5.0.0",
19
+ "fs-extra": "^9.1.0",
19
20
  "git-raw-commits": "^2.0.7",
20
- "glob": "^7.1.6",
21
+ "glob": "^10.2.5",
21
22
  "inquirer": "^7.1.0",
22
23
  "lodash": "^4.17.15",
23
24
  "minimatch": "^3.0.4",
24
25
  "mkdirp": "^1.0.4",
25
26
  "parse-github-url": "^1.0.2",
26
- "semver": "^7.3.2"
27
- },
28
- "devDependencies": {
29
- "chai": "^4.2.0",
30
- "handlebars": "^4.7.6",
31
- "mockery": "^2.1.0",
32
- "mock-fs": "^5.1.2",
33
- "proxyquire": "^2.1.3",
34
- "sinon": "^9.2.4",
35
- "strip-ansi": "^6.0.0"
27
+ "semver": "^7.3.2",
28
+ "upath": "^2.0.1"
36
29
  },
37
30
  "engines": {
38
31
  "node": ">=16.0.0",