relnote docs · CHANGELOG.md · Next version · standard-version alternative · semantic-release alternative · release-please alternative · GitHub Action · Single-file zipapp
+A conventional-changelog and commitizen alternative without Node plugins
+conventional-changelog (and the conventional-changelog-* presets) turns conventional commits into CHANGELOG.md. commitizen and cz-cli prompt for the commit message. Neither command bumps a version file and tags. If you want that changelog — and the bump, commit, and tag — from commits you already wrote, without Node plugins for the release tooling, the relnote zipapp and examples/release.sh do it offline.
conventional-changelog + commitizen, or this?
+| conventional-changelog + commitizen | relnote + release.sh | |
|---|---|---|
| Runtime for release tooling | Node.js and npm packages | bash, git, Python 3.11 stdlib (zipapp) |
| Plugins | conventional-changelog-cli, a preset (conventional-changelog-conventionalcommits, conventional-changelog-angular, …), cz-cli / commitizen, an adapter (cz-conventional-changelog) | None; flags on release.sh |
| Commit prompt | git cz asks type, scope, and subject | None. Commits are already conventional |
| Changelog | conventional-changelog -p angular -i CHANGELOG.md -s (preset plugin; changelog only) | relnote --changelog CHANGELOG.md via release.sh |
| Version bump, commit, tag | Not those two CLIs. conventional-recommended-bump prints a bump type; standard-version / commit-and-tag-version do the full release | One command: optional --bump-file, CHANGELOG.md, chore(release) commit, annotated tag |
| Network while writing the changelog | npm install of plugins; presets resolve from the registry | Offline until you push or gh release create |
| Config | package.json config.commitizen, .czrc, -p preset | None |
Keep commitizen if you want the questions. Keep a conventional-changelog-* preset if you depend on its writer options. This page replaces the changelog command and the separate bumper, not the prompt.
Pin the zipapp
+Python 3.11+ and git on PATH. The download is the v0.3.7 zipapp from the GitHub Release (same pin as the single-file install), plus examples/release.sh from that tag. No npm packages.
mkdir -p ~/.local/bin
+curl -fsSL -o ~/.local/bin/relnote https://github.com/loki-inu/relnote/releases/download/v0.3.7/relnote
+chmod +x ~/.local/bin/relnote
+curl -fsSL -o release.sh https://raw.githubusercontent.com/loki-inu/relnote/v0.3.7/examples/release.sh
+chmod +x release.sh
+ Put ~/.local/bin on PATH. Commit release.sh into the repo (for example scripts/release.sh) so the release command does not depend on the raw URL next time. It is plain bash; read it before you run it.
One local command
+The working tree must be clean. ./release.sh reads conventional commits since the last tag, prepends CHANGELOG.md, commits chore(release): vX.Y.Z, and creates an annotated tag. Nothing is pushed. The version rule is relnote --next-version: breaking → major, feat → minor, otherwise patch (in 0.x a breaking change bumps minor). See next version and the CHANGELOG.md guide.
Omit --bump-file when you have no version file. Pass it (repeatable) to set the first "version" / version = line in package.json, pyproject.toml, or Cargo.toml. --dry-run prints the plan and the notes and writes nothing. These are three separate invocations, not one script to run top to bottom:
./release.sh --dry-run
+./release.sh
+./release.sh --bump-file package.json
+ Afterwards: git push --follow-tags. If HEAD is already tagged, or the next tag already exists, the script exits 1 instead of guessing.
Changelog only, the shape of conventional-changelog -i CHANGELOG.md -s without a preset package — you choose the title, and nothing is committed:
relnote --next-version
+relnote --changelog CHANGELOG.md --changelog-title v1.4.0
+ Use the tag --next-version printed. ./release.sh fills that title in for you and then commits and tags.
Optional GitHub Actions release
+When you also want a GitHub Release, save this as .github/workflows/release.yml and use Actions → Release → Run workflow. It is the same job as the semantic-release alternative and the release-please alternative: a workflow_dispatch button, a has-changes gate, then release.sh. Drop --bump-file pyproject.toml or point it at another file. No Node install for the tooling. Action inputs: GitHub Action guide.
name: Release
+on:
+ workflow_dispatch:
+permissions:
+ contents: write
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - id: relnote
+ uses: loki-inu/relnote@v0.3.7
+ - name: Install relnote and release.sh
+ if: steps.relnote.outputs.has-changes == 'true'
+ env:
+ V: v0.3.7
+ run: |
+ mkdir -p "$HOME/.local/bin"
+ curl -fsSL -o "$HOME/.local/bin/relnote" "https://github.com/loki-inu/relnote/releases/download/$V/relnote"
+ chmod +x "$HOME/.local/bin/relnote"
+ curl -fsSL -o "$RUNNER_TEMP/release.sh" "https://raw.githubusercontent.com/loki-inu/relnote/$V/examples/release.sh"
+ echo "$HOME/.local/bin" >> "$GITHUB_PATH"
+ - name: Bump, changelog, tag, publish
+ if: steps.relnote.outputs.has-changes == 'true'
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ set -euo pipefail
+ if [ -n "$(git tag --points-at HEAD --list 'v*')" ]; then
+ echo "HEAD is already tagged; nothing to release."
+ exit 0
+ fi
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ bash "$RUNNER_TEMP/release.sh" --bump-file pyproject.toml
+ tag=$(git describe --tags --exact-match)
+ git push origin HEAD "refs/tags/$tag"
+ relnote --output "$RUNNER_TEMP/notes.md" --quiet
+ gh release create "$tag" --title "$tag" --notes-file "$RUNNER_TEMP/notes.md"
+ The first relnote step only checks whether anything is unreleased (has-changes). Notes go through a file and --notes-file. Never interpolate the body inside run: (backticks in commit subjects break the YAML). Pin the action and the curl URLs to the same tag.
A tag-push workflow can publish the same notes after you tag locally: checkout with fetch-depth: 0, run the action, write notes.md, then gh release create with --notes-file. Same rule: do not put the notes in run:. See the tag-push example on the Action guide.
What one run does
+Start: v1.3.2 is the last tag, pyproject.toml says version = "1.3.2", and two conventional commits landed since (the kind of subjects commitizen or a squash-merged pull request title would leave). The release step log:
next: v1.4.0
+changelog: CHANGELOG.md
+bump: pyproject.toml -> 1.4.0
+committed: chore(release): v1.4.0
+tagged: v1.4.0
+next step: git push --follow-tags
+To https://github.com/you/project.git
+ * [new branch] HEAD -> main
+ * [new tag] v1.4.0 -> v1.4.0
+ pyproject.toml now says version = "1.4.0", and CHANGELOG.md gains this section above the previous one:
## [v1.4.0] - 2026-10-28
+
+### Features
+
+- add --json output ([#12](https://github.com/you/project/pull/12)) (`e4e9bff`) — Ada Lovelace
+
+### Fixes
+
+- handle empty config ([#13](https://github.com/you/project/pull/13)) (`8c0e206`) — Ada Lovelace
+
+**Full changelog:** https://github.com/you/project/compare/v1.3.2...v1.4.0
+ The GitHub Release v1.4.0 gets this body; on GitHub, #12 links to the pull request:
## What's changed
+
+### Features
+
+- add --json output (#12) (`e4e9bff`) — Ada Lovelace
+
+### Fixes
+
+- handle empty config (#13) (`8c0e206`) — Ada Lovelace
+
+---
+
+Range: `v1.3.2` → `v1.4.0`.
+
+**Full changelog:** https://github.com/you/project/compare/v1.3.2...v1.4.0
+ The CHANGELOG.md section and Release body are produced by running the workflow's last step against a scratch repository in relnote's test suite (tests/test_conventional_changelog_docs.py, with gh stubbed); your hashes and dates differ. The chore(release) commit is not listed.
Bumping an npm package? Use --bump-file package.json. If you also keep a package-lock.json, add --exec "npm install --package-lock-only --ignore-scripts --silent" so the lock tracks the new version. Node runs there because the product is npm. See the npm package guide. The standard-version alternative maps the rest of the flags (--release-as, --skip-commit, --skip-tag, --tag-prefix).
FAQ
+Does relnote replace commitizen prompts?
+No. commitizen and cz-cli (with an adapter such as cz-conventional-changelog) ask for type, scope, and subject so the commit is conventional. relnote does not prompt. It reads commits that are already conventional — from commitizen, a pull-request title, or typed by hand — and writes CHANGELOG.md, an optional version bump, and a tag. Keep commitizen if you want the prompt; you do not need it to generate the changelog.
+What about the conventional-changelog CLI on its own?
+conventional-changelog-cli, with a conventional-changelog-* preset, writes CHANGELOG.md and stops. It does not bump a version file, commit, or tag. relnote --changelog CHANGELOG.md is that step, offline, with no preset package. examples/release.sh adds the optional bump, the chore(release) commit, and the annotated tag. Section shape: the CHANGELOG.md guide.
+How does this differ from standard-version and commit-and-tag-version?
+standard-version and its fork commit-and-tag-version already combine a conventional-changelog preset with a version bump, commit, and tag, and they need Node. release.sh is that local command without Node and without .versionrc. Keep commit-and-tag-version if you need prerelease bumps, lifecycle scripts, or custom presets. Flag mapping is on the standard-version alternative page.
+How does this differ from the semantic-release and release-please pages?
+The semantic-release alternative page is the CI publish loop without Node in the release tooling. The release-please alternative page is the same job for people leaving a release pull request. This page is for the conventional-changelog plus commitizen stack: changelog plugins and commit prompts, with the version bump usually in another tool. The local path is release.sh; the optional Actions job matches those pages.
+How do I dry-run?
+Locally, release.sh --dry-run prints the next version, the changelog path, any version file it would bump, and the notes, and writes nothing. Add --bump-file only when a version file should change. Omit --bump-file when you only want a preview of the changelog, commit, and tag. In the Actions job, has-changes false skips the release steps when nothing is unreleased, and a second run whose HEAD is already tagged exits 0.
+Are chore(release) commits included in the notes?
+No by default. relnote skips chore(release) subjects so the release commit does not appear in its own CHANGELOG.md section or Release body. Pass --include-release-commits on the CLI, or include-release-commits: true on the action, to keep them.
+Does an npm package still need Node?
+Only for the product, not for this release tooling. The zipapp and release.sh stay free of Node plugins. For an npm package, pass --bump-file package.json, and if you keep a package-lock.json add --exec "npm install --package-lock-only --ignore-scripts --silent" so the lock tracks the new version. See the npm package guide.
+ + +