Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
90 changes: 90 additions & 0 deletions docs/src/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,76 @@ changelog:
- "^fix(\\(\\w+\\))?:"
```

### Custom Changelog Entries from PR Descriptions

By default, the changelog entry for a PR is generated from its title. However,
PR authors can override this by adding a "Changelog Entry" section to the PR
description. This allows for more detailed, user-facing changelog entries without
cluttering the PR title.

To use this feature, add a markdown heading (level 2 or 3) titled "Changelog Entry"
to your PR description, followed by the desired changelog text:

```markdown
### Description

Add `foo` function, and add unit tests to thoroughly check all edge cases.

### Changelog Entry

Add a new function called `foo` which prints "Hello, world!"

### Issues

Closes #123
```

The text under "Changelog Entry" will be used verbatim in the changelog instead
of the PR title. If no such section is present, the PR title is used as usual.

#### Advanced Features

1. **Multiple Entries**: If you use multiple top-level bullet points in the
"Changelog Entry" section, each bullet will become a separate changelog entry:

```markdown
### Changelog Entry

- Add OAuth2 authentication
- Add two-factor authentication
- Add session management
```

2. **Nested Content**: Indented bullets (4+ spaces or tabs) are preserved as
nested content under their parent entry:

```markdown
### Changelog Entry

- Add authentication system
- OAuth2 support
- Two-factor authentication
- Session management
```

This will generate:
```markdown
- Add authentication system by @user in [#123](url)
- OAuth2 support
- Two-factor authentication
- Session management
```

Note: Nested items do NOT get author/PR attribution - only the top-level entry does.

3. **Plain Text**: If no bullets are used, the entire content is treated as a
single changelog entry. Multi-line text is automatically joined with spaces
to ensure valid markdown output.

4. **Content Isolation**: Only content within the "Changelog Entry" section is
included in the changelog. Other sections (Description, Issues, etc.) are
ignored.

### Scope Grouping

Changes are automatically grouped by scope (e.g., `feat(api):` groups under "Api"):
Expand All @@ -125,6 +195,26 @@ changelog:
scopeGrouping: true # default
```

Scope headers are only shown for scopes with more than one entry. Entries without
a scope are listed at the bottom of each category section without a sub-header.

Example output with scope grouping:

```text
### New Features

#### Api

- feat(api): add user endpoint by @alice in [#1](https://github.com/...)
- feat(api): add auth endpoint by @bob in [#2](https://github.com/...)

#### Ui

- feat(ui): add dashboard by @charlie in [#3](https://github.com/...)

- feat: general improvement by @dave in [#4](https://github.com/...)
```

### Configuration Options

| Option | Description |
Expand Down
9 changes: 8 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/node_modules/'],
testPathIgnorePatterns: [
'<rootDir>/dist/',
'<rootDir>/node_modules/',
'<rootDir>/src/.*/fixtures/',
],
modulePathIgnorePatterns: ['<rootDir>/dist/'],
transformIgnorePatterns: [
'node_modules/(?!(dot-prop|configstore)/)',
],
moduleNameMapper: {
'^marked$': '<rootDir>/node_modules/marked/lib/marked.umd.js',
},
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,8 @@
"volta": {
"node": "22.12.0",
"yarn": "1.22.19"
},
"dependencies": {
"marked": "^17.0.1"
}
}
104 changes: 104 additions & 0 deletions src/utils/__tests__/__snapshots__/changelog-extract.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`extractChangelogEntry basic extraction extracts from ## Changelog Entry section 1`] = `
[
{
"text": "Add a new function called \`foo\` which prints "Hello, world!"",
},
]
`;

exports[`extractChangelogEntry basic extraction extracts from ### Changelog Entry section 1`] = `
[
{
"text": "Add a new function called \`foo\` which prints "Hello, world!"",
},
]
`;

exports[`extractChangelogEntry basic extraction handles changelog entry at end of body 1`] = `
[
{
"text": "This is the last section with no sections after it.",
},
]
`;

exports[`extractChangelogEntry bullet point handling handles multiple top-level bullets with nested content 1`] = `
[
{
"nestedContent": " - Detail A
- Detail B",
"text": "First feature",
},
{
"nestedContent": " - Detail C",
"text": "Second feature",
},
]
`;

exports[`extractChangelogEntry bullet point handling handles nested bullets 1`] = `
[
{
"nestedContent": " - Nested item 1
- Nested item 2",
"text": "Main entry",
},
]
`;

exports[`extractChangelogEntry bullet point handling parses multiple bullets as separate entries 1`] = `
[
{
"text": "First entry",
},
{
"text": "Second entry",
},
{
"text": "Third entry",
},
]
`;

exports[`extractChangelogEntry edge cases case-insensitive heading match 1`] = `
[
{
"text": "This should still be extracted.",
},
]
`;

exports[`extractChangelogEntry edge cases handles CRLF line endings 1`] = `
[
{
"text": "Entry with CRLF",
},
{
"text": "Another entry",
},
]
`;

exports[`extractChangelogEntry edge cases treats multi-line plain text as single entry 1`] = `
[
{
"text": "This is a multi-line changelog entry that spans several lines.",
},
]
`;

exports[`extractChangelogEntry paragraph and list combinations handles paragraph followed by list with blank line 1`] = `
[
{
"text": "Intro paragraph:",
},
{
"text": "First item",
},
{
"text": "Second item",
},
]
`;
127 changes: 127 additions & 0 deletions src/utils/__tests__/__snapshots__/changelog-generate.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`generateChangesetFromGit category matching applies global exclusions 1`] = `
"### Features

- Normal feature by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)"
`;

exports[`generateChangesetFromGit category matching matches PRs to categories based on labels 1`] = `
"### Features

- Feature PR by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)

### Bug Fixes

- Bug fix PR by @bob in [#2](https://github.com/test-owner/test-repo/pull/2)"
`;

exports[`generateChangesetFromGit category matching supports wildcard category matching 1`] = `
"### Changes

- Any PR by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)"
`;

exports[`generateChangesetFromGit commit patterns labels take precedence over commit_patterns 1`] = `
"### Labeled Features

- feat: labeled feature by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)

### Pattern Features

- feat: pattern-only feature by @bob in [#2](https://github.com/test-owner/test-repo/pull/2)"
`;

exports[`generateChangesetFromGit commit patterns matches PRs based on commit_patterns 1`] = `
"### Features

- feat: add new feature by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)

### Bug Fixes

- fix: fix bug by @bob in [#2](https://github.com/test-owner/test-repo/pull/2)"
`;

exports[`generateChangesetFromGit commit patterns uses default conventional commits config when no config exists 1`] = `
"### New Features ✨

- feat: new feature by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)

### Bug Fixes 🐛

- fix: bug fix by @bob in [#2](https://github.com/test-owner/test-repo/pull/2)

### Documentation 📚

- docs: update readme by @charlie in [#3](https://github.com/test-owner/test-repo/pull/3)"
`;

exports[`generateChangesetFromGit custom changelog entries handles multiple bullets in changelog entry 1`] = `
"### New Features ✨

- First entry by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)
- Second entry by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)
- Third entry by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)"
`;

exports[`generateChangesetFromGit custom changelog entries handles nested bullets in changelog entry 1`] = `
"### New Features ✨

- Main entry by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)
- Nested item 1
- Nested item 2"
`;

exports[`generateChangesetFromGit custom changelog entries uses custom entry from PR body 1`] = `
"### New Features ✨

- Custom changelog entry by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)"
`;

exports[`generateChangesetFromGit output formatting escapes underscores in titles 1`] = `"- Serialized \\_meta in [#123](https://github.com/test-owner/test-repo/pull/123)"`;

exports[`generateChangesetFromGit output formatting formats local commit with short SHA 1`] = `"- Upgraded the kernel in [abcdef12](https://github.com/test-owner/test-repo/commit/abcdef1234567890)"`;

exports[`generateChangesetFromGit output formatting handles multiple commits 1`] = `
"- Upgraded the kernel in [abcdef12](https://github.com/test-owner/test-repo/commit/abcdef1234567890)
- Upgraded the manifold by @alice in [#123](https://github.com/test-owner/test-repo/pull/123)
- Refactored the crankshaft by @bob in [#456](https://github.com/test-owner/test-repo/pull/456)

_Plus 1 more_"
`;

exports[`generateChangesetFromGit output formatting handles null PR author gracefully 1`] = `"- Upgraded the kernel in [#123](https://github.com/test-owner/test-repo/pull/123)"`;

exports[`generateChangesetFromGit output formatting uses PR number and author from remote 1`] = `"- Upgraded the kernel by @sentry in [#123](https://github.com/test-owner/test-repo/pull/123)"`;

exports[`generateChangesetFromGit output formatting uses PR number when available locally 1`] = `"- Upgraded the kernel in [#123](https://github.com/test-owner/test-repo/pull/123)"`;

exports[`generateChangesetFromGit output formatting uses PR title from GitHub instead of commit message 1`] = `
"### New Features ✨

- feat: A much better PR title with more context by @sentry in [#123](https://github.com/test-owner/test-repo/pull/123)"
`;

exports[`generateChangesetFromGit scope grouping groups PRs by scope when multiple entries exist 1`] = `
"### Features

#### Api

- feat(api): add endpoint 1 by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)
- feat(api): add endpoint 2 by @bob in [#2](https://github.com/test-owner/test-repo/pull/2)

- feat(ui): add button by @charlie in [#3](https://github.com/test-owner/test-repo/pull/3)"
`;

exports[`generateChangesetFromGit scope grouping places scopeless entries at bottom 1`] = `
"### Features

#### Api

- feat(api): scoped feature 1 by @alice in [#1](https://github.com/test-owner/test-repo/pull/1)
- feat(api): scoped feature 2 by @bob in [#2](https://github.com/test-owner/test-repo/pull/2)

#### Other

- feat: scopeless feature by @charlie in [#3](https://github.com/test-owner/test-repo/pull/3)"
`;
Loading
Loading