Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
93d2aeb
parse "CHANGELOG entry:"
HowardBraham Jul 9, 2025
7ecd94d
use Octokit to get labels
HowardBraham Jul 30, 2025
8f6dd3e
fixed existing yarn test issues
HowardBraham Sep 1, 2025
6c34f9b
should support useChangelogEntry=true
HowardBraham Sep 1, 2025
d6d7786
Fixed compatibility test by locking "@types/node" to "^22.18.0"
HowardBraham Sep 1, 2025
d92b916
no more hardcoded repo
HowardBraham Sep 3, 2025
1c6d96b
Make description start with an uppercase letter
HowardBraham Sep 3, 2025
2988fe3
ignore commits including certain strings
HowardBraham Sep 3, 2025
3c3cb18
Parse changelog entry adjustments (#248)
gauthierpetetin Sep 17, 2025
fa946ff
Improve regex to require semicolon and remove case sensitivity
gauthierpetetin Sep 17, 2025
40dc385
Only initialize Octokit if we need to fetch PR labels
HowardBraham Sep 17, 2025
8d69b84
Support whitespace between the type and the semicolon
gauthierpetetin Sep 17, 2025
3ea27a8
shave off a bit of time complexity
HowardBraham Sep 17, 2025
42b4068
Support edge case where PR description includes multiple changelog en…
gauthierpetetin Sep 18, 2025
f5ac2da
Make changelog generation idempotent for multi-line changelog entries
gauthierpetetin Sep 18, 2025
f262bce
Gracefully handle edge case where changelog entries include links to …
gauthierpetetin Sep 18, 2025
1ff6678
Add more explicit comment
gauthierpetetin Sep 18, 2025
ac01428
Handle edge case where repoUrl is undefined
gauthierpetetin Sep 18, 2025
9b65fa4
added a CHANGELOG entry 🙂
HowardBraham Sep 18, 2025
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Look for `CHANGELOG entry:` in the PR description, and look for `no-changelog` label, then apply this to the generated CHANGELOG, plus properly categorize by Conventional Commit type, including our custom ConventionalCommitType.RELEASE (#247)

## [5.0.2]

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"test:watch": "jest --watch"
},
"dependencies": {
"@octokit/rest": "^20.0.0",
"diff": "^5.0.0",
"execa": "^5.1.1",
"semver": "^7.3.5",
Expand All @@ -56,6 +57,7 @@
"@types/cross-spawn": "^6.0.2",
"@types/diff": "^5.0.0",
"@types/jest": "^26.0.23",
"@types/node": "^22.18.0",
"@types/semver": "^7.3.6",
"@types/yargs": "^16.0.1",
"@typescript-eslint/eslint-plugin": "^5.42.1",
Expand Down
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
34 changes: 32 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ type UpdateOptions = {
* The package rename properties, used in case of package is renamed
*/
packageRename?: PackageRename;
/**
* Whether to use `CHANGELOG entry:` from the commit body and the no-changelog label
*/
useChangelogEntry: boolean;
/**
* Whether to use short PR links in the changelog entries
*/
useShortPrLink: boolean;
};

/**
Expand All @@ -107,9 +115,12 @@ type UpdateOptions = {
* @param options.projectRootDirectory - The root project directory.
* @param options.tagPrefix - The prefix used in tags before the version number.
* @param options.formatter - A custom Markdown formatter to use.
* @param options.packageRename - The package rename properties.
* @param options.packageRename - The package rename properties. Optional.
* Only needed when retrieving a changelog for a renamed package
* (e.g., utils -> @metamask/utils).
* @param options.autoCategorize - Whether to categorize commits automatically based on their messages.
* An optional, which is required only in case of package renamed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This removed section was originally part of the packageRename option (see https://github.com/MetaMask/auto-changelog/pull/182/files#diff-fa8d4e24d8399e8350f1c8bad05df53e8032ea995835bf911507015e2db61cdd), but it was moved the wrong section later when autoCategorize was added. We can either move it under packageRename or simply remove it as you did.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it was removed in this commit. @HowardBraham was it intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm that was probably an automated rewrite of the JSDoc after I changed the parameters. But actually what does the original even mean? An optional, which is required only in case of package renamed sounds like a mistake itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally wrote it, but reading it now, it feels unclear. What I meant was that it’s only necessary when retrieving a changelog for a package that has been renamed. (eg, utils -> @metamask/utils)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a commit to add your suggestion: 1ff6678

* @param options.useChangelogEntry - Whether to read `CHANGELOG entry:` from the commit body and the no-changelog label.
* @param options.useShortPrLink - Whether to use short PR links in the changelog entries.
*/
async function update({
changelogPath,
Expand All @@ -121,6 +132,8 @@ async function update({
formatter,
packageRename,
autoCategorize,
useChangelogEntry,
useShortPrLink,
}: UpdateOptions) {
const changelogContent = await readChangelog(changelogPath);

Expand All @@ -134,6 +147,8 @@ async function update({
formatter,
packageRename,
autoCategorize,
useChangelogEntry,
useShortPrLink,
});

if (newChangelogContent) {
Expand Down Expand Up @@ -331,6 +346,17 @@ async function main() {
description: `Expect the changelog to be formatted with Prettier.`,
type: 'boolean',
})
.option('useChangelogEntry', {
default: false,
description:
'Read `CHANGELOG entry:` from the commit body and the no-changelog label',
type: 'boolean',
})
.option('useShortPrLink', {
default: false,
description: 'Use short PR links in the changelog entries',
type: 'boolean',
})
.epilog(updateEpilog),
)
.command(
Expand Down Expand Up @@ -388,6 +414,8 @@ async function main() {
tagPrefixBeforePackageRename,
autoCategorize,
prLinks,
useChangelogEntry,
useShortPrLink,
} = argv;
let { currentVersion } = argv;

Expand Down Expand Up @@ -517,6 +545,8 @@ async function main() {
formatter,
packageRename,
autoCategorize,
useChangelogEntry: Boolean(useChangelogEntry),
useShortPrLink: Boolean(useShortPrLink),
});
} else if (command === 'validate') {
let packageRename: PackageRename | undefined;
Expand Down
44 changes: 36 additions & 8 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@ export type Version = string;
* A [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) type.
*/
export enum ConventionalCommitType {
/**
* fix: a commit of the type fix patches a bug in your codebase
*/
Fix = 'fix',
/**
* a commit of the type feat introduces a new feature to the codebase
*/
Feat = 'feat',
FEAT = 'feat', // A new feature
FIX = 'fix', // A bug fix
DOCS = 'docs', // Documentation only changes
STYLE = 'style', // Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
REFACTOR = 'refactor', // A code change that neither fixes a bug nor adds a feature
PERF = 'perf', // A code change that improves performance
TEST = 'test', // Adding missing tests or correcting existing tests
BUILD = 'build', // Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
CI = 'ci', // Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
CHORE = 'chore', // Other changes that don't modify src or test files (use this sparingly)
REVERT = 'revert', // Reverts a previous commit

// Custom types for MetaMask
BUMP = 'bump', // A version bump to dependencies
RELEASE = 'release', // A release commit, made on a release branch or to0 support the release process
}

/**
* Change categories.
*
Expand Down Expand Up @@ -58,6 +66,11 @@ export enum ChangeCategory {
* For any changes that have yet to be categorized.
*/
Uncategorized = 'Uncategorized',

/**
* For changes that should be excluded from the changelog.
*/
Excluded = 'Excluded',
}

/**
Expand All @@ -78,3 +91,18 @@ export const orderedChangeCategories: ChangeCategory[] = [
* The header for the section of the changelog listing unreleased changes.
*/
export const unreleased = 'Unreleased';

/**
* Lowercase keywords that indicate a commit should be excluded from the changelog.
*/
export const keywordsToIndicateExcluded: string[] = [
'Bump main version to',
'changelog',
'cherry-pick',
'cp-',
'e2e',
'flaky test',
'INFRA-',
'merge',
'New Crowdin translations',
].map((word) => word.toLowerCase());
Loading
Loading