mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
A merge advances the patch. Actions -> release -> Run workflow takes a major/minor/patch dropdown, or a version named outright. The publish authenticates to npm over OIDC against a trusted publisher, so the job holds no token. npm matches that publisher against the filename of the workflow that starts the run, which is why the merge path arrives here as a workflow_run rather than as a job at the end of ci. Claude-Session: https://claude.ai/code/session_01ShuAy8q8ZfPi8KHxwc8JpQ
214 lines
7.5 KiB
YAML
214 lines
7.5 KiB
YAML
name: release
|
|
|
|
# Two ways in, one workflow file. npm's trusted publisher is configured against
|
|
# the filename of the workflow that *starts* the run, so a publish reached
|
|
# through `workflow_call` from ci.yml would present ci.yml's name and be
|
|
# refused, and a package carries only one trusted publisher. That is why the
|
|
# merge path arrives as a `workflow_run` off a green ci rather than as a job
|
|
# inside it.
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
bump:
|
|
description: Which part of MAJOR.MINOR.PATCH to advance
|
|
type: choice
|
|
options:
|
|
- patch
|
|
- minor
|
|
- major
|
|
default: patch
|
|
version:
|
|
description: Release this version outright, e.g. 1.0.0 or 1.0.0-rc1. Overrides the bump.
|
|
type: string
|
|
required: false
|
|
workflow_run:
|
|
workflows: [ci]
|
|
types: [completed]
|
|
# ci runs on every pull request too, and each of those completing would
|
|
# otherwise start a run here only to skip every job in it.
|
|
branches: [master]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Two releases must not overlap: both would count a version off the same tag
|
|
# and both would try to cut it.
|
|
concurrency:
|
|
group: release
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
# Nothing is published until the tag is pushed, so a version that cannot be
|
|
# tagged never reaches a registry. npm is the irreversible half of a release
|
|
# and a tag is the cheap half to redo.
|
|
tag:
|
|
name: Tag
|
|
# A dispatch is a deliberate release. A workflow_run is one only when ci
|
|
# went green on a push to master: ci also runs on pull requests, and a red
|
|
# run is not something to publish.
|
|
if: >-
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(github.event.workflow_run.conclusion == 'success' &&
|
|
github.event.workflow_run.event == 'push' &&
|
|
github.event.workflow_run.head_branch == 'master')
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
outputs:
|
|
version: ${{ steps.next.outputs.version }}
|
|
tag: ${{ steps.next.outputs.tag }}
|
|
steps:
|
|
# A workflow_run reports the commit ci ran on, which is the one to
|
|
# release: master may have moved on since it went green.
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
|
# The version is counted off the tags, so the tags have to be here.
|
|
fetch-depth: 0
|
|
|
|
# `inputs` is empty on a workflow_run, which leaves the script on its
|
|
# default of a patch: that is the bump a merge to master cuts.
|
|
- name: Resolve the version
|
|
id: next
|
|
run: .github/scripts/next-version.sh
|
|
env:
|
|
BUMP: ${{ inputs.bump }}
|
|
VERSION: ${{ inputs.version }}
|
|
|
|
- name: Tag the commit
|
|
run: |
|
|
git -c user.name='github-actions[bot]' \
|
|
-c user.email='41898282+github-actions[bot]@users.noreply.github.com' \
|
|
tag -a "$TAG" -m "$TAG"
|
|
git push origin "refs/tags/$TAG"
|
|
env:
|
|
TAG: ${{ steps.next.outputs.tag }}
|
|
|
|
npm:
|
|
name: Release (npm)
|
|
needs: tag
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
# npm authenticates this publish over OIDC against the trusted publisher
|
|
# configured for @sanderling/spec, so the job holds no token at all and
|
|
# there is none to expire. It is also what makes npm attach provenance.
|
|
id-token: write
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
ref: ${{ needs.tag.outputs.tag }}
|
|
# `npm ci` below runs dependency lifecycle scripts, and no step in
|
|
# this job needs the git credential afterwards.
|
|
persist-credentials: false
|
|
|
|
- name: Set up Node 22
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version: "22"
|
|
registry-url: "https://registry.npmjs.org"
|
|
cache: npm
|
|
cache-dependency-path: pkg/spec/package-lock.json
|
|
|
|
# registry-url above writes an `_authToken=${NODE_AUTH_TOKEN}` line into
|
|
# .npmrc whether or not a token exists, and an npm older than 11.5.1 reads
|
|
# that empty line as "auth is configured" and never asks for an OIDC
|
|
# token, so the publish fails needing auth. Node 22 ships npm 10.
|
|
- name: Install an npm that can publish over OIDC
|
|
run: npm install -g npm@latest
|
|
|
|
- name: Install dependencies
|
|
working-directory: pkg/spec
|
|
run: npm ci
|
|
|
|
# The repo keeps package.json at 0.0.0-dev. The tags are the record of
|
|
# what has been released, and a version committed to master would be a
|
|
# second record to hold in step with them.
|
|
- name: Stamp the version
|
|
working-directory: pkg/spec
|
|
run: npm version "$VERSION" --no-git-tag-version --allow-same-version
|
|
env:
|
|
VERSION: ${{ needs.tag.outputs.version }}
|
|
|
|
# A publish that already landed and then failed on its way out leaves npm
|
|
# holding the version, and re-running the job must not be red for it. The
|
|
# registry is asked rather than the tags: only npm knows what npm has.
|
|
# Only stdout decides, because `npm view` on a version that does not exist
|
|
# is empty on some npm releases and an error on others, and an unreachable
|
|
# registry must end in a publish that fails loudly rather than a skip that
|
|
# reads as success.
|
|
- name: Ask npm whether this version is already published
|
|
id: published
|
|
run: |
|
|
if [ -n "$(npm view "@sanderling/spec@$VERSION" version 2>/dev/null || true)" ]; then
|
|
echo "npm already has @sanderling/spec@$VERSION, nothing to publish"
|
|
echo "publish=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "publish=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
env:
|
|
VERSION: ${{ needs.tag.outputs.version }}
|
|
|
|
- name: Publish @sanderling/spec to npm
|
|
if: steps.published.outputs.publish == 'true'
|
|
working-directory: pkg/spec
|
|
# A pre-release is tagged `next` so `npm install @sanderling/spec` keeps
|
|
# resolving the latest stable.
|
|
run: |
|
|
if [[ "$VERSION" == *-* ]]; then
|
|
npm publish --access public --tag next
|
|
else
|
|
npm publish --access public
|
|
fi
|
|
env:
|
|
VERSION: ${{ needs.tag.outputs.version }}
|
|
|
|
cli:
|
|
name: Release (cli)
|
|
needs: tag
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
ref: ${{ needs.tag.outputs.tag }}
|
|
# GoReleaser reads the tag history for its changelog.
|
|
fetch-depth: 0
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v7
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
|
|
- name: Set up JDK 17
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: temurin
|
|
java-version: "17"
|
|
|
|
- name: Set up Android SDK
|
|
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
|
|
|
|
- name: Cache Gradle
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: |
|
|
~/.gradle/caches
|
|
~/.gradle/wrapper
|
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
|
restore-keys: |
|
|
gradle-${{ runner.os }}-
|
|
|
|
- name: Build sidecar JAR
|
|
run: make sidecar
|
|
|
|
- name: Publish the sanderling CLI to GitHub Releases
|
|
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
|
|
with:
|
|
version: "~> v2"
|
|
args: release --clean
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|