Files
qiming/qimingclaw/.github/workflows/release-electron.yml

584 lines
26 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Electron 桌面客户端 — 正式版本发布
# 与 Tauri 客户端完全分开:使用独立 tag 与独立 GitHub Release不共用 v* tag。
#
# 触发:推送 tag electron-v* → 构建并创建 Release。同步请用 workflow_dispatch 手动触发。
#
# 支持平台:
# - macOS arm64macos-latest
# - macOS x64macos-latest, cross-compile
# - Windows x64msi
# - Linux x64AppImage + deb
# - Linux arm64AppImage + deb
#
# 使用的 Secrets可与 Tauri 共用 Apple 相关,也可单独配置):
# - GH_PAT可选创建 Release、上传资产未配置时自动回退到 github.token需 Contents: Write
# - macOS 签名/公证(未配置时 Mac 构建为无签名包,用户打开会提示「已损坏」):
# - APPLE_TEAM_ID / APPLE_SIGNING_IDENTITY / APPLE_CERTIFICATE.p12 Base64/ APPLE_CERTIFICATE_PASSWORD
# - 公证APPLE_API_KEY.p8 Base64/ APPLE_API_KEY_ID / APPLE_ISSUER_ID
# - Windows本 workflow 仅构建产物,不在 CI 内签名;签名在本地 WindowsSimplySign手动完成
# 详见 crates/agent-electron-client/docs/windows-signing.md
# - MINIO_ACCESS_KEY_ID / MINIO_SECRET_ACCESS_KEY上传到 MinIO S3
# - OSS_ACCESS_KEY_ID / OSS_ACCESS_KEY_SECRET上传 latest.json 到阿里云 OSS
#
# 产物目录crates/agent-electron-client/release/${version}/(由 package.json directories.output 决定)
name: Release Electron App
on:
push:
tags:
- "electron-v*"
workflow_dispatch:
inputs:
tag:
description: "Release tag to sync (e.g. electron-v0.9.0)"
required: true
type: string
channel:
description: "Update channel pointer to publish (stable|beta). beta will NOT overwrite latest/latest.json"
required: false
default: stable
type: choice
options:
- stable
- beta
permissions:
contents: write
jobs:
prepare:
name: Prepare release
if: github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get version from tag
id: version
run: |
VERSION="${GITHUB_REF_NAME#electron-v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Electron release version: $VERSION"
- name: Read release notes
id: notes
run: |
VERSION="${{ steps.version.outputs.version }}"
TAG="${{ github.ref_name }}"
NOTES_FILE="release-notes/${TAG}.md"
if [ -f "$NOTES_FILE" ]; then
echo "Using release notes from $NOTES_FILE"
cp "$NOTES_FILE" release_notes.txt
else
echo "QimingClaw ${VERSION}。请在 Assets 中下载对应平台安装包。" > release_notes.txt
fi
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GH_PAT || github.token }}
run: |
TAG="${{ github.ref_name }}"
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" 2>/dev/null; then
echo "Release $TAG already exists"
else
gh release create "$TAG" \
--title "QimingClaw (Electron) ${{ steps.version.outputs.version }}" \
--notes-file release_notes.txt \
--repo "$GITHUB_REPOSITORY"
echo "Created release $TAG"
fi
build-electron:
name: Build Electron (${{ matrix.platform }} ${{ matrix.arch }})
needs: prepare
if: github.event_name == 'push'
strategy:
fail-fast: false
matrix:
include:
- platform: macos-latest
arch: arm64
dist_cmd: dist:mac:arm64
- platform: macos-latest
arch: x64
dist_cmd: dist:mac:x64
- platform: windows-latest
arch: x64
dist_cmd: dist:win
- platform: ubuntu-24.04
arch: x64
dist_cmd: dist:linux:x64
- platform: ubuntu-24.04-arm
arch: arm64
dist_cmd: dist:linux:arm64
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set version in package.json
shell: bash
working-directory: crates/agent-electron-client
run: |
VERSION="${{ needs.prepare.outputs.version }}"
npm pkg set version="$VERSION"
echo "package.json version: $(npm pkg get version)"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
# 安装 pnpm用于 workspace 管理,版本由根 package.json packageManager 字段指定)
- name: Install pnpm
uses: pnpm/action-setup@v4
# Python 3.12+ 移除了 distutilsnode-gypbetter-sqlite3 等 native 模块)依赖它;安装 setuptools 以提供 distutils
- name: Install setuptools for node-gyp (Python 3.12+)
shell: bash
run: python3 -m pip install setuptools --break-system-packages 2>/dev/null || python3 -m pip install setuptools
# Linux 构建 deb/rpm 包需要 rpm 工具arm64 runner 需要系统 fpm内置 fpm 仅 x86
- name: Install Linux build tools
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y rpm ruby ruby-dev
sudo gem install fpm
echo "USE_SYSTEM_FPM=true" >> "$GITHUB_ENV"
# pnpm install 会建立 workspace 链接qiming-mcp-stdio-proxy 通过 prepare 脚本自动构建
- name: Install workspace dependencies
run: pnpm install --filter @qiming-ai/qimingclaw...
# 为 Electron 内嵌 Node 重编 better-sqlite3避免 Linux arm64 等平台运行时 "cannot open shared object file"
- name: Rebuild native modules for Electron
working-directory: crates/agent-electron-client
run: npm run electron-rebuild
# 缓存内置 Node.js所有平台、Git仅 Windows、uv所有平台和 qimingcode所有平台
- name: Cache bundled Node.js
uses: actions/cache@v4
with:
path: crates/agent-electron-client/resources/node
key: bundled-node-24-${{ matrix.platform }}-${{ matrix.arch }}-${{ hashFiles('crates/agent-electron-client/package.json') }}
- name: Cache bundled Git (Windows only)
if: matrix.platform == 'windows-latest'
uses: actions/cache@v4
with:
path: crates/agent-electron-client/resources/git
key: bundled-git-2.47.1-windows-${{ hashFiles('crates/agent-electron-client/package.json') }}
- name: Cache uv
uses: actions/cache@v4
with:
path: crates/agent-electron-client/resources/uv
key: bundled-uv-v2-${{ matrix.platform }}-${{ matrix.arch }}-${{ hashFiles('crates/agent-electron-client/scripts/prepare/prepare-uv.js') }}
- name: Cache qimingcode
uses: actions/cache@v4
with:
path: crates/agent-electron-client/resources/qimingcode
key: bundled-qimingcode-v1-${{ matrix.platform }}-${{ matrix.arch }}-${{ hashFiles('crates/agent-electron-client/scripts/prepare/prepare-qimingcode.js') }}
- name: Prepare bundled resources (uv, node, git, qimingcode)
working-directory: crates/agent-electron-client
env:
TARGET_ARCH: ${{ matrix.arch }}
run: npm run prepare:all
timeout-minutes: 15
# ---- macOS导入证书并配置公证可选 ----
- name: Import Apple certificate (macOS)
if: runner.os == 'macOS'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
if [ -z "${APPLE_CERTIFICATE:-}" ]; then
echo "APPLE_CERTIFICATE not set, signing will be skipped"
exit 0
fi
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD=actions
echo -n "$APPLE_CERTIFICATE" | base64 --decode -o "$CERTIFICATE_PATH"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
security list-keychain -d user -s "$KEYCHAIN_PATH"
rm -f "$CERTIFICATE_PATH"
echo "CSC_KEYCHAIN=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
- name: Prepare Apple notarization key (macOS)
if: runner.os == 'macOS'
env:
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
run: |
if [ -z "${APPLE_API_KEY:-}" ] || [ -z "${APPLE_API_KEY_ID:-}" ]; then
echo "Apple notarization secrets not set, notarization will be skipped"
exit 0
fi
if [ -z "${APPLE_ISSUER_ID:-}" ]; then
echo "APPLE_ISSUER_ID required for notarization"
exit 0
fi
mkdir -p "$RUNNER_TEMP/apple-api-key"
echo -n "$APPLE_API_KEY" | base64 --decode -o "$RUNNER_TEMP/apple-api-key/AuthKey.p8"
echo "APPLE_API_KEY=$RUNNER_TEMP/apple-api-key/AuthKey.p8" >> "$GITHUB_ENV"
echo "APPLE_API_KEY_ID=$APPLE_API_KEY_ID" >> "$GITHUB_ENV"
echo "APPLE_API_ISSUER=$APPLE_ISSUER_ID" >> "$GITHUB_ENV"
echo "APPLE_ISSUER_ID=$APPLE_ISSUER_ID" >> "$GITHUB_ENV"
# 若用户看到「已损坏」:请确认已配置 APPLE_CERTIFICATE / APPLE_SIGNING_IDENTITY 及公证用 APPLE_API_KEY 等,且构建日志中有签名与公证成功
# Windows 签名已废弃,改为本地手签方案
- name: Clean previous build artifacts
shell: bash
working-directory: crates/agent-electron-client
run: |
rm -rf release node_modules/.cache
echo "Cleaned release and cache directories"
- name: Build Electron app
shell: bash
working-directory: crates/agent-electron-client
env:
TARGET_ARCH: ${{ matrix.arch }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_NAME: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
if [ "${{ runner.os }}" = "macOS" ] && [ -z "${{ secrets.APPLE_CERTIFICATE }}" ]; then
echo "No Apple certificate, building unsigned Mac package for ${{ matrix.arch }}"
npm run dist:mac:unsigned:${{ matrix.arch }} -- --publish never
elif [ "${{ runner.os }}" = "Windows" ]; then
npm run ${{ matrix.dist_cmd }} -- --publish never
else
npm run ${{ matrix.dist_cmd }} -- --publish never
fi
- name: Upload artifacts to Release
shell: bash
env:
GH_TOKEN: ${{ secrets.GH_PAT || github.token }}
run: |
TAG="${{ github.ref_name }}"
VERSION="${{ needs.prepare.outputs.version }}"
OUT_DIR="crates/agent-electron-client/release/${VERSION}"
if [ ! -d "$OUT_DIR" ]; then
echo "::error::Output directory not found: $OUT_DIR"
exit 1
fi
for f in "${OUT_DIR}"/*; do
[ -e "$f" ] || continue
[ -f "$f" ] || continue
echo "Uploading $(basename "$f") ..."
gh release upload "$TAG" "$f" --clobber --repo "$GITHUB_REPOSITORY"
done
# ==================== 同步到 MinIO S3构建产物和阿里云 OSSlatest.json ====================
# 手动触发workflow_dispatch 输入 tag如 electron-v0.8.0
sync-to-minio:
name: Sync release to MinIO S3 & OSS
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
continue-on-error: true
env:
# MinIO S3 配置(存放构建产物和 yml 文件)
S3_ENDPOINT: "https://s3.qiming.com:9443"
S3_REGION: "us-east-1"
S3_BUCKET: "qimingclaw"
S3_CDN_BASE: "https://s3.qiming.com:9443/qimingclaw"
# 阿里云 OSS 配置(仅存放 latest.json
OSS_ENDPOINT: "https://oss-rg-china-mainland.aliyuncs.com"
OSS_REGION: "rg-china-mainland"
OSS_BUCKET: "oss://qiming-packages"
OSS_CDN_BASE: "https://qiming-packages.oss-rg-china-mainland.aliyuncs.com"
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_CHANNEL: ${{ inputs.channel || 'stable' }}
steps:
- name: Ensure awscli v2 (for S3)
run: |
if aws --version 2>/dev/null; then
echo "AWS CLI already installed"
else
curl -fSL -o /tmp/awscliv2.zip \
"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"
unzip -q /tmp/awscliv2.zip -d /tmp
sudo /tmp/aws/install --bin-dir /usr/local/bin
fi
aws --version
- name: Install ossutil v2 (for latest.json)
run: |
curl -fSL -o /tmp/ossutil.zip \
"https://gosspublic.alicdn.com/ossutil/v2/2.2.0/ossutil-2.2.0-linux-amd64.zip"
unzip -q /tmp/ossutil.zip -d /tmp
sudo mv /tmp/ossutil-2.2.0-linux-amd64/ossutil /usr/local/bin/
ossutil version
- name: Configure AWS credentials for S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_ACCESS_KEY }}
run: |
aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID"
aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"
aws configure set default.region "$S3_REGION"
aws configure set default.s3.addressing_style path
aws configure set endpoint_url "$S3_ENDPOINT"
- name: Download all release assets
env:
GH_TOKEN: ${{ secrets.GH_PAT || github.token }}
run: |
TAG="${RELEASE_TAG}"
mkdir -p /tmp/release-assets
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --dir /tmp/release-assets
echo "==> Downloaded assets:"
ls -lh /tmp/release-assets/
- name: Generate latest.json and platform yml from assets
env:
GH_TOKEN: ${{ secrets.GH_PAT || github.token }}
run: |
TAG="${RELEASE_TAG}"
VERSION="${TAG#electron-v}"
CHANNEL="${RELEASE_CHANNEL}"
S3_URL_PREFIX="${S3_CDN_BASE}/qimingclaw-electron/${TAG}"
ASSETS_DIR="/tmp/release-assets"
# semver MVP guard: only allow numeric x.y.z
if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::Invalid version '${VERSION}'. MVP only supports numeric x.y.z"
exit 1
fi
if [ "$CHANNEL" != "stable" ] && [ "$CHANNEL" != "beta" ]; then
echo "::error::Invalid channel '${CHANNEL}', expected stable or beta"
exit 1
fi
# 删除 GitHub 自带的 yml仅保留由 latest.json 派生的 yml
rm -f "${ASSETS_DIR}"/*.yml
# 获取 release notes 与 pub_date
RELEASE_JSON=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json body,createdAt 2>/dev/null || echo '{}')
NOTES=$(echo "$RELEASE_JSON" | jq -r '.body // "QimingClaw 功能优化和问题修复。建议更新到最新版本以获得更好的体验。"' | head -c 500)
PUB_DATE=$(echo "$RELEASE_JSON" | jq -r '.createdAt // empty')
if [ -z "$PUB_DATE" ]; then
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
fi
add_platform() {
local key="$1" file="$2"
local filepath="${ASSETS_DIR}/${file}"
if [ -f "$filepath" ]; then
local sig size
sig=$(sha512sum "$filepath" | awk '{print $1}' | xxd -r -p | base64 -w0)
if [ -z "$sig" ]; then
echo " Skipped: ${key} -> ${file} (signature generation failed)"
return 0
fi
size=$(stat -c%s "$filepath" 2>/dev/null || stat -f%z "$filepath")
PLATFORMS=$(echo "$PLATFORMS" | jq \
--arg k "$key" \
--arg u "${S3_URL_PREFIX}/${file}" \
--arg s "$sig" \
--argjson sz "$size" \
'. + {($k): {"url": $u, "signature": $s, "size": $sz}}')
echo " Added: ${key} -> ${file} (${size} bytes)"
fi
}
PLATFORMS="{}"
echo "==> Scanning release assets for platforms..."
add_platform "darwin-aarch64" "QimingClaw-${VERSION}-arm64.dmg"
add_platform "darwin-aarch64-zip" "QimingClaw-${VERSION}-arm64-mac.zip"
add_platform "darwin-x86_64" "QimingClaw-${VERSION}.dmg"
add_platform "darwin-x86_64-zip" "QimingClaw-${VERSION}-mac.zip"
add_platform "windows-x86_64" "QimingClaw.Setup.${VERSION}.exe"
add_platform "windows-x86_64-nsis" "QimingClaw.Setup.${VERSION}.exe"
add_platform "windows-x86_64-msi" "QimingClaw.${VERSION}.msi"
add_platform "linux-x86_64" "QimingClaw-${VERSION}.AppImage"
add_platform "linux-x86_64-appimage" "QimingClaw-${VERSION}.AppImage"
add_platform "linux-x86_64-deb" "QimingClaw-${VERSION}-amd64.deb"
add_platform "linux-x86_64-rpm" "QimingClaw-${VERSION}-x86_64.rpm"
add_platform "linux-aarch64" "QimingClaw-${VERSION}-arm64.AppImage"
add_platform "linux-aarch64-appimage" "QimingClaw-${VERSION}-arm64.AppImage"
add_platform "linux-aarch64-deb" "QimingClaw-${VERSION}-arm64.deb"
add_platform "linux-aarch64-rpm" "QimingClaw-${VERSION}-aarch64.rpm"
if [ "$(echo "$PLATFORMS" | jq 'length')" = "0" ]; then
echo "::warning::No exact filename matches, falling back to fuzzy scan"
for file in "${ASSETS_DIR}"/*; do
filename=$(basename "$file")
case "$filename" in
*.dmg|*.zip|*.exe|*.msi|*.AppImage|*.deb|*.rpm)
key=""
case "$filename" in
*arm64*mac*|*arm64*.dmg) key="darwin-aarch64" ;;
*x64*mac*|*x64*.dmg) key="darwin-x86_64" ;;
*Setup*.exe|*setup*.exe) key="windows-x86_64" ;;
*.msi) key="windows-x86_64-msi" ;;
*arm64*.AppImage) key="linux-aarch64" ;;
*amd64*.AppImage|*x64*.AppImage|*.AppImage) key="linux-x86_64" ;;
*arm64*.deb) key="linux-aarch64-deb" ;;
*amd64*.deb|*x64*.deb) key="linux-x86_64-deb" ;;
*aarch64*.rpm) key="linux-aarch64-rpm" ;;
*x86_64*.rpm|*x64*.rpm) key="linux-x86_64-rpm" ;;
esac
[ -n "$key" ] && add_platform "$key" "$filename"
;;
esac
done
fi
gen_yml() {
local channel="$1"
local keys="$2"
local first_key first_file
first_key=$(echo "$PLATFORMS" | jq -r --argjson keys "$keys" 'limit(1; $keys[] as $k | select(.[$k] != null) | $k)')
[ -z "$first_key" ] || [ "$first_key" = "null" ] && return 0
first_file=$(echo "$PLATFORMS" | jq -r --arg k "$first_key" '.[$k].url' | sed 's|.*/||')
{
echo "version: $VERSION"
echo "path: $first_file"
echo "sha512: $(echo "$PLATFORMS" | jq -r --arg k "$first_key" '.[$k].signature')"
echo "releaseDate: $PUB_DATE"
echo "files:"
echo "$PLATFORMS" | jq -r --argjson keys "$keys" '
[ $keys[] as $k | select(.[$k] != null) | .[$k] |
" - url: " + (.url | split("/") | last) + "\n sha512: " + .signature + "\n size: " + (.size | tostring)
] | join("\n")
'
} > "${ASSETS_DIR}/${channel}.yml"
echo " Generated ${channel}.yml (primary: $first_file)"
}
DARWIN_KEYS='["darwin-aarch64-zip","darwin-x86_64-zip","darwin-aarch64","darwin-x86_64"]'
LINUX_KEYS='["linux-x86_64-appimage","linux-aarch64-appimage","linux-x86_64","linux-aarch64","linux-x86_64-deb","linux-aarch64-deb","linux-x86_64-rpm","linux-aarch64-rpm"]'
LINUX_ARM64_KEYS='["linux-aarch64-appimage","linux-aarch64","linux-aarch64-deb","linux-aarch64-rpm"]'
LINUX_X64_KEYS='["linux-x86_64-appimage","linux-x86_64","linux-x86_64-deb","linux-x86_64-rpm"]'
WIN_KEYS='["windows-x86_64","windows-x86_64-nsis","windows-x86_64-msi"]'
gen_yml "latest-mac" "$DARWIN_KEYS"
gen_yml "latest-linux" "$LINUX_KEYS"
gen_yml "latest-linux-arm64" "$LINUX_ARM64_KEYS"
gen_yml "latest-linux-x64" "$LINUX_X64_KEYS"
gen_yml "latest" "$WIN_KEYS"
echo "==> Platform yml written to ${ASSETS_DIR}"
# 构建 yml 文件的完整 S3 URL供客户端 electron-updater 直接使用)
YML_URLS=$(jq -n \
--arg darwin "${S3_URL_PREFIX}/latest-mac.yml" \
--arg linux "${S3_URL_PREFIX}/latest-linux.yml" \
--arg win "${S3_URL_PREFIX}/latest.yml" \
'{darwin: $darwin, linux: $linux, win: $win}')
LATEST_JSON=$(jq -n \
--arg version "$VERSION" \
--arg notes "$NOTES" \
--arg pub_date "$PUB_DATE" \
--argjson platforms "$PLATFORMS" \
--argjson yml "$YML_URLS" \
'{version: $version, notes: $notes, pub_date: $pub_date, platforms: $platforms, yml: $yml}')
echo "$LATEST_JSON" > /tmp/latest.json
echo "==> Generated latest.json"
echo "$LATEST_JSON" | jq .
- name: Upload all assets to MinIO S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_ACCESS_KEY }}
run: |
TAG="${RELEASE_TAG}"
S3_PREFIX="qimingclaw-electron/${TAG}"
for file in /tmp/release-assets/*; do
filename=$(basename "$file")
echo "Uploading to S3: ${filename}"
aws s3 cp "$file" "s3://${S3_BUCKET}/${S3_PREFIX}/${filename}" \
--endpoint-url "$S3_ENDPOINT"
done
# 同步 latest.json 到 S3版本化路径 + 通道滚动指针)
echo "Uploading latest.json to S3..."
aws s3 cp /tmp/latest.json "s3://${S3_BUCKET}/${S3_PREFIX}/latest.json" \
--endpoint-url "$S3_ENDPOINT"
CHANNEL="${RELEASE_CHANNEL}"
if [ "$CHANNEL" = "stable" ]; then
aws s3 cp /tmp/latest.json "s3://${S3_BUCKET}/qimingclaw-electron/latest/latest.json" \
--endpoint-url "$S3_ENDPOINT"
else
aws s3 cp /tmp/latest.json "s3://${S3_BUCKET}/qimingclaw-electron/beta/latest.json" \
--endpoint-url "$S3_ENDPOINT"
fi
- name: Verify S3 upload
run: |
TAG="${RELEASE_TAG}"
VERIFY_URL="${S3_CDN_BASE}/qimingclaw-electron/${TAG}/latest-mac.yml"
HTTP_STATUS=$(curl -sS -o /dev/null -w "%{http_code}" "$VERIFY_URL")
if [ "$HTTP_STATUS" = "200" ]; then
echo "==> S3 upload verified: latest-mac.yml (HTTP ${HTTP_STATUS})"
else
echo "::error::S3 upload verification failed: latest-mac.yml (HTTP ${HTTP_STATUS})"
exit 1
fi
- name: Upload latest.json to Alibaba OSS
env:
OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
run: |
TAG="${RELEASE_TAG}"
CHANNEL="${RELEASE_CHANNEL}"
echo "==> Publishing latest.json to OSS for channel: ${CHANNEL}"
ossutil cp --force /tmp/latest.json "${OSS_BUCKET}/qimingclaw-electron/${TAG}/latest.json" \
--endpoint "$OSS_ENDPOINT" \
--region "$OSS_REGION" \
--access-key-id "$OSS_ACCESS_KEY_ID" \
--access-key-secret "$OSS_ACCESS_KEY_SECRET"
if [ "$CHANNEL" = "stable" ]; then
ossutil cp --force /tmp/latest.json "${OSS_BUCKET}/qimingclaw-electron/latest/latest.json" \
--endpoint "$OSS_ENDPOINT" \
--region "$OSS_REGION" \
--access-key-id "$OSS_ACCESS_KEY_ID" \
--access-key-secret "$OSS_ACCESS_KEY_SECRET"
echo "==> Uploaded latest.json to stable pointer: latest/latest.json"
else
ossutil cp --force /tmp/latest.json "${OSS_BUCKET}/qimingclaw-electron/beta/latest.json" \
--endpoint "$OSS_ENDPOINT" \
--region "$OSS_REGION" \
--access-key-id "$OSS_ACCESS_KEY_ID" \
--access-key-secret "$OSS_ACCESS_KEY_SECRET"
echo "==> Uploaded latest.json to beta pointer: beta/latest.json (stable pointer untouched)"
fi
- name: Verify OSS upload
run: |
CHANNEL="${RELEASE_CHANNEL}"
if [ "$CHANNEL" = "stable" ]; then
VERIFY_URL="${OSS_CDN_BASE}/qimingclaw-electron/latest/latest.json"
else
VERIFY_URL="${OSS_CDN_BASE}/qimingclaw-electron/beta/latest.json"
fi
HTTP_STATUS=$(curl -sS -o /tmp/oss-latest.json -w "%{http_code}" "$VERIFY_URL")
if [ "$HTTP_STATUS" = "200" ]; then
echo "==> latest.json (${CHANNEL}): OK (HTTP ${HTTP_STATUS})"
cat /tmp/oss-latest.json | jq .
else
echo "::error::latest.json for channel '${CHANNEL}' not found on OSS (HTTP ${HTTP_STATUS})"
exit 1
fi