monitr 0.3.39

A lightweight macOS activity monitor TUI built with Rust and Ratatui
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  check:
    name: Rust (${{ matrix.toolchain }})
    runs-on: macos-latest
    strategy:
      fail-fast: false
      matrix:
        toolchain: [stable, 1.88.0]
    steps:
      - name: Checkout
        uses: actions/checkout@v6.0.2

      - name: Install Rust
        run: rustup toolchain install ${{ matrix.toolchain }} --profile minimal --component clippy --component rustfmt

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.toolchain }}

      - name: Check
        env:
          RUSTUP_TOOLCHAIN: ${{ matrix.toolchain }}
        run: make check

      - name: Package
        if: matrix.toolchain == 'stable'
        env:
          RUSTUP_TOOLCHAIN: ${{ matrix.toolchain }}
        run: cargo package --locked

  release:
    name: Publish crate and GitHub release
    needs: check
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: macos-latest
    permissions:
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@v6.0.2
        with:
          fetch-depth: 0

      - name: Install Rust
        run: rustup toolchain install stable --profile minimal

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2
        with:
          key: release

      - name: Detect release version
        id: version
        env:
          BEFORE_SHA: ${{ github.event.before }}
        run: |
          python3 - <<'PY'
          import os
          import subprocess
          import tomllib
          from pathlib import Path

          current = tomllib.loads(Path("Cargo.toml").read_text())["package"]["version"]
          before_sha = os.environ["BEFORE_SHA"]
          previous = ""

          if (
              subprocess.run(
                  ["git", "cat-file", "-e", f"{before_sha}^{{commit}}"],
                  check=False,
                  stdout=subprocess.DEVNULL,
                  stderr=subprocess.DEVNULL,
              ).returncode
              == 0
          ):
              previous_manifest = subprocess.check_output(
                  ["git", "show", f"{before_sha}:Cargo.toml"],
                  text=True,
              )
              previous = tomllib.loads(previous_manifest)["package"]["version"]

          with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
              fh.write(f"current={current}\n")
              fh.write(f"previous={previous}\n")
              fh.write(f"changed={str(current != previous).lower()}\n")
          PY

      - name: Inspect release state
        id: release_state
        env:
          VERSION: ${{ steps.version.outputs.current }}
          PREVIOUS_VERSION: ${{ steps.version.outputs.previous }}
          GITHUB_SHA: ${{ github.sha }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          python3 - <<'PY'
          import io
          import json
          import os
          import subprocess
          import sys
          import tarfile
          import urllib.error
          import urllib.request

          version = os.environ["VERSION"]
          previous_version = os.environ.get("PREVIOUS_VERSION", "")
          github_sha = os.environ["GITHUB_SHA"]
          repository = os.environ["GITHUB_REPOSITORY"]
          github_token = os.environ.get("GITHUB_TOKEN", "")
          tag = f"v{version}"

          def git_output(*args: str) -> str:
              return subprocess.check_output(["git", *args], text=True).strip()

          def set_output(name: str, value: str) -> None:
              with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
                  fh.write(f"{name}={value}\n")

          tag_exists = (
              subprocess.run(
                  ["git", "rev-parse", "-q", "--verify", f"refs/tags/{tag}"],
                  check=False,
                  stdout=subprocess.DEVNULL,
                  stderr=subprocess.DEVNULL,
              ).returncode
              == 0
          )
          tag_commit = git_output("rev-list", "-n", "1", tag) if tag_exists else ""

          published = False
          published_sha = ""
          crate_metadata_request = urllib.request.Request(
              f"https://crates.io/api/v1/crates/monitr/{version}",
              headers={"User-Agent": "monitr-release-workflow"},
          )
          try:
              with urllib.request.urlopen(crate_metadata_request):
                  published = True
          except urllib.error.HTTPError as exc:
              if exc.code != 404:
                  raise

          if published:
              crate_url = f"https://static.crates.io/crates/monitr/monitr-{version}.crate"
              crate_request = urllib.request.Request(
                  crate_url, headers={"User-Agent": "monitr-release-workflow"}
              )
              with urllib.request.urlopen(crate_request) as response:
                  crate_bytes = response.read()
              with tarfile.open(fileobj=io.BytesIO(crate_bytes), mode="r:gz") as archive:
                  try:
                      with archive.extractfile(
                          f"monitr-{version}/.cargo_vcs_info.json"
                      ) as vcs_info_file:
                          vcs_info = json.load(vcs_info_file)
                  except Exception:
                      vcs_info = {}
              published_sha = vcs_info.get("git", {}).get("sha1", "")

          release_exists = False
          release_url = f"https://api.github.com/repos/{repository}/releases/tags/{tag}"
          release_headers = {
              "Accept": "application/vnd.github+json",
              "User-Agent": "monitr-release-workflow",
          }
          if github_token:
              release_headers["Authorization"] = f"Bearer {github_token}"
          release_request = urllib.request.Request(release_url, headers=release_headers)
          try:
              with urllib.request.urlopen(release_request):
                  release_exists = True
          except urllib.error.HTTPError as exc:
              if exc.code != 404:
                  raise

          if published and published_sha and tag_exists and tag_commit != published_sha:
              print(
                  f"::error::{tag} points to {tag_commit}, but crates.io {version} was "
                  f"published from {published_sha}.",
                  file=sys.stderr,
              )
              sys.exit(1)

          if (
              published
              and published_sha
              and published_sha != github_sha
              and previous_version != version
          ):
              print(
                  f"::error::monitr {version} is already published on crates.io from "
                  f"{published_sha}, but this push is {github_sha}. Bump Cargo.toml "
                  "before publishing another release.",
                  file=sys.stderr,
              )
              sys.exit(1)

          if not published:
              sync_needed = True
              publish_needed = True
              reason = "current-version-unpublished"
          elif published_sha == github_sha:
              sync_needed = (not tag_exists) or (not release_exists)
              publish_needed = False
              reason = (
                  "repair-github-metadata"
                  if sync_needed
                  else "release-already-synced"
              )
          else:
              sync_needed = False
              publish_needed = False
              reason = "version-already-published-from-earlier-commit"

          set_output("tag", tag)
          set_output("tag_exists", str(tag_exists).lower())
          set_output("tag_commit", tag_commit)
          set_output("published", str(published).lower())
          set_output("published_sha", published_sha)
          set_output("release_exists", str(release_exists).lower())
          set_output("sync_needed", str(sync_needed).lower())
          set_output("publish_needed", str(publish_needed).lower())
          set_output("reason", reason)
          PY

      - name: Skip release when current version is already synced
        if: steps.release_state.outputs.sync_needed != 'true'
        run: echo "Skipping release sync for ${{ steps.version.outputs.current }} (${{ steps.release_state.outputs.reason }})."

      - name: Verify package
        if: steps.release_state.outputs.sync_needed == 'true'
        env:
          RUSTUP_TOOLCHAIN: stable
        run: cargo package --locked

      - name: Ensure crates.io token is configured
        if: steps.release_state.outputs.publish_needed == 'true'
        env:
          REPO_CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
          REPO_CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
        run: |
          token="${REPO_CRATES_IO_TOKEN:-${REPO_CARGO_REGISTRY_TOKEN}}"
          if [ -z "${token}" ]; then
            echo "::error::Set the CARGO_REGISTRY_TOKEN or CRATES_IO_TOKEN repository secret before publishing."
            exit 1
          fi
          echo "::add-mask::${token}"
          echo "CARGO_REGISTRY_TOKEN=${token}" >> "${GITHUB_ENV}"

      - name: Publish crate
        if: steps.release_state.outputs.publish_needed == 'true'
        env:
          RUSTUP_TOOLCHAIN: stable
        run: cargo publish --locked

      - name: Create GitHub release
        if: steps.release_state.outputs.sync_needed == 'true' && steps.release_state.outputs.release_exists != 'true'
        uses: softprops/action-gh-release@v2
        with:
          generate_release_notes: true
          tag_name: ${{ steps.release_state.outputs.tag }}
          target_commitish: ${{ github.sha }}