Skip to content

feat: 新增应用内更新检查并优化下载与多语言体验 #20

feat: 新增应用内更新检查并优化下载与多语言体验

feat: 新增应用内更新检查并优化下载与多语言体验 #20

Workflow file for this run

# 构建和发布工作流
# 触发条件:
# 1. 推送到 main 分支且修改了 package.json
# 2. 手动触发 (workflow_dispatch)
# 构建条件:
# - package.json 中的 version 高于 GitHub Releases 已发布的版本
# - 或手动触发
name: Build and Release
on:
push:
branches:
- main
paths:
- 'package.json' # 只有 package.json 变化时才触发
workflow_dispatch: # 支持手动触发
permissions:
contents: write # 需要写权限来创建 Release
jobs:
# ============================================
# 第一步:检查版本号是否需要发布
# ============================================
check-version:
runs-on: ubuntu-latest
outputs:
should_release: ${{ steps.check.outputs.should_release }}
version: ${{ steps.check.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check version against latest release
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# 获取当前 package.json 中的版本号
CURRENT_VERSION=$(jq -r '.version' package.json)
echo "Current version in package.json: $CURRENT_VERSION"
# 获取 GitHub Releases 中最新发布的版本号
LATEST_RELEASE=$(gh release list --limit 1 --json tagName --jq '.[0].tagName // "v0.0.0"' 2>/dev/null || echo "v0.0.0")
# 移除版本号前的 'v' 前缀
LATEST_VERSION=${LATEST_RELEASE#v}
echo "Latest released version: $LATEST_VERSION"
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
# 比较版本号,判断是否需要发布
if [ "$CURRENT_VERSION" != "$LATEST_VERSION" ]; then
echo "Version changed, should release"
echo "should_release=true" >> $GITHUB_OUTPUT
else
echo "Version unchanged, skip release"
echo "should_release=false" >> $GITHUB_OUTPUT
fi
# ============================================
# 第二步:构建应用(macOS 和 Windows)
# ============================================
build:
needs: check-version
# 只有版本号高于已发布版本或手动触发时才构建
if: needs.check-version.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch'
strategy:
fail-fast: false # 一个平台失败不影响其他平台
matrix:
include:
# macOS ARM64 (Apple Silicon)
- platform: macos-latest
args: --target aarch64-apple-darwin
name: macOS-arm64
# Windows x64
- platform: windows-latest
args: ''
name: Windows
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
# 安装 Bun 包管理器
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
# 安装 Rust 工具链
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
# Rust 依赖缓存,加速构建
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'
# macOS 需要添加 ARM64 目标
- name: Add macOS arm64 target
if: matrix.platform == 'macos-latest'
run: rustup target add aarch64-apple-darwin
# 安装前端依赖
- name: Install dependencies
run: bun install
# 同步版本号到 tauri.conf.json
- name: Sync version to tauri.conf.json
shell: bash
run: |
VERSION=$(jq -r '.version' package.json)
jq --arg v "$VERSION" '.version = $v' src-tauri/tauri.conf.json > tmp.json && mv tmp.json src-tauri/tauri.conf.json
# 构建 Tauri 应用
- name: Build Tauri App
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: ${{ matrix.args }}
# 上传构建产物
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: youran-toolbox-${{ matrix.name }}
path: |
src-tauri/target/*/release/bundle/dmg/*.dmg
src-tauri/target/release/bundle/nsis/*.exe
# ============================================
# 第三步:创建 GitHub Release
# ============================================
release:
needs: [check-version, build]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# 从 CHANGELOG.md 提取当前版本的更新内容
- name: Extract changelog
id: changelog
run: |
# 提取第一个 ## 和第二个 ## 之间的内容,跳过标题行
BODY=$(awk '/^## /{if(seen++){exit} next} seen{print}' CHANGELOG.md)
# 写入多行输出
echo "body<<EOF" >> $GITHUB_OUTPUT
echo "$BODY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# 下载所有构建产物
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
# 列出所有产物(调试用)
- name: List artifacts
run: find artifacts -type f
# 创建 Release 并上传安装包
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.check-version.outputs.version }}
name: v${{ needs.check-version.outputs.version }}
body: ${{ steps.changelog.outputs.body }}
files: artifacts/**/*
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ============================================
# 第四步:上传安装包到 阿里云 OSS
# ============================================
upload-oss:
needs: [check-version, build, release]
if: needs.check-version.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
env:
ALIBABA_CLOUD_ACCESS_KEY_ID: ${{ secrets.ALIYUN_ACCESS_KEY_ID }}
ALIBABA_CLOUD_ACCESS_KEY_SECRET: ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }}
ALIBABA_CLOUD_REGION_ID: ${{ secrets.ALIYUN_REGION_ID }}
OSS_BUCKET: ${{ secrets.ALIYUN_OSS_BUCKET }}
PUBLIC_BASE_URL: ${{ secrets.ALIYUN_OSS_PUBLIC_BASE_URL }}
steps:
- name: Validate OSS secrets
run: |
set -euo pipefail
[ -n "${ALIBABA_CLOUD_ACCESS_KEY_ID}" ] || (echo "Missing secret: ALIYUN_ACCESS_KEY_ID" && exit 1)
[ -n "${ALIBABA_CLOUD_ACCESS_KEY_SECRET}" ] || (echo "Missing secret: ALIYUN_ACCESS_KEY_SECRET" && exit 1)
[ -n "${ALIBABA_CLOUD_REGION_ID}" ] || (echo "Missing secret: ALIYUN_REGION_ID" && exit 1)
[ -n "${OSS_BUCKET}" ] || (echo "Missing secret: ALIYUN_OSS_BUCKET" && exit 1)
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Install Alibaba Cloud CLI
run: |
/bin/bash -c "$(curl -fsSL https://aliyuncli.alicdn.com/install.sh)"
echo "$HOME/.aliyun/bin" >> "$GITHUB_PATH"
- name: Prepare installers
shell: bash
run: |
set -euo pipefail
mkdir -p dist
find artifacts -type f \( -name "*.dmg" -o -name "*.exe" \) -print0 | while IFS= read -r -d '' file; do
cp "$file" dist/
done
if ! ls dist/* >/dev/null 2>&1; then
echo "No installer files found in artifacts"
exit 1
fi
ls -lah dist
- name: Upload installers to OSS
shell: bash
run: |
set -euo pipefail
VERSION="${{ needs.check-version.outputs.version }}"
# 固定版本目录:oss://<bucket>/vX.Y.Z/
aliyun ossutil cp dist/ "oss://${OSS_BUCKET}/v${VERSION}/" -r -f --no-progress
# 最新版本目录:oss://<bucket>/latest/
aliyun ossutil rm "oss://${OSS_BUCKET}/latest/" -r -f || true
aliyun ossutil cp dist/ "oss://${OSS_BUCKET}/latest/" -r -f --no-progress
- name: Print download paths
shell: bash
run: |
VERSION="${{ needs.check-version.outputs.version }}"
echo "OSS version path: oss://${OSS_BUCKET}/v${VERSION}/"
echo "OSS latest path: oss://${OSS_BUCKET}/latest/"
if [ -n "${PUBLIC_BASE_URL:-}" ]; then
echo "Public version URL: ${PUBLIC_BASE_URL}/v${VERSION}/"
echo "Public latest URL: ${PUBLIC_BASE_URL}/latest/"
fi
# ============================================
# 第五步:同步 Homebrew Cask
# ============================================
publish-homebrew:
needs: [check-version, release]
runs-on: ubuntu-latest
steps:
- name: Resolve release asset
id: asset
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
VERSION="${{ needs.check-version.outputs.version }}"
TAG="v${VERSION}"
API_URL="https://api.github.com/repos/${{ github.repository }}/releases/tags/${TAG}"
RELEASE_JSON=$(curl -fsSL \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"${API_URL}")
DMG_NAME=$(echo "${RELEASE_JSON}" | jq -r '.assets[] | select(.name | endswith("_aarch64.dmg")) | .name' | head -n 1)
DMG_URL=$(echo "${RELEASE_JSON}" | jq -r --arg name "${DMG_NAME}" '.assets[] | select(.name == $name) | .browser_download_url')
DMG_DIGEST=$(echo "${RELEASE_JSON}" | jq -r --arg name "${DMG_NAME}" '.assets[] | select(.name == $name) | .digest')
if [ -z "${DMG_NAME}" ] || [ -z "${DMG_URL}" ] || [ "${DMG_NAME}" = "null" ] || [ "${DMG_URL}" = "null" ]; then
echo "Cannot find macOS dmg asset in release ${TAG}"
exit 1
fi
DMG_SHA="${DMG_DIGEST#sha256:}"
if [ -z "${DMG_SHA}" ] || [ "${DMG_SHA}" = "null" ] || [ "${DMG_SHA}" = "${DMG_DIGEST}" ]; then
DMG_SHA=$(curl -fsSL "${DMG_URL}" | shasum -a 256 | awk '{print $1}')
fi
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "dmg_url=${DMG_URL}" >> "${GITHUB_OUTPUT}"
echo "dmg_sha=${DMG_SHA}" >> "${GITHUB_OUTPUT}"
- name: Validate tap token
env:
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
if [ -z "${TAP_TOKEN}" ]; then
echo "Missing required secret: HOMEBREW_TAP_TOKEN"
exit 1
fi
- name: Trigger Homebrew tap bump
run: |
set -euo pipefail
VERSION="${{ steps.asset.outputs.version }}"
DMG_SHA="${{ steps.asset.outputs.dmg_sha }}"
PAYLOAD=$(jq -nc \
--arg version "${VERSION}" \
--arg sha256 "${DMG_SHA}" \
'{event_type:"youran_toolbox_release_published", client_payload:{version:$version, sha256:$sha256}}')
curl -fsSL -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.HOMEBREW_TAP_TOKEN }}" \
"https://api.github.com/repos/dufu1991/homebrew-tap/dispatches" \
-d "${PAYLOAD}"