rlls 0.0.36

Cut a version, tag it, and publish a GitHub Release with raw git notes
Documentation
# rlls

**rlls** is a Rust-first release tool for single repos and monorepos (JS, Python, Rust). It bumps versions, 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

```bash
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`.
- **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

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

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

```bash
rlls patch     # or: rlls minor | rlls major
```

### Monorepo

```toml
# rlls.toml
default_bump = "patch"

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

# 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):

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

Release:

```bash
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.

```toml
# ===== 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"]

# ===== 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`)

---

## CLI reference

### Top-level forms

```text
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:
  ```bash
  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:

```bash
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:
  ```bash
  rlls lock rebuild
  git add rlls.lock && git commit -m "chore: refresh rlls.lock" || true
  ```
- For automated tag-only flows (no file edits), consider:
  ```bash
  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

```bash
# 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`.