Compare commits
11 Commits
d91ec2f6b1
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d9cc33802 | ||
|
|
33e1d7c9cc | ||
|
|
4c1a0b87eb | ||
|
|
a139417f02 | ||
|
|
788ef1b49d | ||
|
|
f9f2c6ab62 | ||
|
|
1959aa33d8 | ||
|
|
6a83abe4db | ||
|
|
2d3c27460f | ||
|
|
edb8508e48 | ||
|
|
f79eda21c1 |
@@ -29,6 +29,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
SUMMARY_FILE: ${{ runner.temp }}/do-release-summary.md
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout tagged revision
|
- name: Checkout tagged revision
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -128,7 +129,21 @@ jobs:
|
|||||||
echo "- Tag: ${TAG_NAME}"
|
echo "- Tag: ${TAG_NAME}"
|
||||||
echo "- Release notes sourced from changelog entry ${RELEASE_VERSION}."
|
echo "- Release notes sourced from changelog entry ${RELEASE_VERSION}."
|
||||||
echo "- Published assets: vociferate_${RELEASE_VERSION}_linux_amd64, vociferate_${RELEASE_VERSION}_linux_arm64, checksums.txt"
|
echo "- Published assets: vociferate_${RELEASE_VERSION}_linux_amd64, vociferate_${RELEASE_VERSION}_linux_arm64, checksums.txt"
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
} >> "$SUMMARY_FILE"
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo 'Summary'
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ -s "$SUMMARY_FILE" ]]; then
|
||||||
|
cat "$SUMMARY_FILE"
|
||||||
|
else
|
||||||
|
echo 'No summary generated.'
|
||||||
|
fi
|
||||||
|
|
||||||
validate:
|
validate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -145,6 +160,8 @@ jobs:
|
|||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
SUMMARY_FILE: ${{ runner.temp }}/do-release-validate-summary.md
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout tagged revision
|
- name: Checkout tagged revision
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -211,4 +228,18 @@ jobs:
|
|||||||
echo "- Asset: ${asset_name}"
|
echo "- Asset: ${asset_name}"
|
||||||
echo "- Binary executed successfully via --help."
|
echo "- Binary executed successfully via --help."
|
||||||
echo "- --recommend failed as expected on the tagged checkout because Unreleased is empty."
|
echo "- --recommend failed as expected on the tagged checkout because Unreleased is empty."
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
} >> "$SUMMARY_FILE"
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo 'Summary'
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ -s "$SUMMARY_FILE" ]]; then
|
||||||
|
cat "$SUMMARY_FILE"
|
||||||
|
else
|
||||||
|
echo 'No summary generated.'
|
||||||
|
fi
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ jobs:
|
|||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
SUMMARY_FILE: ${{ runner.temp }}/prepare-release-summary.md
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -61,7 +63,21 @@ jobs:
|
|||||||
echo
|
echo
|
||||||
echo "- Tag pushed: ${tag}"
|
echo "- Tag pushed: ${tag}"
|
||||||
echo "- Calling Do Release workflow for ${tag}."
|
echo "- Calling Do Release workflow for ${tag}."
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
} >> "$SUMMARY_FILE"
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo 'Summary'
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ -s "$SUMMARY_FILE" ]]; then
|
||||||
|
cat "$SUMMARY_FILE"
|
||||||
|
else
|
||||||
|
echo 'No summary generated.'
|
||||||
|
fi
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ jobs:
|
|||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ARTEFACT_BUCKET_WRITE_ACCESS_SECRET }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ARTEFACT_BUCKET_WRITE_ACCESS_SECRET }}
|
||||||
AWS_DEFAULT_REGION: ${{ vars.ARTEFACT_BUCKET_REGION }}
|
AWS_DEFAULT_REGION: ${{ vars.ARTEFACT_BUCKET_REGION }}
|
||||||
AWS_EC2_METADATA_DISABLED: true
|
AWS_EC2_METADATA_DISABLED: true
|
||||||
|
SUMMARY_FILE: ${{ runner.temp }}/push-validation-summary.md
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -98,8 +99,10 @@ jobs:
|
|||||||
|
|
||||||
repo_name="${GITHUB_REPOSITORY##*/}"
|
repo_name="${GITHUB_REPOSITORY##*/}"
|
||||||
prefix="${repo_name}/branch/${GITHUB_REF_NAME}"
|
prefix="${repo_name}/branch/${GITHUB_REF_NAME}"
|
||||||
report_url="${ARTEFACT_BUCKET_ENDPONT%/}/${ARTEFACT_BUCKET_NAME}/${prefix}/coverage.html"
|
display_endpoint="${ARTEFACT_BUCKET_ENDPONT#https://}"
|
||||||
badge_url="${ARTEFACT_BUCKET_ENDPONT%/}/${ARTEFACT_BUCKET_NAME}/${prefix}/coverage-badge.svg"
|
display_endpoint="${display_endpoint#http://}"
|
||||||
|
report_url="//${display_endpoint%/}/${ARTEFACT_BUCKET_NAME}/${prefix}/coverage.html"
|
||||||
|
badge_url="//${display_endpoint%/}/${ARTEFACT_BUCKET_NAME}/${prefix}/coverage-badge.svg"
|
||||||
|
|
||||||
aws --endpoint-url "${ARTEFACT_BUCKET_ENDPONT}" s3 cp coverage.html "s3://${ARTEFACT_BUCKET_NAME}/${prefix}/coverage.html" --content-type text/html
|
aws --endpoint-url "${ARTEFACT_BUCKET_ENDPONT}" s3 cp coverage.html "s3://${ARTEFACT_BUCKET_NAME}/${prefix}/coverage.html" --content-type text/html
|
||||||
aws --endpoint-url "${ARTEFACT_BUCKET_ENDPONT}" s3 cp coverage-badge.svg "s3://${ARTEFACT_BUCKET_NAME}/${prefix}/coverage-badge.svg" --content-type image/svg+xml
|
aws --endpoint-url "${ARTEFACT_BUCKET_ENDPONT}" s3 cp coverage-badge.svg "s3://${ARTEFACT_BUCKET_NAME}/${prefix}/coverage-badge.svg" --content-type image/svg+xml
|
||||||
@@ -110,13 +113,15 @@ jobs:
|
|||||||
|
|
||||||
- name: Add coverage summary
|
- name: Add coverage summary
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
{
|
{
|
||||||
echo '## Coverage'
|
echo '## Coverage'
|
||||||
echo
|
echo
|
||||||
echo '- Total: `${{ steps.coverage.outputs.total }}%`'
|
echo '- Total: `${{ steps.coverage.outputs.total }}%`'
|
||||||
echo '- Report: ${{ steps.upload.outputs.report_url }}'
|
echo '- Report: ${{ steps.upload.outputs.report_url }}'
|
||||||
echo '- Badge: ${{ steps.upload.outputs.badge_url }}'
|
echo '- Badge: ${{ steps.upload.outputs.badge_url }}'
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
} >> "$SUMMARY_FILE"
|
||||||
|
|
||||||
- name: Recommend next release tag on main pushes
|
- name: Recommend next release tag on main pushes
|
||||||
if: ${{ github.ref == 'refs/heads/main' }}
|
if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
@@ -129,7 +134,7 @@ jobs:
|
|||||||
echo '## Release Recommendation'
|
echo '## Release Recommendation'
|
||||||
echo
|
echo
|
||||||
echo "- Recommended next tag: \`${recommended_tag}\`"
|
echo "- Recommended next tag: \`${recommended_tag}\`"
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
} >> "$SUMMARY_FILE"
|
||||||
else
|
else
|
||||||
recommendation_error="$(tr '\n' ' ' < release-recommendation.err | sed 's/[[:space:]]\+/ /g' | sed 's/^ //; s/ $//')"
|
recommendation_error="$(tr '\n' ' ' < release-recommendation.err | sed 's/[[:space:]]\+/ /g' | sed 's/^ //; s/ $//')"
|
||||||
echo "::warning::${recommendation_error}"
|
echo "::warning::${recommendation_error}"
|
||||||
@@ -138,5 +143,19 @@ jobs:
|
|||||||
echo '## Release Recommendation'
|
echo '## Release Recommendation'
|
||||||
echo
|
echo
|
||||||
echo "- No recommended tag emitted: ${recommendation_error}"
|
echo "- No recommended tag emitted: ${recommendation_error}"
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
} >> "$SUMMARY_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo 'Summary'
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ -s "$SUMMARY_FILE" ]]; then
|
||||||
|
cat "$SUMMARY_FILE"
|
||||||
|
else
|
||||||
|
echo 'No summary generated.'
|
||||||
fi
|
fi
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -1,9 +1,9 @@
|
|||||||
# vociferate
|
# vociferate
|
||||||
|
|
||||||
[](https://git.hrafn.xyz/aether/vociferate/actions/runs/latest?workflow=push-validation.yml&branch=main&event=push)
|
[](//git.hrafn.xyz/aether/vociferate/actions/runs/latest?workflow=push-validation.yml&branch=main&event=push)
|
||||||
[](https://git.hrafn.xyz/aether/vociferate/actions/runs/latest?workflow=prepare-release.yml)
|
[](//git.hrafn.xyz/aether/vociferate/actions/runs/latest?workflow=prepare-release.yml)
|
||||||
[](https://git.hrafn.xyz/aether/vociferate/actions/runs/latest?workflow=do-release.yml)
|
[](//git.hrafn.xyz/aether/vociferate/actions/runs/latest?workflow=do-release.yml)
|
||||||
[](https://s3.hrafn.xyz/aether-workflow-report-artefacts/vociferate/branch/main/coverage.html)
|
[](//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
|
`vociferate` is an `aether` release orchestration tool written in Go for repositories that
|
||||||
want changelog-driven versioning, automated release preparation, and repeatable
|
want changelog-driven versioning, automated release preparation, and repeatable
|
||||||
@@ -167,7 +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.
|
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 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.
|
During prepare, vociferate can normalize changelog heading links when it can determine the repository URL (from CI environment variables or `origin` git remote). Actions automatically forward `${{ vars.VOCIFERATE_REPOSITORY_URL }}` to `VOCIFERATE_REPOSITORY_URL`, which has highest priority for changelog link generation. This value should be the server/base URL only, for example `https://git.hrafn.xyz` or `https://git.hrafn.xyz/git`, not a full repository URL.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ runs:
|
|||||||
env:
|
env:
|
||||||
VOCIFERATE_BIN: ${{ steps.resolve-binary.outputs.binary_path }}
|
VOCIFERATE_BIN: ${{ steps.resolve-binary.outputs.binary_path }}
|
||||||
USE_BINARY: ${{ steps.resolve-binary.outputs.use_binary }}
|
USE_BINARY: ${{ steps.resolve-binary.outputs.use_binary }}
|
||||||
|
VOCIFERATE_REPOSITORY_URL: ${{ vars.VOCIFERATE_REPOSITORY_URL }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|||||||
25
changelog.md
25
changelog.md
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](//keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](//semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -13,7 +13,20 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-03-21
|
||||||
|
|
||||||
|
### Breaking
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
- Added a project LICENSE file.
|
- Added a project LICENSE file.
|
||||||
|
- Root and prepare actions now read `${{ vars.VOCIFERATE_REPOSITORY_URL }}` and forward it to `VOCIFERATE_REPOSITORY_URL` for repository URL override.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@@ -21,10 +34,13 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Browser-facing URLs emitted in generated changelog links, workflow summaries, and markdown now use protocol-relative `//` forms.
|
||||||
|
- Release workflows now collect summary markdown into portable temp files and print it in explicit `Summary` steps instead of relying on unsupported `GITHUB_STEP_SUMMARY` output.
|
||||||
- Prepare now recreates the standard `Unreleased` section headers after promoting notes into a tagged release entry.
|
- Prepare now recreates the standard `Unreleased` section headers after promoting notes into a tagged release entry.
|
||||||
- First-release recommendation remains `v1.0.0` when no prior releases exist in the changelog.
|
- First-release recommendation remains `v1.0.0` when no prior releases exist in the changelog.
|
||||||
- Do Release smoke validation now expects `--recommend` to fail on tagged release checkouts where `Unreleased` is intentionally empty.
|
- Do Release smoke validation now expects `--recommend` to fail on tagged release checkouts where `Unreleased` is intentionally empty.
|
||||||
- Changelog reference links now use compare URLs (`previous...current` for releases and `latest...main` for Unreleased), with first release links comparing from the repository's first commit short hash.
|
- Changelog reference links now use compare URLs (`previous...current` for releases and `latest...main` for Unreleased), with first release links comparing from the repository's first commit short hash.
|
||||||
|
- Repository URL derivation now supports `VOCIFERATE_REPOSITORY_URL` as the highest-priority base-URL override for changelog link generation.
|
||||||
|
|
||||||
## [0.1.0] - 2026-03-20
|
## [0.1.0] - 2026-03-20
|
||||||
|
|
||||||
@@ -60,5 +76,6 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
|
|||||||
- Project/automation rename from `releaseprep` to `vociferate` (entrypoint, package paths, outputs).
|
- Project/automation rename from `releaseprep` to `vociferate` (entrypoint, package paths, outputs).
|
||||||
- README guidance focused on primary cross-repository reuse workflows.
|
- README guidance focused on primary cross-repository reuse workflows.
|
||||||
|
|
||||||
[Unreleased]: http://teapot:3000/aether/vociferate/src/branch/main
|
[Unreleased]: //git.hrafn.xyz/aether/vociferate/compare/v0.2.0...main
|
||||||
[0.1.0]: http://teapot:3000/aether/vociferate/releases/tag/v0.1.0
|
[0.2.0]: //git.hrafn.xyz/aether/vociferate/compare/v0.1.0...v0.2.0
|
||||||
|
[0.1.0]: //git.hrafn.xyz/aether/vociferate/compare/2060af6...v0.1.0
|
||||||
|
|||||||
@@ -335,6 +335,17 @@ func readLatestChangelogVersion(rootDir, changelogPath string) (string, bool, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deriveRepositoryURL(rootDir string) (string, bool) {
|
func deriveRepositoryURL(rootDir string) (string, bool) {
|
||||||
|
override := strings.TrimSpace(os.Getenv("VOCIFERATE_REPOSITORY_URL"))
|
||||||
|
if override != "" {
|
||||||
|
repositoryPath, ok := deriveRepositoryPath(rootDir)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
baseURL := strings.TrimSuffix(strings.TrimSpace(override), "/")
|
||||||
|
return baseURL + "/" + repositoryPath, true
|
||||||
|
}
|
||||||
|
|
||||||
serverURL := strings.TrimSpace(os.Getenv("GITHUB_SERVER_URL"))
|
serverURL := strings.TrimSpace(os.Getenv("GITHUB_SERVER_URL"))
|
||||||
repository := strings.TrimSpace(os.Getenv("GITHUB_REPOSITORY"))
|
repository := strings.TrimSpace(os.Getenv("GITHUB_REPOSITORY"))
|
||||||
if serverURL != "" && repository != "" {
|
if serverURL != "" && repository != "" {
|
||||||
@@ -360,6 +371,38 @@ func deriveRepositoryURL(rootDir string) (string, bool) {
|
|||||||
return repoURL, true
|
return repoURL, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deriveRepositoryPath(rootDir string) (string, bool) {
|
||||||
|
repository := strings.TrimSpace(os.Getenv("GITHUB_REPOSITORY"))
|
||||||
|
if repository != "" {
|
||||||
|
return strings.TrimPrefix(repository, "/"), true
|
||||||
|
}
|
||||||
|
|
||||||
|
gitConfigPath := filepath.Join(rootDir, ".git", "config")
|
||||||
|
contents, err := os.ReadFile(gitConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteURL, ok := originRemoteURLFromGitConfig(string(contents))
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
repoURL, ok := normalizeRepoURL(remoteURL)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedURL := strings.TrimPrefix(repoURL, "https://")
|
||||||
|
parsedURL = strings.TrimPrefix(parsedURL, "http://")
|
||||||
|
slash := strings.Index(parsedURL, "/")
|
||||||
|
if slash == -1 || slash == len(parsedURL)-1 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedURL[slash+1:], true
|
||||||
|
}
|
||||||
|
|
||||||
func originRemoteURLFromGitConfig(config string) (string, bool) {
|
func originRemoteURLFromGitConfig(config string) (string, bool) {
|
||||||
inOrigin := false
|
inOrigin := false
|
||||||
for _, line := range strings.Split(config, "\n") {
|
for _, line := range strings.Split(config, "\n") {
|
||||||
@@ -395,7 +438,8 @@ func normalizeRepoURL(remoteURL string) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(remoteURL, "http://") || strings.HasPrefix(remoteURL, "https://") {
|
if strings.HasPrefix(remoteURL, "http://") || strings.HasPrefix(remoteURL, "https://") {
|
||||||
return strings.TrimSuffix(remoteURL, ".git"), true
|
normalized := strings.TrimSuffix(strings.TrimSuffix(remoteURL, "/"), ".git")
|
||||||
|
return normalized, true
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(remoteURL, "ssh://") {
|
if strings.HasPrefix(remoteURL, "ssh://") {
|
||||||
@@ -433,6 +477,8 @@ func addChangelogLinks(text, repoURL, rootDir string) string {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayRepoURL := displayURL(repoURL)
|
||||||
|
|
||||||
// Normalize headings to plain format, stripping any existing inline links.
|
// Normalize headings to plain format, stripping any existing inline links.
|
||||||
text = unreleasedHeadingRe.ReplaceAllString(text, "## [Unreleased]\n")
|
text = unreleasedHeadingRe.ReplaceAllString(text, "## [Unreleased]\n")
|
||||||
text = releaseHeadingRe.ReplaceAllStringFunc(text, func(match string) string {
|
text = releaseHeadingRe.ReplaceAllStringFunc(text, func(match string) string {
|
||||||
@@ -468,30 +514,41 @@ func addChangelogLinks(text, repoURL, rootDir string) string {
|
|||||||
linkDefs := make([]string, 0, len(releasedVersions)+1)
|
linkDefs := make([]string, 0, len(releasedVersions)+1)
|
||||||
if len(releasedVersions) > 0 {
|
if len(releasedVersions) > 0 {
|
||||||
latest := releasedVersions[0]
|
latest := releasedVersions[0]
|
||||||
linkDefs = append(linkDefs, fmt.Sprintf("[Unreleased]: %s", compareURL(repoURL, "v"+latest, "main")))
|
linkDefs = append(linkDefs, fmt.Sprintf("[Unreleased]: %s", compareURL(displayRepoURL, "v"+latest, "main")))
|
||||||
} else {
|
} else {
|
||||||
linkDefs = append(linkDefs, fmt.Sprintf("[Unreleased]: %s/src/branch/main", repoURL))
|
linkDefs = append(linkDefs, fmt.Sprintf("[Unreleased]: %s/src/branch/main", displayRepoURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
firstCommitShort, hasFirstCommit := firstCommitShortHash(rootDir)
|
firstCommitShort, hasFirstCommit := firstCommitShortHash(rootDir)
|
||||||
for i, version := range releasedVersions {
|
for i, version := range releasedVersions {
|
||||||
if i+1 < len(releasedVersions) {
|
if i+1 < len(releasedVersions) {
|
||||||
previousVersion := releasedVersions[i+1]
|
previousVersion := releasedVersions[i+1]
|
||||||
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s", version, compareURL(repoURL, "v"+previousVersion, "v"+version)))
|
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s", version, compareURL(displayRepoURL, "v"+previousVersion, "v"+version)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasFirstCommit {
|
if hasFirstCommit {
|
||||||
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s", version, compareURL(repoURL, firstCommitShort, "v"+version)))
|
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s", version, compareURL(displayRepoURL, firstCommitShort, "v"+version)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s", version, compareURL(repoURL, "v"+version, "main")))
|
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s", version, compareURL(displayRepoURL, "v"+version, "main")))
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimRight(text, "\n") + "\n\n" + strings.Join(linkDefs, "\n") + "\n"
|
return strings.TrimRight(text, "\n") + "\n\n" + strings.Join(linkDefs, "\n") + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayURL(url string) string {
|
||||||
|
trimmed := strings.TrimSpace(url)
|
||||||
|
if strings.HasPrefix(trimmed, "https://") {
|
||||||
|
return "//" + strings.TrimPrefix(trimmed, "https://")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(trimmed, "http://") {
|
||||||
|
return "//" + strings.TrimPrefix(trimmed, "http://")
|
||||||
|
}
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
|
||||||
func firstCommitShortHash(rootDir string) (string, bool) {
|
func firstCommitShortHash(rootDir string) (string, bool) {
|
||||||
command := exec.Command("git", "-C", rootDir, "rev-list", "--max-parents=0", "--abbrev-commit", "HEAD")
|
command := exec.Command("git", "-C", rootDir, "rev-list", "--max-parents=0", "--abbrev-commit", "HEAD")
|
||||||
output, err := command.Output()
|
output, err := command.Output()
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ func TestNormalizeRepoURL(t *testing.T) {
|
|||||||
wantOK bool
|
wantOK bool
|
||||||
}{
|
}{
|
||||||
{name: "https", remoteURL: "https://git.hrafn.xyz/aether/vociferate.git", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
{name: "https", remoteURL: "https://git.hrafn.xyz/aether/vociferate.git", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
||||||
|
{name: "https trailing slash", remoteURL: "https://git.hrafn.xyz/aether/vociferate/", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
||||||
{name: "http", remoteURL: "http://teapot:3000/aether/vociferate.git", wantURL: "http://teapot:3000/aether/vociferate", wantOK: true},
|
{name: "http", remoteURL: "http://teapot:3000/aether/vociferate.git", wantURL: "http://teapot:3000/aether/vociferate", wantOK: true},
|
||||||
|
{name: "http trailing slash", remoteURL: "http://teapot:3000/aether/vociferate/", wantURL: "http://teapot:3000/aether/vociferate", wantOK: true},
|
||||||
{name: "ssh with scheme", remoteURL: "ssh://git@git.hrafn.xyz/aether/vociferate.git", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
{name: "ssh with scheme", remoteURL: "ssh://git@git.hrafn.xyz/aether/vociferate.git", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
||||||
{name: "scp style", remoteURL: "git@git.hrafn.xyz:aether/vociferate.git", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
{name: "scp style", remoteURL: "git@git.hrafn.xyz:aether/vociferate.git", wantURL: "https://git.hrafn.xyz/aether/vociferate", wantOK: true},
|
||||||
{name: "empty", remoteURL: "", wantURL: "", wantOK: false},
|
{name: "empty", remoteURL: "", wantURL: "", wantOK: false},
|
||||||
@@ -121,3 +123,26 @@ func TestDeriveRepositoryURLFromGitConfigFallback(t *testing.T) {
|
|||||||
t.Fatalf("unexpected repository URL: %q", url)
|
t.Fatalf("unexpected repository URL: %q", url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeriveRepositoryURL_UsesOverrideAsHighestPriority(t *testing.T) {
|
||||||
|
t.Setenv("VOCIFERATE_REPOSITORY_URL", "https://git.hrafn.xyz/git")
|
||||||
|
t.Setenv("GITHUB_SERVER_URL", "http://teapot:3000")
|
||||||
|
t.Setenv("GITHUB_REPOSITORY", "aether/vociferate")
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
configPath := filepath.Join(root, ".git", "config")
|
||||||
|
if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
|
||||||
|
t.Fatalf("mkdir .git: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(configPath, []byte("[remote \"origin\"]\n\turl = git@different.host:org/other.git\n"), 0o644); err != nil {
|
||||||
|
t.Fatalf("write git config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url, ok := deriveRepositoryURL(root)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected repository URL from override")
|
||||||
|
}
|
||||||
|
if url != "https://git.hrafn.xyz/git/aether/vociferate" {
|
||||||
|
t.Fatalf("unexpected repository URL: %q", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,7 +75,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)
|
||||||
firstCommit := firstCommitShortHash(s.T(), s.rootDir)
|
firstCommit := firstCommitShortHash(s.T(), s.rootDir)
|
||||||
require.Equal(s.T(), "# Changelog\n\n## [Unreleased]\n\n### Breaking\n\n### Added\n\n### Changed\n\n### Removed\n\n### Fixed\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/compare/v1.1.7...main\n[1.1.7]: https://git.hrafn.xyz/aether/vociferate/compare/v1.1.6...v1.1.7\n[1.1.6]: https://git.hrafn.xyz/aether/vociferate/compare/"+firstCommit+"...v1.1.6\n", string(changelogBytes))
|
require.Equal(s.T(), "# Changelog\n\n## [Unreleased]\n\n### Breaking\n\n### Added\n\n### Changed\n\n### Removed\n\n### Fixed\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]: //git.hrafn.xyz/aether/vociferate/compare/v1.1.7...main\n[1.1.7]: //git.hrafn.xyz/aether/vociferate/compare/v1.1.6...v1.1.7\n[1.1.6]: //git.hrafn.xyz/aether/vociferate/compare/"+firstCommit+"...v1.1.6\n", string(changelogBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PrepareSuite) TestPrepare_ReturnsErrorWhenUnreleasedSectionMissing() {
|
func (s *PrepareSuite) TestPrepare_ReturnsErrorWhenUnreleasedSectionMissing() {
|
||||||
@@ -283,9 +283,9 @@ func (s *PrepareSuite) TestPrepare_UsesGitHrafnXYZEnvironmentForChangelogLinks()
|
|||||||
require.Contains(s.T(), changelog, "### Removed\n")
|
require.Contains(s.T(), changelog, "### Removed\n")
|
||||||
require.Contains(s.T(), changelog, "## [1.1.7] - 2026-03-20")
|
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, "## [1.1.6] - 2017-12-20")
|
||||||
require.Contains(s.T(), changelog, "[Unreleased]: https://git.hrafn.xyz/aether/vociferate/compare/v1.1.7...main")
|
require.Contains(s.T(), changelog, "[Unreleased]: //git.hrafn.xyz/aether/vociferate/compare/v1.1.7...main")
|
||||||
require.Contains(s.T(), changelog, "[1.1.7]: https://git.hrafn.xyz/aether/vociferate/compare/v1.1.6...v1.1.7")
|
require.Contains(s.T(), changelog, "[1.1.7]: //git.hrafn.xyz/aether/vociferate/compare/v1.1.6...v1.1.7")
|
||||||
require.Contains(s.T(), changelog, "[1.1.6]: https://git.hrafn.xyz/aether/vociferate/compare/"+firstCommit+"...v1.1.6")
|
require.Contains(s.T(), changelog, "[1.1.6]: //git.hrafn.xyz/aether/vociferate/compare/"+firstCommit+"...v1.1.6")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PrepareSuite) TestPrepare_UsesGitHubEnvironmentForChangelogLinks() {
|
func (s *PrepareSuite) TestPrepare_UsesGitHubEnvironmentForChangelogLinks() {
|
||||||
@@ -305,7 +305,7 @@ func (s *PrepareSuite) TestPrepare_UsesGitHubEnvironmentForChangelogLinks() {
|
|||||||
require.Contains(s.T(), changelog, "### Removed\n")
|
require.Contains(s.T(), changelog, "### Removed\n")
|
||||||
require.Contains(s.T(), changelog, "## [1.1.7] - 2026-03-20")
|
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, "## [1.1.6] - 2017-12-20")
|
||||||
require.Contains(s.T(), changelog, "[Unreleased]: https://github.com/aether/vociferate/compare/v1.1.7...main")
|
require.Contains(s.T(), changelog, "[Unreleased]: //github.com/aether/vociferate/compare/v1.1.7...main")
|
||||||
require.Contains(s.T(), changelog, "[1.1.7]: https://github.com/aether/vociferate/compare/v1.1.6...v1.1.7")
|
require.Contains(s.T(), changelog, "[1.1.7]: //github.com/aether/vociferate/compare/v1.1.6...v1.1.7")
|
||||||
require.Contains(s.T(), changelog, "[1.1.6]: https://github.com/aether/vociferate/compare/"+firstCommit+"...v1.1.6")
|
require.Contains(s.T(), changelog, "[1.1.6]: //github.com/aether/vociferate/compare/"+firstCommit+"...v1.1.6")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ runs:
|
|||||||
VOCIFERATE_BIN: ${{ steps.resolve-binary.outputs.binary_path }}
|
VOCIFERATE_BIN: ${{ steps.resolve-binary.outputs.binary_path }}
|
||||||
USE_BINARY: ${{ steps.resolve-binary.outputs.use_binary }}
|
USE_BINARY: ${{ steps.resolve-binary.outputs.use_binary }}
|
||||||
INPUT_VERSION: ${{ inputs.version }}
|
INPUT_VERSION: ${{ inputs.version }}
|
||||||
|
VOCIFERATE_REPOSITORY_URL: ${{ vars.VOCIFERATE_REPOSITORY_URL }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.1.0
|
0.2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user