3 Commits

Author SHA1 Message Date
Micheal Wilkinson
6bcc027076 docs: remove deleted release tag references
All checks were successful
Push Validation / validate (push) Successful in 1m48s
2026-03-20 23:12:01 +00:00
Micheal Wilkinson
fd23a8b238 docs: point README badges at latest workflow runs 2026-03-20 23:08:56 +00:00
Micheal Wilkinson
3f7edea46e 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.
2026-03-20 23:07:10 +00:00
4 changed files with 50 additions and 32 deletions

View File

@@ -1,8 +1,8 @@
# vociferate
[![Main Validation](https://git.hrafn.xyz/aether/vociferate/actions/workflows/push-validation.yml/badge.svg?branch=main&event=push)](https://git.hrafn.xyz/aether/vociferate/actions/workflows/push-validation.yml)
[![Prepare Release](https://git.hrafn.xyz/aether/vociferate/actions/workflows/prepare-release.yml/badge.svg?event=workflow_dispatch)](https://git.hrafn.xyz/aether/vociferate/actions/workflows/prepare-release.yml)
[![Do Release](https://git.hrafn.xyz/aether/vociferate/actions/workflows/do-release.yml/badge.svg?event=push)](https://git.hrafn.xyz/aether/vociferate/actions/workflows/do-release.yml)
[![Main Validation](https://git.hrafn.xyz/aether/vociferate/actions/workflows/push-validation.yml/badge.svg?branch=main&event=push)](https://git.hrafn.xyz/aether/vociferate/actions/runs/latest?workflow=push-validation.yml&branch=main&event=push)
[![Prepare Release](https://git.hrafn.xyz/aether/vociferate/actions/workflows/prepare-release.yml/badge.svg?event=workflow_dispatch)](https://git.hrafn.xyz/aether/vociferate/actions/runs/latest?workflow=prepare-release.yml)
[![Do Release](https://git.hrafn.xyz/aether/vociferate/actions/workflows/do-release.yml/badge.svg?event=push)](https://git.hrafn.xyz/aether/vociferate/actions/runs/latest?workflow=do-release.yml)
[![Coverage](https://s3.hrafn.xyz/aether-workflow-report-artefacts/vociferate/branch/main/coverage-badge.svg)](https://s3.hrafn.xyz/aether-workflow-report-artefacts/vociferate/branch/main/coverage.html)
`vociferate` is an `aether` release orchestration tool written in Go for repositories that
@@ -17,7 +17,7 @@ revision.
## Use In Other Repositories
Vociferate ships two composite actions that together cover the full release flow.
Pin both to the same released tag.
Until release tags are created, reference `@main`. Once tags exist again, pin both actions to the same released tag.
### `prepare` — update files, commit, and push tag
@@ -39,7 +39,7 @@ jobs:
with:
fetch-depth: 0
- uses: git.hrafn.xyz/aether/vociferate/prepare@v1.0.0
- uses: git.hrafn.xyz/aether/vociferate/prepare@main
with:
version: ${{ inputs.version }}
@@ -59,7 +59,7 @@ For repositories that embed the version inside source code, pass `version-file`
and `version-pattern`:
```yaml
- uses: git.hrafn.xyz/aether/vociferate/prepare@v1.0.0
- uses: git.hrafn.xyz/aether/vociferate/prepare@main
with:
version-file: internal/myapp/version/version.go
version-pattern: 'const Version = "([^"]+)"'
@@ -98,7 +98,7 @@ assets after it runs:
```yaml
- id: publish
uses: git.hrafn.xyz/aether/vociferate/publish@v1.0.0
uses: git.hrafn.xyz/aether/vociferate/publish@main
- name: Upload my binary
run: |
@@ -167,12 +167,7 @@ Defaults:
When no `--version-file` flag is provided, `vociferate` derives the current version from the most recent released section heading in the changelog (`## [x.y.z] - ...`). If no prior releases exist, it defaults to `0.0.0` and recommends `v1.0.0` as the first tag.
During prepare, vociferate also normalizes changelog heading links when it can determine the repository URL (from CI environment variables or `origin` git remote):
- `## [Unreleased]` becomes a link to the repository main branch.
- `## [x.y.z] - YYYY-MM-DD` becomes a link to the corresponding release page.
If the repository URL cannot be determined, headings remain in plain form.
During prepare, vociferate can normalize changelog heading links when it can determine the repository URL (from CI environment variables or `origin` git remote). If you prefer changelog headings to stay plain while tags are being rebuilt, leave the changelog as plain headings and avoid retaining historical release-tag links.
When running `--version`, the `release-version` file is created automatically if it does not exist, so new repositories do not need to pre-seed it.

View File

@@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
A `### Breaking` section is used in addition to Keep a Changelog's standard sections to explicitly document changes that are backwards-incompatible but would otherwise appear under `### Changed`. Entries under `### Breaking` trigger a major version bump in automated release recommendation logic.
## [Unreleased](http://teapot:3000/aether/vociferate/src/branch/main)
## [Unreleased]
## [1.1.0](http://teapot:3000/aether/vociferate/releases/tag/v1.1.0) - 2026-03-20
### Changed
- README workflow badges now link to the latest workflow run pages instead of the workflow definition pages.
### Fixed
@@ -21,11 +23,6 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
- S3 coverage artefact publishing (HTML report, badge, JSON summary) in push validation pipeline.
- CLI tests and internal helper tests raising total coverage to 84%.
- Test suite isolation against ambient CI environment variables for changelog link generation tests.
## [1.0.0](http://teapot:3000/aether/vociferate/releases/tag/v1.0.0) - 2026-03-20
### Added
- Go CLI for changelog-driven release preparation and semantic version recommendation.
- Version recommendation from changelog release headings, including first-release support (`0.0.0` base -> `v1.0.0`).
- Automatic `release-version` creation/update during release preparation.

View File

@@ -25,6 +25,7 @@ var releasedSectionRe = regexp.MustCompile(`(?m)^## \[(\d+\.\d+\.\d+)\] - `)
var linkedReleasedSectionRe = regexp.MustCompile(`(?m)^## \[(\d+\.\d+\.\d+)\](?:\([^\n)]*\))? - `)
var unreleasedHeadingRe = regexp.MustCompile(`(?m)^## \[Unreleased\](?:\([^\n)]*\))?\n`)
var releaseHeadingRe = regexp.MustCompile(`(?m)^## \[(\d+\.\d+\.\d+)\](?:\([^\n)]*\))? - `)
var refLinkLineRe = regexp.MustCompile(`^\[[^\]]+\]: \S`)
type Options struct {
// VersionFile is the path to the file that stores the current version,
@@ -412,19 +413,38 @@ func addChangelogLinks(text, repoURL string) string {
return text
}
mainURL := repoURL + "/src/branch/main"
text = unreleasedHeadingRe.ReplaceAllString(text, fmt.Sprintf("## [Unreleased](%s)\n", mainURL))
// Normalize headings to plain format, stripping any existing inline links.
text = unreleasedHeadingRe.ReplaceAllString(text, "## [Unreleased]\n")
text = releaseHeadingRe.ReplaceAllStringFunc(text, func(match string) string {
parts := releaseHeadingRe.FindStringSubmatch(match)
if len(parts) < 2 {
return match
}
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) {

View File

@@ -54,7 +54,7 @@ func (s *PrepareSuite) TestPrepare_UpdatesVersionAndPromotesUnreleasedNotes() {
changelogBytes, err := os.ReadFile(filepath.Join(s.rootDir, "changelog.md"))
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() {
@@ -244,9 +244,12 @@ func (s *PrepareSuite) TestPrepare_UsesGitHrafnXYZEnvironmentForChangelogLinks()
require.NoError(s.T(), readErr)
changelog := string(changelogBytes)
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) - 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, "## [Unreleased]\n")
require.Contains(s.T(), changelog, "## [1.1.7] - 2026-03-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() {
@@ -260,7 +263,10 @@ func (s *PrepareSuite) TestPrepare_UsesGitHubEnvironmentForChangelogLinks() {
require.NoError(s.T(), readErr)
changelog := string(changelogBytes)
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) - 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, "## [Unreleased]\n")
require.Contains(s.T(), changelog, "## [1.1.7] - 2026-03-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")
}