@ckeditor/ckeditor5-dev-release-tools 61.2.0 → 61.3.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/README.md +1 -1
- package/lib/index.js +1 -0
- package/lib/tasks/publishpackages.js +3 -10
- package/lib/tasks/reassignnpmtags.js +18 -3
- package/lib/utils/assertnpmauthorization.js +17 -2
- package/lib/utils/getnpmidtoken.js +72 -0
- package/lib/utils/publishpackageonnpmcallback.js +14 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@ CKEditor 5 release tools
|
|
|
2
2
|
========================
|
|
3
3
|
|
|
4
4
|
[](https://www.npmjs.com/package/@ckeditor/ckeditor5-dev-release-tools)
|
|
5
|
-
[](https://app.circleci.com/pipelines/github/ckeditor/ckeditor5-dev?branch=master)
|
|
5
|
+
[](https://app.circleci.com/pipelines/github/ckeditor/ckeditor5-dev?branch=master-v61)
|
|
6
6
|
|
|
7
7
|
Contains tools used during a release of [CKEditor 5](https://ckeditor.com) and related packages.
|
|
8
8
|
|
package/lib/index.js
CHANGED
|
@@ -33,3 +33,4 @@ export { default as validateRepositoryToRelease } from './utils/validatereposito
|
|
|
33
33
|
export { default as getNpmTagFromVersion } from './utils/getnpmtagfromversion.js';
|
|
34
34
|
export { default as provideToken } from './utils/providetoken.js';
|
|
35
35
|
export { default as validateGithubToken } from './utils/validategithubtoken.js';
|
|
36
|
+
export { default as getNpmIdToken } from './utils/getnpmidtoken.js';
|
|
@@ -76,15 +76,7 @@ export default async function publishPackages( options ) {
|
|
|
76
76
|
attempts = 5
|
|
77
77
|
} = options;
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
// npm exchanges the OIDC token only during supported operations, such as `npm publish`, and `npm whoami`
|
|
81
|
-
// does not reflect Trusted Publishing authentication. Hence, only the token presence can be verified upfront.
|
|
82
|
-
if ( !process.env.NPM_ID_TOKEN ) {
|
|
83
|
-
throw new Error( 'The "NPM_ID_TOKEN" environment variable is required when publishing using npm Trusted Publishing (OIDC).' );
|
|
84
|
-
}
|
|
85
|
-
} else {
|
|
86
|
-
await assertNpmAuthorization( npmOwner );
|
|
87
|
-
}
|
|
79
|
+
await assertNpmAuthorization( npmOwner, { useOidc } );
|
|
88
80
|
|
|
89
81
|
// Find packages that would be published...
|
|
90
82
|
const packagePaths = await workspaces.findPathsToPackages( cwd, packagesDirectory );
|
|
@@ -124,7 +116,8 @@ export default async function publishPackages( options ) {
|
|
|
124
116
|
listrTask,
|
|
125
117
|
taskToExecute: publishPackageOnNpmCallback,
|
|
126
118
|
taskOptions: {
|
|
127
|
-
npmTag
|
|
119
|
+
npmTag,
|
|
120
|
+
useOidc
|
|
128
121
|
},
|
|
129
122
|
signal,
|
|
130
123
|
concurrency
|
|
@@ -10,6 +10,7 @@ import { styleText, promisify } from 'node:util';
|
|
|
10
10
|
import columns from 'cli-columns';
|
|
11
11
|
import shellEscape from 'shell-escape';
|
|
12
12
|
import assertNpmAuthorization from '../utils/assertnpmauthorization.js';
|
|
13
|
+
import getNpmIdToken from '../utils/getnpmidtoken.js';
|
|
13
14
|
import { exec } from 'node:child_process';
|
|
14
15
|
|
|
15
16
|
const execPromise = promisify( exec );
|
|
@@ -19,8 +20,14 @@ const execPromise = promisify( exec );
|
|
|
19
20
|
* It supports built-in tags (e.g. `latest`, `staging`, `next`) and custom tags (e.g. `latest-v{X}`).
|
|
20
21
|
* Each operation will be retried up to 3 times in case of failure.
|
|
21
22
|
*
|
|
23
|
+
* When using npm Trusted Publishing (`useOidc`), the `npm whoami` check is replaced by verifying that the `NPM_ID_TOKEN`
|
|
24
|
+
* environment variable is set, as `npm whoami` does not reflect OIDC authentication. It requires npm 11.21.0 or newer
|
|
25
|
+
* and a trusted publisher that allows the `npm dist-tag` command.
|
|
26
|
+
*
|
|
22
27
|
* @param {object} options
|
|
23
|
-
* @param {string} options.npmOwner User that is authorized to release packages.
|
|
28
|
+
* @param {string} [options.npmOwner] User that is authorized to release packages. Required unless `useOidc` is enabled.
|
|
29
|
+
* @param {boolean} [options.useOidc=false] Whether to authorize using npm Trusted Publishing (OIDC). When enabled,
|
|
30
|
+
* the `npm whoami` authorization check is skipped and the `NPM_ID_TOKEN` environment variable must be set instead.
|
|
24
31
|
* @param {string} options.version Specifies the version of packages to reassign the tags for.
|
|
25
32
|
* @param {Array.<string>} options.packages Array of packages' names to reassign tags for.
|
|
26
33
|
* @param {string} [options.npmTag='latest'] Npm dist-tag to assign.
|
|
@@ -29,6 +36,7 @@ const execPromise = promisify( exec );
|
|
|
29
36
|
export default async function reassignNpmTags( options ) {
|
|
30
37
|
const {
|
|
31
38
|
npmOwner,
|
|
39
|
+
useOidc = false,
|
|
32
40
|
version,
|
|
33
41
|
packages,
|
|
34
42
|
npmTag = 'latest'
|
|
@@ -38,14 +46,21 @@ export default async function reassignNpmTags( options ) {
|
|
|
38
46
|
const packagesSkipped = [];
|
|
39
47
|
const packagesUpdated = [];
|
|
40
48
|
|
|
41
|
-
await assertNpmAuthorization( npmOwner );
|
|
49
|
+
await assertNpmAuthorization( npmOwner, { useOidc } );
|
|
42
50
|
|
|
43
51
|
const counter = tools.createSpinner( 'Reassigning npm tags...', { total: packages.length } );
|
|
44
52
|
counter.start();
|
|
45
53
|
|
|
46
54
|
const updateTagPromises = packages.map( async packageName => {
|
|
47
55
|
const command = `npm dist-tag add ${ shellEscape( [ packageName ] ) }@${ shellEscape( [ version ] ) } ${ npmTag }`;
|
|
48
|
-
const updateLatestTagRetryable = retry( () =>
|
|
56
|
+
const updateLatestTagRetryable = retry( async () => {
|
|
57
|
+
if ( !useOidc ) {
|
|
58
|
+
return execPromise( command );
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The calls run in parallel, so the fresh token is passed only to this npm process.
|
|
62
|
+
return execPromise( command, { env: { ...process.env, NPM_ID_TOKEN: await getNpmIdToken() } } );
|
|
63
|
+
} );
|
|
49
64
|
await updateLatestTagRetryable()
|
|
50
65
|
.then( response => {
|
|
51
66
|
if ( response.stdout ) {
|
|
@@ -4,14 +4,29 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { tools } from '@ckeditor/ckeditor5-dev-utils';
|
|
7
|
+
import getNpmIdToken from './getnpmidtoken.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Checks whether a user is logged to npm as the provided account name.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
+
* When using npm Trusted Publishing (`useOidc`), the check verifies that an OIDC token can be obtained instead
|
|
13
|
+
* (see `getNpmIdToken()`). npm exchanges the OIDC token only during supported operations, such as `npm publish` or `npm dist-tag`,
|
|
14
|
+
* and `npm whoami` does not reflect Trusted Publishing authentication.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} [npmOwner] Expected npm account name that should be logged into npm. Required unless `useOidc` is enabled.
|
|
17
|
+
* @param {object} [options={}]
|
|
18
|
+
* @param {boolean} [options.useOidc=false] Whether to verify the npm Trusted Publishing (OIDC) setup instead of the npm account.
|
|
12
19
|
* @returns {Promise}
|
|
13
20
|
*/
|
|
14
|
-
export default async function assertNpmAuthorization( npmOwner ) {
|
|
21
|
+
export default async function assertNpmAuthorization( npmOwner, { useOidc = false } = {} ) {
|
|
22
|
+
if ( useOidc ) {
|
|
23
|
+
// A token is requested again before each npm command. Requesting one here stops the release before any package
|
|
24
|
+
// is processed when no token can be obtained (neither from the CircleCI CLI nor from `NPM_ID_TOKEN`).
|
|
25
|
+
await getNpmIdToken();
|
|
26
|
+
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
15
30
|
return tools.shExec( 'npm whoami', { verbosity: 'error', async: true } )
|
|
16
31
|
.then( npmCurrentUser => {
|
|
17
32
|
if ( npmOwner !== npmCurrentUser.trim() ) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The audience that npm expects in an OIDC token used in the npm Trusted Publishing token exchange.
|
|
10
|
+
*/
|
|
11
|
+
export const NPM_OIDC_AUDIENCE = 'npm:registry.npmjs.org';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns an OIDC token that the npm CLI exchanges for a short-lived npm token (npm Trusted Publishing).
|
|
15
|
+
*
|
|
16
|
+
* npm accepts a CircleCI OIDC token in the exchange only for a few minutes after it was issued, even though the token
|
|
17
|
+
* itself is valid longer. A release that publishes many packages takes longer than that. Hence, on CircleCI, a fresh
|
|
18
|
+
* token is requested each time, right before an npm command that needs it.
|
|
19
|
+
*
|
|
20
|
+
* When the CircleCI CLI is not available, or the command does not run on CircleCI, the `NPM_ID_TOKEN` environment
|
|
21
|
+
* variable is used, if it is set.
|
|
22
|
+
*
|
|
23
|
+
* @returns {Promise<string>}
|
|
24
|
+
*/
|
|
25
|
+
export default async function getNpmIdToken() {
|
|
26
|
+
if ( process.env.CIRCLECI ) {
|
|
27
|
+
const token = await requestCircleciToken();
|
|
28
|
+
|
|
29
|
+
if ( token ) {
|
|
30
|
+
return token;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if ( process.env.NPM_ID_TOKEN ) {
|
|
35
|
+
return process.env.NPM_ID_TOKEN;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
throw new Error(
|
|
39
|
+
'Cannot get an OIDC token for npm Trusted Publishing (OIDC). ' +
|
|
40
|
+
'Run the command on CircleCI or set the "NPM_ID_TOKEN" environment variable.'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Requests a fresh OIDC token using the CircleCI CLI. Resolves `null` when the CLI is not installed.
|
|
46
|
+
*
|
|
47
|
+
* @returns {Promise<string|null>}
|
|
48
|
+
*/
|
|
49
|
+
async function requestCircleciToken() {
|
|
50
|
+
const args = [ 'run', 'oidc', 'get', '--claims', JSON.stringify( { aud: NPM_OIDC_AUDIENCE } ) ];
|
|
51
|
+
let stdout;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
stdout = await new Promise( ( resolve, reject ) => {
|
|
55
|
+
execFile( 'circleci', args, ( error, output ) => error ? reject( error ) : resolve( output ) );
|
|
56
|
+
} );
|
|
57
|
+
} catch ( error ) {
|
|
58
|
+
if ( error.code === 'ENOENT' ) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const token = String( stdout ).trim();
|
|
66
|
+
|
|
67
|
+
if ( !token ) {
|
|
68
|
+
throw new Error( 'The `circleci run oidc get` command returned an empty OIDC token.' );
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return token;
|
|
72
|
+
}
|
|
@@ -9,17 +9,26 @@
|
|
|
9
9
|
* @param {string} packagePath
|
|
10
10
|
* @param {object} taskOptions
|
|
11
11
|
* @param {string} taskOptions.npmTag
|
|
12
|
+
* @param {boolean} [taskOptions.useOidc=false] Whether to request a fresh OIDC token for npm Trusted Publishing before publishing.
|
|
12
13
|
* @returns {Promise}
|
|
13
14
|
*/
|
|
14
15
|
export default async function publishPackageOnNpmCallback( packagePath, taskOptions ) {
|
|
15
|
-
|
|
16
|
+
// The callback runs in a worker thread, so dependencies are imported dynamically and resolve from the project root.
|
|
17
|
+
const { exec } = await import( 'node:child_process' );
|
|
16
18
|
const { rm } = await import( 'node:fs/promises' );
|
|
19
|
+
const { getNpmIdToken } = await import( '@ckeditor/ckeditor5-dev-release-tools' );
|
|
17
20
|
|
|
18
21
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
const env = taskOptions.useOidc ? { ...process.env, NPM_ID_TOKEN: await getNpmIdToken() } : process.env;
|
|
23
|
+
|
|
24
|
+
if ( !/^[a-z0-9][a-z0-9._-]*$/i.test( taskOptions.npmTag ) ) {
|
|
25
|
+
throw new Error( `Invalid npm tag: "${ taskOptions.npmTag }".` );
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const command = `npm publish --access=public --tag ${ taskOptions.npmTag }`;
|
|
29
|
+
|
|
30
|
+
await new Promise( ( resolve, reject ) => {
|
|
31
|
+
exec( command, { cwd: packagePath, env }, error => error ? reject( error ) : resolve() );
|
|
23
32
|
} );
|
|
24
33
|
|
|
25
34
|
await rm( packagePath, { recursive: true, force: true } );
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-dev-release-tools",
|
|
3
|
-
"version": "61.
|
|
3
|
+
"version": "61.3.0",
|
|
4
4
|
"description": "Tools used for releasing CKEditor 5 and related packages.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "CKSource (http://cksource.com/)",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"lib"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@ckeditor/ckeditor5-dev-utils": "^61.
|
|
25
|
+
"@ckeditor/ckeditor5-dev-utils": "^61.3.0",
|
|
26
26
|
"@octokit/rest": "^22.0.1",
|
|
27
27
|
"cli-columns": "^4.0.0",
|
|
28
28
|
"glob": "^13.0.6",
|