@ckeditor/ckeditor5-dev-release-tools 32.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/LICENSE.md +16 -0
- package/README.md +89 -0
- package/lib/index.js +28 -0
- package/lib/tasks/bumpversions.js +354 -0
- package/lib/tasks/generatechangelogformonorepository.js +723 -0
- package/lib/tasks/generatechangelogforsinglepackage.js +202 -0
- package/lib/tasks/releasesubrepositories.js +929 -0
- package/lib/tasks/updateckeditor5dependencies.js +392 -0
- package/lib/templates/commit.hbs +29 -0
- package/lib/templates/footer.hbs +10 -0
- package/lib/templates/header.hbs +23 -0
- package/lib/templates/release-package.json +12 -0
- package/lib/templates/template.hbs +37 -0
- package/lib/utils/changelog.js +67 -0
- package/lib/utils/cli.js +324 -0
- package/lib/utils/creategithubrelease.js +35 -0
- package/lib/utils/displaycommits.js +105 -0
- package/lib/utils/displayskippedpackages.js +32 -0
- package/lib/utils/executeonpackages.js +26 -0
- package/lib/utils/generatechangelog.js +121 -0
- package/lib/utils/getchangedfilesforcommit.js +33 -0
- package/lib/utils/getcommits.js +104 -0
- package/lib/utils/getnewversiontype.js +53 -0
- package/lib/utils/getpackagejson.js +24 -0
- package/lib/utils/getpackagespaths.js +90 -0
- package/lib/utils/getpackagestorelease.js +152 -0
- package/lib/utils/getwriteroptions.js +33 -0
- package/lib/utils/parseroptions.js +26 -0
- package/lib/utils/transformcommitfactory.js +492 -0
- package/lib/utils/transformcommitutils.js +163 -0
- package/lib/utils/updatedependenciesversions.js +32 -0
- package/lib/utils/validatepackagetorelease.js +54 -0
- package/lib/utils/versions.js +59 -0
- package/package.json +52 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { cloneDeepWith } = require( 'lodash' );
|
|
9
|
+
const utils = require( './transformcommitutils' );
|
|
10
|
+
const getChangedFilesForCommit = require( './getchangedfilesforcommit' );
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Factory function.
|
|
14
|
+
*
|
|
15
|
+
* It returns a function that parses a single commit:
|
|
16
|
+
* - makes links to issues and organizations on GitHub,
|
|
17
|
+
* - if the commit contains multi changelog entries, the function will return an array of the commits,
|
|
18
|
+
* - if the commit touches multiple scopes, the commit is cloned as many times as the number of packages in its scope,
|
|
19
|
+
* - normalizes notes (e.g. "MAJOR BREAKING CHANGE" will be replaced with "MAJOR BREAKING CHANGES"),
|
|
20
|
+
* - the commit is always being returned. Even, if it should not be added to the changelog.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} [options={}]
|
|
23
|
+
* @param {Boolean} [options.treatMajorAsMinorBreakingChange=false] If set on true, all "MAJOR BREAKING CHANGES" notes will be replaced
|
|
24
|
+
* with "MINOR BREAKING CHANGES". This behaviour is being disabled automatically if `options.useExplicitBreakingChangeGroups` is
|
|
25
|
+
* set on `false` because all commits will be treated as "BREAKING CHANGES".
|
|
26
|
+
* @param {Boolean} [options.useExplicitBreakingChangeGroups] If set on `true`, notes from parsed commits will be grouped as
|
|
27
|
+
* "MINOR BREAKING CHANGES" and "MAJOR BREAKING CHANGES'. If set on `false` (by default), all breaking changes notes will be treated
|
|
28
|
+
* as "BREAKING CHANGES".
|
|
29
|
+
* @returns {Function}
|
|
30
|
+
*/
|
|
31
|
+
module.exports = function transformCommitFactory( options = {} ) {
|
|
32
|
+
return rawCommit => {
|
|
33
|
+
const commit = transformCommit( rawCommit );
|
|
34
|
+
|
|
35
|
+
if ( !commit ) {
|
|
36
|
+
return commit;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ( Array.isArray( commit ) ) {
|
|
40
|
+
return commit.flatMap( splitMultiScopeCommit );
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return splitMultiScopeCommit( commit );
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* If returned an instance of the Array, it means that single commit contains more than one entry for the changelog.
|
|
48
|
+
*
|
|
49
|
+
* E.g. for the commit below:
|
|
50
|
+
*
|
|
51
|
+
* Feature: Introduced the `Editor` component. See #123.
|
|
52
|
+
*
|
|
53
|
+
* Additional description.
|
|
54
|
+
*
|
|
55
|
+
* Fix: The commit also fixes...
|
|
56
|
+
*
|
|
57
|
+
* the function will return an array with two commits. The first one is the real commit, the second one is a fake commit
|
|
58
|
+
* but its description will be inserted to the changelog.
|
|
59
|
+
*
|
|
60
|
+
* In most of cases the function will return the commit (even if its structure is invalid). However, "Merge branch 'stable'" commits
|
|
61
|
+
* will be always ignored and `undefined` will be returned.
|
|
62
|
+
*
|
|
63
|
+
* @param {Commit} rawCommit
|
|
64
|
+
* @returns {Commit|Array.<Commit>|undefined}
|
|
65
|
+
*/
|
|
66
|
+
function transformCommit( rawCommit ) {
|
|
67
|
+
// Let's clone the commit. We don't want to modify the reference.
|
|
68
|
+
const commit = Object.assign( {}, rawCommit, {
|
|
69
|
+
// Copy the original `type` of the commit.
|
|
70
|
+
rawType: rawCommit.type,
|
|
71
|
+
notes: rawCommit.notes.map( note => Object.assign( {}, note ) )
|
|
72
|
+
} );
|
|
73
|
+
|
|
74
|
+
const parsedType = extractScopeFromPrefix( commit.rawType );
|
|
75
|
+
|
|
76
|
+
commit.files = [];
|
|
77
|
+
commit.rawType = parsedType.rawType;
|
|
78
|
+
commit.scope = parsedType.scope;
|
|
79
|
+
|
|
80
|
+
// Whether the commit will be printed in the changelog.
|
|
81
|
+
commit.isPublicCommit = utils.availableCommitTypes.get( commit.rawType ) || false;
|
|
82
|
+
|
|
83
|
+
// Our merge commit always contains two lines:
|
|
84
|
+
// Merge ...
|
|
85
|
+
// Prefix: Subject of the changes.
|
|
86
|
+
// Unfortunately, merge commit made by Git does not contain the second line.
|
|
87
|
+
// Because of that hash of the commit is parsed as a body and the changelog will crash.
|
|
88
|
+
// See: https://github.com/ckeditor/ckeditor5-dev/issues/276.
|
|
89
|
+
if ( commit.merge && !commit.hash ) {
|
|
90
|
+
commit.hash = commit.body;
|
|
91
|
+
commit.header = commit.merge;
|
|
92
|
+
commit.body = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Ignore the `stable` merge branch.
|
|
96
|
+
if ( isInternalMergeCommit( commit ) ) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
commit.files = getChangedFilesForCommit( commit.hash ) || [];
|
|
101
|
+
commit.repositoryUrl = utils.getRepositoryUrl();
|
|
102
|
+
|
|
103
|
+
if ( commit.isPublicCommit ) {
|
|
104
|
+
// Remove [skip ci] from the commit subject.
|
|
105
|
+
commit.subject = commit.subject.replace( /\[skip ci\]/, '' ).trim();
|
|
106
|
+
|
|
107
|
+
// If a dot is missing at the end of the subject...
|
|
108
|
+
if ( !commit.subject.endsWith( '.' ) ) {
|
|
109
|
+
// ...let's add it.
|
|
110
|
+
commit.subject += '.';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// The `type` below will be key for grouping commits.
|
|
114
|
+
commit.type = utils.getCommitType( commit.rawType );
|
|
115
|
+
commit.subject = mergeCloseReferences( commit.subject );
|
|
116
|
+
commit.subject = makeLinks( commit.subject );
|
|
117
|
+
|
|
118
|
+
// Remove additional notes from the commit's footer.
|
|
119
|
+
// Additional notes are added to the footer. In order to avoid duplication, they should be removed.
|
|
120
|
+
if ( commit.footer && commit.notes.length ) {
|
|
121
|
+
// Clone the notes in order to avoid cleaning those.
|
|
122
|
+
const commitsNotes = commit.notes.slice();
|
|
123
|
+
|
|
124
|
+
commit.footer = commit.footer.split( '\n' )
|
|
125
|
+
.filter( footerLine => {
|
|
126
|
+
// For each footer line checks whether the line starts with note prefix.
|
|
127
|
+
// If so, this footer line should be removed.
|
|
128
|
+
const noteToRemove = commitsNotes.find( note => {
|
|
129
|
+
return footerLine.startsWith( note.title ) && footerLine.endsWith( note.text );
|
|
130
|
+
} );
|
|
131
|
+
|
|
132
|
+
// In order to avoid checking the same note, remove it.
|
|
133
|
+
if ( noteToRemove ) {
|
|
134
|
+
commitsNotes.splice( commitsNotes.indexOf( noteToRemove ), 1 );
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return !noteToRemove;
|
|
138
|
+
} )
|
|
139
|
+
.join( '\n' )
|
|
140
|
+
.trim() || null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// If `body` of the commit is empty but the `footer` isn't, let's swap those.
|
|
144
|
+
if ( commit.footer && !commit.body ) {
|
|
145
|
+
commit.body = commit.footer;
|
|
146
|
+
commit.footer = null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if ( typeof commit.body === 'string' ) {
|
|
150
|
+
commit.body = commit.body.split( '\n' )
|
|
151
|
+
.map( line => {
|
|
152
|
+
if ( !line.length ) {
|
|
153
|
+
return '';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return ' ' + line;
|
|
157
|
+
} )
|
|
158
|
+
.join( '\n' );
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
normalizeNotes( commit );
|
|
162
|
+
|
|
163
|
+
// Clear the references array - we don't want to hoist the issues.
|
|
164
|
+
delete commit.references;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if ( !commit.body ) {
|
|
168
|
+
// It's used only for displaying the commit. Changelog generator will filter out the invalid entries.
|
|
169
|
+
return commit;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const commitEntries = commit.body.match( utils.MULTI_ENTRIES_COMMIT_REGEXP );
|
|
173
|
+
|
|
174
|
+
// The commit does not provide additional entries.
|
|
175
|
+
if ( !commitEntries || !commitEntries.length ) {
|
|
176
|
+
commit.body = makeLinks( commit.body );
|
|
177
|
+
|
|
178
|
+
return commit;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Single commit contains a few entries that should be inserted to the changelog.
|
|
182
|
+
// All of those entries are defined in the array.
|
|
183
|
+
// Additional commits/entries will be called as "fake commits".
|
|
184
|
+
const separatedCommits = [ commit ];
|
|
185
|
+
|
|
186
|
+
// Descriptions of additional entries.
|
|
187
|
+
const parts = commit.body.split( utils.MULTI_ENTRIES_COMMIT_REGEXP );
|
|
188
|
+
|
|
189
|
+
// If the descriptions array contains more entries than fake commit entries,
|
|
190
|
+
// it means that the first element in descriptions array describes the main (real) commit.
|
|
191
|
+
/* istanbul ignore else */
|
|
192
|
+
if ( parts.length > commitEntries.length ) {
|
|
193
|
+
commit.body = escapeNewLines( parts.shift() );
|
|
194
|
+
commit.body = makeLinks( commit.body );
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// For each fake commit, copy hash and repository of the parent.
|
|
198
|
+
for ( let i = 0; i < parts.length; ++i ) {
|
|
199
|
+
const newCommit = {
|
|
200
|
+
revert: null,
|
|
201
|
+
merge: null,
|
|
202
|
+
footer: null,
|
|
203
|
+
hash: commit.hash,
|
|
204
|
+
files: commit.files,
|
|
205
|
+
repositoryUrl: commit.repositoryUrl,
|
|
206
|
+
notes: [],
|
|
207
|
+
mentions: []
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const details = extractScopeFromPrefix( commitEntries[ i ].replace( /:$/, '' ) );
|
|
211
|
+
|
|
212
|
+
newCommit.rawType = details.rawType;
|
|
213
|
+
newCommit.scope = details.scope;
|
|
214
|
+
newCommit.isPublicCommit = utils.availableCommitTypes.get( newCommit.rawType );
|
|
215
|
+
|
|
216
|
+
if ( newCommit.isPublicCommit ) {
|
|
217
|
+
newCommit.type = utils.getCommitType( newCommit.rawType );
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const commitDescription = parts[ i ];
|
|
221
|
+
const subject = commitDescription.match( /^(.*)$/m )[ 0 ];
|
|
222
|
+
|
|
223
|
+
newCommit.header = commitEntries[ i ].trim() + ' ' + subject.trim();
|
|
224
|
+
|
|
225
|
+
newCommit.subject = mergeCloseReferences( subject.trim() );
|
|
226
|
+
newCommit.subject = makeLinks( newCommit.subject );
|
|
227
|
+
|
|
228
|
+
newCommit.body = escapeNewLines( commitDescription.replace( subject, '' ) );
|
|
229
|
+
newCommit.body = makeLinks( newCommit.body );
|
|
230
|
+
|
|
231
|
+
// If a dot is missing at the end of the subject...
|
|
232
|
+
if ( !newCommit.subject.endsWith( '.' ) ) {
|
|
233
|
+
// ...let's add it.
|
|
234
|
+
newCommit.subject += '.';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
separatedCommits.push( newCommit );
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return separatedCommits;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Merges multiple "Closes #x" references as "Closes #x, #y.".
|
|
245
|
+
*
|
|
246
|
+
* @param {String} subject
|
|
247
|
+
* @returns {String}
|
|
248
|
+
*/
|
|
249
|
+
function mergeCloseReferences( subject ) {
|
|
250
|
+
const refs = [];
|
|
251
|
+
|
|
252
|
+
let newSubject = subject;
|
|
253
|
+
let insertedPlaceholder = false;
|
|
254
|
+
|
|
255
|
+
const regexp = /Closes (\/?[\w-]+\/[\w-]+)?#([\d]+)\./ig;
|
|
256
|
+
let match;
|
|
257
|
+
|
|
258
|
+
while ( ( match = regexp.exec( subject ) ) ) {
|
|
259
|
+
const [ matchedText, maybeRepository, issueId ] = match;
|
|
260
|
+
|
|
261
|
+
if ( maybeRepository ) {
|
|
262
|
+
refs.push( maybeRepository + '#' + issueId );
|
|
263
|
+
} else {
|
|
264
|
+
refs.push( '#' + issueId );
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if ( insertedPlaceholder ) {
|
|
268
|
+
newSubject = newSubject.replace( matchedText, '' ).trim();
|
|
269
|
+
} else {
|
|
270
|
+
insertedPlaceholder = true;
|
|
271
|
+
newSubject = newSubject.replace( matchedText, '[[--COMMIT_REFERENCES--]]' ).trim();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
newSubject = newSubject.replace( /\[\[--COMMIT_REFERENCES--\]] ?/, 'Closes ' + refs.join( ', ' ) + '.' );
|
|
276
|
+
|
|
277
|
+
return newSubject;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function makeLinks( comment ) {
|
|
281
|
+
comment = utils.linkToGithubIssue( comment );
|
|
282
|
+
comment = utils.linkToGithubUser( comment );
|
|
283
|
+
|
|
284
|
+
return comment;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function normalizeNotes( commit ) {
|
|
288
|
+
for ( const note of commit.notes ) {
|
|
289
|
+
const details = extractScopeFromNote( note.text );
|
|
290
|
+
|
|
291
|
+
note.text = details.text;
|
|
292
|
+
note.scope = details.scope;
|
|
293
|
+
|
|
294
|
+
// "BREAKING CHANGE" => "BREAKING CHANGES"
|
|
295
|
+
if ( note.title === 'BREAKING CHANGE' ) {
|
|
296
|
+
note.title = 'BREAKING CHANGES';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// "BREAKING CHANGES" => "MAJOR BREAKING CHANGES"
|
|
300
|
+
if ( note.title === 'BREAKING CHANGES' ) {
|
|
301
|
+
note.title = 'MAJOR BREAKING CHANGES';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// "MAJOR BREAKING CHANGE" => "MAJOR BREAKING CHANGES"
|
|
305
|
+
if ( note.title === 'MAJOR BREAKING CHANGE' ) {
|
|
306
|
+
note.title = 'MAJOR BREAKING CHANGES';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// "MINOR BREAKING CHANGE" => "MINOR BREAKING CHANGES"
|
|
310
|
+
if ( note.title === 'MINOR BREAKING CHANGE' ) {
|
|
311
|
+
note.title = 'MINOR BREAKING CHANGES';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Replace "MAJOR" with "MINOR" if major breaking changes are "disabled".
|
|
315
|
+
if ( options.treatMajorAsMinorBreakingChange && note.title === 'MAJOR BREAKING CHANGES' ) {
|
|
316
|
+
note.title = 'MINOR BREAKING CHANGES';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// If explicit breaking changes groups option is disabled, remove MINOR/MAJOR prefix from the title.
|
|
320
|
+
if ( !options.useExplicitBreakingChangeGroups ) {
|
|
321
|
+
note.title = note.title.replace( /^(MINOR|MAJOR) /, '' );
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
note.text = makeLinks( note.text );
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Extracts the prefix and scope from the `Commit#subject`.
|
|
330
|
+
*
|
|
331
|
+
* E.g.:
|
|
332
|
+
* - input: `Fix (engine): Fixed...
|
|
333
|
+
* - output: { rawType: 'Fix', scope: [ 'engine' ] }
|
|
334
|
+
*
|
|
335
|
+
* For commits with no scope, `null` will be returned instead of the array (as `scope`).
|
|
336
|
+
*
|
|
337
|
+
* @param {String} type First line from the commit message.
|
|
338
|
+
* @returns {Object}
|
|
339
|
+
*/
|
|
340
|
+
function extractScopeFromPrefix( type ) {
|
|
341
|
+
if ( !type ) {
|
|
342
|
+
return {};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const parts = type.split( ' (' );
|
|
346
|
+
const data = {
|
|
347
|
+
rawType: parts[ 0 ],
|
|
348
|
+
scope: null
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
if ( parts[ 1 ] ) {
|
|
352
|
+
data.scope = parts[ 1 ].replace( /^\(|\)$/g, '' )
|
|
353
|
+
.split( ',' )
|
|
354
|
+
.map( p => p.trim() )
|
|
355
|
+
.filter( p => p )
|
|
356
|
+
.sort();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return data;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Extracts the prefix and scope from the `CommitNote#text`.
|
|
364
|
+
*
|
|
365
|
+
* E.g.:
|
|
366
|
+
* - input: `(engine): Removed...
|
|
367
|
+
* - output: { text: 'Removed...', scope: [ 'engine' ] }
|
|
368
|
+
*
|
|
369
|
+
* For notes with no scope, `null` will be returned instead of the array (as `scope`).
|
|
370
|
+
*
|
|
371
|
+
* @param {String} text A text that describes a note.
|
|
372
|
+
* @returns {Object}
|
|
373
|
+
*/
|
|
374
|
+
function extractScopeFromNote( text ) {
|
|
375
|
+
const scopeAsText = text.match( /\(([^)]+)\):/ );
|
|
376
|
+
|
|
377
|
+
if ( !scopeAsText ) {
|
|
378
|
+
return {
|
|
379
|
+
text,
|
|
380
|
+
scope: null
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const scope = scopeAsText[ 1 ]
|
|
385
|
+
.split( ',' )
|
|
386
|
+
.map( p => p.trim() )
|
|
387
|
+
.filter( p => p )
|
|
388
|
+
.sort();
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
text: text.replace( scopeAsText[ 0 ], '' ).trim(),
|
|
392
|
+
scope
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function escapeNewLines( message ) {
|
|
397
|
+
// Accept spaces before a sentence because they are ready to be rendered in the changelog template.
|
|
398
|
+
return message.replace( /^\n+|\s+$/g, '' );
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function isInternalMergeCommit( commit ) {
|
|
402
|
+
if ( !commit.merge ) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Internal merges between branches. When creating a release, synchronising the documentation, etc.
|
|
407
|
+
// Also merge those branches into the feature branch.
|
|
408
|
+
if ( commit.merge.match( /^Merge( branch)? '?(master|release|stable)'?( into '?(master|release|stable)'?)?/ ) ) {
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Merge `origin/master` into the feature branch.
|
|
413
|
+
if ( commit.merge.match( /^Merge remote-tracking branch 'origin\/master/ ) ) {
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* If the commit touches multiple scopes (packages), clone this commit as many times as the number of packages in the scope.
|
|
422
|
+
* Then, for each cloned commit, set the scope value to be a single package. Other commit properties remain unchanged.
|
|
423
|
+
*
|
|
424
|
+
* This correction is needed, because otherwise a changelog entry would be generated only for the first found scope.
|
|
425
|
+
*
|
|
426
|
+
* @param {Commit} commit
|
|
427
|
+
* @returns {Commit|Array.<Commit>}
|
|
428
|
+
*/
|
|
429
|
+
function splitMultiScopeCommit( commit ) {
|
|
430
|
+
if ( !commit.scope ) {
|
|
431
|
+
return commit;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if ( commit.scope.length === 1 ) {
|
|
435
|
+
return commit;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Clone the commit as many times as there are scopes.
|
|
439
|
+
return commit.scope.map( ( scope, index ) => cloneDeepWith( commit, ( value, key, parent ) => {
|
|
440
|
+
// The cloned commit should always have a single scope from a commit.
|
|
441
|
+
// Do not copy scopes from commit notes.
|
|
442
|
+
if ( key === 'scope' && !parent.title ) {
|
|
443
|
+
return [ scope ];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Do not copy breaking changes notes. It's enough to keep them in the first commit.
|
|
447
|
+
if ( index && key === 'notes' ) {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
} ) );
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* @typedef {Object} Commit
|
|
456
|
+
*
|
|
457
|
+
* @property {Boolean} isPublicCommit Whether the commit should be added in the changelog.
|
|
458
|
+
*
|
|
459
|
+
* @property {String} rawType Type of the commit without any modifications.
|
|
460
|
+
*
|
|
461
|
+
* @property {String} type Type of the commit (it can be modified).
|
|
462
|
+
*
|
|
463
|
+
* @property {String} header First line of the commit message.
|
|
464
|
+
*
|
|
465
|
+
* @property {String} subject Subject of the commit. It's the header without the type.
|
|
466
|
+
*
|
|
467
|
+
* @property {Array.<String>|null} scope Scope of the changes.
|
|
468
|
+
*
|
|
469
|
+
* @property {Array.<String>} files A list of files tha the commit modified.
|
|
470
|
+
*
|
|
471
|
+
* @property {String} hash The full commit SHA-1 id.
|
|
472
|
+
*
|
|
473
|
+
* @property {String} repositoryUrl The URL to the repository where the parsed commit has been done.
|
|
474
|
+
**
|
|
475
|
+
* @property {String|null} [body] Body of the commit message.
|
|
476
|
+
*
|
|
477
|
+
* @property {String|null} [footer] Footer of the commit message.
|
|
478
|
+
*
|
|
479
|
+
* @property {Array.<CommitNote>} [notes] Notes for the commit.
|
|
480
|
+
*
|
|
481
|
+
* @property {Boolean} [skipCommitsLink] Whether to skip generating a URL to the commit by the generator.
|
|
482
|
+
*/
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* @typedef {Object} CommitNote
|
|
486
|
+
*
|
|
487
|
+
* @property {String} title Type of the note.
|
|
488
|
+
*
|
|
489
|
+
* @property {String} text Text of the note.
|
|
490
|
+
*
|
|
491
|
+
* @property {Array.<String>} scope Scope of the note.
|
|
492
|
+
*/
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const getPackageJson = require( './getpackagejson' );
|
|
9
|
+
|
|
10
|
+
const transformCommitUtils = {
|
|
11
|
+
/**
|
|
12
|
+
* A regexp for extracting additional changelog entries from the single commit.
|
|
13
|
+
* Prefixes of the commit must be synchronized the `getCommitType()` util.
|
|
14
|
+
*/
|
|
15
|
+
MULTI_ENTRIES_COMMIT_REGEXP: /(?:Feature|Other|Fix|Docs|Internal|Tests|Revert|Release)(?: \([\w\-, ]+?\))?:/g,
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Map of available types of the commits.
|
|
19
|
+
* Types marked as `false` will be ignored during generating the changelog.
|
|
20
|
+
*/
|
|
21
|
+
availableCommitTypes: new Map( [
|
|
22
|
+
[ 'Fix', true ],
|
|
23
|
+
[ 'Feature', true ],
|
|
24
|
+
[ 'Other', true ],
|
|
25
|
+
|
|
26
|
+
[ 'Docs', false ],
|
|
27
|
+
[ 'Internal', false ],
|
|
28
|
+
[ 'Tests', false ],
|
|
29
|
+
[ 'Revert', false ],
|
|
30
|
+
[ 'Release', false ]
|
|
31
|
+
] ),
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Order of messages generated in changelog.
|
|
35
|
+
*/
|
|
36
|
+
typesOrder: {
|
|
37
|
+
'Features': 1,
|
|
38
|
+
'Bug fixes': 2,
|
|
39
|
+
'Other changes': 3,
|
|
40
|
+
|
|
41
|
+
'MAJOR BREAKING CHANGES': 1,
|
|
42
|
+
'MINOR BREAKING CHANGES': 2,
|
|
43
|
+
'BREAKING CHANGES': 3
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns an order of a message in the changelog.
|
|
48
|
+
*
|
|
49
|
+
* @param {String} title
|
|
50
|
+
* @returns {Number}
|
|
51
|
+
*/
|
|
52
|
+
getTypeOrder( title ) {
|
|
53
|
+
for ( const typeTitle of Object.keys( transformCommitUtils.typesOrder ) ) {
|
|
54
|
+
if ( title.startsWith( typeTitle ) ) {
|
|
55
|
+
return transformCommitUtils.typesOrder[ typeTitle ];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return 10;
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Replaces reference to the user (`@name`) with a link to the user's profile.
|
|
64
|
+
*
|
|
65
|
+
* @param {String} comment
|
|
66
|
+
* @returns {String}
|
|
67
|
+
*/
|
|
68
|
+
linkToGithubUser( comment ) {
|
|
69
|
+
return comment.replace( /(^|[\s(])@([\w-]+)(?![/\w-])/ig, ( matchedText, charBefore, nickName ) => {
|
|
70
|
+
return `${ charBefore }[@${ nickName }](https://github.com/${ nickName })`;
|
|
71
|
+
} );
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Replaces reference to issue (#ID) with a link to the issue.
|
|
76
|
+
* If comment matches to "organization/repository#ID", link will lead to the specified repository.
|
|
77
|
+
*
|
|
78
|
+
* @param {String} comment
|
|
79
|
+
* @returns {String}
|
|
80
|
+
*/
|
|
81
|
+
linkToGithubIssue( comment ) {
|
|
82
|
+
return comment.replace( /(\/?[\w-]+\/[\w-]+)?#([\d]+)/ig, ( matchedText, maybeRepository, issueId ) => {
|
|
83
|
+
if ( maybeRepository ) {
|
|
84
|
+
if ( maybeRepository.startsWith( '/' ) ) {
|
|
85
|
+
return matchedText;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return `[${ maybeRepository }#${ issueId }](https://github.com/${ maybeRepository }/issues/${ issueId })`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const repositoryUrl = transformCommitUtils.getRepositoryUrl();
|
|
92
|
+
|
|
93
|
+
// But if doesn't, let's add it.
|
|
94
|
+
return `[#${ issueId }](${ repositoryUrl }/issues/${ issueId })`;
|
|
95
|
+
} );
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Changes a singular type of commit to plural which will be displayed in a changelog.
|
|
100
|
+
*
|
|
101
|
+
* The switch cases must be synchronized with the `MULTI_ENTRIES_COMMIT_REGEXP` regexp.
|
|
102
|
+
*
|
|
103
|
+
* @param {String} commitType
|
|
104
|
+
* @returns {String}
|
|
105
|
+
*/
|
|
106
|
+
getCommitType( commitType ) {
|
|
107
|
+
switch ( commitType ) {
|
|
108
|
+
case 'Feature':
|
|
109
|
+
return 'Features';
|
|
110
|
+
|
|
111
|
+
case 'Fix':
|
|
112
|
+
return 'Bug fixes';
|
|
113
|
+
|
|
114
|
+
case 'Other':
|
|
115
|
+
return 'Other changes';
|
|
116
|
+
|
|
117
|
+
default:
|
|
118
|
+
throw new Error( `Given invalid type of commit ("${ commitType }").` );
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {String} sentence
|
|
124
|
+
* @param {Number} length
|
|
125
|
+
* @returns {String}
|
|
126
|
+
*/
|
|
127
|
+
truncate( sentence, length ) {
|
|
128
|
+
if ( sentence.length <= length ) {
|
|
129
|
+
return sentence;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return sentence.slice( 0, length - 3 ).trim() + '...';
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Returns a URL to the repository whether the commit is being parsed.
|
|
137
|
+
*
|
|
138
|
+
* @param {String} [cwd=process.cwd()]
|
|
139
|
+
* @returns {String}
|
|
140
|
+
*/
|
|
141
|
+
getRepositoryUrl( cwd = process.cwd() ) {
|
|
142
|
+
const packageJson = getPackageJson( cwd );
|
|
143
|
+
|
|
144
|
+
// Due to merging our issue trackers, `packageJson.bugs` will point to the same place for every package.
|
|
145
|
+
// We cannot rely on this value anymore. See: https://github.com/ckeditor/ckeditor5/issues/1988.
|
|
146
|
+
// Instead of we can take a value from `packageJson.repository` and adjust it to match to our requirements.
|
|
147
|
+
let repositoryUrl = ( typeof packageJson.repository === 'object' ) ? packageJson.repository.url : packageJson.repository;
|
|
148
|
+
|
|
149
|
+
if ( !repositoryUrl ) {
|
|
150
|
+
throw new Error( `The package.json for "${ packageJson.name }" must contain the "repository" property.` );
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// If the value ends with ".git", we need to remove it.
|
|
154
|
+
repositoryUrl = repositoryUrl.replace( /\.git$/, '' );
|
|
155
|
+
|
|
156
|
+
// Remove "/issues" suffix as well.
|
|
157
|
+
repositoryUrl = repositoryUrl.replace( /\/issues/, '' );
|
|
158
|
+
|
|
159
|
+
return repositoryUrl;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
module.exports = transformCommitUtils;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2022, 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
|
+
* Updates dependencies and devDependencies in `package.json`.
|
|
12
|
+
*
|
|
13
|
+
* @param {Map} dependencies Packages with versions of CKEditor 5 dependencies.
|
|
14
|
+
* @param {String} packageJsonPath An absolute path to the `package.json` file.
|
|
15
|
+
*/
|
|
16
|
+
module.exports = function updateDependenciesVersions( dependencies, packageJsonPath ) {
|
|
17
|
+
tools.updateJSONFile( packageJsonPath, json => {
|
|
18
|
+
for ( const item of dependencies.keys() ) {
|
|
19
|
+
const version = dependencies.get( item );
|
|
20
|
+
|
|
21
|
+
if ( json.dependencies && json.dependencies[ item ] ) {
|
|
22
|
+
json.dependencies[ item ] = `^${ version }`;
|
|
23
|
+
} else if ( json.devDependencies && json.devDependencies[ item ] ) {
|
|
24
|
+
json.devDependencies[ item ] = `^${ version }`;
|
|
25
|
+
} else if ( json.peerDependencies && json.peerDependencies[ item ] ) {
|
|
26
|
+
json.peerDependencies[ item ] = version;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return json;
|
|
31
|
+
} );
|
|
32
|
+
};
|