Documentation
¶
Overview ¶
Package changelog renders a release entry into a per-package CHANGELOG.md file in Keep-a-Changelog format.
Insertion is non-destructive: the new entry is prepended above the existing version history (the first "## " heading), so any prior content (release-please output, hand-written notes, even broken markdown) is preserved verbatim. This is the deliberate "hard cut" monorel makes when migrating a CHANGELOG that was previously maintained by another tool: new entries land in Keep-a-Changelog shape; old entries stay in whatever shape they were written in.
If the target file doesn't exist, it's created with a small standard preamble. If the file exists but has no "## " heading yet (e.g. a pristine preamble-only file), the new entry is appended at the end.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Insert ¶
Insert places entry's rendered text into existing, returning the updated content. Three cases:
- existing has a "## " heading: the entry is inserted above the first one, so the new release is the topmost entry.
- existing has no "## " heading (e.g., a preamble-only file): the entry is appended at the end of existing.
- existing is empty: a default Keep-a-Changelog preamble is prepended and the entry is appended below it.
In all three cases, existing is treated as opaque and preserved verbatim except for the inserted block. This is the "hard cut" guarantee that makes migrating from another tool's CHANGELOG format safe: monorel's new entries land in Keep-a-Changelog shape alongside whatever shape the existing entries were written in.
Example ¶
ExampleInsert prepends a release entry above the existing version history of a CHANGELOG.md. The entry's body is monorel-shaped (Keep-a-Changelog headings) but the existing content is preserved verbatim regardless of whatever format it was previously written in.
package main
import (
"fmt"
"monorel.disaresta.com/changelog"
)
func main() {
existing := `# Changelog
## [1.6.1] - 2026-04-30
Bumped some deps.
`
entry := &changelog.Entry{
Version: "v1.7.0",
Date: "2026-05-01",
Minor: []string{"Adds Lazy() helper for deferred field evaluation."},
Patch: []string{"Fixes a panic when a transport's Send returns nil."},
}
updated := changelog.Insert(existing, entry)
fmt.Print(updated)
}
Output: # Changelog ## [1.7.0] - 2026-05-01 ### Minor Changes - Adds Lazy() helper for deferred field evaluation. ### Patch Changes - Fixes a panic when a transport's Send returns nil. ## [1.6.1] - 2026-04-30 Bumped some deps.
func ParseTopEntry ¶
ParseTopEntry returns the version string and rendered body of the first `## [VERSION] - DATE` section in content. ok is false when there is no such section.
The body is everything from the next line after the heading up to the next `## ` heading (or end of file), trimmed.
func Today ¶
func Today() string
Today returns today's UTC date in YYYY-MM-DD form. Use this when constructing an Entry from production code; tests should pass a fixed date directly.
func WriteFile ¶
WriteFile inserts entry into the file at path. Creates the file (and parent directories) if it doesn't exist.
Returns an error if entry is empty (caller should skip the call rather than write a no-op file).
Example ¶
ExampleWriteFile shows the path most callers actually take: produce an Entry and write it to disk. WriteFile reads the existing file (if any), runs Insert, and writes the result back atomically. A non-existent target is created with a default Keep-a-Changelog preamble.
package main
import (
"fmt"
"os"
"path/filepath"
"monorel.disaresta.com/changelog"
)
func main() {
dir, _ := os.MkdirTemp("", "monorel-changelog")
defer os.RemoveAll(dir)
path := filepath.Join(dir, "CHANGELOG.md")
first := &changelog.Entry{
Version: "v1.0.0",
Date: "2026-04-29",
Major: []string{"Initial release."},
}
if err := changelog.WriteFile(path, first); err != nil {
fmt.Println("error:", err)
return
}
second := &changelog.Entry{
Version: "v1.1.0",
Date: "2026-05-01",
Minor: []string{"Add WithFields helper."},
}
if err := changelog.WriteFile(path, second); err != nil {
fmt.Println("error:", err)
return
}
// The newer entry lands above the older one; the preamble that
// was written on the first call is preserved.
got, _ := os.ReadFile(path)
fmt.Print(string(got))
}
Output: # Changelog All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.1.0] - 2026-05-01 ### Minor Changes - Add WithFields helper. ## [1.0.0] - 2026-04-29 ### Major Changes - Initial release.
Types ¶
type Entry ¶
type Entry struct {
// Version is the semver string with the leading "v"
// (e.g. "v1.6.2"). Render() strips the "v" for the heading
// since Keep-a-Changelog convention uses bare numbers.
Version string
// Date is the release date in YYYY-MM-DD form. Use [Today]
// to fill from the system clock.
Date string
// Major holds the bodies of changesets that requested a
// major bump for the package this entry is for. One body per
// element; rendered as bullets under "### Major Changes".
Major []string
// Minor holds bodies for minor bumps.
Minor []string
// Patch holds bodies for patch bumps.
Patch []string
}
Entry is one CHANGELOG.md release section, ready to render. The fields are bucketed by bump level because Keep-a-Changelog wants section subheadings, and the bump level is the only categorization signal monorel has (changesets carry no commit-type metadata).
func (*Entry) IsEmpty ¶
IsEmpty reports whether the entry has no changeset bullets at all. An empty Entry shouldn't be rendered into a CHANGELOG.
func (*Entry) Render ¶
Render formats e as a Keep-a-Changelog version section. The output starts with a blank line so it sits cleanly below an existing section header.
Format:
## [X.Y.Z] - YYYY-MM-DD ### Major Changes - body 1 - body 2 ### Minor Changes - ...
Sections with no bullets are omitted. Bodies that contain newlines are kept multi-line via two-space indent on continuation lines so the markdown renders as one logical bullet.