mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-08-06 22:24:13 +08:00
fix(release): harden pre-release stability gates
This commit is contained in:
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -80,12 +80,21 @@ jobs:
|
||||
# Rust 编译检查
|
||||
- name: Rust 编译检查
|
||||
working-directory: src-tauri
|
||||
run: cargo check
|
||||
run: cargo check --locked
|
||||
|
||||
# Rust Lint(警告视为错误)
|
||||
- name: Rust Clippy
|
||||
working-directory: src-tauri
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
run: cargo clippy --locked --all-targets -- -D warnings
|
||||
|
||||
# Node 单元测试
|
||||
- name: Node 单元测试
|
||||
run: node --test tests/*.test.js
|
||||
|
||||
# Rust 单元测试
|
||||
- name: Rust 单元测试
|
||||
working-directory: src-tauri
|
||||
run: cargo test --locked
|
||||
|
||||
# 前端构建验证
|
||||
- name: 前端构建验证
|
||||
|
||||
1
.github/workflows/pages.yml
vendored
1
.github/workflows/pages.yml
vendored
@@ -23,6 +23,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 签出代码
|
||||
|
||||
337
.github/workflows/release.yml
vendored
337
.github/workflows/release.yml
vendored
@@ -15,12 +15,107 @@ on:
|
||||
default: 'v1.0.0'
|
||||
|
||||
jobs:
|
||||
validate-release:
|
||||
name: 验证发布目标
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
tag_name: ${{ steps.release.outputs.tag_name }}
|
||||
version: ${{ steps.release.outputs.version }}
|
||||
steps:
|
||||
- name: 签出代码和标签
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 验证 SemVer、Tag 绑定与 Release 唯一性
|
||||
id: release
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
INPUT_TAG: ${{ github.event.inputs.tag_name }}
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
TAG_NAME="$INPUT_TAG"
|
||||
else
|
||||
TAG_NAME="${{ github.ref_name }}"
|
||||
fi
|
||||
|
||||
SEMVER_PATTERN='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'
|
||||
if [[ ! "$TAG_NAME" =~ $SEMVER_PATTERN ]]; then
|
||||
echo "无效发布标签: $TAG_NAME(必须是 vX.Y.Z 格式的 SemVer)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG_COMMIT=$(git rev-parse "refs/tags/$TAG_NAME^{commit}" 2>/dev/null) || {
|
||||
echo "发布标签不存在: $TAG_NAME" >&2
|
||||
exit 1
|
||||
}
|
||||
HEAD_COMMIT=$(git rev-parse HEAD)
|
||||
if [ "$TAG_COMMIT" != "$HEAD_COMMIT" ]; then
|
||||
echo "发布标签 $TAG_NAME 未绑定当前 ref ($HEAD_COMMIT)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if gh release view "$TAG_NAME" >/dev/null 2>&1; then
|
||||
echo "Release $TAG_NAME 已存在,禁止覆盖" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${TAG_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
test-release:
|
||||
name: 发布前单元测试
|
||||
needs: validate-release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 签出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 安装 Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22.19.0
|
||||
cache: npm
|
||||
|
||||
- name: 安装前端依赖
|
||||
run: npm ci
|
||||
|
||||
- name: Node 单元测试
|
||||
run: node --test tests/*.test.js
|
||||
|
||||
- name: 安装 Rust 工具链
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Rust 编译缓存
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: src-tauri -> target
|
||||
|
||||
- name: 安装 Linux 系统依赖
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
librsvg2-dev \
|
||||
patchelf \
|
||||
libssl-dev \
|
||||
libgtk-3-dev \
|
||||
libayatana-appindicator3-dev
|
||||
|
||||
- name: Rust 单元测试
|
||||
working-directory: src-tauri
|
||||
run: cargo test --locked
|
||||
|
||||
# ── 跨平台构建 job ─────────────────────────────────────────────────────────
|
||||
build:
|
||||
name: 构建 (${{ matrix.platform.name }})
|
||||
needs: [validate-release, test-release]
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -29,22 +124,27 @@ jobs:
|
||||
os: macos-latest
|
||||
args: --target aarch64-apple-darwin
|
||||
rust_target: aarch64-apple-darwin
|
||||
artifact: macos-arm64
|
||||
- name: macOS (Intel)
|
||||
os: macos-latest
|
||||
args: --target x86_64-apple-darwin
|
||||
rust_target: x86_64-apple-darwin
|
||||
artifact: macos-x64
|
||||
- name: Linux (x64)
|
||||
os: ubuntu-latest
|
||||
args: ""
|
||||
rust_target: ""
|
||||
artifact: linux-x64
|
||||
- name: Windows (x64)
|
||||
os: windows-latest
|
||||
args: ""
|
||||
rust_target: ""
|
||||
artifact: windows-x64
|
||||
- name: Windows (x64) 完整包
|
||||
os: windows-latest
|
||||
args: ""
|
||||
rust_target: ""
|
||||
artifact: windows-x64-full
|
||||
|
||||
steps:
|
||||
- name: 签出代码
|
||||
@@ -52,16 +152,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 设置版本标签
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "TAG_NAME=${{ github.ref_name }}" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: 安装 Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -74,8 +164,7 @@ jobs:
|
||||
- name: 同步版本号到构建产物
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="${TAG_NAME#v}"
|
||||
node scripts/sync-version.js "$VERSION"
|
||||
node scripts/sync-version.js "${{ needs.validate-release.outputs.version }}"
|
||||
|
||||
- name: 安装 Rust 工具链
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -103,26 +192,22 @@ jobs:
|
||||
|
||||
- name: 构建 Tauri 应用
|
||||
if: matrix.platform.name != 'Windows (x64) 完整包'
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# macOS 代码签名(可选)
|
||||
# APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
# APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
# APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
# APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
# APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
# APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
# Windows 代码签名(可选)
|
||||
# TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
run: npx tauri build ${{ matrix.platform.args }} -- --locked
|
||||
|
||||
- name: 上传平台构建产物
|
||||
if: matrix.platform.name != 'Windows (x64) 完整包'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
tagName: ${{ env.TAG_NAME }}
|
||||
releaseName: "ClawPanel ${{ env.TAG_NAME }}"
|
||||
releaseBody: "正在构建所有平台安装包,请稍候..."
|
||||
releaseDraft: false
|
||||
prerelease: false
|
||||
args: ${{ matrix.platform.args }}
|
||||
name: release-${{ matrix.platform.artifact }}
|
||||
path: |
|
||||
src-tauri/target/**/release/bundle/**/*.dmg
|
||||
src-tauri/target/**/release/bundle/**/*.exe
|
||||
src-tauri/target/**/release/bundle/**/*.msi
|
||||
src-tauri/target/**/release/bundle/**/*.AppImage
|
||||
src-tauri/target/**/release/bundle/**/*.deb
|
||||
src-tauri/target/**/release/bundle/**/*.rpm
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
# ── Windows 完整包(内嵌 WebView2 离线安装器)──────────────────────────────
|
||||
- name: 配置 WebView2 完整包模式
|
||||
@@ -140,57 +225,37 @@ jobs:
|
||||
- name: 构建 Windows 完整包
|
||||
if: matrix.platform.name == 'Windows (x64) 完整包'
|
||||
shell: bash
|
||||
run: npx tauri build
|
||||
run: npx tauri build -- --locked
|
||||
|
||||
- name: 重命名并上传完整包
|
||||
- name: 重命名 Windows 完整包
|
||||
if: matrix.platform.name == 'Windows (x64) 完整包'
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
VERSION="${TAG_NAME#v}"
|
||||
VERSION="${{ needs.validate-release.outputs.version }}"
|
||||
BUNDLE_DIR="src-tauri/target/release/bundle"
|
||||
|
||||
# 确保 Release 已存在(可能被其他 matrix job 的 tauri-action 创建)
|
||||
gh release create "$TAG_NAME" --title "ClawPanel $TAG_NAME" \
|
||||
--notes "正在构建所有平台安装包,请稍候..." 2>/dev/null || true
|
||||
|
||||
# 重命名 NSIS exe 并上传
|
||||
mv "${BUNDLE_DIR}/nsis/ClawPanel_${VERSION}_x64-setup.exe" \
|
||||
"${BUNDLE_DIR}/nsis/ClawPanel_${VERSION}_x64-setup-full.exe"
|
||||
gh release upload "$TAG_NAME" \
|
||||
"${BUNDLE_DIR}/nsis/ClawPanel_${VERSION}_x64-setup-full.exe" --clobber
|
||||
|
||||
# 重命名 MSI 并上传
|
||||
mv "${BUNDLE_DIR}/msi/ClawPanel_${VERSION}_x64_en-US.msi" \
|
||||
"${BUNDLE_DIR}/msi/ClawPanel_${VERSION}_x64-full_en-US.msi"
|
||||
gh release upload "$TAG_NAME" \
|
||||
"${BUNDLE_DIR}/msi/ClawPanel_${VERSION}_x64-full_en-US.msi" --clobber
|
||||
|
||||
# ── 所有平台构建完成后,统一更新 Release Notes ─────────────────────────────
|
||||
# 独立 job 确保只执行一次,彻底避免多个 matrix job 的竞争条件
|
||||
update-release-notes:
|
||||
name: 更新 Release Notes
|
||||
needs: build
|
||||
- name: 上传 Windows 完整包产物
|
||||
if: matrix.platform.name == 'Windows (x64) 完整包'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-${{ matrix.platform.artifact }}
|
||||
path: |
|
||||
src-tauri/target/release/bundle/nsis/ClawPanel_${{ needs.validate-release.outputs.version }}_x64-setup-full.exe
|
||||
src-tauri/target/release/bundle/msi/ClawPanel_${{ needs.validate-release.outputs.version }}_x64-full_en-US.msi
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
build-web:
|
||||
name: 构建 Web 热更新包
|
||||
needs: [validate-release, test-release]
|
||||
runs-on: ubuntu-latest
|
||||
if: always() && needs.build.result != 'cancelled'
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: 签出代码
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 设置版本标签
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "TAG_NAME=${{ github.ref_name }}" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: 安装 Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -198,63 +263,59 @@ jobs:
|
||||
node-version: 22.19.0
|
||||
cache: npm
|
||||
|
||||
- name: 构建前端并上传热更新包
|
||||
- name: 安装前端依赖
|
||||
run: npm ci
|
||||
|
||||
- name: 构建前端
|
||||
run: npm run build
|
||||
|
||||
- name: 打包 Web 热更新产物
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
VERSION="${TAG_NAME#v}"
|
||||
|
||||
# 构建前端
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
# 打包 dist 目录为 zip
|
||||
VERSION="${{ needs.validate-release.outputs.version }}"
|
||||
cd dist
|
||||
zip -r "../web-${VERSION}.zip" .
|
||||
cd ..
|
||||
|
||||
# 计算 SHA-256
|
||||
HASH=$(sha256sum "web-${VERSION}.zip" | cut -d' ' -f1)
|
||||
SIZE=$(stat -c%s "web-${VERSION}.zip" 2>/dev/null || stat -f%z "web-${VERSION}.zip")
|
||||
- name: 上传 Web workflow artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-web
|
||||
path: web-${{ needs.validate-release.outputs.version }}.zip
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
# 上传为 Release Asset
|
||||
gh release upload "$TAG_NAME" "web-${VERSION}.zip" --clobber
|
||||
# ── 所有构建成功后一次性创建并公开 Release ────────────────────────────────
|
||||
publish-release:
|
||||
name: 发布 Release
|
||||
needs: [validate-release, test-release, build, build-web]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
TAG_NAME: ${{ needs.validate-release.outputs.tag_name }}
|
||||
VERSION: ${{ needs.validate-release.outputs.version }}
|
||||
|
||||
# 读取现有 minAppVersion(前端热更新通常不需要更新 Rust 后端,保留旧值)
|
||||
MIN_APP_VER=$(cat docs/update/latest.json 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('minAppVersion','0.9.0'))" 2>/dev/null || echo "0.9.0")
|
||||
steps:
|
||||
- name: 签出发布代码
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# 更新 docs/update/latest.json
|
||||
DL_URL="https://github.com/${{ github.repository }}/releases/download/${TAG_NAME}/web-${VERSION}.zip"
|
||||
cat > docs/update/latest.json << EOF
|
||||
{
|
||||
"version": "${VERSION}",
|
||||
"minAppVersion": "${MIN_APP_VER}",
|
||||
"hash": "sha256:${HASH}",
|
||||
"url": "${DL_URL}",
|
||||
"size": ${SIZE},
|
||||
"changelog": "",
|
||||
"releasedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
}
|
||||
EOF
|
||||
# 去掉 heredoc 缩进
|
||||
sed -i 's/^ //' docs/update/latest.json
|
||||
- name: 下载全部 workflow artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: release-*
|
||||
path: release-assets
|
||||
merge-multiple: true
|
||||
|
||||
# 提交 latest.json 到 main 分支
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add docs/update/latest.json
|
||||
git commit -m "ci: update latest.json for ${TAG_NAME}" || true
|
||||
git push origin HEAD:main || true
|
||||
|
||||
- name: 生成并更新 Release Notes
|
||||
- name: 生成 Release Notes
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_RESULT: ${{ needs.build.result }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
VERSION="${TAG_NAME#v}"
|
||||
DL="https://github.com/${REPO}/releases/download/${TAG_NAME}"
|
||||
|
||||
# ── 生成分类 Changelog ──
|
||||
@@ -385,4 +446,58 @@ jobs:
|
||||
echo "📖 [项目主页](https://github.com/${REPO}) · 💬 [反馈问题](https://github.com/${REPO}/issues) · 📣 [QQ 群](https://qt.cool/c/OpenClaw) · Telegram: https://t.me/clawpanel"
|
||||
} > release_body.md
|
||||
|
||||
gh release edit "$TAG_NAME" --notes-file release_body.md
|
||||
- name: 创建草稿 Release 并上传完整资产
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
mapfile -d '' ASSETS < <(find release-assets -type f -print0)
|
||||
if [ "${#ASSETS[@]}" -eq 0 ]; then
|
||||
echo "没有可发布的 workflow artifacts" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gh release create "$TAG_NAME" --title "ClawPanel $TAG_NAME" --notes-file release_body.md --draft
|
||||
gh release upload "$TAG_NAME" "${ASSETS[@]}"
|
||||
|
||||
- name: 写入本次热更新清单
|
||||
shell: bash
|
||||
run: |
|
||||
WEB_PACKAGE=$(find release-assets -type f -name "web-${VERSION}.zip" -print -quit)
|
||||
if [ -z "$WEB_PACKAGE" ]; then
|
||||
echo "缺少 web-${VERSION}.zip" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HASH=$(sha256sum "$WEB_PACKAGE" | cut -d' ' -f1)
|
||||
SIZE=$(stat -c%s "$WEB_PACKAGE")
|
||||
DL_URL="https://github.com/${{ github.repository }}/releases/download/${TAG_NAME}/web-${VERSION}.zip"
|
||||
|
||||
git fetch origin main
|
||||
git checkout -B release-manifest origin/main
|
||||
cat > docs/update/latest.json << EOF
|
||||
{
|
||||
"version": "${VERSION}",
|
||||
"minAppVersion": "${VERSION}",
|
||||
"hash": "sha256:${HASH}",
|
||||
"url": "${DL_URL}",
|
||||
"size": ${SIZE},
|
||||
"changelog": "",
|
||||
"releasedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
}
|
||||
EOF
|
||||
sed -i 's/^ //' docs/update/latest.json
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add docs/update/latest.json
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "ci: update latest.json for ${TAG_NAME}"
|
||||
git push origin HEAD:main
|
||||
fi
|
||||
|
||||
- name: 一次性公开 Release
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh release edit "$TAG_NAME" --draft=false
|
||||
|
||||
Reference in New Issue
Block a user