diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 50ee761..7228f27 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,10 @@ name: Publish to crates.io # Auth uses crates.io Trusted Publishing (OIDC) — no long-lived token is # stored in GitHub. One-time setup on crates.io: add a Trusted Publisher for # this repository pointing at this workflow file (publish.yml). +# +# Idempotent: if the tag's version is already on crates.io (e.g. it was +# published manually, or a tag was re-pushed), publish is skipped and the run +# succeeds — instead of failing at "crate version already uploaded". on: push: @@ -18,11 +22,12 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - - name: Verify tag matches Cargo.toml version + - name: Resolve version and check tag + id: version run: | TAG="${GITHUB_REF_NAME#v}" CRATE_VER="$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')" @@ -31,12 +36,31 @@ jobs: echo "::error::tag v$TAG does not match Cargo.toml version $CRATE_VER" exit 1 fi + echo "version=$CRATE_VER" >> "$GITHUB_OUTPUT" + + - name: Check whether this version is already on crates.io + id: published + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + CODE="$(curl -sS -o /dev/null -w '%{http_code}' \ + -H 'User-Agent: cch-release-workflow (github.com/Rodeapps/cch)' \ + "https://crates.io/api/v1/crates/cch/${VERSION}" || echo 000)" + echo "crates.io returned HTTP $CODE for cch ${VERSION}" + if [ "$CODE" = "200" ]; then + echo "already=true" >> "$GITHUB_OUTPUT" + echo "::notice::cch ${VERSION} is already published — skipping publish." + else + echo "already=false" >> "$GITHUB_OUTPUT" + fi - name: Authenticate to crates.io (Trusted Publishing) + if: steps.published.outputs.already != 'true' uses: rust-lang/crates-io-auth-action@v1 id: auth - name: Publish + if: steps.published.outputs.already != 'true' run: cargo publish env: CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}