tui-checkbox 0.4.4

A customizable checkbox widget for Ratatui TUI applications
Documentation
#!/usr/bin/env nu
# Automated version bump script for tui-checkbox
# Usage: nu scripts/bump_version.nu [--yes] <new_version>
# Example: nu scripts/bump_version.nu 0.5.0
#          nu scripts/bump_version.nu --yes 0.5.0   # skip confirmation
#
# Updates:
#   • version in Cargo.toml
#   • Version badge in README.md (if present)
#   • Cargo.lock (cargo update -p tui-checkbox)
#   • CHANGELOG.md (via git-cliff)
# Then commits and tags locally.  Use `just push-release-all` to push.

# ── Helpers ───────────────────────────────────────────────────────────────────

# Read the current [package] version from Cargo.toml.
def read_version [] {
    open Cargo.toml
    | get package.version
}

# ── Main ──────────────────────────────────────────────────────────────────────

def main [
    new_version: string,  # New version in X.Y.Z format
    --yes (-y),           # Skip confirmation prompt (non-interactive)
] {
    let red    = (ansi red)
    let green  = (ansi green)
    let yellow = (ansi yellow)
    let cyan   = (ansi cyan)
    let reset  = (ansi reset)

    # ── Validate version format ───────────────────────────────────────────────
    if not ($new_version =~ '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$') {
        error make { msg: $"($red)Error: Invalid version format($reset)
Version must be in format: X.Y.Z or X.Y.Z-suffix \(e.g., 0.5.0 or 0.5.0-beta.1\)" }
    }

    print $"($cyan)════════════════════════════════════════($reset)"
    print $"($cyan)  tui-checkbox Version Bump($reset)"
    print $"($cyan)════════════════════════════════════════($reset)"
    print ""

    # ── Read current version ──────────────────────────────────────────────────
    let current_version = (read_version)

    if ($current_version | is-empty) {
        error make { msg: $"($red)Error: Could not read version from Cargo.toml($reset)" }
    }

    print $"Current version: ($yellow)($current_version)($reset)"
    print $"New version:     ($green)($new_version)($reset)"
    print ""

    # ── Guard: already at requested version ──────────────────────────────────
    if $current_version == $new_version {
        error make { msg: $"($red)Error: Cargo.toml is already at version ($new_version).($reset)
($yellow)  Bump to the next version, or delete the tag if you need to re-release:($reset)
      git tag -d v($new_version) && git push origin :refs/tags/v($new_version)" }
    }

    # ── Guard: tag already exists locally ────────────────────────────────────
    let tag_name = $"v($new_version)"
    let existing_tags = (git tag | lines)
    if ($existing_tags | any { |t| $t == $tag_name }) {
        error make { msg: $"($red)Error: Tag ($tag_name) already exists locally.($reset)
($yellow)  Delete it first if you really want to recreate it:($reset)
      git tag -d ($tag_name)" }
    }

    # ── Confirmation ─────────────────────────────────────────────────────────
    if $yes {
        print $"($cyan)Running non-interactively \(--yes passed\).($reset)"
    } else {
        let reply = (input "Continue with version bump? (y/n) ")
        if not ($reply =~ '^[Yy]') {
            print $"($yellow)Aborted($reset)"
            return
        }
    }

    print ""

    # ── Step 1: Update version in Cargo.toml ──────────────────────────────────
    print $"($cyan)Step 1/7: Updating version in Cargo.toml...($reset)"

    let cargo_content = (open Cargo.toml --raw)
    let updated_cargo = (
        $cargo_content
        | str replace --regex '(?m)^version\s*=\s*"[^"]*"' $'version = "($new_version)"'
    )
    $updated_cargo | save --force Cargo.toml

    # Verify
    let got = (read_version)
    if $got != $new_version {
        error make { msg: $"($red)Failed to update Cargo.toml \(got ($got), expected ($new_version)\)($reset)" }
    }
    print $"($green)✓ Cargo.toml updated \(($current_version) → ($new_version)\)($reset)"

    # ── Step 2: Update README.md badges ──────────────────────────────────────
    print ""
    print $"($cyan)Step 2/7: Updating README.md badges...($reset)"

    if ("README.md" | path exists) {
        let readme = (open README.md --raw)
        if ($readme =~ 'version-[0-9]+\.[0-9]+\.[0-9]+-blue') {
            let updated_readme = (
                $readme
                | str replace --all --regex 'version-[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?-blue' $"version-($new_version)-blue"
            )
            $updated_readme | save --force README.md
            print $"($green)✓ README.md badges updated($reset)"
        } else {
            print $"($yellow)⚠ No version badge found in README.md — skipping($reset)"
        }
    } else {
        print $"($yellow)⚠ README.md not found — skipping($reset)"
    }

    # ── Step 3: Update Cargo.lock ─────────────────────────────────────────────
    print ""
    print $"($cyan)Step 3/7: Updating Cargo.lock...($reset)"
    run-external "cargo" "update" "-p" "tui-checkbox"
    print $"($green)✓ Cargo.lock updated($reset)"

    # ── Step 4: cargo fmt ─────────────────────────────────────────────────────
    print ""
    print $"($cyan)Step 4/7: Running cargo fmt...($reset)"
    run-external "cargo" "fmt"
    print $"($green)✓ Code formatted($reset)"

    # ── Step 5: cargo clippy ──────────────────────────────────────────────────
    print ""
    print $"($cyan)Step 5/7: Running cargo clippy...($reset)"
    let clippy = (do {
        run-external "cargo" "clippy" "--" "-D" "warnings"
    } | complete)
    if $clippy.exit_code != 0 {
        error make { msg: $"($red)✗ Clippy found issues. Please fix them before continuing.($reset)" }
    }
    print $"($green)✓ Clippy passed($reset)"

    # ── Step 6: cargo test ────────────────────────────────────────────────────
    print ""
    print $"($cyan)Step 6/7: Running tests...($reset)"
    let tests = (do {
        run-external "cargo" "test" "--locked" "--all-features" "--all-targets"
    } | complete)
    if $tests.exit_code != 0 {
        error make { msg: $"($red)✗ Tests failed. Please fix them before continuing.($reset)" }
    }
    print $"($green)✓ All tests passed($reset)"

    # ── Step 7: Generate CHANGELOG.md + commit + tag ──────────────────────────
    print ""
    print $"($cyan)Step 7/7: Generating CHANGELOG.md and creating git commit + tag...($reset)"

    if (which git-cliff | length) > 0 {
        run-external "git-cliff" "--tag" $tag_name "-o" "CHANGELOG.md"
        print $"($green)✓ CHANGELOG.md generated($reset)"
    } else {
        print $"($yellow)⚠ git-cliff not found — skipping changelog generation($reset)"
        print $"($yellow)  Install it with: cargo install git-cliff($reset)"
    }

    # Stage changed files
    let diff = (do {
        run-external "git" "diff" "--quiet" "Cargo.toml" "Cargo.lock" "README.md" "CHANGELOG.md"
    } | complete)

    if $diff.exit_code == 0 {
        print $"($yellow)⚠ No changes to commit($reset)"
    } else {
        run-external "git" "add" "Cargo.toml" "Cargo.lock" "README.md" "CHANGELOG.md"
        let commit_msg = $"chore: bump version to ($new_version)

- Update version in Cargo.toml to ($new_version)
- Update Cargo.lock
- Update README.md badges
- Generate updated CHANGELOG.md"
        run-external "git" "commit" "-m" $commit_msg
        print $"($green)✓ Changes committed($reset)"
    }

    let tag_msg = $"Release ($tag_name)

Includes all changes documented in CHANGELOG.md for version ($new_version)."
    run-external "git" "tag" "-a" $tag_name "-m" $tag_msg
    print $"($green)✓ Tag ($tag_name) created($reset)"

    # ── Summary ───────────────────────────────────────────────────────────────
    print ""
    print $"($cyan)════════════════════════════════════════($reset)"
    print $"($green)✓ Version bump complete! 🚀($reset)"
    print $"($cyan)════════════════════════════════════════($reset)"
    print ""
    print $"($yellow)Next steps:($reset)"
    print  "  1. Review the changes:"
    print $"     ($cyan)git show($reset)"
    print  ""
    print  "  2. Push to GitHub (triggers the release workflow):"
    print $"     ($cyan)git push --follow-tags origin main($reset)"
    print  ""
    print  "  3. Push to Gitea as well:"
    print $"     ($cyan)git push --follow-tags gitea main($reset)"
    print  ""
    print  "  4. Or use the just shortcuts:"
    print $"     ($cyan)just push-release-all($reset)   # push branch + tags to all remotes"
    print ""
}