@ckeditor/ckeditor5-dev-ci 55.6.2 → 56.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.
- package/README.md +26 -1
- package/bin/notify-github-actions-status.js +159 -0
- package/lib/format-message.js +20 -10
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ CKEditor 5 CI utilities
|
|
|
6
6
|
|
|
7
7
|
Utils for [CKEditor 5](https://ckeditor.com) CI builds.
|
|
8
8
|
|
|
9
|
-
Contains tools for sending Slack notifications
|
|
9
|
+
Contains tools for sending Slack notifications from Circle CI and GitHub Actions workflows.
|
|
10
10
|
|
|
11
11
|
## Available scripts
|
|
12
12
|
|
|
@@ -111,6 +111,31 @@ These commands accept a mix of environment variables and command line arguments.
|
|
|
111
111
|
- `--trigger-commit-hash` — Commit SHA to construct the commit URL. Useful when a pipeline was triggered via a different repository.
|
|
112
112
|
- `--hide-author` — `"true"`/`"false"` to hide the author in Slack.
|
|
113
113
|
|
|
114
|
+
- ⚙️ **`ckeditor5-dev-ci-notify-github-actions-status`**
|
|
115
|
+
|
|
116
|
+
Sends a Slack notification summarizing the current GitHub Actions workflow run.
|
|
117
|
+
Designed to be called from a step (or dedicated job) gated by `if: failure()`; the script does not check the workflow status before sending the notification.
|
|
118
|
+
Fetches the commit author and workflow run start time via the GitHub API (works with private repositories).
|
|
119
|
+
|
|
120
|
+
**Environment variables:**
|
|
121
|
+
- `CKE5_GITHUB_TOKEN` — GitHub token with the `repo` scope, used to fetch commit author and workflow run metadata.
|
|
122
|
+
- `CKE5_SLACK_WEBHOOK_URL` — Incoming Webhook URL for the Slack channel receiving notifications.
|
|
123
|
+
|
|
124
|
+
**GitHub-provided variables:**
|
|
125
|
+
- `GITHUB_REPOSITORY` — `<owner>/<repo>` of the current workflow.
|
|
126
|
+
- `GITHUB_REF_NAME` — Branch or tag name that triggered the workflow.
|
|
127
|
+
- `GITHUB_SHA` — Commit SHA of the current run.
|
|
128
|
+
- `GITHUB_RUN_ID` — ID of the current workflow run.
|
|
129
|
+
- `GITHUB_RUN_ATTEMPT` — *(Optional)* Run attempt number; included in the Slack message when greater than `1`.
|
|
130
|
+
- `GITHUB_WORKFLOW` — *(Optional)* Display name of the current workflow.
|
|
131
|
+
- `GITHUB_SERVER_URL` — *(Optional)* Server URL; defaults to `https://github.com`.
|
|
132
|
+
- `GITHUB_API_URL` — *(Optional)* API base URL; defaults to `https://api.github.com`.
|
|
133
|
+
|
|
134
|
+
**Parameters:**
|
|
135
|
+
- `--trigger-repository-slug` — `<org>/<repo>` to construct the commit URL when provided with `--trigger-commit-hash`. Useful when a workflow was triggered via a different repository.
|
|
136
|
+
- `--trigger-commit-hash` — Commit SHA to construct the commit URL. Useful when a workflow was triggered via a different repository.
|
|
137
|
+
- `--hide-author` — `"true"`/`"false"` to hide the author in Slack.
|
|
138
|
+
|
|
114
139
|
- ⚙️ **`ckeditor5-dev-ci-trigger-circle-build`**
|
|
115
140
|
|
|
116
141
|
Triggers a **new CircleCI pipeline** for a specified repository.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
5
|
+
* For licensing, see LICENSE.md.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { parseArgs } from 'node:util';
|
|
9
|
+
import slackNotify from 'slack-notify';
|
|
10
|
+
import formatMessage from '../lib/format-message.js';
|
|
11
|
+
|
|
12
|
+
// This script assumes that it is being executed in a GitHub Actions runner.
|
|
13
|
+
// The step using it should be conditional on the workflow having failed
|
|
14
|
+
// (for example via `if: failure()` or a dedicated job depending on the failed one),
|
|
15
|
+
// since the script itself does not check whether the workflow failed before sending
|
|
16
|
+
// the notification.
|
|
17
|
+
//
|
|
18
|
+
// Described environment variables starting with "CKE5" must be added by the integrator.
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
/**
|
|
22
|
+
* Required. Token to a GitHub account with the `repo` scope. It is required for obtaining
|
|
23
|
+
* the author of the commit that triggered the failed workflow run. The repository can be
|
|
24
|
+
* private, so the public, unauthenticated API cannot be used.
|
|
25
|
+
*/
|
|
26
|
+
CKE5_GITHUB_TOKEN,
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Required. Webhook URL of the Slack channel where the notification should be sent.
|
|
30
|
+
*/
|
|
31
|
+
CKE5_SLACK_WEBHOOK_URL,
|
|
32
|
+
|
|
33
|
+
// Variables that are available by default in the GitHub Actions environment.
|
|
34
|
+
// See: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables.
|
|
35
|
+
GITHUB_REPOSITORY,
|
|
36
|
+
GITHUB_REF_NAME,
|
|
37
|
+
GITHUB_SHA,
|
|
38
|
+
GITHUB_RUN_ID,
|
|
39
|
+
GITHUB_RUN_ATTEMPT,
|
|
40
|
+
GITHUB_WORKFLOW,
|
|
41
|
+
GITHUB_SERVER_URL,
|
|
42
|
+
GITHUB_API_URL
|
|
43
|
+
} = process.env;
|
|
44
|
+
|
|
45
|
+
const { values: cliArguments } = parseArgs( {
|
|
46
|
+
options: {
|
|
47
|
+
/**
|
|
48
|
+
* Optional. If both are defined, the script will use the URL as the commit URL.
|
|
49
|
+
* Otherwise, the URL will be constructed using the current repository data.
|
|
50
|
+
*/
|
|
51
|
+
'trigger-repository-slug': {
|
|
52
|
+
type: 'string',
|
|
53
|
+
default: process.env.CKE5_TRIGGER_REPOSITORY_SLUG
|
|
54
|
+
},
|
|
55
|
+
'trigger-commit-hash': {
|
|
56
|
+
type: 'string',
|
|
57
|
+
default: process.env.CKE5_TRIGGER_COMMIT_HASH
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Optional. If set to "true" or "1", commit author will be hidden.
|
|
62
|
+
* See: https://github.com/ckeditor/ckeditor5/issues/9252.
|
|
63
|
+
*/
|
|
64
|
+
'hide-author': {
|
|
65
|
+
type: 'string',
|
|
66
|
+
default: process.env.CKE5_SLACK_NOTIFY_HIDE_AUTHOR
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
notifyGitHubActionsStatus().catch( err => {
|
|
72
|
+
console.error( err );
|
|
73
|
+
process.exit( 1 );
|
|
74
|
+
} );
|
|
75
|
+
|
|
76
|
+
async function notifyGitHubActionsStatus() {
|
|
77
|
+
assertRequiredEnvironmentVariables();
|
|
78
|
+
|
|
79
|
+
const serverUrl = ( GITHUB_SERVER_URL || 'https://github.com' ).replace( /\/$/, '' );
|
|
80
|
+
const apiUrl = ( GITHUB_API_URL || 'https://api.github.com' ).replace( /\/$/, '' );
|
|
81
|
+
const [ repositoryOwner, repositoryName ] = GITHUB_REPOSITORY.split( '/' );
|
|
82
|
+
const runAttempt = GITHUB_RUN_ATTEMPT ? Number( GITHUB_RUN_ATTEMPT ) : 1;
|
|
83
|
+
|
|
84
|
+
const runData = await getWorkflowRunData( { apiUrl, repositoryOwner, repositoryName } );
|
|
85
|
+
const buildUrl = [ serverUrl, GITHUB_REPOSITORY, 'actions', 'runs', GITHUB_RUN_ID, 'attempts', runAttempt ].join( '/' );
|
|
86
|
+
const startedAtIso = runData.run_started_at || runData.created_at;
|
|
87
|
+
|
|
88
|
+
const message = await formatMessage( {
|
|
89
|
+
slackMessageUsername: 'GitHub Actions',
|
|
90
|
+
iconUrl: 'https://avatars.githubusercontent.com/in/15368?s=80&v=4',
|
|
91
|
+
repositoryOwner,
|
|
92
|
+
repositoryName,
|
|
93
|
+
branch: GITHUB_REF_NAME,
|
|
94
|
+
buildTitle: GITHUB_WORKFLOW || 'Workflow run',
|
|
95
|
+
buildUrl,
|
|
96
|
+
buildId: `#${ GITHUB_RUN_ID }${ runAttempt > 1 ? ` (attempt ${ runAttempt })` : '' }`,
|
|
97
|
+
githubToken: CKE5_GITHUB_TOKEN,
|
|
98
|
+
triggeringCommitUrl: getTriggeringCommitUrl( serverUrl ),
|
|
99
|
+
apiUrl,
|
|
100
|
+
startTime: startedAtIso ? Math.ceil( new Date( startedAtIso ).getTime() / 1000 ) : null,
|
|
101
|
+
endTime: Math.ceil( Date.now() / 1000 ),
|
|
102
|
+
shouldHideAuthor: isTrueLike( cliArguments[ 'hide-author' ] )
|
|
103
|
+
} );
|
|
104
|
+
|
|
105
|
+
return slackNotify( CKE5_SLACK_WEBHOOK_URL )
|
|
106
|
+
.send( message )
|
|
107
|
+
.catch( err => console.log( 'API error occurred:', err ) );
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function assertRequiredEnvironmentVariables() {
|
|
111
|
+
const required = {
|
|
112
|
+
CKE5_GITHUB_TOKEN,
|
|
113
|
+
CKE5_SLACK_WEBHOOK_URL,
|
|
114
|
+
GITHUB_REPOSITORY,
|
|
115
|
+
GITHUB_REF_NAME,
|
|
116
|
+
GITHUB_SHA,
|
|
117
|
+
GITHUB_RUN_ID
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
for ( const [ name, value ] of Object.entries( required ) ) {
|
|
121
|
+
if ( !value ) {
|
|
122
|
+
throw new Error( `Missing environment variable: ${ name }` );
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function getWorkflowRunData( { apiUrl, repositoryOwner, repositoryName } ) {
|
|
128
|
+
const fetchUrl = [ apiUrl, 'repos', repositoryOwner, repositoryName, 'actions', 'runs', GITHUB_RUN_ID ].join( '/' );
|
|
129
|
+
const fetchOptions = {
|
|
130
|
+
method: 'GET',
|
|
131
|
+
headers: {
|
|
132
|
+
'Accept': 'application/vnd.github+json',
|
|
133
|
+
'Authorization': `Bearer ${ CKE5_GITHUB_TOKEN }`,
|
|
134
|
+
'X-GitHub-Api-Version': '2022-11-28'
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const response = await fetch( fetchUrl, fetchOptions );
|
|
139
|
+
|
|
140
|
+
if ( !response.ok ) {
|
|
141
|
+
throw new Error( `Failed to fetch workflow run ${ GITHUB_RUN_ID }: HTTP ${ response.status }.` );
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return response.json();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getTriggeringCommitUrl( serverUrl ) {
|
|
148
|
+
const cliRepoSlug = cliArguments[ 'trigger-repository-slug' ];
|
|
149
|
+
const cliCommitHash = cliArguments[ 'trigger-commit-hash' ];
|
|
150
|
+
|
|
151
|
+
const repoSlug = cliRepoSlug && cliCommitHash ? cliRepoSlug.trim() : GITHUB_REPOSITORY;
|
|
152
|
+
const hash = cliRepoSlug && cliCommitHash ? cliCommitHash.trim() : GITHUB_SHA;
|
|
153
|
+
|
|
154
|
+
return [ serverUrl, repoSlug, 'commit', hash ].join( '/' );
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isTrueLike( value ) {
|
|
158
|
+
return value === true || value === 1 || value === '1' || value === 'true';
|
|
159
|
+
}
|
package/lib/format-message.js
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { bots, members } from './data/index.js';
|
|
7
7
|
|
|
8
|
-
const REPOSITORY_REGEXP = /github\.com\/([^/]+)\/([^/]+)/;
|
|
9
|
-
|
|
10
8
|
/**
|
|
11
9
|
* @param {object} options
|
|
12
10
|
* @param {string} options.slackMessageUsername
|
|
@@ -19,13 +17,15 @@ const REPOSITORY_REGEXP = /github\.com\/([^/]+)\/([^/]+)/;
|
|
|
19
17
|
* @param {string} options.buildId
|
|
20
18
|
* @param {string} options.githubToken
|
|
21
19
|
* @param {string} options.triggeringCommitUrl
|
|
20
|
+
* @param {string} [options.apiUrl]
|
|
22
21
|
* @param {number} options.startTime
|
|
23
22
|
* @param {number} options.endTime
|
|
24
23
|
* @param {boolean} options.shouldHideAuthor
|
|
25
24
|
*/
|
|
26
25
|
export default async function formatMessage( options ) {
|
|
27
|
-
const commitDetails = await getCommitDetails( options.triggeringCommitUrl, options.githubToken );
|
|
28
|
-
const
|
|
26
|
+
const commitDetails = await getCommitDetails( options.triggeringCommitUrl, options.githubToken, options.apiUrl );
|
|
27
|
+
const serverUrl = new URL( options.triggeringCommitUrl ).origin;
|
|
28
|
+
const repoUrl = `${ serverUrl }/${ options.repositoryOwner }/${ options.repositoryName }`;
|
|
29
29
|
|
|
30
30
|
return {
|
|
31
31
|
username: options.slackMessageUsername,
|
|
@@ -162,14 +162,16 @@ function getFormattedMessage( commitMessage, triggeringCommitUrl ) {
|
|
|
162
162
|
return '_Unavailable._';
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
const
|
|
165
|
+
const url = new URL( triggeringCommitUrl );
|
|
166
|
+
const serverUrl = url.origin;
|
|
167
|
+
const [ , repoOwner, repoName ] = url.pathname.split( '/' );
|
|
166
168
|
|
|
167
169
|
return commitMessage
|
|
168
170
|
.replace( / #(\d+)/g, ( _, issueId ) => {
|
|
169
|
-
return `
|
|
171
|
+
return ` <${ serverUrl }/${ repoOwner }/${ repoName }/issues/${ issueId }|#${ issueId }>`;
|
|
170
172
|
} )
|
|
171
173
|
.replace( /([\w-]+\/[\w-]+)#(\d+)/g, ( _, repoSlug, issueId ) => {
|
|
172
|
-
return
|
|
174
|
+
return `<${ serverUrl }/${ repoSlug }/issues/${ issueId }|${ repoSlug }#${ issueId }>`;
|
|
173
175
|
} );
|
|
174
176
|
}
|
|
175
177
|
|
|
@@ -178,10 +180,11 @@ function getFormattedMessage( commitMessage, triggeringCommitUrl ) {
|
|
|
178
180
|
*
|
|
179
181
|
* @param {string} triggeringCommitUrl The URL to the commit on GitHub.
|
|
180
182
|
* @param {string} githubToken Github token used for authorization a request,
|
|
183
|
+
* @param {string} [apiUrl] Optional base URL of the GitHub API.
|
|
181
184
|
* @returns {Promise.<object>}
|
|
182
185
|
*/
|
|
183
|
-
function getCommitDetails( triggeringCommitUrl, githubToken ) {
|
|
184
|
-
const apiGithubUrlCommit = getGithubApiUrl( triggeringCommitUrl );
|
|
186
|
+
function getCommitDetails( triggeringCommitUrl, githubToken, apiUrl ) {
|
|
187
|
+
const apiGithubUrlCommit = getGithubApiUrl( triggeringCommitUrl, apiUrl );
|
|
185
188
|
const options = {
|
|
186
189
|
method: 'GET',
|
|
187
190
|
credentials: 'include',
|
|
@@ -204,8 +207,15 @@ function getCommitDetails( triggeringCommitUrl, githubToken ) {
|
|
|
204
207
|
* Returns a URL to GitHub API which returns details of the commit that caused the CI to fail its job.
|
|
205
208
|
*
|
|
206
209
|
* @param {string} triggeringCommitUrl The URL to the commit on GitHub.
|
|
210
|
+
* @param {string} [apiUrl] Optional base URL of the GitHub API.
|
|
207
211
|
* @returns {string}
|
|
208
212
|
*/
|
|
209
|
-
function getGithubApiUrl( triggeringCommitUrl ) {
|
|
213
|
+
function getGithubApiUrl( triggeringCommitUrl, apiUrl ) {
|
|
214
|
+
if ( apiUrl ) {
|
|
215
|
+
const [ , owner, repo, , sha ] = new URL( triggeringCommitUrl ).pathname.split( '/' );
|
|
216
|
+
|
|
217
|
+
return `${ apiUrl.replace( /\/$/, '' ) }/repos/${ owner }/${ repo }/commits/${ sha }`;
|
|
218
|
+
}
|
|
219
|
+
|
|
210
220
|
return triggeringCommitUrl.replace( 'github.com/', 'api.github.com/repos/' ).replace( '/commit/', '/commits/' );
|
|
211
221
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-dev-ci",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "56.0.0",
|
|
4
4
|
"description": "Utils used on various Continuous Integration services.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "CKSource (http://cksource.com/)",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"ckeditor5-dev-ci-is-job-triggered-by-member": "bin/is-job-triggered-by-member.js",
|
|
30
30
|
"ckeditor5-dev-ci-is-workflow-restarted": "bin/is-workflow-restarted.js",
|
|
31
31
|
"ckeditor5-dev-ci-notify-circle-status": "bin/notify-circle-status.js",
|
|
32
|
+
"ckeditor5-dev-ci-notify-github-actions-status": "bin/notify-github-actions-status.js",
|
|
32
33
|
"ckeditor5-dev-ci-trigger-circle-build": "bin/trigger-circle-build.js",
|
|
33
34
|
"ckeditor5-dev-ci-trigger-snyk-scan": "bin/trigger-snyk-scan.js"
|
|
34
35
|
},
|