Repository navigation
feat(release): semantic versions and release notes from Conventional Commits in one workflow run - #17
Merged
Merged
Conversation
Release (workflow_dispatch with patch/minor/major/X.Y.Z) moves the Unreleased changelog entries under the new version, bumps the version files, commits and tags vX.Y.Z on main, and publishes the release with that changelog section as notes. A hand-pushed vX.Y.Z tag is published the same way. Package workflows are started with gh workflow run, since a release published with GITHUB_TOKEN doesn't fire release: published; with a RELEASE_TOKEN secret the event fires and nothing is dispatched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016KX8N4jaryKPQD3n7MZPx6
Release defaults to bump=auto: the version and changelog section come from the Conventional Commit titles merged since the last release, under any hand-written Unreleased text. PR titles are checked in CI and commit messages by a commit-msg pre-commit hook (`make hooks`); the hooks also check YAML, TOML and workflows (actionlint). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016KX8N4jaryKPQD3n7MZPx6
…ms, Dependabot Adds CONTRIBUTING.md, SECURITY.md (private reporting through GitHub), CODE_OF_CONDUCT.md (Contributor Covenant 2.1), bug and feature issue forms, a PR template asking for a Conventional Commit title, and Dependabot for uv, pnpm, Docker and Actions with build(deps)/ci(deps) titles, which the changelog lists under Dependencies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016KX8N4jaryKPQD3n7MZPx6
Vulnerabilities go privately to the maintainer while the repository is private (GitHub's private reporting only works on public ones), with no bug bounty. CONTRIBUTING says the license is undecided and that LICENSE.txt is the template's, with a placeholder for when Lens goes public. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016KX8N4jaryKPQD3n7MZPx6
- The default-branch check reads the ref from env, not interpolated into the script. - A run that pushed its commit and tag but didn't publish is resumed: the cut job checks out the branch tip, and when that is an unpublished chore(release) commit it publishes that tag instead of cutting. A tag push is always published if it isn't yet (the concurrency group and the existing-release check replace the commit-message skip). - An exact version must be newer than the current one, and --latest is set only for the newest version. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016KX8N4jaryKPQD3n7MZPx6
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016KX8N4jaryKPQD3n7MZPx6
…ange once - For merge commits, BREAKING CHANGE: / BREAKING-CHANGE: footers are read from the PR's own commits, so a breaking PR bumps major. - A PR that wrote its own Unreleased entry isn't listed again from its title (unless it's breaking). - A re-run resumes from the newest unreleased chore(release) tag, even after more merges on main. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016KX8N4jaryKPQD3n7MZPx6
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNbRrpHTCqLewRKdkmd45X
adeelahmad
marked this pull request as ready for review
October 2, 2026 13:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Adeel · project thread
Description
Before: to release, you bumped two version files and wrote the changelog by hand, then ran the template workflow, which only made a draft for you to publish. Commit messages and PR titles followed no format. The repo had no contributing guide, security policy, issue forms or Dependabot config.
After: PR titles are Conventional Commits (
feat(chat): …,fix!: …). A PR title check enforces them on every PR, and acommit-msghook checks commit messages once you runmake hooks. One run of Release (Actions tab, orgh workflow run release.yml) then does the rest:featbumps minor, and anything else bumps patch. You can givepatch,minor,majoror a newerX.Y.Zinstead.## Unreleasedtext comes first. After it come the PRs, grouped into Breaking changes, Features, Fixes, Performance, Reverts, Dependencies and Other changes, each linked to its PR.docs,refactor,test,build,ci,choreandstyleare left out, exceptbuild(deps), which goes under Dependencies. The## Unreleasedheading stays in place, so open branches that add bullets under it still merge.pyproject.toml,uv.lock,package.jsonandCloudronManifest.json(when present).chore(release): vX.Y.Ztomain, tags it, and publishes the release with that section as its notes. Only the newest version is marked Latest.Pushing a
vX.Y.Ztag by hand also publishes, without the commit. If a run pushes its commit and tag but fails before publishing, running it again (or re-running it) publishes that version instead of cutting another.The pre-commit hooks already ran ruff, ESLint, Prettier and tsc. They now also check YAML, TOML and the workflows (actionlint). I kept the repo's existing pre-commit framework, which installs through
uv, so husky isn't added.How:
.github/scripts/release.pyholds the rules (cut,notes,check-title,check-message), so CI, the hook and the release all use one parser. It reads first-parent history:(#N);Older titles that don't follow the format go under Other changes.
Repository health files
CONTRIBUTING.md: setup, hooks, tests, Conventional Commits and the release flow. It links todocs/contributing.mdfor the full guide. A License section says the license isn't decided yet and thatLICENSE.txtis still the template's MIT notice, left untouched. ATODO(license)placeholder marks what to change when Lens goes public.SECURITY.md: while the repo is private, reports go privately to the maintainer, since GitHub's private vulnerability reporting only works on public repos. There's no bug bounty. It lists what matters most for Lens: namespace access, signed media links, auth, stored credentials, SSRF and injection.CODE_OF_CONDUCT.md: Contributor Covenant 2.1, with @adeelahmad as the contact..github/dependabot.yml: uv and pnpm updates run weekly, with minor and patch updates grouped. Docker images and Actions update monthly. Titles look likebuild(deps): bump …orci(deps): ….Packages
A release published with
GITHUB_TOKENdoesn't firerelease: publishedin other workflows. The publish job therefore startscloudron.yml,qnap.ymlandsynology.ymlitself withgh workflow run <file> --ref vX.Y.Z -f tag=vX.Y.Z. It skips any workflow that isn't in the tag.You can instead add a
RELEASE_TOKENsecret (fine-grained, Contents: write). The workflow then commits and publishes with that token, which also gets past branch protection onmain, and the normal event fires. That token's tag push starts a second run. The second run waits for the first (concurrency group) and finds the release already published, so nothing is published twice.What each packaging PR needs:
tagdispatch input. Whentagis set, it should check out that ref and attach the files to that release.tagdispatch input, used asRELEASE_TAG.releases/latest.Open PRs that edit CHANGELOG.md
They need no change: their Unreleased entries become the top of the next section. They only need a Conventional Commit title before they merge.
Motivation and Context
Releases should be automated, and every packaging PR waits on a published release.
Steps to reproduce (if appropriate):
I tested on a clone of this repo's history, with no tags on it.
release.py cut autowith afeat!:commit on top gave 0.4.0, with the right sections and all 4 version files bumped. Abuild(deps): bump …commit was listed under Dependencies.cut 0.3.0andcut 0.2.9are refused because neither is newer than the current version.check-titleaccepts conventional and Dependabot titles. It rejectsChat: answers,feature: xandfeat:x.commit-msghook blocked a bad message.pre-commit runand actionlint pass, and GitHub validateddependabot.yml.No tags or releases were made on this repo.
Types of changes
Checklist:
🤖 Generated with Claude Code
https://claude.ai/code/session_016KX8N4jaryKPQD3n7MZPx6