Files
claude-code-usage-bubble/.github/workflows/release-legacy.yml
T
tiennm99 9a7cb5afc2 ci(release-legacy): refuse a version not above the live release
Before building, compare the requested version with the tag /releases/latest returns and fail unless it sorts strictly after it. With no published release yet, any version passes.
2026-10-23 03:07:15 +07:00

216 lines
7.8 KiB
YAML

name: Release legacy
# Rollback for a broken Tauri release: rebuild the Win32 sources at `ref`
# under a higher version and publish them as a Windows-only release. Legacy
# clients take the exe through the GitHub API, Tauri Windows clients through
# the signed `latest.json`, and Tauri Linux clients find no entry for their
# platform, which they report as up to date. The Win32 app accepts the
# `--wait-pid <pid> --updated-to X` relaunch the Tauri updater uses.
#
# The build job holds a read-only token that is never written to disk, and
# only the steps that check or use the updater signing key see it. The write
# token lives in the publish job, which builds nothing.
on:
workflow_dispatch:
inputs:
ref:
description: 'Git ref holding the Win32 sources (e.g. v0.5.0)'
required: true
version:
description: 'Version to publish as X.Y.Z, higher than every release in the field'
required: true
tag:
description: 'Release tag, v<version>'
required: true
draft:
description: 'Publish as a draft'
type: boolean
default: true
permissions:
contents: read
jobs:
# A version at or below the live release is no update for any client, and
# publishing it as latest would hide that release from legacy clients.
check-version:
name: Check the version is above the live release
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
steps:
- name: Compare with /releases/latest
run: |
err=$(mktemp)
if latest=$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" -q .tag_name 2>"$err"); then
live="${latest#v}"
highest=$(printf '%s\n%s\n' "$live" "$VERSION" | sort -V | tail -n 1)
if [ "$VERSION" = "$live" ] || [ "$highest" != "$VERSION" ]; then
echo "::error::version '$VERSION' is not higher than the live release $latest."
exit 1
fi
echo "$VERSION is higher than the live release $latest."
elif grep -q 'HTTP 404' "$err"; then
echo "No published release yet, so any version is higher."
else
cat "$err" >&2
echo "::error::Could not read /releases/latest."
exit 1
fi
win-x64:
name: Windows x64 (legacy)
needs: check-version
runs-on: windows-latest
outputs:
sha: ${{ steps.sha.outputs.sha }}
env:
VERSION: ${{ inputs.version }}
TAG: ${{ inputs.tag }}
defaults:
run:
shell: bash
steps:
- name: Check the inputs and the signing key
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::version '$VERSION' is not X.Y.Z."
exit 1
fi
if [ "$TAG" != "v$VERSION" ]; then
echo "::error::tag '$TAG' must be v$VERSION."
exit 1
fi
if [ -z "${TAURI_SIGNING_PRIVATE_KEY//[[:space:]]/}" ]; then
echo "::error::The TAURI_SIGNING_PRIVATE_KEY secret is empty; an unsigned build cannot be released."
exit 1
fi
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}
persist-credentials: false
# The legacy ref predates package.json and the release script, so take
# both from the default branch.
- uses: actions/checkout@v7
with:
ref: ${{ github.event.repository.default_branch }}
path: current
persist-credentials: false
- name: Record the commit the release points at
id: sha
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v7
with:
node-version: lts/*
cache: npm
cache-dependency-path: current/package-lock.json
# The Tauri CLI that signs the exe comes from the reviewed lockfile,
# not from a version resolved at run time.
- run: npm ci --prefix current
# Only the package's own version changes, in Cargo.toml and its
# Cargo.lock entry, so the `--locked` build below resolves nothing and
# fails if the lockfile differs in anything else. Git for Windows checks
# the files out with CRLF endings, so the patterns accept a trailing CR
# and keep it.
- name: Set the Cargo.toml and Cargo.lock version
run: |
sed -i -E '0,/^version[[:space:]]*=/s/^version[[:space:]]*=[[:space:]]*"[^"]*"/version = "'"$VERSION"'"/' Cargo.toml
actual=$(sed -nE 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' Cargo.toml | head -n 1)
if [ "$actual" != "$VERSION" ]; then
echo "::error::Cargo.toml version is '$actual' after patching, expected '$VERSION'."
exit 1
fi
# Refs from before the rename use the old package name, so read it
# from the ref instead of naming it here.
crate=$(tr -d '\r' < Cargo.toml | sed -nE 's/^name[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' | head -n 1)
if [ -z "$crate" ]; then
echo "::error::Cargo.toml has no package name."
exit 1
fi
echo "CRATE=$crate" >> "$GITHUB_ENV"
sed -i -E '/^name = "'"$crate"'"\r?$/{n;s/^version = "[^"]*"(\r?)$/version = "'"$VERSION"'"\1/}' Cargo.lock
if ! tr -d '\r' < Cargo.lock | grep -A1 -x "name = \"$crate\"" | grep -qx "version = \"$VERSION\""; then
echo "::error::Cargo.lock has no $crate entry at version '$VERSION' after patching."
exit 1
fi
- run: cargo build --release --locked
# `--no` refuses to download a CLI that the lockfile did not install.
- name: Sign the exe
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# The release always carries the exe as `usage-monitor.exe`, whatever
# the ref named it, and a copy keeps the bytes the digest covers.
run: |
exe=target/release/usage-monitor.exe
if [ "$CRATE" != usage-monitor ]; then
cp "target/release/$CRATE.exe" "$exe"
fi
npm exec --prefix current --no -- tauri signer sign --app-version "$VERSION" "$exe"
test -s "$exe.sig"
- uses: actions/upload-artifact@v7
with:
name: release-legacy-win-x64
path: |
target/release/usage-monitor.exe
target/release/usage-monitor.exe.sig
retention-days: 7
if-no-files-found: error
publish:
name: Publish
needs: win-x64
runs-on: ubuntu-latest
permissions:
contents: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
TARGET: ${{ needs.win-x64.outputs.sha }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: lts/*
- uses: actions/download-artifact@v8
with:
name: release-legacy-win-x64
path: dist
- name: Write the Windows-only latest.json and check the asset set
run: node scripts/make-latest-json.js --windows-only "$TAG" dist
- name: Create the release
env:
DRAFT: ${{ inputs.draft }}
run: |
if [ "$DRAFT" = true ]; then
mode=(--draft)
else
mode=(--latest)
fi
gh release create "$TAG" dist/* --repo "$GITHUB_REPOSITORY" --target "$TARGET" \
--title "$TAG" --generate-notes "${mode[@]}"