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
39 changes: 0 additions & 39 deletions .bumpy/_changelog-formatter.ts

This file was deleted.

2 changes: 1 addition & 1 deletion .bumpy/_config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"baseBranch": "main",
"changelog": "./_changelog-formatter.ts"
"changelog": ["github", { "internalAuthors": ["theoephraim", "philmillman"] }]
}
158 changes: 158 additions & 0 deletions docs/changelog-formatters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Changelog Formatters

Bumpy generates `CHANGELOG.md` entries automatically when releasing.

There are several built-in formatters, or you can provide a custom function.

## Built-in formatters

### `default`

Simple markdown formatter. Produces a version heading, date, and bullet points from bump file summaries. _This is the default -- no settting required to enable_.

**Example output:**

```markdown
## 1.2.0

_2026-04-19_

- Added support for custom themes
- Fixed a bug with config loading
```

No options are available for the default formatter.

### `github`

Enhanced formatter that adds PR links, commit links, and contributor attribution. Requires the `gh` CLI to be installed and authenticated.

Enable in your `.bumpy/_config.json` using `"changelog": "github",`

Or with options using a tuple format:

```json
{
"changelog": ["github", { "internalAuthors": ["username1"] }]
}
```

**Example output:**

```markdown
## 1.2.0

_2026-04-19_

- [#42](https://github.com/myorg/myrepo/pull/42) [`abc1234`](https://github.com/myorg/myrepo/commit/abc1234) Thanks [@contributor](https://github.com/contributor)! - Added support for custom themes
- [#43](https://github.com/myorg/myrepo/pull/43) [`def5678`](https://github.com/myorg/myrepo/commit/def5678) - Fixed a bug with config loading
```

#### Options

| Option | Type | Default | Description |
| ------------------- | ---------- | ------- | ----------------------------------------------------------------- |
| `repo` | `string` | — | `"owner/repo"` slug. Auto-detected from `gh` CLI if not provided. |
| `thankContributors` | `boolean` | `true` | Whether to include "Thanks @user" messages for contributors. |
| `internalAuthors` | `string[]` | `[]` | GitHub usernames (without `@`) to skip "Thanks" messages for. |

#### Bump file metadata overrides

The GitHub formatter also supports metadata lines in bump file summaries to override auto-detected values:

```markdown
---
'my-package': minor
---

pr: 42
commit: abc1234
author: @someuser

Added support for custom themes
```

These overrides take precedence over git-derived info, which is useful when the automatic detection doesn't find the right PR or author.

## Custom formatters

You can write your own formatter as a TypeScript or JavaScript module. The module should export a `ChangelogFormatter` function as its default export.

Create a file, for example `.bumpy/_changelog-formatter.ts`, and then reference it in your config:

```json
{
"changelog": "./.bumpy/_changelog-formatter.ts"
}
```

### Formatter interface

```typescript
import type { ChangelogFormatter } from '@varlock/bumpy';

interface ChangelogContext {
release: PlannedRelease;
/** Bump files that contributed to this release */
bumpFiles: BumpFile[];
/** ISO date string (YYYY-MM-DD) */
date: string;
}

type ChangelogFormatter = (ctx: ChangelogContext) => string | Promise<string>;
```

### Example: custom formatter

```typescript
import type { ChangelogFormatter } from '@varlock/bumpy';

const formatter: ChangelogFormatter = (ctx) => {
const { release, bumpFiles, date } = ctx;
const lines: string[] = [];
lines.push(`## ${release.newVersion}`);
lines.push('');
lines.push(`_${date}_`);
lines.push('');

const relevantBumpFiles = bumpFiles.filter((bf) => release.bumpFiles.includes(bf.id));

for (const bf of relevantBumpFiles) {
if (bf.summary) {
lines.push(`- ${bf.summary.split('\n')[0]}`);
}
}

if (release.isDependencyBump && relevantBumpFiles.length === 0) {
lines.push('- Updated dependencies');
}

lines.push('');
return lines.join('\n');
};

export default formatter;
```

Custom formatters can also accept options. Export a factory function that returns a `ChangelogFormatter`:

```typescript
import type { ChangelogFormatter } from '@varlock/bumpy';

export default function createFormatter(options: { emoji?: boolean }): ChangelogFormatter {
return (ctx) => {
const prefix = options.emoji ? '🚀 ' : '';
const lines: string[] = [];
lines.push(`## ${prefix}${ctx.release.newVersion}`);
// ... rest of formatting
lines.push('');
return lines.join('\n');
};
}
```

```json
{
"changelog": ["./.bumpy/_changelog-formatter.ts", { "emoji": true }]
}
```
12 changes: 2 additions & 10 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,9 @@ Per-package settings can be defined in two places:

## Changelog formatters

Set `changelog` in config to control how changelog entries are generated:
Set `changelog` in config to control how changelog entries are generated. Built-in options are `"default"` and `"github"`, or you can provide a path to a custom formatter module.

- **`"default"`** — simple markdown with dates and bump file descriptions
- **`"github"`** — includes PR links and author attribution (requires `GH_TOKEN`)
- **Custom** — path to a TypeScript or JavaScript module that exports a formatter function

```json
{
"changelog": ["github", { "repo": "myorg/myrepo" }]
}
```
See the [Changelog Formatters](./changelog-formatters.md) docs for full details and examples.

## Example config

Expand Down
2 changes: 2 additions & 0 deletions packages/bumpy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"version",
"version management"
],
"homepage": "https://bumpy.varlock.dev",
"bugs": "https://github.com/dmno-dev/bumpy/issues",
"license": "MIT",
"author": "dmno-dev",
"repository": {
Expand Down
11 changes: 8 additions & 3 deletions packages/bumpy/src/core/changelog-github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { ChangelogContext, ChangelogFormatter } from './changelog.ts';
export interface GithubChangelogOptions {
/** "owner/repo" — auto-detected from gh CLI if not provided */
repo?: string;
/** Whether to include "Thanks @user" messages for contributors (default: true) */
thankContributors?: boolean;
/** GitHub usernames (without @) to skip "Thanks" messages for (e.g. internal team members) */
internalAuthors?: string[];
}
Expand All @@ -15,9 +17,11 @@ export interface GithubChangelogOptions {
* Usage in config:
* "changelog": "github"
* "changelog": ["github", { "repo": "dmno-dev/bumpy" }]
* "changelog": ["github", { "repo": "dmno-dev/bumpy", "internalAuthors": ["theoephraim"] }]
* "changelog": ["github", { "thankContributors": false }]
* "changelog": ["github", { "internalAuthors": ["theoephraim"] }]
*/
export function createGithubFormatter(options: GithubChangelogOptions = {}): ChangelogFormatter {
const thankContributors = options.thankContributors ?? true;
const internalAuthorsSet = new Set((options.internalAuthors ?? []).map((a) => a.toLowerCase()));

return async (ctx: ChangelogContext) => {
Expand Down Expand Up @@ -47,7 +51,7 @@ export function createGithubFormatter(options: GithubChangelogOptions = {}): Cha
const firstLine = linkifyIssueRefs(summaryLines[0]!, serverUrl, repoSlug);

// Build the prefix: PR link, commit link, thanks
const prefix = formatPrefix(gitInfo, serverUrl, repoSlug, internalAuthorsSet);
const prefix = formatPrefix(gitInfo, serverUrl, repoSlug, thankContributors, internalAuthorsSet);

lines.push(`-${prefix ? ` ${prefix} -` : ''} ${firstLine}`);

Expand Down Expand Up @@ -239,6 +243,7 @@ function formatPrefix(
info: BumpFileGitInfo,
serverUrl: string,
repo: string | undefined,
thankContributors: boolean,
internalAuthors: Set<string>,
): string {
const parts: string[] = [];
Expand All @@ -252,7 +257,7 @@ function formatPrefix(
parts.push(`[\`${short}\`](${serverUrl}/${repo}/commit/${info.commitHash})`);
}

if (info.author && !internalAuthors.has(info.author.toLowerCase())) {
if (thankContributors && info.author && !internalAuthors.has(info.author.toLowerCase())) {
parts.push(`Thanks [@${info.author}](${serverUrl}/${info.author})!`);
}

Expand Down