docs: require CLI-created buttons (#250) #170
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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@v6 | |
| 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@v6 | |
| 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}" |