Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions src/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,14 @@ type ChangelogChanges = Record<Version, ReleaseChanges> & {
* @param category - The title of the changelog category.
* @param changes - The changes included in this category.
* @param repoUrl - The URL of the repository.
* @param useShortPrLink - Whether to use short PR links in the changelog entries.
* @returns The stringified category section.
*/
function stringifyCategory(
category: ChangeCategory,
changes: Change[],
repoUrl: string,
useShortPrLink = false,
) {
const categoryHeader = `### ${category}`;
if (changes.length === 0) {
Expand All @@ -122,7 +124,11 @@ function stringifyCategory(
.map(({ description, prNumbers }) => {
const [firstLine, ...otherLines] = description.split('\n');
const stringifiedPrLinks = prNumbers
.map((prNumber) => `[#${prNumber}](${repoUrl}/pull/${prNumber})`)
.map((prNumber) =>
useShortPrLink
? `#${prNumber}`
: `[#${prNumber}](${repoUrl}/pull/${prNumber})`,
)
.join(', ');
const parenthesizedPrLinks =
stringifiedPrLinks.length > 0 ? ` (${stringifiedPrLinks})` : '';
Expand All @@ -140,6 +146,7 @@ function stringifyCategory(
* @param version - The release version.
* @param categories - The categories of changes included in this release.
* @param repoUrl - The URL of the repository.
* @param useShortPrLink - Whether to use short PR links in the changelog entries.
* @param options - Additional release options.
* @param options.date - The date of the release.
* @param options.status - The status of the release (e.g., "DEPRECATED").
Expand All @@ -149,6 +156,7 @@ function stringifyRelease(
version: Version | typeof unreleased,
categories: ReleaseChanges,
repoUrl: string,
useShortPrLink = false,
{ date, status }: Partial<ReleaseMetadata> = {},
) {
const releaseHeader = `## [${version}]${date ? ` - ${date}` : ''}${
Expand All @@ -158,7 +166,7 @@ function stringifyRelease(
.filter((category) => categories[category])
.map((category) => {
const changes = categories[category] ?? [];
return stringifyCategory(category, changes, repoUrl);
return stringifyCategory(category, changes, repoUrl, useShortPrLink);
})
.join('\n\n');
if (categorizedChanges === '') {
Expand All @@ -173,21 +181,27 @@ function stringifyRelease(
* @param releases - The releases to stringify.
* @param changes - The set of changes to include, organized by release.
* @param repoUrl - The URL of the repository.
* @param useShortPrLink - Whether to use short PR links in the changelog entries.
* @returns The stringified set of release sections.
*/
function stringifyReleases(
releases: ReleaseMetadata[],
changes: ChangelogChanges,
repoUrl: string,
useShortPrLink = false,
) {
const stringifiedUnreleased = stringifyRelease(
unreleased,
changes[unreleased],
repoUrl,
useShortPrLink,
);
const stringifiedReleases = releases.map(({ version, date, status }) => {
const categories = changes[version];
return stringifyRelease(version, categories, repoUrl, { date, status });
return stringifyRelease(version, categories, repoUrl, useShortPrLink, {
date,
status,
});
});

return [stringifiedUnreleased, ...stringifiedReleases].join('\n\n');
Expand Down Expand Up @@ -586,15 +600,22 @@ export default class Changelog {
* Throws an error if no such release exists.
*
* @param version - The version of the release to stringify.
* @param useShortPrLink - Whether to use short PR links in the changelog entries.
* @returns The stringified release, as it appears in the changelog.
*/
getStringifiedRelease(version: Version) {
getStringifiedRelease(version: Version, useShortPrLink = false) {
const release = this.getRelease(version);
if (!release) {
throw new Error(`Specified release version does not exist: '${version}'`);
}
const releaseChanges = this.getReleaseChanges(version);
return stringifyRelease(version, releaseChanges, this.#repoUrl, release);
return stringifyRelease(
version,
releaseChanges,
this.#repoUrl,
useShortPrLink,
release,
);
}

/**
Expand All @@ -619,13 +640,14 @@ export default class Changelog {
/**
* The stringified changelog, formatted according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
*
* @param useShortPrLink - Whether to use short PR links in the changelog entries.
* @returns The stringified changelog.
*/
async toString(): Promise<string> {
async toString(useShortPrLink = false): Promise<string> {
const changelog = `${changelogTitle}
${changelogDescription}

${stringifyReleases(this.#releases, this.#changes, this.#repoUrl)}
${stringifyReleases(this.#releases, this.#changes, this.#repoUrl, useShortPrLink)}

${stringifyLinkReferenceDefinitions(
this.#repoUrl,
Expand Down
37 changes: 34 additions & 3 deletions src/get-new-changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Octokit } from '@octokit/rest';
import { strict as assert } from 'assert';

import { ConventionalCommitType } from './constants';
import { getOwnerAndRepoFromUrl } from './repo';
import { runCommand, runCommandAndSplit } from './run-command';

Expand All @@ -17,6 +18,12 @@ export type AddNewCommitsOptions = {
useShortPrLink: boolean;
};

// Get array of all ConventionalCommitType values
const conventionalCommitTypes = Object.values(ConventionalCommitType);

// Create a regex pattern that matches any of the ConventionalCommitTypes
const typesWithPipe = conventionalCommitTypes.join('|');

/**
* Get all commit hashes included in the given commit range.
*
Expand All @@ -35,6 +42,27 @@ async function getCommitHashesInRange(
return await runCommandAndSplit('git', revListArgs);
}

/**
* Remove outer backticks if present in the given message.
*
* @param message - The changelog entry message.
* @returns The message without outer backticks.
*/
function removeOuterBackticksIfPresent(message: string) {
return message.replace(/^`(.*)`$/u, '$1');
}

/**
* Remove Conventional Commit prefix if it exists in the given message.
*
* @param message - The changelog entry message.
* @returns The message without Conventional Commit prefix.
*/
function removeConventionalCommitPrefixIfPresent(message: string) {
const regex = new RegExp(`^(${typesWithPipe})(\\([^)]*\\))?:\\s*`, 'iu');
return message.replace(regex, '');
}

/**
* Get commit details for each given commit hash.
*
Expand Down Expand Up @@ -90,6 +118,12 @@ async function getCommits(
description = changelogEntry; // This may be string 'null' to indicate no description

if (description !== 'null') {
// Remove outer backticks if present. Example: `feat: new feature description` -> feat: new feature description
description = removeOuterBackticksIfPresent(description);

// Remove Conventional Commit prefix if present. Example: feat: new feature description -> new feature description
description = removeConventionalCommitPrefixIfPresent(description);

// Make description coming from `CHANGELOG entry:` start with an uppercase letter
description =
description.charAt(0).toUpperCase() + description.slice(1);
Expand All @@ -101,9 +135,6 @@ async function getCommits(
if (description !== 'null') {
const prLabels = await getPrLabels(repoUrl, prNumber);

// TODO: eliminate this debug log
console.log(`PR #${prNumber} labels:`, prLabels);

if (prLabels.includes('no-changelog')) {
description = 'null'; // Has the no-changelog label, use string 'null' to indicate no description
}
Expand Down
Loading