diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df2fdb7..5626f9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,3 +36,48 @@ jobs: run: swift build - name: Test run: swift test + + release: + name: Tag and release + needs: [test-linux, test-macos] + 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.version.outputs.tag }} + created: ${{ steps.release.outputs.created }} + steps: + - uses: actions/checkout@v7 + - name: Read the version from the README install snippet + id: version + run: | + VERSION="$(sed -nE 's/.*from: *"([^"]+)".*/\1/p' README.md | head -1)" + if [ -z "$VERSION" ]; then + echo "No 'from:' version found in the README.md install snippet." >&2 + exit 1 + fi + echo "Version in README.md: $VERSION" + echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" + - name: Create the tag and the GitHub release when the tag is missing + id: release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.version.outputs.tag }} + run: | + status=0 + git ls-remote --exit-code --tags origin "refs/tags/$TAG" > /dev/null || status=$? + if [ "$status" -eq 0 ]; then + echo "Tag $TAG already exists; nothing to release." + echo "created=false" >> "$GITHUB_OUTPUT" + exit 0 + elif [ "$status" -ne 2 ]; then + echo "Could not check origin for tag $TAG (git ls-remote exited with $status)." >&2 + exit "$status" + fi + echo "Tag $TAG does not exist; creating it and the GitHub release at $GITHUB_SHA." + gh release create "$TAG" --target "$GITHUB_SHA" --title "$TAG" --generate-notes + echo "created=true" >> "$GITHUB_OUTPUT" diff --git a/README.md b/README.md index 5c8fc52..d659353 100644 --- a/README.md +++ b/README.md @@ -244,3 +244,7 @@ One entry per list in `threat.blocklists`: Some feeds, such as ipdata's own VPN feed, omit `firstSeen`, `lastSeen` and `daysSeen`; those entries decode with the three properties set to `nil`. The dates are kept as strings exactly as the API sends them. All model types conform to `Codable`, `Sendable`, and `Equatable`. + +## Releasing + +The package version is the one in the `from:` argument of the Swift Package Manager snippet under [Installation](#installation); a Swift package has no other version file. To release, bump that version in a pull request and merge it. On every push to `main` that passes the test matrices, CI reads the version from `README.md` and, when the tag `vx.y.z` does not exist yet, creates the tag and the GitHub release at the merge commit with generated release notes. Swift Package Manager and the [Swift Package Index](https://swiftpackageindex.com) pick new versions up from the Git tags, so nothing else is published. A merge whose version is already tagged releases nothing.