diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a88c7c4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,57 @@ +name: Release + +on: + push: + tags: + - "v*" + +env: + CARGO_TERM_COLOR: always + +jobs: + publish: + name: Publish to crates.io + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + + - name: Validate tag version matches Cargo.toml + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 | python -c 'import json,sys;print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"]=="ripdiff"))') + if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then + echo "Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)" + exit 1 + fi + + - name: Package check + run: cargo package --allow-dirty + + - name: Publish crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --locked + + - name: Generate release notes + run: | + { + echo "## ripdiff $GITHUB_REF_NAME" + echo + echo "Published to crates.io from tag \\`$GITHUB_REF_NAME\\`." + echo + echo "Install with:" + echo "\\`\\`\\`bash" + echo "cargo install ripdiff" + echo "\\`\\`\\`" + } > release-notes.md + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + body_path: release-notes.md diff --git a/Cargo.toml b/Cargo.toml index 3ffd16d..f4ad587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,26 @@ name = "ripdiff" version = "0.1.0" edition = "2021" +rust-version = "1.85" default-run = "ripdiff" +description = "Terminal UI for watching and reviewing agent progress" +license = "MIT" +repository = "https://github.com/martinabeleda/ripdiff" +homepage = "https://github.com/martinabeleda/ripdiff" +documentation = "https://docs.rs/ripdiff" +readme = "README.md" +keywords = ["git", "diff", "tui", "review", "agent"] +categories = ["command-line-utilities", "development-tools"] +include = ["src/**", "Cargo.toml", "README.md", "LICENSE"] [[bin]] name = "ripdiff" path = "src/main.rs" +[[bin]] +name = "rd" +path = "src/main.rs" + [dependencies] ratatui = "0.29" crossterm = "0.28" diff --git a/README.md b/README.md index 16d51ff..dd991cf 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,35 @@ ░░░░░ ``` -A terminal UI for navigating git diffs, designed for a tmux panel workflow where you monitor AI agent changes on one side while working on the other. +A terminal UI for watching and reviewing agent progress, designed for a tmux panel workflow where you monitor agent changes on one side while working on the other. Uses [difftastic](https://difftastic.wilfred.me/) for structural, syntax-aware diffs with ANSI color output via an in-process Rust dependency. ## Install +### Install from crates.io + +```bash +cargo install ripdiff ``` + +This installs `ripdiff` into `~/.cargo/bin/`. + +### Install from source (local checkout) + +```bash cargo install --path . ``` -This puts `ripdiff` in `~/.cargo/bin/`. +For best results, install difftastic: + +```bash +cargo install difftastic +``` + +## Releasing + +Maintainer release instructions live in [RELEASING.md](/home/mabeleda/Development/ripdiff/RELEASING.md). ## Usage diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..b2b4764 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,79 @@ +# Releasing + +This document describes the maintainer release process for `ripdiff`. + +## Versioning + +`ripdiff` follows [Semantic Versioning](https://semver.org/) (`MAJOR.MINOR.PATCH`): + +- `PATCH` for bug fixes and internal improvements. +- `MINOR` for backwards-compatible features. +- `MAJOR` for breaking changes. + +## Release Workflow + +Releases are driven by git tags. + +The GitHub Actions workflow in [.github/workflows/release.yml](/home/mabeleda/Development/ripdiff/.github/workflows/release.yml#L1) runs when a tag matching `v*` is pushed. It: + +1. Checks that the tag version matches `Cargo.toml`. +2. Verifies the crate can be packaged. +3. Publishes the crate to crates.io. +4. Creates a GitHub release for the tag. + +To publish from CI, the repository must have a `CARGO_REGISTRY_TOKEN` secret containing a crates.io API token. + +## Recommended Process + +Use [`cargo-release`](https://github.com/crate-ci/cargo-release) to bump the crate version and create the matching tag in one step. + +Releases should only be cut from `main`. This keeps each published version tied to the reviewed, canonical branch state and avoids accidentally releasing from an unmerged branch or local-only commit. The repository's `cargo-release` configuration enforces this with `allow-branch = ["main"]`. + +Install it once: + +```bash +cargo install cargo-release +``` + +Preview a release: + +```bash +cargo release patch +``` + +Run a release: + +```bash +cargo release patch --execute +``` + +Replace `patch` with `minor` or `major` as needed. + +With the repository configured for `cargo-release`, the executed command should: + +1. Update `Cargo.toml` to the next version. +2. Create a release commit. +3. Create a matching tag like `v0.1.1`. +4. Push the commit and tag to `origin`. + +After the tag is pushed, GitHub Actions performs the actual crates.io publish and creates the GitHub release. + +## Maintainer Checklist + +1. Ensure CI is green locally or on `main`: + ```bash + cargo fmt + cargo clippy --all-targets --all-features + cargo test + cargo build + ``` +2. Run a dry run: + ```bash + cargo release patch + ``` +3. Execute the release: + ```bash + cargo release patch --execute + ``` +4. Verify the GitHub Actions release workflow succeeds. +5. Verify the new version appears on crates.io. diff --git a/release.toml b/release.toml new file mode 100644 index 0000000..e1c0fd1 --- /dev/null +++ b/release.toml @@ -0,0 +1,12 @@ +allow-branch = ["main"] +consolidate-commits = true + +tag-name = "v{{version}}" +tag-message = "ripdiff {{version}}" + +pre-release-commit-message = "release: {{version}}" +push-remote = "origin" + +publish = false +push = true +tag = true