rlls

Crates.iorlls
lib.rsrlls
version0.0.40-rc.1
created_at2025-10-08 22:42:55.001028+00
updated_at2025-10-11 23:48:16.363804+00
descriptionCut a version, tag it, and publish a GitHub Release with raw git notes
homepage
repositoryhttps://github.com/rccyx/rlls
max_upload_size
id1874671
size162,126
(rccyx)

documentation

README

rlls

rlls is a Rust-first release tool for single repos and monorepos (JS, Python, Rust). It bumps versions, runs a post-bump command (lock refresh / installs), creates annotated Git tags (package-scoped in monorepos), writes a clean CHANGELOG, and (optionally) makes GitHub Releases — powered by Git history, not rituals.

  • Per-package baselines. In monorepos, each package uses its own last tag as the baseline window.
  • Package-scoped tags. Default tag shape is {{name}}@v{{version}} (e.g. @scope/core@v1.4.2). Fully configurable (keep slashes, keep @).
  • Lock file safety. rlls.lock records baselines (including SHAs) so you can rebuild even if tags vanish or history shifts.
  • Zero registry publishing. rlls doesn’t publish to npm/PyPI/Crates. It prepares Git artifacts; your CI owns the rest.

Installation

cargo install --locked rlls

Requires: git.
Optional: gh (GitHub CLI) to resolve PR titles and create GitHub Releases.
Optional (JS projects): pnpm / yarn / npm if you want lockfiles refreshed when versions change.


What it does

  • Detects project type (JS / Python / Rust).
  • Bumps versions in package.json, pyproject.toml/setup.*, or Cargo.toml.
  • Runs a post-bump command before staging/commit/tag (e.g. pnpm i, bun install, yarn install --mode update-lockfile, pip-compile, etc.).
  • Creates annotated tags:
    • single repo: vX.Y.Z
    • monorepo: {{name}}@vX.Y.Z (configurable)
  • Writes CHANGELOG using git log from the correct baseline (last_tag..HEAD or per-package).
  • Pushes commits and tags.
  • Monorepo: can create one GitHub Release per package with scoped notes (when gh is available).

Quick start

Single repo

# rlls.toml
default_bump = "patch"
bump_commit_message = "chore(release): bump to {{version}}"
bump_tag_message    = "release {{version}}"

# IMPORTANT for JS/Python: refresh locks after version bump
# choose what your repo needs (examples):
post_bump_command = "pnpm i"

[changelog]
enable = true
path   = "CHANGELOG.md"
rlls patch     # or: rlls minor | rlls major

Monorepo

# rlls.toml
default_bump = "patch"

# tag/message shapes
pkg_tag_template = "{{name}}@v{{version}}"
include_v_prefix = true
bump_tag_message = "{{name}}@{{version}}"

# run after bumping versions (pick one that fits your workspace)
post_bump_command = "pnpm i"

# IMPORTANT: only manage these packages (exact manifest names)
packages = ["@acme/core", "@acme/adapters"]

# optional, used for multi-package commits
monorepo_bump_commit_message = "chore(release): bump {{count}} packages\n\n{{list}}"

[changelog]
enable = true
path   = "CHANGELOG.md"

# optional migration (discover old tag shapes)
[migration."@acme/core"]
legacy_tag_patterns = ["core-v*", "core@*"]

First time (or after migrations):

rlls lock rebuild \
  --baseline @acme/core:v1.2.3 \
  --baseline @acme/adapters:v0.8.0   # only if a package lacks a discoverable baseline

Release:

rlls --monorepo                # interactive per-package bumps
# subset:
rlls monorepo --packages "@acme/core,@acme/adapters"

Want tags to match your package names exactly (keep slashes and @)?
Use: pkg_tag_template = "{{name}}@{{version}}" and set include_v_prefix as you prefer.
rlls will push tags safely even with @ and /.


Configuration reference (rlls.toml)

All keys are optional unless noted. Defaults are shown.

# ===== General =====
# Default semver bump when CLI omits one
default_bump = "patch"         # "patch" | "minor" | "major"

# Commit message when bumping (used in single or multi, see below)
# Vars: {{name}} {{version}} {{count}} {{list}}
# Default is multi-friendly; override if you prefer a single-repo style.
bump_commit_message = "chore(release): bump {{count}} packages\n\n{{list}}"

# Multi-package (monorepo) commit message; falls back to bump_commit_message if unset
monorepo_bump_commit_message = "chore(release): bump {{count}} packages\n\n{{list}}"

# Tag annotation (tag *message/body*, not the tag name)
# Vars: {{name}} {{version}}
bump_tag_message = "release {{version}}"

# Header inserted at top of the changelog if missing
changelog_header = "# Changelog"

# Tag *name* template for monorepo tags
# Use {{name}} to keep exact pkg name (including @scope and /).
# Use {{id}} to use a sanitized id (slashes -> _; leading @ removed).
pkg_tag_template = "{{name}}@v{{version}}"

# Whether to include "v" in {{version}} when building tags: v1.2.3 vs 1.2.3
include_v_prefix = true

# Allow releasing with a dirty working tree (not recommended)
allow_dirty = false

# Limit rlls to packages listed here (exact manifest "name" values)
packages = ["@acme/core", "@acme/adapters"]

# Command to run after bumping versions, before changelog/tag (optional)
post_bump_command = "pnpm i"

# ===== Changelog =====
[changelog]
enable = true
path   = "CHANGELOG.md"

# ===== Migration hints (optional) =====
# Helps rlls discover historical baselines when tag shapes changed.
[migration."@acme/core"]
legacy_tag_patterns = ["core-v*", "core@*"]

Environment variable overrides

All optional; values mirror TOML:

  • RLLS_DEFAULT_BUMP
  • RLLS_BUMP_COMMIT_MESSAGE
  • RLLS_MONOREPO_BUMP_COMMIT_MESSAGE
  • RLLS_BUMP_TAG_MESSAGE
  • RLLS_CHANGELOG_HEADER
  • RLLS_CHANGELOG_ENABLE (true/false)
  • RLLS_CHANGELOG_PATH
  • RLLS_PKG_TAG_TEMPLATE
  • RLLS_INCLUDE_V_PREFIX (true/false)
  • RLLS_POST_BUMP_COMMAND

CLI reference

Top-level forms

rlls [KIND] [FLAGS]                  # single repo release (KIND: patch | minor | major)
rlls --monorepo [FLAGS]              # monorepo (interactive, filters to config packages)
rlls monorepo [--packages CSV]       # monorepo (interactive), subset override

rlls prerelease [--id <str>] [--bump KIND]   # single repo prerelease
rlls finalize                                # single repo: convert last prerelease to stable

rlls rollback [--local]               # remove tags at HEAD; rewind release commits; default also updates remote
rlls selfupdate                       # install latest from crates.io / prefer GH prerelease if on prerelease

rlls lock rebuild [--force] [--baseline NAME:REF ...] [--from-file PATH]
rlls lock verify
rlls tag synthesize --baseline NAME:REF [...]

Common flags

  • --dry : perform everything except pushing
  • --no-changelog : skip changelog write for this run
  • --repo owner/repo : override detected GitHub repo (for URLs and GH release creation)
  • --monorepo : force monorepo mode if auto-detect is ambiguous
  • --packages "<a,b>" : monorepo subcommand only; filter a subset
  • --ignore_package : single repo only; bump/tag without rewriting manifest (tag-only)

prerelease notes

  • --id default: rc. Examples: v1.2.3-rc.1, v1.2.3-nightly.20250131[.N].
  • --bump controls the base bump (defaults to default_bump).

finalize (single repo)

  • Takes the last reachable prerelease tag, strips the -pre suffix, and creates vX.Y.Z.
  • Refuses if the stable tag already exists.

lock rebuild

  • Builds/updates rlls.lock by discovering packages and anchoring per-package baselines.
  • --baseline NAME:REF lets you pin a baseline: REF may be v1.2.3, 1.2.3, or a raw commit SHA.
  • --force overwrites an existing lock file.
  • --from-file is reserved for future use.

tag synthesize

  • Mints annotated tags at given refs without bumping files:
    rlls tag synthesize --baseline @acme/core:v1.2.3 --baseline @acme/adapters:deadbeef
    

rollback

  • Without --local, removes remote tags at HEAD and force-pushes branch after rewinding obvious release commits.
  • With --local, operates locally only.

Monorepo is currently interactive. Non-interactive per-package bump flags are on the roadmap. For CI flows today, run rlls lock rebuild in a preparatory step and use rlls --monorepo only when a human is present, or script tag synthesize for tag-only flows.


CHANGELOG behavior

  • New sections are prepended.
  • Header: ## <tag> <YYYY-MM-DD>
  • Sections:
    • Changes since <base>: git log --no-merges --pretty=- %h %s
    • Pull requests: numbers like (#123) optionally resolved via gh to titles
    • Authors: from git shortlog -sn (singular/plural friendly)
    • Compare: link to the GitHub compare page

Monorepo: a single batch section (e.g. ## batch-20250131235959) with one sub-section per package tagged, each with its scoped commit window.


Tagging details & special characters

  • Single repo tag name: v<semver>.
  • Monorepo tag name: built from pkg_tag_template. To keep slashes and @ in tag names, use {{name}} (not {{id}}).
  • Tags are annotated and include durable trailers:
    rlls:id=<sanitized-tag-id>
    rlls:pkg=<package-name>
    rlls:ver=<version-core>
    rlls:sha=<target-commit-sha>
    

If your remote/refspec complains about special chars, push the explicit ref:

git push origin "refs/tags/@scope/pkg@0.2.0"

Supported projects

  • Node: package.json (and lockfile refresh if pnpm/yarn/npm exist)
  • Python: pyproject.toml (PEP 621 / Poetry), setup.cfg, setup.py
  • Rust: Cargo.toml (single crate or workspace; workspace members are updated)

Troubleshooting

  • “Working tree is not clean.” Commit/stash changes or allow_dirty = true (use sparingly).
  • “No baseline for package X.”
    rlls lock rebuild --baseline <name>:<v1.2.3|sha> or add migration.<name>.legacy_tag_patterns.
  • “Push failed for tag with @/ /.”
    Use pkg_tag_template = "{{name}}@{{version}}", and if necessary push refs/tags/<tag>.
  • “It asked me to bump 70 packages.”
    Ensure your packages = [...] allowlist names exactly match each manifest’s "name". Use rlls monorepo --packages "a,b,c" for ad-hoc subsets.
  • “Monorepo ‘kind’ flag didn’t apply.”
    Current monorepo flow is interactive; bump kind flags are reserved for future non-interactive mode.

Safety rails

  • Clean tree check (unless allow_dirty).
  • Baseline reachability checks on lock verify/rebuild.
  • Semver-aware tag ordering & collision detection across legacy patterns.
  • Rollback removes tags at HEAD and rewinds release commits (optionally pushes the branch).

CI hints

  • Generate/refresh lock in a dedicated job:
    rlls lock rebuild
    git add rlls.lock && git commit -m "chore: refresh rlls.lock" || true
    
  • For automated tag-only flows (no file edits), consider:
    rlls tag synthesize --baseline @acme/core:v1.2.3
    
  • For human-in-the-loop monorepo releases, run rlls --monorepo locally or in a TTY-enabled runner.

Command cheatsheet

# Single repo
rlls patch|minor|major
rlls prerelease --bump minor --id rc
rlls finalize
rlls rollback [--local]
rlls selfupdate

# Monorepo
rlls lock rebuild [--force] [--baseline NAME:REF ...]
rlls lock verify
rlls --monorepo
rlls monorepo --packages "@acme/core,@acme/adapters"

# Tags without bumping files
rlls tag synthesize --baseline @acme/core:v1.2.3

Notes on defaults you may want to change immediately

  • If you want single-repo commits like chore(release): bump to 1.2.3, override bump_commit_message accordingly (the default is multi-package friendly).
  • If you want tag names to mirror packages (keep @scope/ and /):
    pkg_tag_template = "{{name}}@{{version}}" and choose your include_v_prefix.
Commit count: 0

cargo fmt