dtg-credentials 0.9.1

Decentralized Trust Graph (DTG) Credentials Library
Documentation
name: Publish

# Publish `dtg-credentials` to crates.io on a `vX.Y.Z` tag push.
#
# This repository had no release path at all: every version on crates.io got
# there by someone running `cargo publish` from a laptop. That is why 0.6.0 -
# the release carrying the VAC and VDC, which the data-rooms work in
# `verifiable-trust-infrastructure` depends on - sat merged and unpublished
# while the consumer could not compile against it. A tag is now the release.
#
# The trigger is the tag push rather than `release: published` on purpose: a
# Release created with the default GITHUB_TOKEN does not cascade-trigger other
# workflows, so a `release:` handler would silently never fire.
#
# Auth is crates.io Trusted Publishing (OIDC), so there is no long-lived token
# in this repository. ONE-TIME SETUP, on crates.io under the crate's Settings ->
# Trusted Publishing: add owner `OpenVTC`, repository `dtg-credentials`,
# workflow `publish.yml`. Until that exists the job fails at the auth step with
# a message naming exactly this, which is the right failure - better than a
# token sitting in the repo for the one day a year it is used.
#
# The run is idempotent: a tag re-pushed after a failed release job finds the
# crate already on crates.io at that version and skips rather than dying on
# "crate version already uploaded", which would otherwise force a version bump
# nothing needed.
on:
  push:
    tags: ["v*.*.*"]
  workflow_dispatch:

permissions:
  id-token: write # OIDC token for crates.io Trusted Publishing
  contents: read

jobs:
  publish:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - uses: dtolnay/rust-toolchain@stable

      # The tag says one version and Cargo.toml says another exactly once - the
      # time someone tags before the bump lands - and the result is a release
      # whose number means nothing. Cheap to check, unrecoverable to fix.
      - name: Tag must match the crate version
        if: startsWith(github.ref, 'refs/tags/v')
        run: |
          set -euo pipefail
          tag="${GITHUB_REF_NAME#v}"
          crate=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
          if [ "$tag" != "$crate" ]; then
            echo "::error::tag v${tag} does not match Cargo.toml version ${crate}"
            exit 1
          fi
          echo "publishing dtg-credentials ${crate}"

      # Before authenticating, not after. The skip is what makes a re-pushed tag
      # recoverable, and it cannot do that job from behind the auth step: the
      # v0.6.0 tag push found 0.6.0 already on crates.io (published by hand
      # minutes earlier) and still failed, because it never got past
      # "No Trusted Publishing config found" to reach the check that would have
      # said there was nothing to do. A step that decides whether to act should
      # not sit downstream of acquiring the means to act.
      - name: Skip if this version is already published
        id: check
        run: |
          set -euo pipefail
          version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
          echo "version=${version}" >> "$GITHUB_OUTPUT"

          # The sparse index is newline-delimited JSON, one object per version.
          # A 404 (name never published) is a clean "not there".
          if curl -sSf "https://index.crates.io/dt/g-/dtg-credentials" 2>/dev/null \
              | jq -se --arg v "$version" 'any(.[]; .vers == $v)' >/dev/null; then
            echo "::notice::dtg-credentials ${version} is already on crates.io — nothing to do"
            echo "published=true" >> "$GITHUB_OUTPUT"
          else
            echo "published=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Authenticate to crates.io
        if: steps.check.outputs.published == 'false'
        uses: rust-lang/crates-io-auth-action@v1
        id: auth

      - name: Publish
        if: steps.check.outputs.published == 'false'
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: cargo publish --locked