serde-structprop 0.1.3

Serde serializer and deserializer for the structprop config file format
Documentation
# Justfile for serde-structprop
# Install just: https://just.systems

# Default: list available recipes
default:
    @just --list

# Run the full test suite
test:
    cargo test

# Check formatting and lints (mirrors CI)
check:
    cargo fmt --check
    cargo clippy --all-targets -- -W clippy::pedantic

# Auto-format the source
fmt:
    cargo fmt

# Validate conventional commit history
commits:
    cog check

# ---------------------------------------------------------------------------
# Release
# ---------------------------------------------------------------------------
#
# Two-step process to work with branch protection on main:
#
#   Step 1 — just release
#     Runs pre-flight checks, calls `cog bump --auto` on a local release
#     branch, opens a PR.  Review and merge the PR normally.
#
#   Step 2 — just push-tag
#     After the PR is merged, pulls main, tags the merge commit with the
#     version from Cargo.toml, and pushes the tag.
#     The tag push triggers release.yml which publishes to crates.io.
#
# Prerequisites:
#   - `cocogitto` installed: cargo install cocogitto
#   - `gh` CLI installed and authenticated: https://cli.github.com
# ---------------------------------------------------------------------------

# Step 1: open a release PR.
#
# Runs pre-flight checks, bumps the version on a release branch,
# generates CHANGELOG.md, and opens a pull request against main.
# After the PR is merged, run `just push-tag` to trigger the publish.
release:
    #!/usr/bin/env bash
    set -euo pipefail

    # Guard: must be on main.
    branch=$(git rev-parse --abbrev-ref HEAD)
    if [[ "$branch" != "main" ]]; then
        echo "error: releases must be cut from main (currently on '$branch')" >&2
        exit 1
    fi

    # Guard: working tree must be clean.
    if ! git diff --quiet || ! git diff --cached --quiet; then
        echo "error: working tree has uncommitted changes; commit or stash them first" >&2
        exit 1
    fi

    # Guard: local main must not be behind origin.
    git fetch --quiet origin main
    if [[ $(git rev-list --count HEAD..origin/main) -gt 0 ]]; then
        echo "error: local main is behind origin/main; run 'git pull' first" >&2
        exit 1
    fi

    # Run the full test suite before touching anything.
    echo "==> Running tests..."
    cargo test

    # Check formatting and lints.
    echo "==> Checking formatting and lints..."
    cargo fmt --check
    cargo clippy --all-targets -- -W clippy::pedantic

    # Determine the next version without making any changes yet.
    # cog bump --auto --dry-run prints e.g. "v0.1.0" to stdout.
    echo "==> Determining next version..."
    next_version=$(cog bump --auto --dry-run)
    echo "    Next version: ${next_version}"

    # Create and switch to a release branch.
    release_branch="release/${next_version}"
    git checkout -b "${release_branch}"

    # Bump version, generate CHANGELOG.md, commit, and create the local tag.
    # cog bump --auto:
    #   - Updates the version field in Cargo.toml
    #   - Writes CHANGELOG.md
    #   - Creates a commit "chore(version): bump to vX.Y.Z"
    #   - Creates an annotated tag vX.Y.Z  (local only until push-tag)
    echo "==> Bumping version with cog..."
    cog bump --auto

    # Push the release branch (not the tag — that comes after PR merge).
    echo "==> Pushing release branch..."
    git push -u origin "${release_branch}"

    # Open the pull request.
    echo "==> Opening pull request..."
    gh pr create \
        --title "chore(release): ${next_version}" \
        --body "$(cat CHANGELOG.md)" \
        --base main \
        --head "${release_branch}"

    echo ""
    echo "==> Release PR opened."
    echo "    Review and merge the PR, then run:"
    echo ""
    echo "        just push-tag"
    echo ""
    echo "    to push the tag and trigger the crates.io publish."

# Step 2: tag main HEAD and push the tag.
#
# Run this after the release PR has been merged into main.
# Pulls the latest main, reads the version from Cargo.toml, creates an
# annotated tag on the current HEAD, and pushes it to trigger release.yml.
push-tag:
    #!/usr/bin/env bash
    set -euo pipefail

    # Must be on main.
    branch=$(git rev-parse --abbrev-ref HEAD)
    if [[ "$branch" != "main" ]]; then
        echo "error: push-tag must be run from main (currently on '$branch')" >&2
        exit 1
    fi

    # Pull so we are at the merge commit.
    echo "==> Pulling latest main..."
    git pull --ff-only origin main

    # Read the version from Cargo.toml.
    version=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')
    tag="v${version}"
    echo "==> Tagging HEAD as ${tag}..."

    # Guard: tag must not already exist on origin.
    if git ls-remote --tags origin "${tag}" | grep -q "refs/tags/${tag}$"; then
        echo "error: tag ${tag} already exists on origin" >&2
        exit 1
    fi

    # Delete stale local tag if present (leftover from the release branch).
    git tag -d "${tag}" 2>/dev/null || true

    # Create a fresh annotated tag on the current (merged) HEAD.
    git tag -a "${tag}" -m "chore(release): ${tag}"

    echo "==> Pushing tag ${tag}..."
    git push origin "${tag}"

    echo "==> Done. Monitor the release workflow at:"
    echo "    https://github.com/anthonyoteri/serde-structprop/actions"