feat: use reference-style links in changelog instead of inline links
Moves changelog link generation from inline heading format ## [1.1.0](https://...) - date to reference-style definitions at the bottom of the file ## [1.1.0] - date ... [Unreleased]: https://.../src/branch/main [1.1.0]: https://.../releases/tag/v1.1.0 This keeps headings plain, which simplifies changelog parsing (the awk pattern in publish/action.yml now matches without special-casing the inline URL), and follows the canonical Keep a Changelog style.
This commit is contained in:
@@ -25,6 +25,7 @@ var releasedSectionRe = regexp.MustCompile(`(?m)^## \[(\d+\.\d+\.\d+)\] - `)
|
|||||||
var linkedReleasedSectionRe = regexp.MustCompile(`(?m)^## \[(\d+\.\d+\.\d+)\](?:\([^\n)]*\))? - `)
|
var linkedReleasedSectionRe = regexp.MustCompile(`(?m)^## \[(\d+\.\d+\.\d+)\](?:\([^\n)]*\))? - `)
|
||||||
var unreleasedHeadingRe = regexp.MustCompile(`(?m)^## \[Unreleased\](?:\([^\n)]*\))?\n`)
|
var unreleasedHeadingRe = regexp.MustCompile(`(?m)^## \[Unreleased\](?:\([^\n)]*\))?\n`)
|
||||||
var releaseHeadingRe = regexp.MustCompile(`(?m)^## \[(\d+\.\d+\.\d+)\](?:\([^\n)]*\))? - `)
|
var releaseHeadingRe = regexp.MustCompile(`(?m)^## \[(\d+\.\d+\.\d+)\](?:\([^\n)]*\))? - `)
|
||||||
|
var refLinkLineRe = regexp.MustCompile(`^\[[^\]]+\]: \S`)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// VersionFile is the path to the file that stores the current version,
|
// VersionFile is the path to the file that stores the current version,
|
||||||
@@ -412,19 +413,38 @@ func addChangelogLinks(text, repoURL string) string {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
mainURL := repoURL + "/src/branch/main"
|
// Normalize headings to plain format, stripping any existing inline links.
|
||||||
text = unreleasedHeadingRe.ReplaceAllString(text, fmt.Sprintf("## [Unreleased](%s)\n", mainURL))
|
text = unreleasedHeadingRe.ReplaceAllString(text, "## [Unreleased]\n")
|
||||||
|
|
||||||
text = releaseHeadingRe.ReplaceAllStringFunc(text, func(match string) string {
|
text = releaseHeadingRe.ReplaceAllStringFunc(text, func(match string) string {
|
||||||
parts := releaseHeadingRe.FindStringSubmatch(match)
|
parts := releaseHeadingRe.FindStringSubmatch(match)
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
version := parts[1]
|
version := parts[1]
|
||||||
return fmt.Sprintf("## [%s](%s/releases/tag/v%s) - ", version, repoURL, version)
|
return fmt.Sprintf("## [%s] - ", version)
|
||||||
})
|
})
|
||||||
|
|
||||||
return text
|
// Strip any trailing reference link block (blank lines followed by ref link lines).
|
||||||
|
lines := strings.Split(strings.TrimRight(text, "\n"), "\n")
|
||||||
|
cutAt := len(lines)
|
||||||
|
for i := len(lines) - 1; i >= 0; i-- {
|
||||||
|
if strings.TrimSpace(lines[i]) == "" || refLinkLineRe.MatchString(lines[i]) {
|
||||||
|
cutAt = i
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = strings.Join(lines[:cutAt], "\n") + "\n"
|
||||||
|
|
||||||
|
// Build and append reference link definitions.
|
||||||
|
linkDefs := []string{fmt.Sprintf("[Unreleased]: %s/src/branch/main", repoURL)}
|
||||||
|
for _, match := range releasedSectionRe.FindAllStringSubmatch(text, -1) {
|
||||||
|
if len(match) >= 2 {
|
||||||
|
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s/releases/tag/v%s", match[1], repoURL, match[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimRight(text, "\n") + "\n\n" + strings.Join(linkDefs, "\n") + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSemver(version string) (semver, error) {
|
func parseSemver(version string) (semver, error) {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (s *PrepareSuite) TestPrepare_UpdatesVersionAndPromotesUnreleasedNotes() {
|
|||||||
|
|
||||||
changelogBytes, err := os.ReadFile(filepath.Join(s.rootDir, "changelog.md"))
|
changelogBytes, err := os.ReadFile(filepath.Join(s.rootDir, "changelog.md"))
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
require.Equal(s.T(), "# Changelog\n\n## [Unreleased](https://git.hrafn.xyz/aether/vociferate/src/branch/main)\n\n## [1.1.7](https://git.hrafn.xyz/aether/vociferate/releases/tag/v1.1.7) - 2026-03-20\n\n### Breaking\n\n### Added\n\n- New thing.\n\n### Fixed\n\n- Old thing.\n\n## [1.1.6](https://git.hrafn.xyz/aether/vociferate/releases/tag/v1.1.6) - 2017-12-20\n\n### Fixed\n\n- Historical note.\n", string(changelogBytes))
|
require.Equal(s.T(), "# Changelog\n\n## [Unreleased]\n\n## [1.1.7] - 2026-03-20\n\n### Breaking\n\n### Added\n\n- New thing.\n\n### Fixed\n\n- Old thing.\n\n## [1.1.6] - 2017-12-20\n\n### Fixed\n\n- Historical note.\n\n[Unreleased]: https://git.hrafn.xyz/aether/vociferate/src/branch/main\n[1.1.7]: https://git.hrafn.xyz/aether/vociferate/releases/tag/v1.1.7\n[1.1.6]: https://git.hrafn.xyz/aether/vociferate/releases/tag/v1.1.6\n", string(changelogBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PrepareSuite) TestPrepare_ReturnsErrorWhenUnreleasedSectionMissing() {
|
func (s *PrepareSuite) TestPrepare_ReturnsErrorWhenUnreleasedSectionMissing() {
|
||||||
@@ -244,9 +244,12 @@ func (s *PrepareSuite) TestPrepare_UsesGitHrafnXYZEnvironmentForChangelogLinks()
|
|||||||
require.NoError(s.T(), readErr)
|
require.NoError(s.T(), readErr)
|
||||||
changelog := string(changelogBytes)
|
changelog := string(changelogBytes)
|
||||||
|
|
||||||
require.Contains(s.T(), changelog, "## [Unreleased](https://git.hrafn.xyz/aether/vociferate/src/branch/main)")
|
require.Contains(s.T(), changelog, "## [Unreleased]\n")
|
||||||
require.Contains(s.T(), changelog, "## [1.1.7](https://git.hrafn.xyz/aether/vociferate/releases/tag/v1.1.7) - 2026-03-20")
|
require.Contains(s.T(), changelog, "## [1.1.7] - 2026-03-20")
|
||||||
require.Contains(s.T(), changelog, "## [1.1.6](https://git.hrafn.xyz/aether/vociferate/releases/tag/v1.1.6) - 2017-12-20")
|
require.Contains(s.T(), changelog, "## [1.1.6] - 2017-12-20")
|
||||||
|
require.Contains(s.T(), changelog, "[Unreleased]: https://git.hrafn.xyz/aether/vociferate/src/branch/main")
|
||||||
|
require.Contains(s.T(), changelog, "[1.1.7]: https://git.hrafn.xyz/aether/vociferate/releases/tag/v1.1.7")
|
||||||
|
require.Contains(s.T(), changelog, "[1.1.6]: https://git.hrafn.xyz/aether/vociferate/releases/tag/v1.1.6")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PrepareSuite) TestPrepare_UsesGitHubEnvironmentForChangelogLinks() {
|
func (s *PrepareSuite) TestPrepare_UsesGitHubEnvironmentForChangelogLinks() {
|
||||||
@@ -260,7 +263,10 @@ func (s *PrepareSuite) TestPrepare_UsesGitHubEnvironmentForChangelogLinks() {
|
|||||||
require.NoError(s.T(), readErr)
|
require.NoError(s.T(), readErr)
|
||||||
changelog := string(changelogBytes)
|
changelog := string(changelogBytes)
|
||||||
|
|
||||||
require.Contains(s.T(), changelog, "## [Unreleased](https://github.com/aether/vociferate/src/branch/main)")
|
require.Contains(s.T(), changelog, "## [Unreleased]\n")
|
||||||
require.Contains(s.T(), changelog, "## [1.1.7](https://github.com/aether/vociferate/releases/tag/v1.1.7) - 2026-03-20")
|
require.Contains(s.T(), changelog, "## [1.1.7] - 2026-03-20")
|
||||||
require.Contains(s.T(), changelog, "## [1.1.6](https://github.com/aether/vociferate/releases/tag/v1.1.6) - 2017-12-20")
|
require.Contains(s.T(), changelog, "## [1.1.6] - 2017-12-20")
|
||||||
|
require.Contains(s.T(), changelog, "[Unreleased]: https://github.com/aether/vociferate/src/branch/main")
|
||||||
|
require.Contains(s.T(), changelog, "[1.1.7]: https://github.com/aether/vociferate/releases/tag/v1.1.7")
|
||||||
|
require.Contains(s.T(), changelog, "[1.1.6]: https://github.com/aether/vociferate/releases/tag/v1.1.6")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user