@ckeditor/ckeditor5-dev-changelog 50.3.1 → 51.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/dist/index.js CHANGED
@@ -18,6 +18,7 @@ const CHANGELOG_HEADER = 'Changelog\n=========';
18
18
  const NPM_URL = 'https://www.npmjs.com/package';
19
19
  const VERSIONING_POLICY_URL = 'https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/versioning-policy.html';
20
20
  const CHANGESET_DIRECTORY = '.changelog';
21
+ const PRE_RELEASE_DIRECTORY = 'pre-release';
21
22
  upath.join(import.meta.dirname, '../template/template.md');
22
23
  const SECTIONS = {
23
24
  major: {
@@ -282,6 +283,9 @@ function getIssueLinkObject(issue, gitHubUrl) {
282
283
  const repoUrlMatch = issue.match(ISSUE_URL_PATTERN);
283
284
  if (repoUrlMatch) {
284
285
  const { owner, repository, number } = repoUrlMatch.groups;
286
+ if (issue.startsWith(gitHubUrl)) {
287
+ return { displayName: `#${number}`, link: issue };
288
+ }
285
289
  return { displayName: `${owner}/${repository}#${number}`, link: issue };
286
290
  }
287
291
  return { displayName: '', link: '' };
@@ -529,6 +533,49 @@ function prepareChangelogContent(existingChangelog, newChangelog) {
529
533
  class UserAbortError extends Error {
530
534
  }
531
535
 
536
+ /**
537
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
538
+ * For licensing, see LICENSE.md.
539
+ */
540
+ async function validateInputVersion(options) {
541
+ const { newVersion, version, releaseType, packageName, suggestedVersion } = options;
542
+ const [newChannel] = semver.prerelease(newVersion) || ['latest'];
543
+ const [currentChannel] = semver.prerelease(version) || ['latest'];
544
+ // Generic semantic‑version checks.
545
+ if (!semver.valid(newVersion)) {
546
+ return 'Please provide a valid version.';
547
+ }
548
+ if (!semver.gt(newVersion, version)) {
549
+ return `Provided version must be higher than "${version}".`;
550
+ }
551
+ if (!(await npm.checkVersionAvailability(newVersion, packageName))) {
552
+ return 'Given version is already taken.';
553
+ }
554
+ // Rules that depend on release type.
555
+ const isPrerelease = releaseType === 'prerelease';
556
+ const isPrereleasePromote = releaseType === 'prerelease-promote';
557
+ const isLatest = releaseType === 'latest';
558
+ // Pre‑release types must always include a channel suffix.
559
+ if ((isPrerelease || isPrereleasePromote) && newChannel === 'latest') {
560
+ return 'You chose the "pre-release" release type. Please provide a version with a channel suffix.';
561
+ }
562
+ // Promoting a pre‑release: new version ≥ suggested version.
563
+ if (isPrereleasePromote && !semver.gte(newVersion, suggestedVersion)) {
564
+ return `Provided version must be higher or equal to "${suggestedVersion}".`;
565
+ }
566
+ // Continuing a pre‑release stream: channel cannot change.
567
+ if (isPrerelease &&
568
+ currentChannel !== 'latest' &&
569
+ currentChannel !== newChannel) {
570
+ return `Provided channel must be the same existing channel ${currentChannel}.`;
571
+ }
572
+ // Latest release must not carry a channel suffix.
573
+ if (isLatest && newChannel !== 'latest') {
574
+ return 'You chose the "latest" release type. Please provide a version without a channel suffix.';
575
+ }
576
+ return true;
577
+ }
578
+
532
579
  /**
533
580
  * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
534
581
  * For licensing, see LICENSE.md.
@@ -584,39 +631,69 @@ async function askContinueConfirmation(indentLevel = 0) {
584
631
  * Creates a prompt question for version input with validation.
585
632
  */
586
633
  function createVersionQuestion(options) {
587
- const { version, packageName, bumpType, indentLevel = 0 } = options;
588
- const suggestedVersion = semver.inc(version, bumpType) || version;
634
+ const { version, packageName, bumpType, releaseChannel, releaseType, indentLevel = 0 } = options;
635
+ const suggestedVersion = getSuggestedVersion(bumpType, version, releaseChannel) || version;
589
636
  const message = 'Type the new version ' +
590
- `(current: "${version}", suggested: "${suggestedVersion}", or "internal" for internal changes):`;
637
+ `(current: "${version}", suggested: "${suggestedVersion}":`;
591
638
  return [{
592
639
  type: 'input',
593
640
  name: 'version',
594
641
  default: suggestedVersion,
595
642
  message,
596
643
  filter: (newVersion) => newVersion.trim(),
597
- async validate(newVersion) {
598
- // Allow 'internal' as a special version.
599
- if (newVersion === 'internal') {
600
- return true;
601
- }
602
- // Require a semver valid version, e.g., `1.0.0`, `1.0.0-alpha.0`, etc.
603
- if (!semver.valid(newVersion)) {
604
- return 'Please provide a valid version or "internal" for internal changes.';
605
- }
606
- // The provided version must be higher than the current version.
607
- if (!semver.gt(newVersion, version)) {
608
- return `Provided version must be higher than "${version}".`;
609
- }
610
- const isAvailable = await npm.checkVersionAvailability(newVersion, packageName);
611
- // Check against availability in the npm registry.
612
- if (!isAvailable) {
613
- return 'Given version is already taken.';
614
- }
615
- return true;
616
- },
644
+ validate: (newVersion) => validateInputVersion({ newVersion, version, releaseType, packageName, suggestedVersion }),
617
645
  prefix: ' '.repeat(indentLevel * CLI_INDENT_SIZE) + chalk.cyan('?')
618
646
  }];
619
647
  }
648
+ function getSuggestedVersion(bumpType, version, releaseChannel) {
649
+ if (bumpType === 'prerelease' && releaseChannel !== 'latest') {
650
+ return semver.inc(version, bumpType, releaseChannel);
651
+ }
652
+ else if (bumpType === 'prerelease' && releaseChannel === 'latest') {
653
+ // Using 'premajor` and `alpha` channel for a case, when introducing a prerelease for the next major.
654
+ // E.g. 1.0.0 -> 2.0.0-alpha.0.
655
+ return semver.inc(version, 'premajor', 'alpha');
656
+ }
657
+ else {
658
+ return semver.inc(version, bumpType);
659
+ }
660
+ }
661
+
662
+ /**
663
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
664
+ * For licensing, see LICENSE.md.
665
+ */
666
+ /**
667
+ * Detects the release channel from a version string.
668
+ */
669
+ function detectReleaseChannel(version, promotePrerelease = false) {
670
+ const prerelease = semver.prerelease(version);
671
+ if (!prerelease) {
672
+ return 'latest';
673
+ }
674
+ const currentChannel = prerelease[0];
675
+ if (promotePrerelease) {
676
+ if (currentChannel === 'alpha') {
677
+ return 'beta';
678
+ }
679
+ if (currentChannel === 'beta') {
680
+ return 'rc';
681
+ }
682
+ logInfo(chalk.yellow(`Warning! Unknown release channel to promote from ${currentChannel}.`));
683
+ return 'alpha';
684
+ }
685
+ return currentChannel;
686
+ }
687
+
688
+ /**
689
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
690
+ * For licensing, see LICENSE.md.
691
+ */
692
+ /**
693
+ * Custom error class for handling validation errors in the changelog generation process.
694
+ */
695
+ class InternalError extends Error {
696
+ }
620
697
 
621
698
  /**
622
699
  * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
@@ -624,27 +701,39 @@ function createVersionQuestion(options) {
624
701
  */
625
702
  /**
626
703
  * Determines the next version for a single package or a mono-repository setup based on
627
- * the change sections, * user input, and semantic versioning rules.
704
+ * the change sections, user input, and semantic versioning rules.
628
705
  *
629
706
  * The function handles:
630
707
  * * Automatic version bump calculation from categorized changelog sections (major, minor, patch).
631
- * * Accepting explicit next version overrides, including a special `internal` version bump.
708
+ * * Version bump for prerelease channels.
632
709
  * * User prompts for version input when no explicit version is provided.
633
710
  */
634
711
  async function determineNextVersion(options) {
635
- const { sections, currentVersion, packageName, nextVersion } = options;
636
- if (nextVersion === 'internal') {
637
- const internalVersionBump = getInternalVersionBump(currentVersion);
638
- logInfo(`○ ${chalk.cyan(`Determined the next version to be ${internalVersionBump.newVersion}.`)}`);
639
- return internalVersionBump;
640
- }
712
+ const { sections, currentVersion, packageName, nextVersion, releaseType } = options;
641
713
  if (nextVersion) {
642
714
  logInfo(`○ ${chalk.cyan(`Determined the next version to be ${nextVersion}.`)}`);
643
- return { newVersion: nextVersion, isInternal: false };
715
+ const isNightlyVersion = nextVersion.startsWith('0.0.0-');
716
+ if (isNightlyVersion) {
717
+ return nextVersion;
718
+ }
719
+ const validationResult = await validateInputVersion({
720
+ newVersion: nextVersion,
721
+ suggestedVersion: nextVersion,
722
+ version: currentVersion,
723
+ releaseType,
724
+ packageName
725
+ });
726
+ if (typeof validationResult === 'string') {
727
+ throw new InternalError(validationResult);
728
+ }
729
+ return nextVersion;
644
730
  }
645
731
  logInfo(`○ ${chalk.cyan('Determining the new version...')}`);
646
732
  let bumpType = 'patch';
647
- if (sections.major.entries.length || sections.breaking.entries.length) {
733
+ if (releaseType === 'prerelease' || releaseType === 'prerelease-promote') {
734
+ bumpType = 'prerelease';
735
+ }
736
+ else if (sections.major.entries.length || sections.breaking.entries.length) {
648
737
  bumpType = 'major';
649
738
  }
650
739
  else if (sections.minor.entries.length || sections.feature.entries.length) {
@@ -655,20 +744,12 @@ async function determineNextVersion(options) {
655
744
  const userProvidedVersion = await provideNewVersion({
656
745
  packageName,
657
746
  bumpType,
747
+ releaseType,
658
748
  version: currentVersion,
749
+ releaseChannel: detectReleaseChannel(currentVersion, releaseType === 'prerelease-promote'),
659
750
  displayValidationWarning: areErrorsPresent || areWarningsPresent
660
751
  });
661
- if (userProvidedVersion === 'internal') {
662
- return getInternalVersionBump(currentVersion);
663
- }
664
- return { newVersion: userProvidedVersion, isInternal: false };
665
- }
666
- function getInternalVersionBump(currentVersion) {
667
- const version = semver.inc(currentVersion, 'patch');
668
- if (!version) {
669
- throw new Error('Unable to determine new version based on the version in root package.json.');
670
- }
671
- return { newVersion: version, isInternal: true };
752
+ return userProvidedVersion;
672
753
  }
673
754
 
674
755
  /**
@@ -819,11 +900,12 @@ function getPackageName(value) {
819
900
  * Gathers changelog entry file paths (Markdown files) from the main repository and any configured external repositories.
820
901
  */
821
902
  async function findChangelogEntryPaths(options) {
822
- const { cwd, externalRepositories, shouldSkipLinks } = options;
903
+ const { cwd, externalRepositories, shouldSkipLinks, includeSubdirectories = true } = options;
904
+ const globPattern = includeSubdirectories ? '**/*.md' : '*.md';
823
905
  return AsyncArray
824
906
  .from(Promise.resolve(externalRepositories))
825
907
  .map(async (repo) => {
826
- const changesetGlob = await glob('**/*.md', {
908
+ const changesetGlob = await glob(globPattern, {
827
909
  cwd: upath.join(repo.cwd, CHANGESET_DIRECTORY),
828
910
  absolute: true
829
911
  });
@@ -836,7 +918,7 @@ async function findChangelogEntryPaths(options) {
836
918
  };
837
919
  })
838
920
  .then(async (externalResults) => {
839
- const mainChangesetGlob = await glob('**/*.md', {
921
+ const mainChangesetGlob = await glob(globPattern, {
840
922
  cwd: upath.join(cwd, CHANGESET_DIRECTORY),
841
923
  absolute: true
842
924
  });
@@ -994,25 +1076,6 @@ function extractDateFromFilename(changesetPath) {
994
1076
  return parsedDate;
995
1077
  }
996
1078
 
997
- /**
998
- * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
999
- * For licensing, see LICENSE.md.
1000
- */
1001
- /**
1002
- * Custom error class for handling validation errors in the changelog generation process.
1003
- */
1004
- class InternalError extends Error {
1005
- constructor() {
1006
- const message = 'No valid entries were found. Please ensure that:\n' +
1007
- '1) Input files exist in the `.changelog/` directory.\n' +
1008
- '2) The `cwd` parameter points to the root of your project.\n' +
1009
- '3) The `packagesDirectory` parameter correctly specifies the packages folder.\n' +
1010
- 'If no errors appear in the console but inputs are present, your project configuration may be incorrect.\n' +
1011
- 'If validation errors are shown, please resolve them according to the details provided.\n';
1012
- super(message);
1013
- }
1014
- }
1015
-
1016
1079
  /**
1017
1080
  * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
1018
1081
  * For licensing, see LICENSE.md.
@@ -1031,7 +1094,13 @@ function filterVisibleSections(sectionsWithEntries) {
1031
1094
  })
1032
1095
  .map(([, section]) => section);
1033
1096
  if (!sectionsToDisplay.length) {
1034
- throw new InternalError();
1097
+ const message = 'No valid entries were found. Please ensure that:\n' +
1098
+ '1) Input files exist in the `.changelog/` directory.\n' +
1099
+ '2) The `cwd` parameter points to the root of your project.\n' +
1100
+ '3) The `packagesDirectory` parameter correctly specifies the packages folder.\n' +
1101
+ 'If no errors appear in the console but inputs are present, your project configuration may be incorrect.\n' +
1102
+ 'If validation errors are shown, please resolve them according to the details provided.\n';
1103
+ throw new InternalError(message);
1035
1104
  }
1036
1105
  return sectionsToDisplay;
1037
1106
  }
@@ -1058,13 +1127,12 @@ function getDateFormatted(date) {
1058
1127
  * * A version header with a link to the GitHub comparison view (except for an initial version).
1059
1128
  * * Sections with grouped changelog entries and their messages.
1060
1129
  * * A collapsible summary of released packages and their version bumps for a mono-repository setup.
1061
- * * Special handling for internal-only releases and single-package repositories.
1130
+ * * Special handling for single-package repositories.
1062
1131
  */
1063
1132
  async function composeChangelog(options) {
1064
- const { cwd, date, currentVersion, newVersion, sections, releasedPackagesInfo, isInternal, isSinglePackage, packagesMetadata } = options;
1133
+ const { cwd, date, currentVersion, newVersion, sections, releasedPackagesInfo, isSinglePackage } = options;
1065
1134
  const gitHubUrl = await workspaces.getRepositoryUrl(cwd, { async: true });
1066
1135
  const dateFormatted = getDateFormatted(date);
1067
- const packagesNames = [...packagesMetadata.keys()];
1068
1136
  const header = currentVersion === '0.0.1' ?
1069
1137
  `## ${newVersion} (${dateFormatted})` :
1070
1138
  `## [${newVersion}](${gitHubUrl}/compare/v${currentVersion}...v${newVersion}) (${dateFormatted})`;
@@ -1080,19 +1148,13 @@ async function composeChangelog(options) {
1080
1148
  '',
1081
1149
  ...packages.map(packageName => `* [${packageName}](${NPM_URL}/${packageName}/v/${newVersion}): ${version}`)
1082
1150
  ])).flat().join('\n');
1083
- const internalVersionsBumps = [
1084
- '',
1085
- SECTIONS.other.title + ':',
1086
- '',
1087
- packagesNames.map(name => `* [${name}](${NPM_URL}/${name}/v/${newVersion}): v${currentVersion} => v${newVersion}`)
1088
- ].flat().join('\n');
1089
1151
  const changelog = [
1090
1152
  header,
1091
1153
  '',
1092
- isInternal ? 'Internal changes only (updated dependencies, documentation, etc.).\n' : sectionsAsString
1154
+ sectionsAsString
1093
1155
  ];
1094
1156
  if (!isSinglePackage) {
1095
- changelog.push('### Released packages', '', `Check out the [Versioning policy](${VERSIONING_POLICY_URL}) guide for more information.`, '', '<details>', '<summary>Released packages (summary)</summary>', isInternal ? internalVersionsBumps : packagesVersionBumps, '</details>', '');
1157
+ changelog.push('### Released packages', '', `Check out the [Versioning policy](${VERSIONING_POLICY_URL}) guide for more information.`, '', '<details>', '<summary>Released packages (summary)</summary>', packagesVersionBumps, '</details>', '');
1096
1158
  }
1097
1159
  return changelog.join('\n');
1098
1160
  }
@@ -1112,6 +1174,40 @@ async function removeChangelogEntryFiles(entryPaths) {
1112
1174
  .map(file => fs$1.unlink(file)));
1113
1175
  }
1114
1176
 
1177
+ /**
1178
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
1179
+ * For licensing, see LICENSE.md.
1180
+ */
1181
+ /**
1182
+ * Moves changelog entry files to cycle-specific directories instead of deleting them.
1183
+ * This preserves the history of changes across prerelease cycles.
1184
+ * Returns an array of entry paths that were modified by the move operation
1185
+ */
1186
+ async function moveChangelogEntryFiles(entryPaths) {
1187
+ const targetDir = PRE_RELEASE_DIRECTORY;
1188
+ const modifiedEntryPaths = [];
1189
+ logInfo(`○ ${chalk.cyan(`Moving changelog entries to ${targetDir}/ directory...`)}`);
1190
+ for (const repo of entryPaths) {
1191
+ const { cwd, filePaths } = repo;
1192
+ const changelogDir = upath.join(cwd, CHANGESET_DIRECTORY);
1193
+ const targetPath = upath.join(changelogDir, targetDir);
1194
+ await fs$1.ensureDir(targetPath);
1195
+ const modifiedFilePaths = [];
1196
+ for (const filePath of filePaths) {
1197
+ const fileName = upath.basename(filePath);
1198
+ const targetFilePath = upath.join(targetPath, fileName);
1199
+ await fs$1.rename(filePath, targetFilePath);
1200
+ modifiedFilePaths.push(targetFilePath);
1201
+ modifiedFilePaths.push(filePath);
1202
+ }
1203
+ modifiedEntryPaths.push({
1204
+ ...repo,
1205
+ filePaths: modifiedFilePaths
1206
+ });
1207
+ }
1208
+ return modifiedEntryPaths;
1209
+ }
1210
+
1115
1211
  /**
1116
1212
  * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
1117
1213
  * For licensing, see LICENSE.md.
@@ -1134,6 +1230,61 @@ async function commitChanges(version, repositories) {
1134
1230
  }
1135
1231
  }
1136
1232
 
1233
+ /**
1234
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
1235
+ * For licensing, see LICENSE.md.
1236
+ */
1237
+ /**
1238
+ * Prompts the user to choose between latest or prerelease
1239
+ */
1240
+ async function promptReleaseType(currentVersion) {
1241
+ const { releaseType } = await inquirer.prompt([
1242
+ {
1243
+ type: 'list',
1244
+ name: 'releaseType',
1245
+ message: 'Please select the release type.',
1246
+ choices: getQuestions(currentVersion)
1247
+ }
1248
+ ]);
1249
+ return releaseType;
1250
+ }
1251
+ function getQuestions(currentVersion) {
1252
+ const currentVersionPrerelease = semver.prerelease(currentVersion);
1253
+ if (!currentVersionPrerelease) {
1254
+ return [
1255
+ { name: 'Latest (stable) release (e.g. 1.0.0 -> 2.0.0)', value: 'latest' },
1256
+ { name: 'Pre-release (e.g. 1.0.0 -> 2.0.0-alpha.0)', value: 'prerelease' }
1257
+ ];
1258
+ }
1259
+ if (currentVersionPrerelease[0] === 'rc') {
1260
+ return [
1261
+ { name: 'Latest (stable) release (e.g. 1.0.0-beta.2 -> 1.0.0)', value: 'latest' },
1262
+ { name: 'Pre-release continuation (e.g. 1.0.0-alpha.0 -> 1.0.0-alpha.1)', value: 'prerelease' }
1263
+ ];
1264
+ }
1265
+ return [
1266
+ { name: 'Latest (stable) release (e.g. 1.0.0-beta.2 -> 1.0.0)', value: 'latest' },
1267
+ { name: 'Pre-release continuation (e.g. 1.0.0-alpha.0 -> 1.0.0-alpha.1)', value: 'prerelease' },
1268
+ { name: 'Pre-release promotion (e.g. 1.0.0-alpha.1 -> 1.0.0-beta.0)', value: 'prerelease-promote' }
1269
+ ];
1270
+ }
1271
+
1272
+ /**
1273
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
1274
+ * For licensing, see LICENSE.md.
1275
+ */
1276
+ function getReleaseType(currentVersion, nextVersion) {
1277
+ const [currentChannel] = semver.prerelease(currentVersion) || ['latest'];
1278
+ const [nextChannel] = semver.prerelease(nextVersion) || ['latest'];
1279
+ if (nextChannel === 'latest') {
1280
+ return 'latest';
1281
+ }
1282
+ if (nextChannel === currentChannel) {
1283
+ return 'prerelease';
1284
+ }
1285
+ return 'prerelease-promote';
1286
+ }
1287
+
1137
1288
  /**
1138
1289
  * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
1139
1290
  * For licensing, see LICENSE.md.
@@ -1164,10 +1315,12 @@ const main = async (options) => {
1164
1315
  shouldIgnoreRootPackage,
1165
1316
  externalRepositories
1166
1317
  });
1318
+ const releaseType = nextVersion ? getReleaseType(currentVersion, nextVersion) : await promptReleaseType(currentVersion);
1167
1319
  const entryPaths = await findChangelogEntryPaths({
1168
1320
  cwd,
1169
1321
  externalRepositories,
1170
- shouldSkipLinks
1322
+ shouldSkipLinks,
1323
+ includeSubdirectories: releaseType === 'latest' || releaseType === 'prerelease-promote'
1171
1324
  });
1172
1325
  const parsedChangesetFiles = await parseChangelogEntries(entryPaths, isSinglePackage);
1173
1326
  const sectionsWithEntries = groupEntriesBySection({
@@ -1191,9 +1344,10 @@ const main = async (options) => {
1191
1344
  });
1192
1345
  }
1193
1346
  // Display a prompt to provide a new version in the console.
1194
- const { isInternal, newVersion } = await determineNextVersion({
1347
+ const newVersion = await determineNextVersion({
1195
1348
  currentVersion,
1196
1349
  nextVersion,
1350
+ releaseType,
1197
1351
  sections: sectionsWithEntries,
1198
1352
  packageName: shouldIgnoreRootPackage ? npmPackageToCheck : rootPackageName
1199
1353
  });
@@ -1208,18 +1362,23 @@ const main = async (options) => {
1208
1362
  cwd,
1209
1363
  date,
1210
1364
  newVersion,
1211
- isInternal,
1212
1365
  isSinglePackage,
1213
- packagesMetadata,
1214
1366
  releasedPackagesInfo,
1215
1367
  sections: filterVisibleSections(sectionsWithEntries)
1216
1368
  });
1217
1369
  if (disableFilesystemOperations) {
1218
1370
  return newChangelog;
1219
1371
  }
1220
- await removeChangelogEntryFiles(entryPaths);
1372
+ let pathsToCommit = entryPaths;
1373
+ // Handle changelog entry files based on release type.
1374
+ if (releaseType === 'latest') {
1375
+ await removeChangelogEntryFiles(entryPaths);
1376
+ }
1377
+ else {
1378
+ pathsToCommit = await moveChangelogEntryFiles(entryPaths);
1379
+ }
1221
1380
  await modifyChangelog(newChangelog, cwd);
1222
- await commitChanges(newVersion, entryPaths.map(({ cwd, isRoot, filePaths }) => ({ cwd, isRoot, filePaths })));
1381
+ await commitChanges(newVersion, pathsToCommit.map(({ cwd, isRoot, filePaths }) => ({ cwd, isRoot, filePaths })));
1223
1382
  logInfo('○ ' + chalk.green('Done!'));
1224
1383
  };
1225
1384
  /**
package/dist/types.d.ts CHANGED
@@ -6,9 +6,9 @@ import type { SECTIONS } from './utils/constants.js';
6
6
  export type ConfigBase = RepositoryConfig & {
7
7
  /**
8
8
  * The next version number to use. If not provided, will be calculated based on changes.
9
- * Can be a semver string or 'internal' for internal changes only.
9
+ * Must be a valid version in terms of the semantic versioning specification.
10
10
  */
11
- nextVersion?: string | 'internal';
11
+ nextVersion?: string;
12
12
  /**
13
13
  * Array of external repository configurations to include in the changelog.
14
14
  */
@@ -117,4 +117,8 @@ export type FileMetadata = {
117
117
  communityCredits: Array<string>;
118
118
  validations: Array<string>;
119
119
  };
120
+ export type ChangelogReleaseType = 'latest' | // e.g. 1.0.0 -> 2.0.0 or 2.0.0-beta.1 -> 2.0.0.
121
+ 'prerelease' | // e.g. 1.0.0 -> 2.0.0-alpha.0 or 2.0.0-alpha.0 -> 2.0.0-alpha.1.
122
+ 'prerelease-promote';
123
+ export type ReleaseChannel = 'alpha' | 'beta' | 'rc' | 'latest';
120
124
  export {};
@@ -3,16 +3,14 @@
3
3
  * For licensing, see LICENSE.md.
4
4
  */
5
5
  import type { ReleaseInfo, Section } from '../types.js';
6
- type ComposeChangelogOptions = {
6
+ export type ComposeChangelogOptions = {
7
7
  cwd: string;
8
8
  date: string;
9
9
  currentVersion: string;
10
10
  newVersion: string;
11
11
  sections: Array<Section>;
12
12
  releasedPackagesInfo: Array<ReleaseInfo>;
13
- isInternal: boolean;
14
13
  isSinglePackage: boolean;
15
- packagesMetadata: Map<string, string>;
16
14
  };
17
15
  /**
18
16
  * Generates a formatted changelog string for a new version release.
@@ -21,7 +19,6 @@ type ComposeChangelogOptions = {
21
19
  * * A version header with a link to the GitHub comparison view (except for an initial version).
22
20
  * * Sections with grouped changelog entries and their messages.
23
21
  * * A collapsible summary of released packages and their version bumps for a mono-repository setup.
24
- * * Special handling for internal-only releases and single-package repositories.
22
+ * * Special handling for single-package repositories.
25
23
  */
26
24
  export declare function composeChangelog(options: ComposeChangelogOptions): Promise<string>;
27
- export {};
@@ -7,6 +7,7 @@ export declare const CHANGELOG_HEADER = "Changelog\n=========";
7
7
  export declare const NPM_URL = "https://www.npmjs.com/package";
8
8
  export declare const VERSIONING_POLICY_URL = "https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/versioning-policy.html";
9
9
  export declare const CHANGESET_DIRECTORY = ".changelog";
10
+ export declare const PRE_RELEASE_DIRECTORY = "pre-release";
10
11
  export declare const TEMPLATE_FILE: string;
11
12
  export declare const SECTIONS: {
12
13
  readonly major: {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+ import type { ReleaseChannel } from '../types.js';
6
+ /**
7
+ * Detects the release channel from a version string.
8
+ */
9
+ export declare function detectReleaseChannel(version: string, promotePrerelease?: boolean): ReleaseChannel;
@@ -2,25 +2,21 @@
2
2
  * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md.
4
4
  */
5
- import type { SectionsWithEntries } from '../types.js';
6
- type NextVersionOutput = {
7
- isInternal: boolean;
8
- newVersion: string;
9
- };
5
+ import type { ChangelogReleaseType, SectionsWithEntries } from '../types.js';
10
6
  export type DetermineNextVersionOptions = {
11
7
  sections: SectionsWithEntries;
12
8
  currentVersion: string;
13
9
  packageName: string;
14
10
  nextVersion: string | undefined;
11
+ releaseType: ChangelogReleaseType;
15
12
  };
16
13
  /**
17
14
  * Determines the next version for a single package or a mono-repository setup based on
18
- * the change sections, * user input, and semantic versioning rules.
15
+ * the change sections, user input, and semantic versioning rules.
19
16
  *
20
17
  * The function handles:
21
18
  * * Automatic version bump calculation from categorized changelog sections (major, minor, patch).
22
- * * Accepting explicit next version overrides, including a special `internal` version bump.
19
+ * * Version bump for prerelease channels.
23
20
  * * User prompts for version input when no explicit version is provided.
24
21
  */
25
- export declare function determineNextVersion(options: DetermineNextVersionOptions): Promise<NextVersionOutput>;
26
- export {};
22
+ export declare function determineNextVersion(options: DetermineNextVersionOptions): Promise<string>;
@@ -7,6 +7,7 @@ type FindChangelogEntryPathsOptions = {
7
7
  cwd: string;
8
8
  externalRepositories: Array<RepositoryConfig>;
9
9
  shouldSkipLinks: boolean;
10
+ includeSubdirectories?: boolean;
10
11
  };
11
12
  /**
12
13
  * Gathers changelog entry file paths (Markdown files) from the main repository and any configured external repositories.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+ import { type ChangelogReleaseType } from '../types.js';
6
+ export declare function getReleaseType(currentVersion: string, nextVersion: string): ChangelogReleaseType;
@@ -6,5 +6,4 @@
6
6
  * Custom error class for handling validation errors in the changelog generation process.
7
7
  */
8
8
  export declare class InternalError extends Error {
9
- constructor();
10
9
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+ import type { ChangesetPathsWithGithubUrl } from '../types.js';
6
+ /**
7
+ * Moves changelog entry files to cycle-specific directories instead of deleting them.
8
+ * This preserves the history of changes across prerelease cycles.
9
+ * Returns an array of entry paths that were modified by the move operation
10
+ */
11
+ export declare function moveChangelogEntryFiles(entryPaths: Array<ChangesetPathsWithGithubUrl>): Promise<Array<ChangesetPathsWithGithubUrl>>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+ import type { ChangelogReleaseType } from '../types.js';
6
+ /**
7
+ * Prompts the user to choose between latest or prerelease
8
+ */
9
+ export declare function promptReleaseType(currentVersion: string): Promise<ChangelogReleaseType>;
@@ -3,12 +3,15 @@
3
3
  * For licensing, see LICENSE.md.
4
4
  */
5
5
  import { type ReleaseType } from 'semver';
6
+ import type { ChangelogReleaseType, ReleaseChannel } from '../types.js';
6
7
  type Options = {
7
8
  packageName: string;
8
9
  version: string;
9
10
  bumpType: ReleaseType;
11
+ releaseChannel: ReleaseChannel;
10
12
  indentLevel?: number;
11
13
  displayValidationWarning: boolean;
14
+ releaseType: ChangelogReleaseType;
12
15
  };
13
16
  /**
14
17
  * Prompts the user to provide a new version for a package.
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+ import type { ChangelogReleaseType } from '../types.js';
6
+ type ValidateOptions = {
7
+ newVersion: string;
8
+ version: string;
9
+ releaseType: ChangelogReleaseType;
10
+ packageName: string;
11
+ suggestedVersion: string;
12
+ };
13
+ export declare function validateInputVersion(options: ValidateOptions): Promise<string | true>;
14
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-dev-changelog",
3
- "version": "50.3.1",
3
+ "version": "51.0.0",
4
4
  "description": "A CKEditor 5 development tool for handling changelogs.",
5
5
  "keywords": [],
6
6
  "author": "CKSource (http://cksource.com/)",
@@ -28,7 +28,7 @@
28
28
  "ckeditor5-dev-changelog-create-entry": "bin/generate-template.js"
29
29
  },
30
30
  "dependencies": {
31
- "@ckeditor/ckeditor5-dev-utils": "^50.3.1",
31
+ "@ckeditor/ckeditor5-dev-utils": "^51.0.0",
32
32
  "chalk": "^5.0.0",
33
33
  "date-fns": "^4.0.0",
34
34
  "fs-extra": "^11.0.0",
@@ -9,10 +9,10 @@
9
9
  #
10
10
  # For guidance on breaking changes, see:
11
11
  # https://ckeditor.com/docs/ckeditor5/latest/updating/versioning-policy.html#major-and-minor-breaking-changes
12
- type:
12
+ type:
13
13
 
14
14
  # Optional: Affected package(s), using short names.
15
- # Can be skipped when processing a non-mono-repository.
15
+ # Leave empty when used in a single-package repository.
16
16
  # Example: ckeditor5-core
17
17
  scope:
18
18
  -