diff --git a/cmd/vociferate/main.go b/cmd/vociferate/main.go index 81de004..e5d2f37 100644 --- a/cmd/vociferate/main.go +++ b/cmd/vociferate/main.go @@ -13,6 +13,7 @@ func main() { version := flag.String("version", "", "semantic version to release, with or without leading v") date := flag.String("date", "", "release date in YYYY-MM-DD format") recommend := flag.Bool("recommend", false, "print the recommended next release tag based on the changelog") + printUnreleased := flag.Bool("print-unreleased", false, "print the current Unreleased changelog body") root := flag.String("root", ".", "repository root to update") versionFile := flag.String("version-file", "", "path to the version file, relative to --root") versionPattern := flag.String("version-pattern", "", "regexp with one capture group for the version value") @@ -41,8 +42,18 @@ func main() { return } + if *printUnreleased { + body, err := vociferate.UnreleasedBody(absRoot, opts) + if err != nil { + fmt.Fprintf(os.Stderr, "print unreleased: %v\n", err) + os.Exit(1) + } + fmt.Print(body) + return + } + if *version == "" || *date == "" { - fmt.Fprintln(os.Stderr, "usage: vociferate --version --date [--root ] [--version-file ] [--version-pattern ] [--changelog ] | --recommend [--root ] [--version-file ] [--version-pattern ] [--changelog ]") + fmt.Fprintln(os.Stderr, "usage: vociferate --version --date [--root ] [--version-file ] [--version-pattern ] [--changelog ] | --recommend [--root ] [--version-file ] [--version-pattern ] [--changelog ] | --print-unreleased [--root ] [--changelog ]") os.Exit(2) } diff --git a/decorate-pr/action.yml b/decorate-pr/action.yml index 4759f04..ce3fb53 100644 --- a/decorate-pr/action.yml +++ b/decorate-pr/action.yml @@ -147,9 +147,15 @@ runs: -H "Content-Type: application/json" \ "$comments_url" >/dev/null + - name: Setup Go for vociferate + uses: actions/setup-go@v5 + with: + go-version-file: ${{ github.action_path }}/../go.mod + - name: Extract changelog unreleased entries id: extract-changelog shell: bash + working-directory: ${{ github.action_path }}/.. env: CHANGELOG: ${{ inputs.changelog }} run: | @@ -160,24 +166,10 @@ runs: exit 0 fi - # Extract everything between [Unreleased] header and the next [X.Y.Z] header - unreleased="$(awk ' - /^## \[Unreleased\]/ { in_unreleased=1; next } - /^## \[[0-9]+\.[0-9]+\.[0-9]+\]/ { if (in_unreleased) exit } - in_unreleased && NF { print } - ' "$CHANGELOG")" - - # Use a temporary file to handle multiline content - tmp_file=$(mktemp) - printf '%s' "$unreleased" > "$tmp_file" - - # Read it back and set as output delimiter="EOF_CHANGELOG" - printf '%s<<%s\n' "unreleased_entries<<$delimiter" "$delimiter" >> "$GITHUB_OUTPUT" - cat "$tmp_file" >> "$GITHUB_OUTPUT" + printf 'unreleased_entries<<%s\n' "$delimiter" >> "$GITHUB_OUTPUT" + go run ./cmd/vociferate --print-unreleased --root "$GITHUB_WORKSPACE" --changelog "$CHANGELOG" >> "$GITHUB_OUTPUT" printf '%s\n' "$delimiter" >> "$GITHUB_OUTPUT" - - rm -f "$tmp_file" - name: Validate changelog gate id: changelog-gate diff --git a/internal/vociferate/vociferate.go b/internal/vociferate/vociferate.go index 7d5a566..d1d9b62 100644 --- a/internal/vociferate/vociferate.go +++ b/internal/vociferate/vociferate.go @@ -209,6 +209,12 @@ func RecommendedTag(rootDir string, options Options) (string, error) { return defaultService().RecommendedTag(rootDir, options) } +// UnreleasedBody returns the current Unreleased changelog body exactly as it +// should appear in downstream tooling. +func UnreleasedBody(rootDir string, options Options) (string, error) { + return defaultService().UnreleasedBody(rootDir, options) +} + // RecommendedTag returns the next semantic release tag based on current changelog state. func (s *Service) RecommendedTag(rootDir string, options Options) (string, error) { resolved, err := resolveOptions(options) @@ -265,6 +271,16 @@ func (s *Service) RecommendedTag(rootDir string, options Options) (string, error return fmt.Sprintf("v%d.%d.%d", parsed.major, parsed.minor, parsed.patch), nil } +// UnreleasedBody returns the current Unreleased changelog body. +func (s *Service) UnreleasedBody(rootDir string, options Options) (string, error) { + resolved, err := resolveOptions(options) + if err != nil { + return "", err + } + + return s.readUnreleasedBody(rootDir, resolved.Changelog) +} + func sectionHasEntries(unreleasedBody, sectionName string) bool { heading := "### " + sectionName sectionStart := strings.Index(unreleasedBody, heading)