openlogi 0.6.21

OpenLogi command-line interface — a local-first companion for Logitech HID++ peripherals.
name: Release PR

on:
  workflow_dispatch:
  push:
    branches: [master]

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: 0

jobs:
  # Opens/updates a release PR that bumps the shared workspace version and writes
  # changelogs from the conventional commits since the last release. Merging that
  # PR is what triggers the actual release in the job below.
  release-pr:
    name: release-plz PR
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    concurrency:
      group: release-plz-pr-${{ github.ref }}
      cancel-in-progress: false
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Install Linux build deps
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            libudev-dev pkg-config gcc g++ clang libssl-dev libzstd-dev
      # GitHub App token (not a PAT) so the release PR / tag trigger CI and the
      # DMG release workflow — the default GITHUB_TOKEN cannot trigger workflows.
      # App credentials come from the same 1Password item as release.yml.
      - name: Mint GitHub App token
        id: app-token
        uses: ./.github/actions/github-app-token-from-1password
        with:
          op-service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
          op-github-app-item: ${{ secrets.OP_GITHUB_APP_ITEM }}
      - name: Run release-plz (release-pr)
        id: release_pr
        uses: release-plz/action@v0.5
        with:
          command: release-pr
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
          CARGO_REGISTRY_TOKEN: ${{ steps.app-token.outputs.cargo-registry-token }}
      # `release-plz/action` swallows a release-pr HTTP 422 as a warning and
      # reports no PR, which silently stalls releases (it looks identical to a
      # quiet week of commits). Fail loudly when release-plz opened/updated no
      # release PR, none is already open, yet release-worthy commits exist since
      # the last tag — that combination means a swallowed failure, not a no-op.
      # (The blanket 422-swallow is an acknowledged action hack: release-plz/action#264
      # added it to fix that action's own CI; #427 aims to remove it. Once #427
      # lands and we pin an action release that includes it, this guard can go.)
      - name: Guard against a silently stalled release
        if: steps.release_pr.outputs.prs_created == 'false'
        uses: actions/github-script@v8
        with:
          github-token: ${{ steps.app-token.outputs.token }}
          script: |
            const { readFileSync } = require("node:fs");
            const { owner, repo } = context.repo;

            // An already-open release PR is the normal "updated, not created" path.
            const openPulls = await github.paginate(github.rest.pulls.list, {
              owner,
              repo,
              state: "open",
              per_page: 100,
            });
            if (openPulls.some((pull) => pull.head.ref.startsWith("release-plz/"))) {
              core.info("A release PR is already open — nothing to flag.");
              return;
            }

            const tags = await github.paginate(github.rest.repos.listTags, {
              owner,
              repo,
              per_page: 100,
            });
            const lastTag = tags
              .map((tag) => tag.name)
              .filter((tag) => /^v\d+\.\d+\.\d+/.test(tag))
              .sort((left, right) => right.localeCompare(left, undefined, { numeric: true, sensitivity: "base" }))[0] ?? "";

            // The release job runs in parallel and cuts the `v{version}` tag only
            // after publishing, so that tag is usually absent from this job's
            // checkout — never trust the local tag list to decide "did a release
            // just happen". Read the manifest instead: merging a release PR bumps
            // the workspace version, so a manifest version ahead of the last tag
            // means the release already landed (the parallel job is tagging it),
            // not a stall. Only when the manifest still sits at the last released
            // tag can leftover release-worthy commits mean a swallowed release-pr.
            const manifestVersion = readFileSync("Cargo.toml", "utf8")
              .match(/\[workspace\.package\][\s\S]*?^version\s*=\s*"([^"]+)"/m)?.[1] ?? "";
            if (lastTag && manifestVersion && `v${manifestVersion}` !== lastTag) {
              core.info(`Workspace version ${manifestVersion} is ahead of ${lastTag} — a release PR already merged; the release job cuts the tag. Nothing to flag.`);
              return;
            }

            const commits = lastTag
              ? (await github.rest.repos.compareCommitsWithBasehead({
                  owner,
                  repo,
                  basehead: `${lastTag}...${context.sha}`,
                })).data.commits
              : await github.paginate(github.rest.repos.listCommits, {
                  owner,
                  repo,
                  sha: context.sha,
                  per_page: 100,
                });
            const worthy = commits.filter(({ commit }) => /^(feat|fix|perf)(\(.+\))?!?:|^[a-z]+(\(.+\))?!:/.test(commit.message.split("\n")[0])).length;
            if (worthy !== 0) {
              core.setFailed(`release-plz opened no release PR, but ${worthy} release-worthy commit(s) exist since ${lastTag || "repo start"} and none is open — likely a swallowed release-plz failure (see the release-pr step).`);
              return;
            }
            core.info(`No release PR and no release-worthy commits since ${lastTag || "repo start"}; nothing to release.`);

  # On every push to master, publishes any crate whose manifest version is not yet
  # on crates.io — i.e. a no-op until the release PR is merged, at which point it
  # publishes the whole workspace and cuts one `v{version}` tag + GitHub Release.
  release:
    name: release-plz release
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: read
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Install Linux build deps
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            libudev-dev pkg-config gcc g++ clang libssl-dev libzstd-dev
      - name: Mint GitHub App token
        id: app-token
        uses: ./.github/actions/github-app-token-from-1password
        with:
          op-service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
          op-github-app-item: ${{ secrets.OP_GITHUB_APP_ITEM }}
      - name: Run release-plz (release)
        uses: release-plz/action@v0.5
        with:
          command: release
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
          CARGO_REGISTRY_TOKEN: ${{ steps.app-token.outputs.cargo-registry-token }}