mirror of
https://github.com/tiennm99/claude-code-usage-bubble.git
synced 2026-10-24 06:17:23 +00:00
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.
216 lines
7.8 KiB
YAML
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[@]}"
|