mirror of
https://github.com/VirtualHotBar/NetMount.git
synced 2026-05-07 05:38:52 +08:00
416 lines
14 KiB
YAML
416 lines
14 KiB
YAML
name: "CI/CD"
|
||
|
||
# 发布Release(创建tag):git tag v1.2.0 && git push origin v1.2.0
|
||
|
||
on:
|
||
push:
|
||
branches: [main, master]
|
||
tags: ['v*']
|
||
pull_request:
|
||
branches: [main, master]
|
||
workflow_dispatch:
|
||
inputs:
|
||
release:
|
||
description: 'Create release'
|
||
required: false
|
||
default: false
|
||
type: boolean
|
||
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||
cancel-in-progress: true
|
||
|
||
env:
|
||
NODE_VERSION: '20'
|
||
RUST_TOOLCHAIN: stable
|
||
# 用于缓存二进制文件的 key
|
||
RCLONE_VERSION: "current"
|
||
OPENLIST_VERSION: "v4.1.10"
|
||
|
||
jobs:
|
||
# ========== 快速检查(合并为一个 job,并行执行所有检查)==========
|
||
quick-checks:
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 8
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 9
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: pnpm
|
||
- uses: dtolnay/rust-toolchain@stable
|
||
- uses: Swatinem/rust-cache@v2
|
||
with:
|
||
workspaces: src-tauri
|
||
shared-key: check
|
||
|
||
# 一次性安装所有依赖
|
||
- name: Install dependencies
|
||
run: pnpm install --frozen-lockfile
|
||
|
||
# 并行运行所有检查
|
||
- name: Run checks
|
||
run: |
|
||
# 使用后台进程并行运行
|
||
pnpm run check:i18n &
|
||
I18N_PID=$!
|
||
|
||
pnpm run lint &
|
||
LINT_PID=$!
|
||
|
||
npx tsc --noEmit &
|
||
TSC_PID=$!
|
||
|
||
# 等待所有后台任务完成,并收集退出码
|
||
wait $I18N_PID
|
||
I18N_EXIT=$?
|
||
|
||
wait $LINT_PID
|
||
LINT_EXIT=$?
|
||
|
||
wait $TSC_PID
|
||
TSC_EXIT=$?
|
||
|
||
# 如果任何检查失败,返回非零退出码
|
||
if [ $I18N_EXIT -ne 0 ] || [ $LINT_EXIT -ne 0 ] || [ $TSC_EXIT -ne 0 ]; then
|
||
echo "Checks failed: i18n=$I18N_EXIT lint=$LINT_EXIT tsc=$TSC_EXIT"
|
||
exit 1
|
||
fi
|
||
|
||
# ========== 前端构建(独立 job,可并行)==========
|
||
frontend-build:
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 5
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 9
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: pnpm
|
||
- run: pnpm install --frozen-lockfile
|
||
- run: pnpm run build
|
||
|
||
# ========== Tauri 构建检查(使用缓存的二进制文件)==========
|
||
tauri-check:
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 15
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 9
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: pnpm
|
||
- uses: dtolnay/rust-toolchain@stable
|
||
- uses: Swatinem/rust-cache@v2
|
||
with:
|
||
workspaces: src-tauri
|
||
shared-key: tauri-check
|
||
|
||
- name: Install system dependencies
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||
|
||
# 缓存二进制文件(rclone/openlist)
|
||
- name: Cache binaries
|
||
uses: actions/cache@v4
|
||
id: cache-binaries
|
||
with:
|
||
path: |
|
||
src-tauri/binaries/rclone
|
||
src-tauri/binaries/openlist
|
||
src-tauri/binaries/rclone-x86_64-unknown-linux-gnu
|
||
src-tauri/binaries/openlist-x86_64-unknown-linux-gnu
|
||
key: binaries-x86_64-unknown-linux-gnu-${{ env.RCLONE_VERSION }}-${{ env.OPENLIST_VERSION }}
|
||
|
||
- name: Resolve skip-downloads flag
|
||
id: resolve-skip-downloads
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
if [ "${{ steps.cache-binaries.outputs.cache-hit }}" != "true" ]; then
|
||
echo "skip_downloads=false" >> "$GITHUB_OUTPUT"
|
||
exit 0
|
||
fi
|
||
|
||
has_rclone=false
|
||
has_openlist=false
|
||
|
||
if [ -f "src-tauri/binaries/rclone" ] || [ -f "src-tauri/binaries/rclone-x86_64-unknown-linux-gnu" ]; then
|
||
has_rclone=true
|
||
fi
|
||
|
||
if [ -f "src-tauri/binaries/openlist" ] || [ -f "src-tauri/binaries/openlist-x86_64-unknown-linux-gnu" ]; then
|
||
has_openlist=true
|
||
fi
|
||
|
||
if [ "$has_rclone" = "true" ] && [ "$has_openlist" = "true" ]; then
|
||
echo "skip_downloads=true" >> "$GITHUB_OUTPUT"
|
||
else
|
||
echo "skip_downloads=false" >> "$GITHUB_OUTPUT"
|
||
echo "Cache hit but required binaries are missing; downloads remain enabled."
|
||
fi
|
||
|
||
- run: pnpm install --frozen-lockfile
|
||
- run: pnpm run pretauri-build
|
||
|
||
# 如果缓存命中,跳过下载
|
||
- name: Build Tauri (check only)
|
||
run: pnpm run tauri build --no-bundle
|
||
env:
|
||
RUST_BACKTRACE: 1
|
||
# Tauri 签名私钥
|
||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||
# Tauri 签名私钥密码(如果密钥已加密)
|
||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||
NETMOUNT_SKIP_BIN_DOWNLOADS: ${{ steps.resolve-skip-downloads.outputs.skip_downloads }}
|
||
|
||
# ========== Release 创建 ==========
|
||
create-release:
|
||
needs: [quick-checks, frontend-build, tauri-check]
|
||
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'true')
|
||
permissions:
|
||
contents: write
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 5
|
||
outputs:
|
||
release_id: ${{ steps.create-release.outputs.result }}
|
||
package_version: ${{ steps.get-version.outputs.package_version }}
|
||
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
|
||
- id: get-version
|
||
run: |
|
||
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
||
echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> $GITHUB_ENV
|
||
echo "package_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
|
||
|
||
- id: create-release
|
||
uses: actions/github-script@v7
|
||
with:
|
||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const tag = `v${process.env.PACKAGE_VERSION}`
|
||
try {
|
||
const { data } = await github.rest.repos.getReleaseByTag({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
tag
|
||
})
|
||
core.info(`Release exists for ${tag}, reusing id=${data.id}`)
|
||
return data.id
|
||
} catch {
|
||
core.info(`Creating new draft release for ${tag}`)
|
||
}
|
||
const { data } = await github.rest.repos.createRelease({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
tag_name: tag,
|
||
target_commitish: context.sha,
|
||
name: `NetMount ${tag}`,
|
||
body: 'Take a look at the assets to download and install this app.',
|
||
draft: true,
|
||
prerelease: false
|
||
})
|
||
return data.id
|
||
|
||
# ========== 多平台构建 Tauri 应用(使用缓存优化)==========
|
||
build-tauri:
|
||
needs: [create-release]
|
||
if: needs.create-release.result == 'success'
|
||
permissions:
|
||
contents: write
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- platform: 'macos-latest'
|
||
target: 'aarch64-apple-darwin'
|
||
args: '--target aarch64-apple-darwin'
|
||
arch: 'aarch64'
|
||
- platform: 'macos-latest'
|
||
target: 'x86_64-apple-darwin'
|
||
args: '--target x86_64-apple-darwin'
|
||
arch: 'x86_64'
|
||
- platform: 'ubuntu-22.04'
|
||
target: 'x86_64-unknown-linux-gnu'
|
||
args: ''
|
||
arch: 'x86_64'
|
||
- platform: 'ubuntu-22.04-arm'
|
||
target: 'aarch64-unknown-linux-gnu'
|
||
args: ''
|
||
arch: 'aarch64'
|
||
- platform: 'windows-latest'
|
||
target: 'x86_64-pc-windows-msvc'
|
||
args: ''
|
||
arch: 'x86_64'
|
||
- platform: 'windows-11-arm'
|
||
target: 'aarch64-pc-windows-msvc'
|
||
args: ''
|
||
arch: 'aarch64'
|
||
runs-on: ${{ matrix.platform }}
|
||
timeout-minutes: 30
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
version: 9
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
cache: pnpm
|
||
|
||
- uses: dtolnay/rust-toolchain@stable
|
||
with:
|
||
toolchain: stable
|
||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||
|
||
# Rust 缓存 - 按目标平台分离缓存
|
||
- name: Cache Rust dependencies
|
||
uses: Swatinem/rust-cache@v2
|
||
with:
|
||
workspaces: src-tauri
|
||
key: ${{ matrix.target }}
|
||
cache-on-failure: true
|
||
|
||
# 缓存二进制文件
|
||
- name: Cache binaries
|
||
uses: actions/cache@v4
|
||
id: cache-binaries
|
||
with:
|
||
path: |
|
||
src-tauri/binaries/rclone
|
||
src-tauri/binaries/openlist
|
||
src-tauri/binaries/rclone-${{ matrix.target }}${{ contains(matrix.platform, 'windows') && '.exe' || '' }}
|
||
src-tauri/binaries/openlist-${{ matrix.target }}${{ contains(matrix.platform, 'windows') && '.exe' || '' }}
|
||
src-tauri/binaries/winfsp.msi
|
||
key: binaries-${{ matrix.target }}-${{ env.RCLONE_VERSION }}-${{ env.OPENLIST_VERSION }}
|
||
|
||
- name: Resolve skip-downloads flag
|
||
id: resolve-skip-downloads
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
if [ "${{ steps.cache-binaries.outputs.cache-hit }}" != "true" ]; then
|
||
echo "skip_downloads=false" >> "$GITHUB_OUTPUT"
|
||
exit 0
|
||
fi
|
||
|
||
ext=""
|
||
if [ "${{ contains(matrix.platform, 'windows') }}" = "true" ]; then
|
||
ext=".exe"
|
||
fi
|
||
|
||
has_rclone=false
|
||
has_openlist=false
|
||
|
||
if [ -f "src-tauri/binaries/rclone${ext}" ] || [ -f "src-tauri/binaries/rclone-${{ matrix.target }}${ext}" ]; then
|
||
has_rclone=true
|
||
fi
|
||
|
||
if [ -f "src-tauri/binaries/openlist${ext}" ] || [ -f "src-tauri/binaries/openlist-${{ matrix.target }}${ext}" ]; then
|
||
has_openlist=true
|
||
fi
|
||
|
||
if [ "$has_rclone" = "true" ] && [ "$has_openlist" = "true" ]; then
|
||
echo "skip_downloads=true" >> "$GITHUB_OUTPUT"
|
||
else
|
||
echo "skip_downloads=false" >> "$GITHUB_OUTPUT"
|
||
echo "Cache hit but required binaries are missing for target ${{ matrix.target }}; downloads remain enabled."
|
||
fi
|
||
|
||
- name: Install Linux dependencies
|
||
if: contains(matrix.platform, 'ubuntu')
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||
|
||
- run: pnpm install --frozen-lockfile
|
||
- run: pnpm run pretauri-build
|
||
|
||
# 如果二进制文件已缓存,跳过下载
|
||
- name: Build Tauri app
|
||
uses: tauri-apps/tauri-action@v0
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
# Tauri 签名私钥
|
||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||
# Tauri 签名私钥密码(如果密钥已加密)
|
||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||
# 使用缓存时跳过下载
|
||
NETMOUNT_SKIP_BIN_DOWNLOADS: ${{ steps.resolve-skip-downloads.outputs.skip_downloads }}
|
||
with:
|
||
releaseId: ${{ needs.create-release.outputs.release_id }}
|
||
tauriScript: pnpm tauri
|
||
args: ${{ matrix.args }}
|
||
|
||
# ========== 生成 Changelog ==========
|
||
generate-changelog:
|
||
needs: [create-release]
|
||
if: needs.create-release.result == 'success'
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 5
|
||
outputs:
|
||
changelog: ${{ steps.gen.outputs.changelog }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: ${{ env.NODE_VERSION }}
|
||
- run: git fetch --tags --force
|
||
- id: gen
|
||
env:
|
||
CURRENT_TAG: v${{ needs.create-release.outputs.package_version }}
|
||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||
run: |
|
||
node scripts/generate-changelog.mjs > changelog.md
|
||
cat changelog.md
|
||
{
|
||
echo 'changelog<<CHANGELOG_EOF'
|
||
cat changelog.md
|
||
echo ''
|
||
echo 'CHANGELOG_EOF'
|
||
} >> "$GITHUB_OUTPUT"
|
||
|
||
# ========== 发布 Release ==========
|
||
publish-release:
|
||
needs: [create-release, build-tauri, generate-changelog]
|
||
if: always() && needs.create-release.result == 'success' && needs.build-tauri.result == 'success'
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 5
|
||
permissions:
|
||
contents: write
|
||
steps:
|
||
- uses: actions/github-script@v7
|
||
env:
|
||
RELEASE_ID: ${{ needs.create-release.outputs.release_id }}
|
||
CHANGELOG: ${{ needs.generate-changelog.outputs.changelog }}
|
||
with:
|
||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const changelog = process.env.CHANGELOG || '';
|
||
const prBody = context.payload.pull_request?.body || '';
|
||
await github.rest.repos.updateRelease({
|
||
owner: context.repo.owner,
|
||
repo: context.repo.repo,
|
||
release_id: process.env.RELEASE_ID,
|
||
draft: false,
|
||
prerelease: false,
|
||
body: `${changelog}\n\n${prBody}`.trim()
|
||
});
|