From cdfe75f36054bc98e6ed78d94ec4077fe7b92c85 Mon Sep 17 00:00:00 2001 From: Micheal Wilkinson Date: Fri, 20 Mar 2026 23:24:01 +0000 Subject: [PATCH] fix: restore unreleased template behavior --- internal/vociferate/vociferate.go | 37 +++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/internal/vociferate/vociferate.go b/internal/vociferate/vociferate.go index 8c6a69c..830c6db 100644 --- a/internal/vociferate/vociferate.go +++ b/internal/vociferate/vociferate.go @@ -16,9 +16,10 @@ import ( ) const ( - defaultVersionFile = "release-version" - defaultVersionExpr = `^\s*([^\r\n]+)\s*$` - defaultChangelog = "changelog.md" + defaultVersionFile = "release-version" + defaultVersionExpr = `^\s*([^\r\n]+)\s*$` + defaultChangelog = "changelog.md" + defaultUnreleasedBody = "### Breaking\n\n### Added\n\n### Changed\n\n### Removed\n\n### Fixed\n" ) var releasedSectionRe = regexp.MustCompile(`(?m)^## \[(\d+\.\d+\.\d+)\] - `) @@ -95,8 +96,8 @@ func Prepare(rootDir, version, releaseDate string, options Options) error { // - minor: Unreleased contains Added entries // - patch: all other cases // -// When no previous release is present in the changelog, the base version is -// treated as 0.0.0. +// When no previous release is present in the changelog, the first +// recommendation is always v1.0.0. func RecommendedTag(rootDir string, options Options) (string, error) { resolved, err := resolveOptions(options) if err != nil { @@ -104,6 +105,7 @@ func RecommendedTag(rootDir string, options Options) (string, error) { } var currentVersion string + noPriorRelease := false if options.VersionFile != "" { currentVersion, err = readCurrentVersion(rootDir, resolved) if err != nil { @@ -116,6 +118,7 @@ func RecommendedTag(rootDir string, options Options) (string, error) { } if !found { currentVersion = "0.0.0" + noPriorRelease = true } else { currentVersion = version } @@ -131,8 +134,12 @@ func RecommendedTag(rootDir string, options Options) (string, error) { return "", err } + if noPriorRelease { + return "v1.0.0", nil + } + switch { - case strings.Contains(unreleasedBody, "### Breaking"), sectionHasEntries(unreleasedBody, "Removed"): + case sectionHasEntries(unreleasedBody, "Breaking"), sectionHasEntries(unreleasedBody, "Removed"): parsed.major++ parsed.minor = 0 parsed.patch = 0 @@ -226,7 +233,7 @@ func updateChangelog(rootDir, version, releaseDate, changelogPath string) error return err } - if strings.TrimSpace(unreleasedBody) == "" { + if !unreleasedHasEntries(unreleasedBody) { return fmt.Errorf("unreleased section is empty") } @@ -236,7 +243,7 @@ func updateChangelog(rootDir, version, releaseDate, changelogPath string) error newSection += "\n" } - updated := text[:afterHeader] + "\n" + newSection + text[nextSectionStart:] + updated := text[:afterHeader] + "\n" + defaultUnreleasedBody + "\n" + newSection + text[nextSectionStart:] repoURL, ok := deriveRepositoryURL(rootDir) if ok { updated = addChangelogLinks(updated, repoURL) @@ -269,13 +276,25 @@ func readUnreleasedBody(rootDir, changelogPath string) (string, error) { return "", err } - if strings.TrimSpace(unreleasedBody) == "" { + if !unreleasedHasEntries(unreleasedBody) { return "", fmt.Errorf("unreleased section is empty") } return unreleasedBody, nil } +func unreleasedHasEntries(unreleasedBody string) bool { + for _, line := range strings.Split(unreleasedBody, "\n") { + trimmed := strings.TrimSpace(line) + if trimmed == "" || strings.HasPrefix(trimmed, "### ") { + continue + } + return true + } + + return false +} + func readChangelogState(rootDir, changelogPath string) (string, string, int, int, string, error) { path := filepath.Join(rootDir, changelogPath) contents, err := os.ReadFile(path)