Skip to content

Conversation

@2bndy5
Copy link
Contributor

@2bndy5 2bndy5 commented Sep 6, 2025

ref #47

This introduces 3 workflows:

labeler.yml

A reusable workflow that employs actions/labeler to auto manage certain labels based on branch name and changed files. It also has a step to add labels based on PR title (using gh-cli). This workflow is designed to be a replacement of the auto-labeler behavior in release-drafter.

The config for actions/labeler resides in this org repo under .github/labeler.yml.

unreleased-summary.yml

A reusable workflow that will summarize unreleased changes in the run's summary (GITHUB_STEP_SUMMARY).

tag-release.yml

A manually-triggered workflow that invokes the tag-release.nu script (primarily designed to be executed in CI).

This tag-release workflow will take 2 inputs:

  • project (required): The project to deploy. Supported projects can be selected from the drop-down options.
    options:
    - cpp-linter-action
    - cpp-linter
    - clang-tools-pip
    - cpp-linter-hooks
    - clang-tools-docker
    # add more cpp-linter projects here
    # NOTE: cpp-linter-rs has it's own bump-n-release.yml workflow
  • component (optional): The version component to bump. Supported values are
    options:
    - auto
    - patch
    - minor
    - major
    If auto is selected (the default value), the version will be bumped according to the unreleased changes (via git-cliff bump policy in .github/cliff.toml). Observe the step summary from the unreleased-summary.yml workflow because it uses the auto version bump feature.

nu .github/workflows/tag-release.nu -h

A script that will

1. Generate Release notes (from unreleased changes).
2. (Re)generate the CHANGELOG.md in repo root and push any changes
3. Create a GitHub release, which in turn creates a git tag on the default branch  
4. Move any major tags (eg. v2) that correspond with the new release's tag.        
   This step does nothing if no major tag exists.

This script is designed to be run in CI or locally.
Either way, this script will abort if the current branch is not 'main'.

The following tools are used and must be installed:

- git (https://git-scm.com/)
- uv (https://docs.astral.sh/uv/) used to install/run git-cliff.
  See .github/requirements.txt for the pinned version of git-cliff.
- gh-cli (https://cli.github.com/)

Supported component parameter values are:

- auto (relies on git-cliff to determine the next version)
- patch
- minor
- major

If no component is given, then auto is used.

Usage:
  > tag-release.nu (component)

Flags:
  -h, --help: Display the help message for this command

Parameters:
  component <string>: The version component to bump. (optional)

Summary by CodeRabbit

  • New Features

    • Automated changelog generation with rich commit grouping, contributor highlights, and configurable templates.
    • One-click release orchestration with automated version tagging, release notes generation, and rolling major-tag maintenance.
    • Unreleased changes summary job and PR labeler workflow that suggests labels from file/branch/title patterns.
  • Chores

    • Added tooling dependency for changelog generation and monthly Dependabot pip updates.
    • Recommended editor settings/extension and expanded spell-check dictionary.

@2bndy5 2bndy5 added the enhancement New feature or request label Sep 6, 2025
2bndy5 added a commit to cpp-linter/cpp-linter that referenced this pull request Sep 6, 2025
See cpp-linter/.github#50 for new release workflow

See cpp-linter/.github#49 for changes to pre-commit workflow

I generated the CHANGELOG doc which summarizes all previous releases.
2bndy5 added a commit to cpp-linter/cpp-linter that referenced this pull request Sep 6, 2025
See cpp-linter/.github#50 for new release workflow

See cpp-linter/.github#49 for changes to pre-commit workflow

I generated the CHANGELOG doc which summarizes all previous releases.
2bndy5 added a commit to cpp-linter/cpp-linter that referenced this pull request Sep 7, 2025
See cpp-linter/.github#50 for new release workflow

See cpp-linter/.github#49 for changes to pre-commit workflow

I generated the CHANGELOG doc which summarizes all previous releases.
@2bndy5 2bndy5 marked this pull request as ready for review September 7, 2025 21:39
@coderabbitai
Copy link

coderabbitai bot commented Sep 7, 2025

Walkthrough

Adds organization-wide release tooling: a git-cliff changelog configuration, Nu shell release orchestration script, and GitHub Actions workflows for tagging releases, generating unreleased summaries, and labeling PRs. Also updates Dependabot, spell-check words, and VS Code workspace settings.

Changes

Cohort / File(s) Summary of changes
Changelog configuration
.github/cliff.toml, .github/requirements.txt
Adds a complete git-cliff configuration (templates, macros, commit parsers, preprocessors, grouping, and options) and pins git-cliff==2.10.0 in .github/requirements.txt.
Nu-based release orchestration
.github/workflows/tag-release.nu
Adds a Nushell script exporting helpers and a main orchestrator to compute next versions, find repo/first-commit, generate changelogs (via git-cliff), create GitHub releases, and manage rolling major tags.
Tag release workflow
.github/workflows/tag-release.yml
Adds a GitHub Actions workflow that invokes the Nu script to tag releases across project repos, checks out org and project repos, installs uv and Nu, and passes required secrets/env.
Unreleased summary workflow
.github/workflows/unreleased-summary.yml
Adds a workflow_call job to generate unreleased changelog output with uvx git-cliff --unreleased, using the org cliff.toml and writing to the GitHub step summary.
PR labeling config & workflow
.github/labeler.yml, .github/workflows/labeler.yml
Adds labeler rules for documentation, bug, and enhancement and a workflow that syncs labels and applies additional labels based on PR title via a Nu script and gh CLI.
Dependabot config
.github/dependabot.yml
Adds a new Dependabot update block for package-ecosystem: pip targeting the .github/ directory with monthly schedule and a wildcard group.
Spell-check additions
cspell.config.yml
Adds words to the cspell dictionary: endfor, endmacro, hustcer, NUSHELL, timeit, topo.
VS Code workspace settings
.vscode/extensions.json, .vscode/settings.json
Adds a recommended extension github.vscode-github-actions and disables YAML schema store (yaml.schemaStore.enable: false) in workspace settings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • shenxianpeng

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: phase out release-drafter" is directly related to the primary intent of the changeset (removing/ replacing release-drafter) and is concise enough for history scanning while aligning with the PR that introduces git-cliff, gh CLI, and new workflows as replacements. It therefore serves as an appropriate short summary of the main change.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the main objective of this changeset—phasing out release-drafter—and aligns with the conventional commit style, making it clear and specific to the primary change introduced by the pull request.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tag-release

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@2bndy5 2bndy5 force-pushed the tag-release branch 2 times, most recently from c84f242 to cf14c37 Compare September 9, 2025 17:10
coderabbitai[bot]

This comment was marked as resolved.

@2bndy5 2bndy5 marked this pull request as draft September 9, 2025 22:10
@2bndy5 2bndy5 force-pushed the tag-release branch 4 times, most recently from 1c1fb5b to 8f641fc Compare September 10, 2025 02:12
@2bndy5 2bndy5 marked this pull request as ready for review September 10, 2025 03:38
@2bndy5 2bndy5 requested a review from shenxianpeng September 10, 2025 03:38
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@2bndy5
Copy link
Contributor Author

2bndy5 commented Sep 10, 2025

Instead of using a nushell script, I guess I could rewrite it as a TS (using deno for simplicity).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
.github/workflows/tag-release.nu (1)

161-176: Handle SSH remotes without double-prefixing; fix SCP vs ssh:// cases.

Wrapping any non-HTTPS origin with ssh://(...) yields ssh://ssh://... for URLs that already include a scheme and can also mis-handle SCP-style remotes. Branch explicitly.

 export def find-repo-name [] {
     $env | get --optional "GITHUB_REPO" | default {
         let origin = (^git remote get-url origin) | str trim
-        (
-            if (not ($origin | str starts-with "https://")) {
-                let patched_ssh = $origin | str replace --regex '([^:]):' '$1:/'
-                $"ssh://($patched_ssh)"
-            } else {
-                $origin
-            }
-        )
+        let normalized = (
+            if ($origin | str starts-with "http://") or ($origin | str starts-with "https://") {
+                $origin
+            } else if ($origin | str starts-with "ssh://") {
+                # Ensure host:/path when a bare colon separates host and path
+                $origin | str replace --regex '^(ssh://[^/]+):(?=[^/])' '${1}:/'
+            } else {
+                # SCP-style: git@host:owner/repo(.git)
+                $"ssh://($origin | str replace --regex '^([^:]+):' '${1}:/')"
+            }
+        )
-        | url parse
+        $normalized
+        | url parse
         | get path
         | str trim --left --char "/"
         | path parse
         | format pattern "{parent}/{stem}"
     }
 }
🧹 Nitpick comments (5)
.github/cliff.toml (3)

34-45: Avoid dropping distinct commits that share the same summary line.

unique(attribute="message") will collapse different commits/PRs with identical first lines. Prefer de-duping by (message, remote.pr_number, id) or just remove the unique to list all entries.

-    {% for commit in commits | unique(attribute="message") %}
+    {% for commit in commits %}

99-101: Confirm final casing for the “Breaking …” group; keep it consistent with your chosen UX.

You previously noted the title should be “Breaking Changes” (uppercase C) for continuity with Release Drafter. Current config uses “Breaking changes”. If you intend the uppercase variant, update these lines so all rules map to exactly the same string to avoid split sections.

-    { field = "github.pr_labels", pattern = "breaking", group = "<!-- 0 --> 💥 Breaking changes" },
-    { field = "github.pr_labels", pattern = "breaking-change", group = "<!-- 0 --> 💥 Breaking changes" },
+    { field = "github.pr_labels", pattern = "breaking", group = "<!-- 0 --> 💥 Breaking Changes" },
+    { field = "github.pr_labels", pattern = "breaking-change", group = "<!-- 0 --> 💥 Breaking Changes" },
@@
-    { field = "breaking", pattern = true, group = "<!-- 0 --> 💥 Breaking changes" },
+    { field = "breaking", pattern = true, group = "<!-- 0 --> 💥 Breaking Changes" },

Also applies to: 133-133


140-155: Tighten some message heuristics to reduce false positives.

Patterns like ^.*: support and ^.*: fix match anywhere after a colon; combined with the case-insensitive variants they can over-capture. Consider anchoring only when intended or removing duplicates where the (?i)^ versions already cover them.

-    { message = "^.*: add", group = "<!-- 1 --> 🚀 New features and improvements" },
+    { message = "(?i)^add", group = "<!-- 1 --> 🚀 New features and improvements" },
@@
-    { message = "^.*: remove", group = "<!-- 3 --> 🚨 Removed" },
+    { message = "(?i)^remove", group = "<!-- 3 --> 🚨 Removed" },
@@
-    { message = "^.*: fix", group = "<!-- 4 --> 🐛 Bug fixes" },
+    { message = "(?i)^fix", group = "<!-- 4 --> 🐛 Bug fixes" },
@@
-    { message = "^.*: security", group = "<!-- 5 --> 🔐 Security" },
+    { message = "(?i)^security", group = "<!-- 5 --> 🔐 Security" },
.github/workflows/tag-release.nu (2)

34-43: is-on-main breaks on detached HEAD in CI; add a robust fallback.

GitHub Actions often checks out a detached HEAD. Use GITHUB_REF_NAME when present, and fall back to git symbolic-ref --short -q HEAD.

-export def is-on-main [] {
-    let branch = (
-        ^git branch
-        | lines
-        | where {$in | str starts-with '*'}
-        | first
-        | str trim --left --char '*'
-        | str trim
-    ) == 'main'
-    $branch
-}
+export def is-on-main [] {
+    let from_env = ($env | get --optional GITHUB_REF_NAME | default null)
+    if ($from_env | is-not-empty) { $from_env == 'main' } else {
+        let name = (^git symbolic-ref --short -q HEAD | str trim)
+        $name == 'main'
+    }
+}

128-146: Rolling tag mover works; consider idempotency on missing remote tag.

Logic correctly deletes and repoints the major tag. Optional: guard remote deletion with --delete semantics via git push origin :refs/tags/$major_tag is fine; no change required.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 676dc15 and 49637e9.

📒 Files selected for processing (13)
  • .github/cliff.toml (1 hunks)
  • .github/dependabot.yml (1 hunks)
  • .github/labeler.yml (1 hunks)
  • .github/release-drafter.yml (0 hunks)
  • .github/requirements.txt (1 hunks)
  • .github/workflows/labeler.yml (1 hunks)
  • .github/workflows/release-drafter.yml (0 hunks)
  • .github/workflows/tag-release.nu (1 hunks)
  • .github/workflows/tag-release.yml (1 hunks)
  • .github/workflows/unreleased-summary.yml (1 hunks)
  • .vscode/extensions.json (1 hunks)
  • .vscode/settings.json (1 hunks)
  • cspell.config.yml (1 hunks)
💤 Files with no reviewable changes (2)
  • .github/workflows/release-drafter.yml
  • .github/release-drafter.yml
🚧 Files skipped from review as they are similar to previous changes (9)
  • .vscode/settings.json
  • .github/requirements.txt
  • .github/dependabot.yml
  • .vscode/extensions.json
  • cspell.config.yml
  • .github/labeler.yml
  • .github/workflows/labeler.yml
  • .github/workflows/tag-release.yml
  • .github/workflows/unreleased-summary.yml
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:52-59
Timestamp: 2025-09-09T13:44:00.556Z
Learning: In repositories using git-cliff with conventional_commits = true and proper tag handling templates (like cpp-linter/.github's cliff.toml), the command `git-cliff --bumped-version` outputs properly formatted version tags with the "v" prefix (e.g., "v1.11.0"), not bare version numbers without the prefix.
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:164-191
Timestamp: 2025-09-08T04:22:14.942Z
Learning: In git-cliff workflows, the --tag parameter should be passed even when generating release notes from unreleased changes because the tag being summarized doesn't exist yet when git-cliff runs. The --tag parameter tells git-cliff what version to associate with the unreleased changes, and the actual tag creation happens after git-cliff generates the notes/changelog.
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/labeler.yml:1-16
Timestamp: 2025-09-09T13:10:30.243Z
Learning: 2bndy5 prefers to indent list items in YAML files for better readability, even when it may differ from common formatting conventions.
📚 Learning: 2025-09-09T13:44:00.556Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:52-59
Timestamp: 2025-09-09T13:44:00.556Z
Learning: In repositories using git-cliff with conventional_commits = true and proper tag handling templates (like cpp-linter/.github's cliff.toml), the command `git-cliff --bumped-version` outputs properly formatted version tags with the "v" prefix (e.g., "v1.11.0"), not bare version numbers without the prefix.

Applied to files:

  • .github/cliff.toml
  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-08T04:26:49.968Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/cliff.toml:133-133
Timestamp: 2025-09-08T04:26:49.968Z
Learning: In the cpp-linter/.github repository's .github/cliff.toml file, the "Breaking Changes" group title (uppercase C) is intentionally kept consistent with previous release-drafter output format to maintain user experience, even though other group titles use lowercase (e.g., "Breaking changes"). This inconsistency is by design for backward compatibility.

Applied to files:

  • .github/cliff.toml
  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T13:44:00.556Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:52-59
Timestamp: 2025-09-09T13:44:00.556Z
Learning: In the cpp-linter/.github repository, git-cliff --bumped-version outputs version numbers with the "v" prefix (e.g., "v1.11.0") when using the configured .github/cliff.toml, contrary to assumptions about default git-cliff behavior outputting versions without the "v" prefix.

Applied to files:

  • .github/cliff.toml
  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-08T04:22:14.942Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:164-191
Timestamp: 2025-09-08T04:22:14.942Z
Learning: In git-cliff workflows, the --tag parameter should be passed even when generating release notes from unreleased changes because the tag being summarized doesn't exist yet when git-cliff runs. The --tag parameter tells git-cliff what version to associate with the unreleased changes, and the actual tag creation happens after git-cliff generates the notes/changelog.

Applied to files:

  • .github/cliff.toml
  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-08T04:20:32.371Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:142-158
Timestamp: 2025-09-08T04:20:32.371Z
Learning: In git-cliff, when the GIT_CLIFF_CONFIG environment variable is set, git-cliff automatically uses it for configuration. There's no need to pass the --config parameter with the same value, as the environment variable takes precedence. Functions that detect GIT_CLIFF_CONFIG should return null/empty when it's present, allowing git-cliff to use the env var directly.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-08T06:25:14.311Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:123-133
Timestamp: 2025-09-08T06:25:14.311Z
Learning: Nushell's url parse command fails on SSH URLs in the format "githost:path" due to a bug in the underlying url crate (https://github.com/servo/rust-url/issues/599). The parser cannot distinguish between port and path in SSH URLs without the ssh:// scheme. For robust parsing of git remote URLs, use string manipulation for SSH format (githost:path) and url parse only for HTTPS URLs.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T15:59:38.232Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:0-0
Timestamp: 2025-09-09T15:59:38.232Z
Learning: The cpp-linter organization projects use simple "v1.2.3" tag format without scoped prefixes (not "scope/v1.2.3" style), as they don't deploy from monorepos using fancy scoped tags.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T15:59:38.232Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:0-0
Timestamp: 2025-09-09T15:59:38.232Z
Learning: The cpp-linter-rs project is a monorepo that uses its own separate bump-n-release workflow for deploying various packages, rather than using the unified tag-release workflow used by other cpp-linter projects.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T16:23:11.069Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:26-28
Timestamp: 2025-09-09T16:23:11.069Z
Learning: In Nushell, `$in` is a valid variable that refers to the piped input value in pipeline contexts. It's not invalid syntax and can be used safely in expressions like `$env | get --optional CI | default 'false' | (($in == 'true') or ($in == true))`.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T16:23:11.069Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:26-28
Timestamp: 2025-09-09T16:23:11.069Z
Learning: All CI platforms consistently use the string value `'true'` for the `CI` environment variable when it's set, making simple string comparison the most efficient approach for CI detection.

Applied to files:

  • .github/workflows/tag-release.nu
🔇 Additional comments (5)
.github/cliff.toml (1)

20-33: Nice job wiring FIRST_COMMIT and compare links.

Initialization via FIRST_COMMIT and the Unreleased/Released compare ranges look correct and resilient for first release scenarios.

Also applies to: 47-61, 82-96

.github/workflows/tag-release.nu (4)

74-83: Auto-bump path is clean and trims git-cliff output.

Parsing with version parse after str trim avoids newline issues; returning via version join guarantees a v-prefixed tag. LGTM.


281-291: Good UX: warning (local) vs error (CI) when GITHUB_TOKEN is missing.

This will prevent confusing partial outputs in CI while allowing local dry runs. Nice.


232-236: Pass-through of FIRST_COMMIT and GITHUB_REPO to git-cliff is solid.

Ensures correct diff links and avoids brittle remote parsing inside git-cliff.


216-228: Keep passing --tag for both full and notes paths.

This aligns with your workflow where the tag doesn’t exist yet at generation time. No changes needed.

Copy link
Contributor Author

@2bndy5 2bndy5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shenxianpeng I don't expect you to review all of this. It is a pretty big patch. I've highlighted some of the important points in this review.

run-cmd git config --global user.name $"($env.GITHUB_ACTOR)"
run-cmd git config --global user.email $"($env.GITHUB_ACTOR_ID)+($env.GITHUB_ACTOR)@users.noreply.github.com"
}
run-cmd git commit -m $"build: bump version to ($ver)"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the title of the commit pushed after updating the CHANGELOG.md file.

We could change this to something else, like maybe:

Suggested change
run-cmd git commit -m $"build: bump version to ($ver)"
run-cmd git commit -m $"chore: release ($ver)"

Tip

git-cliff transforms commit titles when adding them to the CHANGELOG.
For example,

chore: release v1.11.0

is displayed as

Release v1.11.0

Comment on lines +128 to +146
def mv-rolling-tag [
ver: string # The fully qualified version of the new tag.
] {
let tag = version parse $ver
let major_tag = $"v($tag | get major)"
print $"Moving any tags named '($major_tag)'"
let tags = (^git tag --list) | lines | each {$in | str trim}
if ($major_tag in $tags) {
# delete local tag
run-cmd git tag -d $major_tag
# delete remote tag
run-cmd git push origin $":refs/tags/($major_tag)"

# create new tag
run-cmd git tag $major_tag
run-cmd git push origin $major_tag
print $"Adjusted tag ($major_tag)"
}
}
Copy link
Contributor Author

@2bndy5 2bndy5 Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This command will

  1. Take the new version (ver) and extract the major_tag from it (eg v2 from v2.16.3)
  2. Get all tags listed in git tag --list.
  3. If the major_tag exists, then it is moved to HEAD (both locally and on remote).

So, there's no need to have an extra CI workflow triggered by a published release in cpp-linter-action.

Note

This command is called from main command after pushing the CHANGELOG update (if any) and creating the GitHub release (via gh-cli).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested this workflow on cpp-linter/cpp-linter#156. See CI summary page.

Note

When run on a non-default branch, all commit on the branch are included.
So, only runs on main branch will resemble what the release notes will look like.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI summary page looks good. Can we also have the draft release so that once we publish a release, it will also show on the GitHub Release page

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR What you see in the CI summary page is what will be used as the next release's notes.
To be clear, we won't be keeping a draft release anymore.

This PR is not meant to be an exact drop-in replacement of release-drafter. This PR proposes an alternative to release-drafter. See #47 for why I don't want to keep using release-drafter.

gh release create will create a new tag (if it doesn't exist already) when it creates the release.

We would manually trigger the tag-release workflow (in this repo) using gh-cli or the github.com UI (or using the github mobile app):
sample img
This picture is from a similar approach I use for the nRF24 org. There will be no "default branch" option for this org (see OP).

This means that every release of other cpp-linter repos will be triggered from this repo's tag-release workflow.

Old way (release-drafter)

  1. browsing to the project's releases
  2. edit a draft release
  3. click "Publish Release"

New way (tag-release.yml)

  1. browse to this repo's tag-release CI
  2. select the CI parameters
  3. click the "Run Workflow" button

The workflow will update the changelog, push the changes (if any), and publish a release (using release notes from the changelog about the new version).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested this workflow on cpp-linter/cpp-linter#156.

I verified that the title will affect the PR labels.
I had to exclude CHANGELOG.md file from adding the documentation label.
Other exceptions can be added in the .github/labeler.yml config file.
Currently, it is designed to resemble our old release-drafter config about auto-labeling.

Comment on lines +16 to +19
- package-ecosystem: pip
directory: .github/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just for updating the pinned version of git-cliff. (see .github/requirements.txt)

@shenxianpeng
Copy link
Contributor

shenxianpeng commented Sep 11, 2025

@shenxianpeng I don't expect you to review all of this. It is a pretty big patch. I've highlighted some of the important points in this review.

I’m a bit concerned that adding a separate CHANGELOG.md might make things more complicated.

labeler.yml

Right now, the new labeler.yml may not be as good as before since it does not support adding labels based on the PR title. (release-drafter autoleber already supports branch, title, and body
https://github.com/release-drafter/release-drafter?tab=readme-ov-file#autolabeler)

unreleased-summary.yml

This looks similar to a GitHub draft release, but displayed on the summary page. I think using GitHub draft releases directly would be more effective than the information here.

tag-release.yml

It looks good. Currently, we are using git-cliff, which is quite popular right now. release-drafter isn’t dead—it just wasn’t very active before. Any popular project can become less active if the maintainer shifts focus. For now, it’s good that release-drafter has a new owner. From the perspective of simplicity and ease of use, I feel that release-drafter’s configuration is more straightforward and easier to understand.

# The rest of this simply adds labels based on the PR title.
# This isn't really necessary due to how git-cliff parses commits.
# This can be removed once actions/labeler resolves the feature request:
# https://github.com/actions/labeler/issues/809
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

release-drafter autoleber already supports branch, title, and body
https://github.com/release-drafter/release-drafter?tab=readme-ov-file#autolabeler

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but it also requires write permission when it shouldn't need it. This workflow fixes that problem by separating the auto-label behavior (for PR only) from drafting release behavior (for main branch only)

Copy link
Contributor

@shenxianpeng shenxianpeng Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but it also requires write permission when it shouldn't need it

It needs both write permission to create a GitHub release and add label to PR. release-drafter/release-drafter#1187

contents: write  # for release-drafter to create a github release
pull-requests: write  # for release-drafter to add label to PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't create a draft release for PR events. PR events have no bearing on releases until the changes are merged to main branch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI summary page looks good. Can we also have the draft release so that once we publish a release, it will also show on the GitHub Release page

@2bndy5 2bndy5 changed the title feat: replace release-drafter with git-cliff and gh-cli feat: phase out release-drafter Sep 11, 2025
coderabbitai[bot]

This comment was marked as resolved.

2bndy5 added 4 commits October 1, 2025 16:27
ref #47

supports SSH when finding repo name from git remote URL
for bumping git-cliff automatically

and group dependabot updates into 1 PR (for each ecosystem)
Some schemas are used where they shouldn't be (.github/workflows/mkdocs.yml).
Some schemas are just outdated or incorrect (.github/labeler.yml).

Prefer using GitHub Actions ext in VSCode to validate workflows
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
.github/workflows/labeler.yml (1)

58-75: Normalize existing labels before membership checks.

actions/labeler@v6 returns all-labels as a comma-separated string, so splitting without trimming/normalizing leaves leading whitespace and preserves original casing. If the repo keeps labels like Bug or Enhancement, the case-sensitive in check will miss them and we’ll try to add bug/enhancement again, which makes gh pr edit fail with “label does not exist.” Normalizing up front avoids the duplicate add(es) and keeps the step resilient to formatting differences. (github.com)

-          let labels = $env.ALL_LABELS | split row ","
+          let labels = (
+            $env.ALL_LABELS
+            | default ""
+            | split row ","
+            | each {|it| $it | str trim }
+            | compact
+          )
+          let labels_lc = ($labels | each {|it| $it | str downcase })
@@
-            and not ("bug" in $labels)
+            and not ("bug" in $labels_lc)
@@
-            and not ("enhancement" in $labels)
+            and not ("enhancement" in $labels_lc)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f00aa84 and 5378b1d.

📒 Files selected for processing (11)
  • .github/cliff.toml (1 hunks)
  • .github/dependabot.yml (1 hunks)
  • .github/labeler.yml (1 hunks)
  • .github/requirements.txt (1 hunks)
  • .github/workflows/labeler.yml (1 hunks)
  • .github/workflows/tag-release.nu (1 hunks)
  • .github/workflows/tag-release.yml (1 hunks)
  • .github/workflows/unreleased-summary.yml (1 hunks)
  • .vscode/extensions.json (1 hunks)
  • .vscode/settings.json (1 hunks)
  • cspell.config.yml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .vscode/settings.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • .vscode/extensions.json
  • .github/labeler.yml
  • .github/dependabot.yml
  • .github/workflows/tag-release.yml
  • .github/requirements.txt
  • .github/cliff.toml
  • cspell.config.yml
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-09-10T04:11:21.820Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/labeler.yml:23-33
Timestamp: 2025-09-10T04:11:21.820Z
Learning: In GitHub Actions reusable workflows (workflow_call event), the github context is inherited from the calling workflow, including PR details and event data. Permissions are also inherited from the calling workflow, so actions like actions/labeler can access the necessary context and permissions without explicit token passing if the caller has granted the required permissions.

Applied to files:

  • .github/workflows/labeler.yml
📚 Learning: 2025-09-08T04:20:32.371Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:142-158
Timestamp: 2025-09-08T04:20:32.371Z
Learning: In git-cliff, when the GIT_CLIFF_CONFIG environment variable is set, git-cliff automatically uses it for configuration. There's no need to pass the --config parameter with the same value, as the environment variable takes precedence. Functions that detect GIT_CLIFF_CONFIG should return null/empty when it's present, allowing git-cliff to use the env var directly.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T13:44:00.565Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:52-59
Timestamp: 2025-09-09T13:44:00.565Z
Learning: In the cpp-linter/.github repository, git-cliff --bumped-version outputs version numbers with the "v" prefix (e.g., "v1.11.0") when using the configured .github/cliff.toml, contrary to assumptions about default git-cliff behavior outputting versions without the "v" prefix.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-08T04:22:14.942Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:164-191
Timestamp: 2025-09-08T04:22:14.942Z
Learning: In git-cliff workflows, the --tag parameter should be passed even when generating release notes from unreleased changes because the tag being summarized doesn't exist yet when git-cliff runs. The --tag parameter tells git-cliff what version to associate with the unreleased changes, and the actual tag creation happens after git-cliff generates the notes/changelog.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T13:44:00.565Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:52-59
Timestamp: 2025-09-09T13:44:00.565Z
Learning: In repositories using git-cliff with conventional_commits = true and proper tag handling templates (like cpp-linter/.github's cliff.toml), the command `git-cliff --bumped-version` outputs properly formatted version tags with the "v" prefix (e.g., "v1.11.0"), not bare version numbers without the prefix.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-08T06:25:14.311Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:123-133
Timestamp: 2025-09-08T06:25:14.311Z
Learning: Nushell's url parse command fails on SSH URLs in the format "githost:path" due to a bug in the underlying url crate (https://github.com/servo/rust-url/issues/599). The parser cannot distinguish between port and path in SSH URLs without the ssh:// scheme. For robust parsing of git remote URLs, use string manipulation for SSH format (githost:path) and url parse only for HTTPS URLs.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-08T04:26:49.968Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/cliff.toml:133-133
Timestamp: 2025-09-08T04:26:49.968Z
Learning: In the cpp-linter/.github repository's .github/cliff.toml file, the "Breaking Changes" group title (uppercase C) is intentionally kept consistent with previous release-drafter output format to maintain user experience, even though other group titles use lowercase (e.g., "Breaking changes"). This inconsistency is by design for backward compatibility.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T15:59:38.273Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:0-0
Timestamp: 2025-09-09T15:59:38.273Z
Learning: The cpp-linter organization projects use simple "v1.2.3" tag format without scoped prefixes (not "scope/v1.2.3" style), as they don't deploy from monorepos using fancy scoped tags.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T15:59:38.273Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:0-0
Timestamp: 2025-09-09T15:59:38.273Z
Learning: The cpp-linter-rs project is a monorepo that uses its own separate bump-n-release workflow for deploying various packages, rather than using the unified tag-release workflow used by other cpp-linter projects.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T16:23:11.088Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:26-28
Timestamp: 2025-09-09T16:23:11.088Z
Learning: In Nushell, `$in` is a valid variable that refers to the piped input value in pipeline contexts. It's not invalid syntax and can be used safely in expressions like `$env | get --optional CI | default 'false' | (($in == 'true') or ($in == true))`.

Applied to files:

  • .github/workflows/tag-release.nu
📚 Learning: 2025-09-09T16:23:11.088Z
Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/tag-release.nu:26-28
Timestamp: 2025-09-09T16:23:11.088Z
Learning: All CI platforms consistently use the string value `'true'` for the `CI` environment variable when it's set, making simple string comparison the most efficient approach for CI detection.

Applied to files:

  • .github/workflows/tag-release.nu
🔇 Additional comments (16)
.github/workflows/unreleased-summary.yml (1)

1-45: LGTM! Workflow structure is well-designed.

The unreleased-summary workflow correctly:

  • Uses workflow_call for reusability across projects
  • Sets minimal required permissions (contents: read, pull-requests: read)
  • Checks out both the project repo (with full history via fetch-depth: 0) and the org repo for shared configuration
  • Properly configures uv with caching against the project's uv.lock
  • Passes required environment variables (GITHUB_TOKEN, GITHUB_REPO, GIT_CLIFF_CONFIG, UV_CONSTRAINT) to git-cliff
  • Outputs the unreleased summary directly to GITHUB_STEP_SUMMARY for easy preview

The multiline run command with >- correctly folds arguments without introducing unwanted newlines.

.github/workflows/tag-release.nu (15)

4-21: LGTM! Well-designed utility function.

The run-cmd function provides good UX by:

  • Displaying the full command being executed
  • Measuring and reporting elapsed time
  • Smartly abbreviating the command name for common tools (git, gh, uvx)

The use of --wrapped parameter and command spreading is correct.


26-28: LGTM! CI detection is correct.

Based on learnings, $in is valid in Nushell pipeline contexts, and all CI platforms use the string 'true' for the CI environment variable. The current implementation is both correct and efficient.


30-43: LGTM! Branch detection is implemented correctly.

The function properly filters for the current branch (marked with *), trims whitespace, and compares against 'main'. The implementation is clear and correct.


45-65: LGTM! Version parsing utilities are well-designed.

The version parse and version join helper functions correctly:

  • Strip the leading "v" prefix before parsing
  • Parse into major/minor/patch components with type conversion
  • Format back into the standard "v{major}.{minor}.{patch}" pattern

Clean and reusable design.


74-83: LGTM! Auto-bump logic is correct.

The auto-bump path properly:

  • Passes --bumped-version to git-cliff
  • Includes the cliff config when available
  • Trims the output (addressing previous review feedback)
  • Parses and re-joins to normalize the version format

Based on learnings, git-cliff with this repo's configuration outputs versions with the "v" prefix, and the trim/parse/join flow ensures consistency.


85-120: LGTM! Manual bump logic handles all cases correctly.

The manual bump implementation:

  • Falls back to "v0.0.0" when no tags exist (addressing previous feedback about new repos)
  • Uses complete to safely handle git errors
  • Correctly increments each component (major resets minor/patch; minor resets patch)
  • Validates the component parameter with a clear error message

Well thought-out and defensive.


128-146: LGTM! Rolling tag management is correctly implemented.

The mv-rolling-tag function:

  • Extracts the major version (e.g., "v2" from "v2.16.3")
  • Checks if that major tag exists in the repo
  • Deletes the local and remote tag
  • Recreates and pushes the tag pointing to HEAD

This enables convenient major-version tracking for users who want to stay on the latest v2.x.x release, for example.


152-154: LGTM! First commit detection is straightforward.

Using git rev-list --max-parents=0 HEAD correctly finds the initial commit, which git-cliff needs for proper diff hyperlink generation.


160-177: LGTM! Repo name detection handles multiple URL formats.

Based on learnings, the function correctly:

  • Checks for GITHUB_REPO in the environment first
  • Falls back to parsing the git remote URL
  • Handles SCP-style SSH URLs (git@host:org/repo.git) by converting them to a parseable format
  • Uses url parse for HTTPS URLs
  • Extracts the org/repo path correctly

The workaround for Nushell's URL parsing limitation with SCP-style URLs is appropriate.


185-203: LGTM! Config discovery respects environment variable.

Based on learnings, when GIT_CLIFF_CONFIG is set, git-cliff automatically uses it, so returning null here is correct—the function doesn't need to pass --config with the same value. The fallback logic searches plausible locations when the env var is not set.

This design allows the workflow to override the config via environment while maintaining a sensible default.


205-206: LGTM! Constants are appropriately defined.

CHANGELOG and RELEASE_NOTES are clearly named and correctly scoped (RELEASE_NOTES is exported for potential use by callers).


209-236: LGTM! Changelog generation is correctly implemented.

The gen-changes function:

  • Accepts all necessary parameters (version, first commit, repo name, cliff config)
  • Passes --tag even for unreleased changes (correct per learnings—the tag doesn't exist yet)
  • Conditionally generates either the full CHANGELOG.md or just release notes
  • Properly sets FIRST_COMMIT and GITHUB_REPO in the environment for git-cliff (addressing previous feedback)

The use of with-env to pass environment variables without polluting the global scope is a good practice.


264-316: LGTM! Early validation and setup is thorough.

The main function correctly:

  • Discovers the repo name and first commit
  • Blocks execution on the .github org repo to prevent accidental deployment
  • Checks for GITHUB_TOKEN and warns/errors appropriately based on CI context
  • Finds the cliff config
  • Validates and normalizes the component parameter with helpful messages
  • Determines the next version
  • Ensures the script only runs on the main branch

Comprehensive guards prevent common mistakes.


317-330: LGTM! Changelog update and push logic is sound.

The script:

  • Regenerates the full CHANGELOG.md with --full
  • Stages all changes with git add --all
  • Checks for actual changes before committing
  • Configures git identity in CI using GitHub actor information
  • Uses a clear commit message ("build: bump version to vX.Y.Z")
  • Pushes changes to remote

The conditional commit ensures no empty commits are created.


332-350: LGTM! Release creation and rolling tag management complete the workflow.

The final steps:

  • Generate release notes for just the new version (via gen-changes without --full)
  • Create a GitHub release with the new version tag using gh-cli
  • Pass the release notes file, title, and repo correctly
  • Move any corresponding major version tags to point at the new release (e.g., move v2 to v2.16.3)

The sequence is correct: changelog update is pushed first, then the release is created (which creates the git tag), then rolling tags are adjusted. This ensures the release points to the commit containing the updated CHANGELOG.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request