diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54aa445..d0c7a0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,3 +23,59 @@ jobs: - run: cargo clippy --all-targets -- -D warnings - run: cargo test + + # Tags and releases the version in Cargo.toml once a push to the default + # branch is green. Nothing happens when that version is already tagged. + release: + needs: test + if: github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + runs-on: ubuntu-latest + permissions: + contents: write + concurrency: + group: release + cancel-in-progress: false + outputs: + tag: ${{ steps.release.outputs.tag }} + created: ${{ steps.release.outputs.created }} + steps: + - uses: actions/checkout@v5 + + - id: release + env: + GH_TOKEN: ${{ github.token }} + run: | + VERSION="$(sed -nE 's/^version *= *"([^"]+)".*/\1/p' Cargo.toml | head -1)" + if [ -z "$VERSION" ]; then + echo "::error::No version found in Cargo.toml" + exit 1 + fi + TAG="v$VERSION" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + STATUS=0 + git ls-remote --exit-code --tags origin "refs/tags/$TAG" > /dev/null || STATUS=$? + case "$STATUS" in + 0) + echo "Tag $TAG already exists; nothing to release." + echo "created=false" >> "$GITHUB_OUTPUT" + ;; + 2) + gh release create "$TAG" --target "$GITHUB_SHA" --title "$TAG" --generate-notes + echo "created=true" >> "$GITHUB_OUTPUT" + ;; + *) + echo "::error::Could not check whether tag $TAG exists (git ls-remote exited with $STATUS)" + exit "$STATUS" + ;; + esac + + # A release created with the workflow's own token does not trigger the + # publish workflow, so it is called from this run instead. + publish: + needs: release + if: needs.release.outputs.created == 'true' + uses: ./.github/workflows/publish.yml + with: + tag: ${{ needs.release.outputs.tag }} + secrets: inherit diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index abb8481..2a1c0d8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,6 +3,12 @@ name: Publish to crates.io on: release: types: [published] + workflow_call: + inputs: + tag: + description: Tag of the release to publish, for example v0.1.2 + required: true + type: string env: CARGO_TERM_COLOR: always @@ -12,6 +18,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 + with: + # The tag being published: the input when called from the release job + # in ci.yml, the release's own tag when a release is published by hand. + ref: refs/tags/${{ inputs.tag || github.event.release.tag_name }} - uses: dtolnay/rust-toolchain@stable with: diff --git a/README.md b/README.md index 97fa449..c78584e 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,10 @@ The main response type is `IpInfo` which includes: All response structs ignore unknown keys and decode a missing key and an explicit `null` alike: `Option` fields read `None`, every other field reads its default (`""`, `false`, `0`, `0.0` or an empty list), so a `null` anywhere in a response does not fail the lookup. +## Releasing + +Releases are cut by CI; nothing is tagged by hand. Bump `version` in `Cargo.toml` in a pull request and merge it: once the tests on the merge commit pass, CI tags it `vX.Y.Z`, creates the GitHub release with generated notes and publishes the crate to [crates.io](https://crates.io/crates/ipdata). A merge whose version is already tagged publishes nothing. A release published by hand on GitHub still runs the same publish workflow. + ## License MIT