diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index ee222f3..503de5b 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -37,3 +37,61 @@ jobs: run: npm ci - name: Test run: npm test + + release: + needs: [lint, 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.version.outputs.tag }} + created: ${{ steps.release.outputs.created }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Read version + id: version + run: | + version=$(sed -nE 's/^ *"version": *"([^"]+)".*/\1/p' package.json | head -1) + if [ -z "$version" ]; then + echo "No version found in package.json" >&2 + exit 1 + fi + echo "tag=v$version" >> "$GITHUB_OUTPUT" + - name: Create tag and release + 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=$? + 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 "Could not check whether tag $TAG exists (git ls-remote exited with $status)" >&2 + exit "$status" + ;; + esac + + publish: + needs: release + if: needs.release.outputs.created == 'true' + permissions: + contents: read + id-token: write + uses: ./.github/workflows/release.yml + with: + tag: ${{ needs.release.outputs.tag }} + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f890738..9dd731d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,12 @@ on: push: tags: - '**' + workflow_call: + inputs: + tag: + description: Tag to publish, for example v3.1.0 + required: true + type: string jobs: lint: @@ -11,6 +17,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag || github.ref }} - name: Install node uses: actions/setup-node@v4 with: @@ -25,6 +33,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag || github.ref }} - name: Install node uses: actions/setup-node@v4 with: @@ -43,6 +53,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag || github.ref }} - name: Install node uses: actions/setup-node@v4 with: diff --git a/README.md b/README.md index 551be0a..f231721 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Requires **Node.js 20.18.1** or later. HTTP requests go through [undici](https:/ - [Connection reuse and HTTP/2](#connection-reuse-and-http2) - [Response Fields](#response-fields) - [TypeScript](#typescript) +- [Releasing](#releasing) ## Install @@ -236,3 +237,7 @@ The library is written in TypeScript and exports the following types: ```ts import IPData, { EU_BASE_URL, CacheConfig, HttpConfig, LookupParams, LookupResponse } from 'ipdata'; ``` + +## Releasing + +Releases are cut by CI. Bump `version` in `package.json` in a pull request (`npm version x.y.z --no-git-tag-version` also updates `package-lock.json`) and merge it. On the merge to `main`, CI tags the commit `vx.y.z`, creates the GitHub release with generated notes and publishes the package to [npm](https://www.npmjs.com/package/ipdata). A merge whose version is already tagged publishes nothing. Pushing a `vx.y.z` tag by hand still publishes that tag.