From bd708edd75076991c5ecebb3ec6e94b8193725bc Mon Sep 17 00:00:00 2001 From: mosherBT Date: Mon, 5 Oct 2026 10:54:21 -0400 Subject: [PATCH 1/3] ci: add a Tag release workflow that bumps and pushes the release tag Mirrors the release workflow in airflow-matchers-dags. workflow_dispatch with a patch/minor/major choice, refuses to run off master, derives the next version from the latest vX.Y.Z tag and pushes it. A tag pushed with GITHUB_TOKEN does not start another workflow run, so release.yml gains a workflow_call trigger with a version input and is invoked directly rather than left to fire on its own push trigger. The push trigger stays, so a hand-pushed tag still releases unchanged. --- .github/workflows/release.yml | 57 +++++++++++---- .github/workflows/reusable-build.yml | 7 +- .github/workflows/tag.yml | 103 +++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/tag.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 936c0271..c46fdd07 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,27 +4,55 @@ on: tags: - v[0-9]+.[0-9]+.[0-9]+ - v[0-9]+.[0-9]+.[0-9]+-* + # Called by tag.yml after it pushes a tag. A tag pushed with GITHUB_TOKEN does + # not start another workflow run, so the push trigger above will not fire for + # that tag and the release has to be invoked directly. + workflow_call: + inputs: + version: + description: "Tag to release, e.g. v1.2.3" + required: true + type: string env: workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }} service-account: ${{ secrets.SERVICE_ACCOUNT }} jobs: + # On a tag push the version is the tag that triggered the run. When tag.yml + # calls this workflow the run is still on master, so github.ref_name is the + # branch and the version has to come from the caller instead. + version: + runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.resolve.outputs.version }} + steps: + - name: Resolve version + id: resolve + env: + INPUT_VERSION: ${{ inputs.version }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + version="${INPUT_VERSION:-$REF_NAME}" + echo "Releasing ${version}" + echo "version=${version}" >>"$GITHUB_OUTPUT" + tests-prettier: uses: ./.github/workflows/reusable-lint-test.yml build: - needs: [tests-prettier] + needs: [tests-prettier, version] uses: ./.github/workflows/reusable-build.yml permissions: contents: read id-token: write with: - version: ${{ github.ref_name }} + version: ${{ needs.version.outputs.version }} # This job needs to run before deploying to npm otherwise `latest` won't be updated define-gcs-versions-to-update: - needs: [build] + needs: [build, version] runs-on: ubuntu-22.04 outputs: @@ -49,7 +77,7 @@ jobs: - name: Determine versions to update id: sdk-versions env: - REF_NAME: ${{ github.ref_name }} + REF_NAME: ${{ needs.version.outputs.version }} run: ./scripts/versions-to-update.sh @optable/web-sdk "$REF_NAME" >> "$GITHUB_OUTPUT" - name: Show versions that will be updated @@ -59,7 +87,7 @@ jobs: echo "SDK versions: $SDK_VERSIONS" deploy-sdk-to-npm: - needs: [build, define-gcs-versions-to-update] + needs: [build, define-gcs-versions-to-update, version] runs-on: ubuntu-22.04 permissions: id-token: write @@ -80,10 +108,10 @@ jobs: name: dist-lib path: lib/dist - # Step will fail if the version is invalid, github.ref_name is the tag name (v.*.*.*) + # Step will fail if the version is invalid, the resolved version is the tag name (v.*.*.*) - name: Patch version env: - REF_NAME: ${{ github.ref_name }} + REF_NAME: ${{ needs.version.outputs.version }} run: ./scripts/patch-version.sh "$REF_NAME" - name: Setup pnpm @@ -99,7 +127,7 @@ jobs: run: pnpm publish --provenance --access public --no-git-checks deploy-sdk-to-gcs: - needs: [deploy-sdk-to-npm, define-gcs-versions-to-update] + needs: [deploy-sdk-to-npm, define-gcs-versions-to-update, version] strategy: matrix: sdk-version: ${{ fromJSON(needs.define-gcs-versions-to-update.outputs.sdk-versions) }} @@ -127,10 +155,10 @@ jobs: destination: "optable-web-sdk/${{ matrix.sdk-version }}" process_gcloudignore: false headers: | - x-goog-meta-optable-sdk-version: ${{ github.ref_name }} + x-goog-meta-optable-sdk-version: ${{ needs.version.outputs.version }} deploy-demo: - needs: [deploy-sdk-to-npm] + needs: [deploy-sdk-to-npm, version] permissions: contents: "read" id-token: "write" @@ -172,7 +200,7 @@ jobs: - name: Build web-sdk-demos Docker Image env: - REF_NAME: ${{ github.ref_name }} + REF_NAME: ${{ needs.version.outputs.version }} run: | DOCKER_BUILDKIT=1 docker build \ -t "us-docker.pkg.dev/optable-artifact-registry/optable/optable-web-sdk-demos:$REF_NAME" \ @@ -187,11 +215,12 @@ jobs: - name: Publish web-sdk-demos to us-docker.pkg.dev env: - REF_NAME: ${{ github.ref_name }} + REF_NAME: ${{ needs.version.outputs.version }} run: docker push "us-docker.pkg.dev/optable-artifact-registry/optable/optable-web-sdk-demos:$REF_NAME" slack-notification: - needs: [tests-prettier, build, deploy-sdk-to-npm, define-gcs-versions-to-update, deploy-sdk-to-gcs, deploy-demo] + needs: + [version, tests-prettier, build, deploy-sdk-to-npm, define-gcs-versions-to-update, deploy-sdk-to-gcs, deploy-demo] runs-on: ubuntu-22.04 if: ${{ failure() }} steps: @@ -214,7 +243,7 @@ jobs: - type: "mrkdwn" text: "*Status:*\nFailure" - type: "mrkdwn" - text: "*Branch:*\n${{ github.ref_name }}" + text: "*Version:*\n${{ needs.version.outputs.version }}" - type: "mrkdwn" text: "*Author:*\n${{ github.actor || github.triggering_actor }}" - type: "mrkdwn" diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 729e024d..4e620420 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -64,8 +64,11 @@ jobs: build-vanilla-demo: runs-on: ubuntu-22.04 env: - # The ref_name (tag) is not relevant when it runs on something other than a tag so we use latest - SDK_URI: https://cdn.optable.co/web-sdk/${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || 'latest' }}/sdk.js + # Only a release passes a vX.Y.Z version; PR builds pass 0.0.0-experimental, + # which was never published, so those fall back to latest. Keyed off the + # input rather than github.ref because a release dispatched through tag.yml + # runs on master and would otherwise read as "not a tag". + SDK_URI: https://cdn.optable.co/web-sdk/${{ startsWith(inputs.version, 'v') && inputs.version || 'latest' }}/sdk.js DCN_HOST: ca.edge.optable.co DCN_SITE: web-sdk-demo DCN_NODE: optable diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 00000000..ef4a3c2c --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,103 @@ +name: Tag release + +# Tags master and ships the tag through the release pipeline. Releasing is still +# a deliberate act -- this is workflow_dispatch, not a push trigger -- but it +# replaces doing the tag dance by hand, and it cannot mis-derive the version or +# tag the wrong branch. +# +# Pre-releases (vX.Y.Z-rc.1 and friends) are not covered here. Push those tags by +# hand; release.yml still picks them up on its own. + +on: + workflow_dispatch: + inputs: + bump: + description: "Which part of the version to increment" + type: choice + options: + - patch + - minor + - major + default: patch + +permissions: + contents: read + +jobs: + tag: + name: Tag master + runs-on: ubuntu-22.04 + permissions: + contents: write + outputs: + version: ${{ steps.next.outputs.version }} + steps: + - name: Refuse to release from anything but master + if: github.ref != 'refs/heads/master' + run: | + echo "::error::Release must run on master, got ${{ github.ref }}. Re-run and pick master." + exit 1 + + - name: Checkout code + uses: actions/checkout@v6 + with: + # Tags are needed to work out what the current version is. + fetch-depth: 0 + + - name: Compute next version + id: next + env: + BUMP: ${{ inputs.bump }} + run: | + set -euo pipefail + # Only strict vX.Y.Z tags seed the next version. Pre-release tags sort + # above the release they precede, so letting them through would bump + # from a version that was never shipped. + # `|| true` keeps the fallback below reachable: with pipefail a grep that + # matches nothing fails the pipeline, and set -e would kill the step + # before the default could apply. + latest=$(git tag -l 'v*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || true) + latest=${latest:-v0.0.0} + IFS=. read -r major minor patch <<<"${latest#v}" + case "$BUMP" in + major) major=$((major + 1)); minor=0; patch=0 ;; + minor) minor=$((minor + 1)); patch=0 ;; + patch) patch=$((patch + 1)) ;; + esac + version="v${major}.${minor}.${patch}" + echo "${latest} -> ${version}" + echo "version=${version}" >>"$GITHUB_OUTPUT" + { + echo "### Release \`${version}\`" + echo "" + echo "Previous: \`${latest}\` · bump: \`${BUMP}\` · commit: \`${GITHUB_SHA:0:7}\`" + } >>"$GITHUB_STEP_SUMMARY" + + - name: Create and push tag + env: + VERSION: ${{ steps.next.outputs.version }} + run: | + set -euo pipefail + if git rev-parse -q --verify "refs/tags/${VERSION}" >/dev/null; then + echo "::error::${VERSION} already exists. Someone tagged out of band." + exit 1 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "${VERSION}" -m "Release ${VERSION}" + git push origin "${VERSION}" + + release: + # A tag pushed with GITHUB_TOKEN does not start another workflow run, so + # release.yml's `on: push: tags` will not fire for the tag created above. The + # release is therefore called explicitly rather than left to trigger itself. + # release.yml keeps its push trigger, so a hand-pushed tag still releases. + name: Release + needs: tag + permissions: + contents: read + id-token: write + uses: ./.github/workflows/release.yml + secrets: inherit + with: + version: ${{ needs.tag.outputs.version }} From 8546a5fa17637a88b9b7cd8d8123798c2114e08e Mon Sep 17 00:00:00 2001 From: mosherBT Date: Fri, 30 Oct 2026 13:11:41 -0400 Subject: [PATCH 2/3] ci: default the Tag release bump to minor --- .github/workflows/tag.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index ef4a3c2c..89ee29e5 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -15,10 +15,10 @@ on: description: "Which part of the version to increment" type: choice options: - - patch - minor - major - default: patch + - patch + default: minor permissions: contents: read From f0148d887b7707bf25f37bd775a45217e1a4ab23 Mon Sep 17 00:00:00 2001 From: mosherBT Date: Fri, 30 Oct 2026 13:17:46 -0400 Subject: [PATCH 3/3] comments --- .github/workflows/release.yml | 8 +------- .github/workflows/reusable-build.yml | 4 ---- .github/workflows/tag.yml | 20 +------------------- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c46fdd07..d5ac5dc6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,6 @@ on: tags: - v[0-9]+.[0-9]+.[0-9]+ - v[0-9]+.[0-9]+.[0-9]+-* - # Called by tag.yml after it pushes a tag. A tag pushed with GITHUB_TOKEN does - # not start another workflow run, so the push trigger above will not fire for - # that tag and the release has to be invoked directly. workflow_call: inputs: version: @@ -19,9 +16,6 @@ env: service-account: ${{ secrets.SERVICE_ACCOUNT }} jobs: - # On a tag push the version is the tag that triggered the run. When tag.yml - # calls this workflow the run is still on master, so github.ref_name is the - # branch and the version has to come from the caller instead. version: runs-on: ubuntu-22.04 outputs: @@ -108,7 +102,7 @@ jobs: name: dist-lib path: lib/dist - # Step will fail if the version is invalid, the resolved version is the tag name (v.*.*.*) + # Step will fail if the version is invalid (v.*.*.*) - name: Patch version env: REF_NAME: ${{ needs.version.outputs.version }} diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 4e620420..83c0fb89 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -64,10 +64,6 @@ jobs: build-vanilla-demo: runs-on: ubuntu-22.04 env: - # Only a release passes a vX.Y.Z version; PR builds pass 0.0.0-experimental, - # which was never published, so those fall back to latest. Keyed off the - # input rather than github.ref because a release dispatched through tag.yml - # runs on master and would otherwise read as "not a tag". SDK_URI: https://cdn.optable.co/web-sdk/${{ startsWith(inputs.version, 'v') && inputs.version || 'latest' }}/sdk.js DCN_HOST: ca.edge.optable.co DCN_SITE: web-sdk-demo diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 89ee29e5..8cb7863b 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -1,13 +1,5 @@ name: Tag release -# Tags master and ships the tag through the release pipeline. Releasing is still -# a deliberate act -- this is workflow_dispatch, not a push trigger -- but it -# replaces doing the tag dance by hand, and it cannot mis-derive the version or -# tag the wrong branch. -# -# Pre-releases (vX.Y.Z-rc.1 and friends) are not covered here. Push those tags by -# hand; release.yml still picks them up on its own. - on: workflow_dispatch: inputs: @@ -41,7 +33,6 @@ jobs: - name: Checkout code uses: actions/checkout@v6 with: - # Tags are needed to work out what the current version is. fetch-depth: 0 - name: Compute next version @@ -50,12 +41,6 @@ jobs: BUMP: ${{ inputs.bump }} run: | set -euo pipefail - # Only strict vX.Y.Z tags seed the next version. Pre-release tags sort - # above the release they precede, so letting them through would bump - # from a version that was never shipped. - # `|| true` keeps the fallback below reachable: with pipefail a grep that - # matches nothing fails the pipeline, and set -e would kill the step - # before the default could apply. latest=$(git tag -l 'v*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || true) latest=${latest:-v0.0.0} IFS=. read -r major minor patch <<<"${latest#v}" @@ -88,10 +73,7 @@ jobs: git push origin "${VERSION}" release: - # A tag pushed with GITHUB_TOKEN does not start another workflow run, so - # release.yml's `on: push: tags` will not fire for the tag created above. The - # release is therefore called explicitly rather than left to trigger itself. - # release.yml keeps its push trigger, so a hand-pushed tag still releases. + # Tags pushed with GITHUB_TOKEN don't trigger other workflows, so release.yml is called directly. name: Release needs: tag permissions: