diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1549a18..7df2103 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,3 +53,54 @@ jobs: - name: Run tests run: ./vendor/bin/phpunit + + release: + name: Release + needs: + - build-lowest-version + - build + 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: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Read the version from the changelog + id: version + run: | + version=$(sed -nE 's/^## ([0-9][^[:space:]]*).*/\1/p' CHANGELOG.md | head -1) + if [ -z "$version" ]; then + echo "::error file=CHANGELOG.md::No '## x.y.z' heading found in CHANGELOG.md" + exit 1 + fi + echo "tag=$version" >> "$GITHUB_OUTPUT" + echo "The changelog version is $version; the tag is $version." + + - name: Create the tag and the GitHub release + id: release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.version.outputs.tag }} + run: | + rc=0 + git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null || rc=$? + if [ "$rc" -eq 0 ]; then + echo "Tag $TAG already exists; nothing to release." + echo "created=false" >> "$GITHUB_OUTPUT" + exit 0 + elif [ "$rc" -ne 2 ]; then + echo "::error::Could not check whether tag $TAG exists (git ls-remote exited with $rc)." + exit "$rc" + fi + gh release create "$TAG" --target "$GITHUB_SHA" --title "$TAG" --generate-notes + echo "created=true" >> "$GITHUB_OUTPUT" + echo "Created tag $TAG and its GitHub release from $GITHUB_SHA." diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f84189..04f54e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Documentation and tests for the new API response fields: top-level `is_satellite` and `is_anycast` (present only when `true`) and `first_seen`, `last_seen` (`YYYY-MM-DD`, UTC) and `days_seen` on `threat.blocklists` entries (absent for some feeds). The client passes them through unchanged. - Every request carries a `User-Agent: ipdata-php/` header. The version is the installed version of `ipdata/api-client` as recorded by Composer (`Composer\InstalledVersions`, Composer 2 and newer); without it the header is `ipdata-php`. - `Ipdata::DEFAULT_BASE_URL` is public, so the trailing `$httpVersion` argument can be passed without repeating the endpoint: `new Ipdata('my_api_key', $httpClient, $psr17Factory, Ipdata::DEFAULT_BASE_URL, '2.0')`. +- Releases are cut by CI: when a merge to `master` puts a new `## x.y.z` heading at the top of this file, the `release` job of the CI workflow creates the tag `x.y.z` and the GitHub release, and Packagist picks the tag up through its GitHub hook (see "Releasing" in the README). ### Fixed diff --git a/README.md b/README.md index 432ad6a..0455c45 100644 --- a/README.md +++ b/README.md @@ -245,3 +245,13 @@ Some feeds, for example ipdata's own vpn feed, carry none of the three blocklist ## Errors A list of possible errors is available at [Status Codes](https://docs.ipdata.co/api-reference/status-codes) + +## Releasing + +Releases are cut by CI, and the version source is the first `## x.y.z` heading in `CHANGELOG.md`: `composer.json` +carries no `version` field, and Packagist reads the versions of `ipdata/api-client` from the Git tags. + +To release, add a `## x.y.z` heading with the release notes at the top of `CHANGELOG.md` in a pull request and merge +it. On the push to `master`, once the tests have passed, the `release` job of the CI workflow tags the merge commit +`x.y.z`, creates the GitHub release with generated notes, and Packagist picks the new tag up through its GitHub hook. +A merge whose version is already tagged publishes nothing, so there is no manual tagging.