# rlyx
rlyx is a smart release manager for Git and GitHub. You run one command and it reads your repo, figures out what changed, and ships a clean release without drama. It bumps versions, writes the tag, pushes, generates real release notes from Git history, updates a changelog if you want one, and can publish GitHub releases through the GitHub CLI. It works for single repositories and monorepos across JavaScript, Rust, and Python. No conventional-commits. No corny emojis. No manual tagging. Just a fast, automatic CLI release.
> [!NOTE]
> Status is still in betaa, Interfaces, defaults, and behaviors are still subject to change until the first stable release.
## Install
Use Cargo to install the binary.
```bash
cargo install rlyx
```
Git is required. If the GitHub CLI `gh` is present, rlyx can publish releases and pull PR titles automatically. For JavaScript projects, if a lockfile already exists, rlyx will refresh it using whichever of `pnpm`, `yarn`, or `npm` it finds.
## What happens in a single repository
rlyx starts by finding your current version. For JavaScript it reads `package.json`. For Python it reads `pyproject.toml`, `setup.cfg`, or `setup.py`. For Rust it reads `Cargo.toml` when the repo is not a Cargo workspace. If it cannot find a version in files, it falls back to the most recent lightweight tag that looks like `vX.Y.Z`.
From that base it computes the next version based on the bump you asked for. It writes that version back into your project files unless you explicitly tell it not to write package files. It renders a commit message from your template and creates a commit if anything changed. It creates an annotated tag like `v1.2.3` and includes machine trailers in the tag body so tools can parse it later without guesswork.
The trailers are `rlyx:id`, `rlyx:pkg`, `rlyx:ver`, and `rlyx:sha`. These are key value lines inside the tag annotation so machines can trace a tag to a package and commit with zero ambiguity.
After tagging, it pushes the commit and the tag. It builds release notes from `git log`, optionally updates `CHANGELOG.md`, and if `gh` is available it creates a GitHub release and sets an explicit title so GitHub does not guess from the body. If your working tree is dirty and you did not allow dirty runs, rlyx stops before doing anything.
## What happens in a monorepo
If rlyx detects a monorepo or you force it with `--monorepo`, it discovers packages for JavaScript, Rust, and Python. It filters that list to whatever you declared in configuration. You can run it headless using config defaults or interactively if you prefer to choose per package.
Headless mode applies the bump you asked for on the CLI or the `default_bump` from config. Interactive mode prompts for each package so you choose patch, minor, major, or skip. `rlyx` writes versions for the selected packages, stages once, creates a single summary commit using your template, creates one annotated tag per bumped package using your tag format, pushes the summary commit, pushes all tags, generates per package notes, optionally writes one grouped changelog entry, and can create one GitHub release per package if you enabled that behavior in config.
## Commands
You can be explicit about the bump or let config decide.
```bash
# single repo
rlyx
rlyx patch
rlyx minor
rlyx major
# prereleases
rlyx prerelease --id rc --bump minor
rlyx prerelease --id nightly
# finalize latest prerelease to stable
rlyx finalize
# rollback most recent release work
rlyx rollback
rlyx rollback --local
# update rlyx itself
rlyx selfupdate
# monorepo flows
rlyx --monorepo
rlyx --monorepo patch
rlyx monorepo minor
rlyx monorepo major
rlyx monorepo patch --packages @acme/core,@acme/ui
```
Global flags are available when you need control during a run.
```text
--no-changelog skip changelog writes
--repo owner/name override GitHub repo detection
--dry run everything except push and release
--ignore_package do not write version files
--since <rev> narrow single-repo finalize baselines
--post_bump_cmd "<CMD>" run a shell command after bumping
--skip_post_bump disable the post bump step
--monorepo force monorepo mode
```
## Configuration
Place `rlyx.toml` at the repository root. If you only call `rlyx` with no path, it looks for `.rlyxrc` first and falls back to `rlyx.toml`. Environment variables with an `RLYX_` prefix can override scalar keys at runtime.
A minimal single repo config looks like this.
```toml
default_bump = "patch"
bump_commit_message = "release v{{version}}"
bump_tag_message = "v{{version}}"
changelog_header = "# Changelog"
[changelog]
enable = true
path = "CHANGELOG.md"
# optional
allow_dirty = false
include_v_prefix = true
post_bump_command = "pnpm -w build"
```
A monorepo config adds the package list and a per package tag format. You declare the exact package names rlyx is allowed to manage and you control how tags render. The tag template can reference `{{name}}`, `{{id}}`, and `{{version}}`. When `include_v_prefix` is true, `{{version}}` renders with a leading `v`.
```toml
packages = ["@acme/api", "@acme/ui", "cli"]
include_v_prefix = true
pkg_tag_template = "{{name}}@{{version}}"
releases_per_package = true
monorepo_bump_commit_message = "release {{count}} packages\n\n{{list}}"
[changelog]
enable = true
path = "CHANGELOG.md"
```
You can keep continuity across package renames using a migration map. This matters when tags embed the package name. If `@acme/ui` becomes `@acme/web-ui`, add a migration entry with glob patterns that match how tags used to look. rlyx will search those patterns to find the previous tag when it computes diffs.
```toml
[migration."@acme/web-ui"]
legacy_tag_patterns = ["@acme/ui@v*", "ui@v*"]
```
## Environment overrides
You can mirror scalar config keys with environment variables.
```text
RLYX_DEFAULT_BUMP
RLYX_BUMP_COMMIT_MESSAGE
RLYX_BUMP_TAG_MESSAGE
RLYX_CHANGELOG_HEADER
RLYX_CHANGELOG_ENABLE=1|0
RLYX_CHANGELOG_PATH
RLYX_PKG_TAG_TEMPLATE
RLYX_INCLUDE_V_PREFIX=1|0
RLYX_RELEASES_PER_PACKAGE=1|0
RLYX_UMBRELLA_RELEASE=1|0
RLYX_MONOREPO_BUMP_COMMIT_MESSAGE
RLYX_POST_BUMP_CMD
```
## Behavior details
Prereleases are computed from your current version. If you run `rlyx prerelease --id rc --bump minor`, rlyx increments the minor, appends `-rc.1`, and tags that. Repeating the command on the same base increments the trailing number. Nightly prereleases use a `YYYYMMDD` channel and increment a suffix if you create more than one nightly on the same day.
Finalize converts the latest reachable prerelease to a stable `vX.Y.Z` tag. It refuses to run if that stable tag already exists.
Annotated tags carry the trailer lines so machines can pull the package id, package name, semantic version, and commit SHA without parsing your prose. The GitHub release title is always set explicitly to avoid auto derived titles. In monorepos, rlyx can publish one release per package when `releases_per_package` is true. In single repo mode it publishes one release per tag.
When rlyx writes the changelog it creates a section for the tag and date, a Changes block with non merge commits in the range, a Pull requests block if it detects PR numbers, an Authors block from `git shortlog`, and a Compare link to the exact range on GitHub. You can set a static header at the top of the file using `changelog_header`.
The post bump step can run a command to rebuild lockfiles, run tests, or update docs. You can pass it on the CLI, set it in config, or accept the interactive prompt. You can skip it for a single run with `--skip_post_bump`.
The dirty tree rule is strict by default. If anything is staged or unstaged, rlyx exits unless `allow_dirty` is true. Rollback deletes tags at `HEAD`, rewinds recent release commits, and optionally pushes the forced update unless you used `--local`.
## Package rename handling
Single repo tags are plain `vX.Y.Z`, so renaming a package does not affect tag continuity. In a monorepo, tags include the package name by design. If you rename a package, update the `packages` list to the new name and add a `migration` entry with legacy patterns.
That preserves history across the rename so diffs and author counts stay correct. Scoped JavaScript names are normalized internally by removing the leading `@` and replacing slashes with underscores for the machine `rlyx:id` in trailers. The visible tag format you configure is not changed by that internal id.
## Safety and dry runs
You can see the exact plan without touching remotes by adding `--dry`. rlyx will compute versions, construct notes, and render messages, but it will not push or publish. If you want to inspect the plan without writing version files, add `--ignore_package`.
## Support matrix
JavaScript projects are supported at any package root with `package.json`. Monorepo discovery walks a few levels deep and ignores `node_modules`, build directories, and VCS folders. Rust is supported for single crates and workspace layouts. Python is supported for modern `pyproject.toml` and for older `setup.cfg` or `setup.py` when a version can be updated in place. If your layout is unusual but you still keep semantic versions in text files, that is fine. Opening issues is not enabled yet.
## Examples
A typical single repo patch.
```bash
rlyx
```
A named prerelease.
```bash
rlyx prerelease --id rc --bump minor
```
A monorepo batch scoped to one package.
```bash
rlyx monorepo patch --packages @acme/core
```
Finalize after a series of prereleases.
```bash
rlyx finalize
```
Rollback and clean up remote tags as well.
```bash
rlyx rollback
```
## Changelog output shape
This is the exact shape rlyx writes so you can wire your renderer around it.
```
## v1.2.3 2025-11-12
### Changes since v1.2.2
- abc123 fix: useful title
- def456 feat: another title
### Pull requests
- #123: Improve performance in parser
- #124
### Authors
- user1 (4 commits)
- user2 (1 commits)
### Compare
https://github.com/owner/repo/compare/v1.2.2...v1.2.3
```
Monorepo grouped entries add one `### <package> (<tag>)` subsection per bumped package under the batch marker so readers can skim per package diffs in one place.
## License
Licensed under the [Apache License 2.0](./LICENSE) © [@rccyx](https://rccyx.com)