Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 46 additions & 56 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
name: release

# Two release flavors, both gated on the CI workflow passing first:
# Manual, versioned release — the only way a new version ships.
#
# • push to main → refreshes a rolling "latest" pre-release with fresh
# cross-platform binaries (a new release every push).
# • push a tag v* → publishes a stable, versioned GitHub Release.
# Trigger from the Actions tab ("Run workflow") or:
# gh workflow run release.yml --field version=v0.1.3
# (or: make release VERSION=v0.1.3)
#
# It runs the full pipeline for the given version: CI gate → cross-platform
# binaries → publish every npm package (the CLI launcher + 5 platform binaries
# + the MCP server, all at the same version) → tag the commit + GitHub Release.
# Nothing is released on push; releasing is always a deliberate manual action.
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
inputs:
version:
description: 'Release version (vX.Y.Z)'
required: true
type: string

permissions:
contents: write

jobs:
# Reuse the full CI gate (gofmt, vet, build, race tests) — no binaries are
# built or released unless this passes.
validate:
name: validate version
runs-on: ubuntu-latest
steps:
- name: Check format
env:
VERSION: ${{ inputs.version }}
run: |
case "$VERSION" in
v[0-9]*.[0-9]*.[0-9]*) echo "releasing $VERSION" ;;
*) echo "::error::version must look like vX.Y.Z (got '$VERSION')"; exit 1 ;;
esac

# Reuse the full CI gate (gofmt, vet, build, race tests + mcp-server tests) —
# no binaries are built or published unless this passes.
ci:
needs: validate
uses: ./.github/workflows/ci.yml

build:
Expand All @@ -40,21 +62,12 @@ jobs:
go-version: '1.22'
cache: true

- name: Resolve version
id: ver
run: |
if [ "${{ github.ref_type }}" = "tag" ]; then
echo "version=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
else
echo "version=latest-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
fi

- name: Build & package
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: '0'
VERSION: ${{ steps.ver.outputs.version }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
bin=csdd
Expand All @@ -78,14 +91,12 @@ jobs:
path: dist/*
retention-days: 7

# Publish to npm on tagged releases only (npm versions are immutable, so the
# rolling "latest" main builds are not published here). Authenticates with the
# NPM_TOKEN secret (an Automation token, which bypasses 2FA); id-token: write
# is kept so --provenance can attach a signed build attestation.
# Publish every npm package at the requested version. Authenticates with the
# NPM_TOKEN secret (must be an Automation token, which bypasses 2FA);
# id-token: write lets --provenance attach a signed build attestation.
npm-publish:
name: publish npm packages
needs: build
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -115,7 +126,9 @@ jobs:
npm --prefix mcp-server run build

- name: Assemble npm packages
run: node npm/scripts/build-packages.mjs "${{ github.ref_name }}" artifacts
env:
VERSION: ${{ inputs.version }}
run: node npm/scripts/build-packages.mjs "$VERSION" artifacts

- name: Publish (platform packages + mcp-server first, then the root)
env:
Expand All @@ -131,9 +144,11 @@ jobs:
done
npm publish npm/dist/csdd --access public --provenance

# Tag the released commit and create the GitHub Release — only after npm
# publishing succeeds, so a failed publish never leaves a dangling tag/release.
release:
name: publish release
needs: build
name: tag + GitHub release
needs: npm-publish
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
Expand All @@ -144,36 +159,11 @@ jobs:
- name: Checksums
run: cd dist && sha256sum * | tee checksums.txt

# --- Stable, versioned release (on tag push) ---
- name: Publish stable release
if: github.ref_type == 'tag'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
make_latest: 'true'
files: dist/*

# --- Rolling "latest" pre-release (on main push) ---
# Remove the previous rolling release + tag so the new one points at the
# current commit, then recreate it.
- name: Reset previous "latest"
if: github.ref_type != 'tag'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release delete latest --cleanup-tag --yes \
--repo "$GITHUB_REPOSITORY" || true

- name: Publish "latest" pre-release
if: github.ref_type != 'tag'
- name: Create tag + release
uses: softprops/action-gh-release@v2
with:
tag_name: latest
name: latest
prerelease: true
tag_name: ${{ inputs.version }}
name: ${{ inputs.version }}
target_commitish: ${{ github.sha }}
body: |
Rolling build from `main` — updated on every push.
Commit: ${{ github.sha }}
make_latest: 'true'
files: dist/*
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# make build # local binary -> ./csdd
# make check # gofmt + vet + race tests (the CI gate)
#
# Release (recommended — CI builds + publishes to npm on the pushed tag):
# make release VERSION=v0.2.0
# Release (manualdispatches the release workflow; CI builds + publishes npm):
# make release VERSION=v0.2.0 # = gh workflow run release.yml --field version=v0.2.0
#
# Manual npm publish (bootstrap / fallback, when CI can't do it):
# make dist VERSION=v0.2.0 # cross-compile all 5 targets into dist/
Expand Down Expand Up @@ -110,9 +110,9 @@ npm-publish: ## Publish the assembled packages (CLI + mcp-server), skips already
done

.PHONY: release
release: require-version ## Tag VERSION and push -> CI builds + publishes (set VERSION=vX.Y.Z)
git tag -a '$(VERSION)' -m 'csdd $(VERSION)'
git push origin '$(VERSION)'
release: require-version ## Dispatch the manual release workflow (CI builds binaries + publishes npm) for VERSION
gh workflow run release.yml --field version='$(VERSION)'
@echo "dispatched release $(VERSION) — track it with: gh run list --workflow=release.yml"

.PHONY: clean
clean: ## Remove build artifacts (dist/, npm/dist/, ./csdd, coverage)
Expand Down
Loading