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