174 lines
8.2 KiB
YAML
174 lines
8.2 KiB
YAML
name: Release
|
|
|
|
# CLI binary line. Tag namespace `v<semver>` triggers this — it builds the
|
|
# release archives + checksums, publishes the GitHub release, and updates the
|
|
# Homebrew cask. npm is a SEPARATE line (release-npm.yml, `npm-v*`) so an npm
|
|
# prerelease can ship without forcing the stable cask, and vice versa; desktop is
|
|
# `desktop-v*` (release-desktop.yml). A prerelease `v*-rc*` still builds archives
|
|
# and a prerelease GitHub release, but goreleaser auto-skips the cask upload
|
|
# (brew has no prerelease channel — see .goreleaser.yaml).
|
|
on:
|
|
push:
|
|
tags: ['v*']
|
|
|
|
permissions:
|
|
contents: write # create the release and upload archives
|
|
|
|
jobs:
|
|
cache-guard:
|
|
name: cache hit guard
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
- run: ./scripts/cache-guard.sh
|
|
|
|
goreleaser:
|
|
name: archives + checksums + homebrew tap
|
|
needs: cache-guard
|
|
runs-on: ubuntu-latest
|
|
# CLI stable release (archives + GitHub release + Homebrew cask). It does not
|
|
# claim GitHub's repository-wide Latest badge; that belongs to desktop so the
|
|
# repository homepage points users at the installable app. Gated on the
|
|
# `release` environment so only esengine can approve it going public.
|
|
environment: release
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
- uses: actions/setup-go@v6
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
- uses: goreleaser/goreleaser-action@v7
|
|
with:
|
|
version: '~> v2'
|
|
args: release --clean
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
|
|
|
- name: Attach desktop manifest compatibility asset
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TAG: ${{ github.ref_name }}
|
|
HAS_R2: ${{ secrets.R2_ACCESS_KEY_ID != '' && secrets.R2_SECRET_ACCESS_KEY != '' && secrets.R2_ACCOUNT_ID != '' && secrets.R2_BUCKET != '' }}
|
|
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
|
|
R2_BUCKET: ${{ secrets.R2_BUCKET }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TAG" in
|
|
*-*)
|
|
echo "prerelease $TAG — GitHub latest does not move here; skipping desktop manifest compatibility asset"
|
|
exit 0
|
|
;;
|
|
esac
|
|
if [ "$HAS_R2" != "true" ]; then
|
|
echo "R2 secrets not configured; skipping desktop manifest compatibility asset"
|
|
exit 0
|
|
fi
|
|
# dl.reasonix.io serves 403 to GitHub Actions egress IPs (Cloudflare bot
|
|
# protection), so read the manifest over the authenticated S3 API instead
|
|
# of the public edge.
|
|
aws configure set aws_access_key_id "${{ secrets.R2_ACCESS_KEY_ID }}"
|
|
aws configure set aws_secret_access_key "${{ secrets.R2_SECRET_ACCESS_KEY }}"
|
|
aws configure set region auto
|
|
aws s3 cp "s3://${R2_BUCKET}/latest/latest.json" latest.raw.json \
|
|
--endpoint-url "https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
|
|
jq '.download_page = "https://reasonix.io/#start"' latest.raw.json > latest.json
|
|
jq -e '
|
|
([.platforms[] | (.url, .sig)] |
|
|
all(type == "string" and startswith("https://dl.reasonix.io/") and (contains("/releases/latest/") | not)))
|
|
' latest.json >/dev/null
|
|
gh release upload "$TAG" latest.json --clobber
|
|
|
|
# The compatibility asset exists for pre-v1.16 desktop updaters that
|
|
# still poll GitHub's repository-wide latest URL. Desktop releases now
|
|
# own that Latest badge, but this check still exercises the public fallback
|
|
# path exactly the way those clients fetch it: anonymously, over the public
|
|
# edge, with a Go client UA. Unlike dl.reasonix.io (whose bot protection
|
|
# 403s Actions egress — see the R2 note above), GitHub serves its own
|
|
# runners, so this can hard-fail. #5826/#5858 shipped a broken update check
|
|
# for weeks precisely because nothing exercised the public path. Retries
|
|
# cover the release CDN propagating the freshly uploaded asset.
|
|
- name: Smoke public compatibility manifest
|
|
env:
|
|
TAG: ${{ github.ref_name }}
|
|
HAS_R2: ${{ secrets.R2_ACCESS_KEY_ID != '' && secrets.R2_SECRET_ACCESS_KEY != '' && secrets.R2_ACCOUNT_ID != '' && secrets.R2_BUCKET != '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TAG" in
|
|
*-*)
|
|
echo "prerelease $TAG — no compatibility asset uploaded; skipping"
|
|
exit 0
|
|
;;
|
|
esac
|
|
if [ "$HAS_R2" != "true" ]; then
|
|
echo "R2 secrets not configured; no compatibility asset uploaded; skipping"
|
|
exit 0
|
|
fi
|
|
url="https://github.com/${{ github.repository }}/releases/latest/download/latest.json"
|
|
for attempt in 1 2 3 4 5 6; do
|
|
if curl -fsSL -A "Go-http-client/2.0" -o /tmp/compat-latest.json "$url"; then
|
|
jq -e '(.version | type == "string") and (.platforms | type == "object")' /tmp/compat-latest.json >/dev/null
|
|
echo "public compatibility manifest OK (desktop version $(jq -r .version /tmp/compat-latest.json))"
|
|
exit 0
|
|
fi
|
|
echo "attempt $attempt failed; retrying in 10s"
|
|
sleep 10
|
|
done
|
|
echo "::error::public compatibility manifest unreachable at $url"
|
|
exit 1
|
|
|
|
# A stable CLI release must never leave the npm line behind: v1.17.5
|
|
# shipped as binaries/Homebrew while npm `latest` still pointed at 0.53.2
|
|
# (#5822) — every `npm update -g` user was silently downgraded to a
|
|
# months-old version, and nothing noticed because the npm line
|
|
# (release-npm.yml, `npm-vX.Y.Z` tags) is triggered independently and the
|
|
# stable npm tag was simply never pushed. release-npm.yml's own verify
|
|
# step only guards runs that happen; this guard catches the run that
|
|
# DIDN'T.
|
|
#
|
|
# Two distinct states, two responses (the runs race: both workflows wait
|
|
# on the release environment independently, and npm dist-tags propagate
|
|
# asynchronously, so "tag pushed but latest not moved yet" is a NORMAL
|
|
# mid-release state, not a failure):
|
|
# - npm-v<version> tag missing -> hard fail. This is the #5822 gap:
|
|
# nobody pushed the npm release at all.
|
|
# - tag pushed, latest lagging -> poll briefly, then WARN and pass.
|
|
# The npm run may still be awaiting its own environment approval;
|
|
# release-npm.yml's verify step owns asserting the dist-tag lands.
|
|
- name: Check npm latest dist-tag freshness
|
|
env:
|
|
TAG: ${{ github.ref_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "$TAG" in
|
|
*-*)
|
|
echo "prerelease $TAG — npm latest does not move on prereleases; skipping"
|
|
exit 0
|
|
;;
|
|
esac
|
|
version="${TAG#v}"
|
|
if ! git ls-remote --exit-code origin "refs/tags/npm-v$version" >/dev/null; then
|
|
echo "::error::the npm-v$version tag was never pushed — the npm channel is being left behind and 'npm update -g' users will be downgraded to the old 'latest'. Push it: git tag npm-v$version ${TAG} && git push origin npm-v$version (or 'npm dist-tag add reasonix@$version latest' for an already-published version)."
|
|
exit 1
|
|
fi
|
|
for attempt in 1 2 3 4 5 6; do
|
|
got="$(npm view reasonix dist-tags.latest 2>/dev/null || true)"
|
|
if [ -n "$got" ]; then
|
|
newest="$(printf '%s\n%s\n' "$got" "$version" | sort -V | tail -1)"
|
|
if [ "$newest" = "$got" ]; then
|
|
echo "npm latest -> $got (>= $version) OK"
|
|
exit 0
|
|
fi
|
|
fi
|
|
echo "npm latest -> ${got:-<unreadable>}, want >= $version (attempt $attempt)"
|
|
sleep 10
|
|
done
|
|
echo "::warning::npm-v$version is pushed but npm 'latest' is still ${got:-<unreadable>} — the npm publish run is likely awaiting release-environment approval or still propagating. Approve/monitor the release-npm run; its verify step asserts the dist-tag lands."
|
|
exit 0
|