Release Notes Generation
Learn how to customize Versu's release notes generation.
Changelogs vs Release Notes
Alongside changelogs, Versu generates release notes: a self-contained summary of a single release, written to a separate file (default RELEASE.md). While a changelog accumulates the full history of a module, release notes describe only what changed in the current release - making them perfect as the body of a GitHub release.
| Changelog | Release Notes | |
|---|---|---|
| Default file | CHANGELOG.md | RELEASE.md |
| Configuration key | changelog | release |
| Content | Full history, new entries prepended | Summary of the current release |
| Committed by default | Yes (with version changes) | No (opt-in via --commit-release-notes) |
| Toggle | --generate-changelog | --generate-release-notes |
Both are rendered by the same engine (conventional-changelog-writer), so everything you learned in the Changelog Generation guide - grouping, transforms, handlebars templates - applies here too.
Basic Configuration
Release notes are configured through the top-level release key, with the exact same structure as changelog: a root and a module section, each accepting a context and options passed to conventional-changelog-writer.
export default {
// Other configuration options
release: {
// Release notes configuration options
root: {
// Root release notes configuration (optional)
context: { /* */ }, // Context<Commit>
options: { /* */ }, // Options<Commit>
},
module: {
// Module release notes configuration (optional)
context: { /* */ }, // Context<Commit>
options: { /* */ }, // Options<Commit>
},
},
};Merged with defaults
Your configuration is deep-merged with Versu's defaults. If you only override options.mainTemplate, the default commit grouping, transform and partials are kept.
Default Output
Versu ships with release notes templates out of the box, so you get useful release notes with zero configuration. Commits are grouped by type with the same emoji sections used in changelogs (✨ Features, 🐛 Bug Fixes, ...).
To illustrate, let's reuse the running example from Multi-Module Projects - common, auth, api, web and mobile - right after common received a new feature and the bump cascaded through all dependents.
Module Release Notes
Each versioned module gets its own release notes file (e.g., packages/common/RELEASE.md) summarizing its release, ending with a compare link between the previous and current tag:
## What's changed
### ✨ Features
- Add shared validation helper ([abc1234](https://github.com/my-org/my-monorepo/commit/abc1234))
**Full Changelog:** [`2.3.1...2.4.0`](https://github.com/my-org/my-monorepo/compare/common@2.3.1...common@2.4.0)
---
*Generated by versu*Root Release Notes
In multi-module projects, the root RELEASE.md starts with a Module Releases overview - one line per released module, linking its changelog and a version compare - followed by any changes not belonging to a specific module:
## What's changed
### 🚀 Module Releases
- [common](packages/common/CHANGELOG.md) - [2.4.0](https://github.com/my-org/my-monorepo/compare/common@2.3.1...common@2.4.0)
- [auth](packages/auth/CHANGELOG.md) - [1.6.0](https://github.com/my-org/my-monorepo/compare/auth@1.5.2...auth@1.6.0)
- [api](packages/api/CHANGELOG.md) - [1.3.0](https://github.com/my-org/my-monorepo/compare/api@1.2.0...api@1.3.0)
- [web](packages/web/CHANGELOG.md) - [3.2.0](https://github.com/my-org/my-monorepo/compare/web@3.1.0...web@3.2.0)
- [mobile](packages/mobile/CHANGELOG.md) - [0.10.0](https://github.com/my-org/my-monorepo/compare/mobile@0.9.0...mobile@0.10.0)
## 📝 Other Changes
#### ✨ Features
- Add shared validation helper ([abc1234](https://github.com/my-org/my-monorepo/commit/abc1234))Modules without a declared version (like the root in the running example) are excluded from the Module Releases list.
Fresh files, not history
Release notes share the prepend mechanism with changelogs: if the target file already exists, new content is inserted at the prependPlaceholder. However, the default release templates intentionally don't emit the placeholder - release notes are meant to be regenerated per release, not accumulated. Since they aren't committed by default, each CI run starts from a clean checkout and renders fresh files.
Customizing Release Notes
All the customization options from the Changelog Generation guide work here: groupBy, transform, commitGroupsSort and the handlebars templates (mainTemplate, headerPartial, commitPartial, footerPartial).
As an example, let's extend the module release notes with install instructions:
import * as fs from "fs";
import * as path from "path";
const cwd = import.meta.dirname;
const releaseMainTemplate = fs.readFileSync(
path.join(cwd, "templates", "release-main.hbs"),
"utf-8",
);
export default {
// Other configuration options
release: {
module: {
options: {
mainTemplate: releaseMainTemplate,
},
},
},
};Since the configuration is merged with the defaults, the default grouping, transform and commit partial keep working inside your custom template.
Template Variables
Versu provides these context variables to both changelog and release notes templates:
| Variable | Description |
|---|---|
version | The new version being released (only set for final releases) |
currentTag | Tag created for this release (e.g., common@2.4.0) |
previousTag | The module's previous release tag |
previousTagVersion | Version extracted from the previous tag |
linkCompare | Whether both tags exist, enabling a compare link |
repoUrl, host, owner, repository | Repository information detected from Git |
prepend | Whether an existing file is being updated |
provider | Detected provider (e.g., github) |
Root templates additionally receive moduleResults: an array with one entry per processed module (id, name, path, type, from, to, bumpType, declaredVersion, isRelease, tagName) - this is what powers the Module Releases section.
The following handlebars helpers are also registered: eq, ne, and, or, not, and getModuleTagName (builds a module's tag name from a name and a version, as used by the default root template for the compare links).
Controlling Generation
Release notes generation is enabled by default and can be controlled through the CLI or the GitHub Action:
| CLI flag | Action input | Description |
|---|---|---|
--no-generate-release-notes | generate-release-notes: false | Disable release notes generation |
--release-notes-filename | release-notes-filename | Change the output filename (default RELEASE.md) |
--commit-release-notes | commit-release-notes | Include release notes in the release commit (default off) |
The paths of the generated files are reported in the runner result (releaseNotesPaths) and in the action's release-notes-paths output as a JSON array of { moduleId, path }.
Publishing GitHub Releases
The typical use for release notes is feeding them into GitHub releases. Combining the created-tags and release-notes-paths outputs:
- name: Run Versu
id: versu
uses: versuhq/versu@v3
# ... inputs
- name: Create GitHub releases
if: steps.versu.outputs.bumped == 'true'
uses: actions/github-script@v9
env:
CREATED_TAGS: ${{ steps.versu.outputs.created-tags }}
NOTES_PATHS: ${{ steps.versu.outputs.release-notes-paths }}
with:
script: |
const fs = require("fs");
const createdTags = JSON.parse(process.env.CREATED_TAGS);
const notesPaths = JSON.parse(process.env.NOTES_PATHS);
for (const { moduleId, tag } of createdTags) {
const notes = notesPaths.find((entry) => entry.moduleId === moduleId);
await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tag,
name: tag,
body: notes ? fs.readFileSync(notes.path, "utf8") : undefined,
// Fall back to GitHub's auto-generated notes if none were rendered
generate_release_notes: !notes,
});
}Each released module gets its own GitHub release, with its tag as the title and its generated release notes as the body. The workflow's GITHUB_TOKEN needs the contents: write permission to create releases.
Next Steps
- Changelog Generation - The changelog counterpart
- Pre-release Versions - Pre-release setup
- GitHub Action - Full CI integration reference
Ready to set up pre-releases? Check out the Pre-release Versions guide!
