mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-17 11:07:11 +08:00
168 lines
7.3 KiB
YAML
168 lines
7.3 KiB
YAML
name: Publish Desktop
|
|
|
|
# STAGE 3 of the decoupled release pipeline: PROMOTE a built + notarized version
|
|
# to "live". By this point:
|
|
# - stage 1 (Release Desktop) built the installers, mirrored them to R2, and
|
|
# registered them in D1 as unpublished (is_latest=0);
|
|
# - stage 2 (local desktop/build/notarize-dmg.sh) notarized + stapled the mac
|
|
# dmgs and re-uploaded the stapled bytes to R2.
|
|
#
|
|
# This workflow, triggered manually with the version to publish:
|
|
# 1. pulls every artifact for that version back from R2,
|
|
# 2. recomputes sha512 from the real (stapled) bytes and updates D1,
|
|
# 3. flips is_latest=1 for that version (clearing the previous latest per
|
|
# platform) UNLESS it's a pre-release, which is recorded but never latest,
|
|
# 4. creates/updates the GitHub Release and attaches the installers.
|
|
#
|
|
# Run only after the mac dmgs for this version are notarized + re-uploaded.
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Version to publish (e.g. 1.2.0). Must already be built + notarized."
|
|
type: string
|
|
required: true
|
|
make_latest:
|
|
description: "Mark this version as latest on the site (uncheck for a dry re-hash only)."
|
|
type: boolean
|
|
default: true
|
|
github_release:
|
|
description: "Create/update the GitHub Release and attach installers."
|
|
type: boolean
|
|
default: true
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
publish:
|
|
name: Publish ${{ github.event.inputs.version }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Guard on Cloudflare secrets
|
|
id: guard
|
|
env:
|
|
CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
run: |
|
|
if [ -z "$CF_TOKEN" ]; then
|
|
echo "::error::CLOUDFLARE_API_TOKEN not set — cannot publish."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Resolve version artifacts from D1
|
|
id: rows
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
VER: ${{ github.event.inputs.version }}
|
|
run: |
|
|
# The build stage inserted one row per artifact with filename = v<VER>/<base>.
|
|
# Read them back so we know exactly which objects to pull from R2.
|
|
out="$(npx --yes wrangler@latest d1 execute cow-desktop --remote --json \
|
|
--command "SELECT platform, filename FROM releases WHERE version = '${VER}';")"
|
|
echo "$out"
|
|
echo "$out" | node -e '
|
|
const fs = require("fs");
|
|
const data = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
const rows = (Array.isArray(data) ? data : [data])
|
|
.flatMap(r => (r.results || []));
|
|
if (!rows.length) {
|
|
console.error("No D1 rows for this version — did stage 1 (build) run?");
|
|
process.exit(1);
|
|
}
|
|
fs.writeFileSync(process.env.GITHUB_OUTPUT, "count=" + rows.length + "\n", { flag: "a" });
|
|
fs.writeFileSync("rows.json", JSON.stringify(rows));
|
|
'
|
|
|
|
- name: Download version artifacts from R2
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
R2_BUCKET: ${{ vars.R2_BUCKET != '' && vars.R2_BUCKET || 'cow-skills' }}
|
|
run: |
|
|
mkdir -p dist
|
|
# filename is "v<VER>/<base>"; the R2 key is "desktop/<filename>".
|
|
for filename in $(node -e 'JSON.parse(require("fs").readFileSync("rows.json")).forEach(r => console.log(r.filename))'); do
|
|
base="$(basename "$filename")"
|
|
key="desktop/${filename}"
|
|
echo "==> Downloading r2://${R2_BUCKET}/${key} -> dist/${base}"
|
|
npx --yes wrangler@latest r2 object get "${R2_BUCKET}/${key}" \
|
|
--file "dist/${base}" --remote
|
|
done
|
|
echo "Downloaded:"; ls -la dist
|
|
|
|
- name: Reminder — mac dmgs must be notarized before publishing
|
|
run: |
|
|
# Stapling can only be validated on macOS (xcrun stapler validate),
|
|
# which this Linux runner doesn't have. The authoritative check runs in
|
|
# stage 2 (desktop/build/notarize-dmg.sh) before re-uploading to R2.
|
|
# This step is just a loud reminder in the log.
|
|
echo "::notice::Publishing assumes the mac dmgs pulled from R2 are already notarized + stapled (stage 2). If you skipped stage 2, users will hit Gatekeeper warnings."
|
|
ls -la dist/*.dmg 2>/dev/null || echo "(no dmg in this version — win-only publish)"
|
|
|
|
- name: Update D1 (recompute sha512 + set latest)
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
VER: ${{ github.event.inputs.version }}
|
|
MAKE_LATEST: ${{ github.event.inputs.make_latest }}
|
|
run: |
|
|
sql_file="$(mktemp)"
|
|
sha_for_file() { openssl dgst -sha512 -binary "$1" | openssl base64 -A; }
|
|
|
|
# Pre-releases (e.g. 1.2.0-beta / -rc.1 / -test) are recorded but never
|
|
# become latest, so the site keeps serving the last stable build.
|
|
case "$VER" in
|
|
*-*) is_pre=1 ;;
|
|
*) is_pre=0 ;;
|
|
esac
|
|
if [ "$MAKE_LATEST" = "true" ] && [ "$is_pre" = "0" ]; then
|
|
do_latest=1; echo "==> Publishing $VER as latest."
|
|
else
|
|
do_latest=0; echo "==> Publishing $VER without latest flag (pre-release or dry re-hash)."
|
|
fi
|
|
|
|
for f in dist/*; do
|
|
base="$(basename "$f")"
|
|
size="$(stat -c%s "$f")"
|
|
case "$base" in
|
|
*arm64.dmg) platform="mac-arm64" ;;
|
|
*x64.dmg) platform="mac-x64" ;;
|
|
*.exe) platform="win" ;;
|
|
*) echo "Skipping unrecognized artifact: $base"; continue ;;
|
|
esac
|
|
key="v${VER}/${base}"
|
|
sha="$(sha_for_file "$f")"
|
|
sha="${sha//\'/\'\'}"
|
|
if [ "$do_latest" = "1" ]; then
|
|
echo "UPDATE releases SET is_latest = 0 WHERE platform = '${platform}';" >> "$sql_file"
|
|
fi
|
|
# Re-store the row with the authoritative (post-staple) sha and the
|
|
# resolved latest flag.
|
|
echo "INSERT OR REPLACE INTO releases (version, platform, filename, size, sha512, is_latest) VALUES ('${VER}', '${platform}', '${key}', ${size}, '${sha}', ${do_latest});" >> "$sql_file"
|
|
done
|
|
echo "==> D1 statements:"; cat "$sql_file"
|
|
npx --yes wrangler@latest d1 execute cow-desktop --remote --file "$sql_file"
|
|
|
|
- name: Create/update GitHub Release and attach installers
|
|
if: github.event.inputs.github_release == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VER: ${{ github.event.inputs.version }}
|
|
run: |
|
|
tag="v${VER}"
|
|
case "$VER" in
|
|
*-*) prerelease="--prerelease" ;;
|
|
*) prerelease="" ;;
|
|
esac
|
|
if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
|
gh release create "$tag" --repo "$GITHUB_REPOSITORY" \
|
|
--title "$tag" --generate-notes $prerelease
|
|
fi
|
|
# --clobber so re-runs overwrite the stapled/updated assets.
|
|
gh release upload "$tag" dist/*.dmg dist/*.exe \
|
|
--repo "$GITHUB_REPOSITORY" --clobber
|