Skip to content

Merge pull request #258 from autonoco/dependabot/github_actions/actio… #178

Merge pull request #258 from autonoco/dependabot/github_actions/actio…

Merge pull request #258 from autonoco/dependabot/github_actions/actio… #178

Workflow file for this run

name: Auto-release
# On pushes to main, tags the next minor version and runs goreleaser to
# publish binaries + Docker image in ONE workflow. Rapid pushes are
# serialized and may be coalesced by the concurrency group below; the newest
# queued main commit is released after the active release finishes.
#
# Previous approach (separate auto-release → release.yml) broke
# because GitHub blocks cross-workflow triggering from GITHUB_TOKEN:
# "Events triggered by GITHUB_TOKEN will not create a new workflow run."
#
# By combining tag + goreleaser in a single workflow, we avoid
# cross-workflow triggering entirely.
#
# To skip a release: include [skip release] in the merge commit.
# Bot commits (docs-sync) are skipped automatically.
on:
push:
branches: [main]
workflow_dispatch:
inputs:
tag:
description: Existing semver tag to recover (for example, v0.135.0)
required: true
type: string
# GoReleaser updates shared mutable destinations (:latest, Homebrew, npm).
# Never let this workflow overlap another release workflow in this repository.
concurrency:
group: buttons-release
cancel-in-progress: false
permissions:
contents: write
packages: write
# Required for npm Trusted Publishing: the workflow exchanges this short-
# lived OIDC token for publish access on configured @autono/* packages.
# No long-lived NPM_TOKEN secret is needed — npm validates the token
# against the trusted-publisher config on npmjs.com.
id-token: write
jobs:
release:
name: Tag and release
runs-on: ubuntu-latest
timeout-minutes: 30
if: |
github.event_name == 'workflow_dispatch' ||
(
github.actor != 'github-actions[bot]' &&
!contains(github.event.head_commit.message, '[skip release]')
)
steps:
- name: Check out code
uses: actions/checkout@v7
with:
fetch-depth: 0
# Manual recovery must build the existing tag, not the branch used
# to dispatch the workflow. Push-triggered releases use their SHA.
ref: ${{ inputs.tag || github.sha }}
- name: Determine next version
id: version
run: |
REQUESTED_TAG="${{ inputs.tag }}"
if [ -n "$REQUESTED_TAG" ]; then
if [[ ! "$REQUESTED_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid recovery tag: $REQUESTED_TAG" >&2
exit 1
fi
TAG_SHA=$(git rev-parse --verify "refs/tags/$REQUESTED_TAG^{commit}")
HEAD_SHA=$(git rev-parse HEAD)
if [ "$TAG_SHA" != "$HEAD_SHA" ]; then
echo "Recovery checkout mismatch: $REQUESTED_TAG points to $TAG_SHA, checked out $HEAD_SHA" >&2
exit 1
fi
echo "latest=$REQUESTED_TAG" >> "$GITHUB_OUTPUT"
echo "next=$REQUESTED_TAG" >> "$GITHUB_OUTPUT"
echo "create_tag=false" >> "$GITHUB_OUTPUT"
echo "Recovering existing release: $REQUESTED_TAG"
exit 0
fi
LATEST=$(git tag --list 'v*' --sort=-version:refname | head -n1)
if [ -z "$LATEST" ]; then
NEXT="v0.1.0"
else
VERSION="${LATEST#v}"
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
NEXT="v${MAJOR}.$((MINOR + 1)).0"
fi
echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
echo "next=$NEXT" >> "$GITHUB_OUTPUT"
echo "create_tag=true" >> "$GITHUB_OUTPUT"
echo "Releasing: $LATEST → $NEXT"
- name: Create and push tag
if: steps.version.outputs.create_tag == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "${{ steps.version.outputs.next }}" -m "Auto-release ${{ steps.version.outputs.next }}"
git push origin "${{ steps.version.outputs.next }}"
- name: Set up Go
uses: actions/setup-go@v7
with:
# Keep in sync with GO_VERSION in ci.yml.
go-version: "1.26.5"
cache: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Required for the brews: block in .goreleaser.yaml. Must be a PAT
# with `contents:write` on autonoco/homebrew-tap. If the secret is
# not set, ${{ secrets.X }} evaluates to '' and goreleaser's
# skip_upload template kicks in — the brew step becomes a no-op
# rather than failing the release.
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
# Publish to npm once goreleaser has written the platform binaries into
# dist/. Auth is via npm Trusted Publishing (OIDC) — the `id-token:
# write` permission above lets the npm CLI exchange a GitHub-issued
# JWT for short-lived publish credentials. Trusted publisher config
# lives on npmjs.com under the @autono org.
#
# If TP is not yet configured for a package on the npm side, this
# step fails loudly with a clear error from npm. Fix by adding the
# package to the org's trusted publishers and re-running the job.
#
# Node 24 is pinned intentionally: npm Trusted Publishing needs npm
# >= 11.5.1, which ships natively with Node 24.x. We previously tried
# Node 22 + `npm install -g npm@11`, but npm's in-place self-upgrade
# corrupts its own module graph (MODULE_NOT_FOUND on promise-retry)
# because the running npm's dependencies get swapped out mid-install.
# Using Node 24 avoids the self-upgrade entirely.
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
# Dogfood: rather than calling the node script directly, build the
# buttons CLI from this checkout and press the publish-npm button
# committed at .buttons/buttons/publish-npm/. That button is itself
# a thin wrapper around scripts/publish-npm.mjs, so the behavior is
# identical — but CI validating the button end-to-end keeps the
# "project-local .buttons/ is a real thing you can use" story
# coherent. If the button breaks, releases break; the press becomes
# one more sample to read when someone's learning the tool.
#
# Version must be passed without the leading 'v' — that's the
# contract publish-npm.mjs expects (it reads process.env.VERSION).
- name: Publish to npm (via buttons press)
run: |
go build -o ./buttons .
VERSION="${{ steps.version.outputs.next }}"
./buttons press publish-npm --arg version="${VERSION#v}"