Files
clawpanel/.github/workflows/release.yml

504 lines
18 KiB
YAML
Raw 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.
# ClawPanel 发布构建工作流
# 推送 v* 标签时自动构建跨平台产物并创建 GitHub Release
name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag_name:
description: '发布版本号 (例如: v1.0.0)'
required: true
type: string
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: read
strategy:
fail-fast: false
matrix:
platform:
- name: macOS (Apple Silicon)
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: 签出代码
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 安装 Node.js
uses: actions/setup-node@v4
with:
node-version: 22.19.0
cache: npm
- name: 安装前端依赖
run: npm ci
- name: 同步版本号到构建产物
shell: bash
run: |
node scripts/sync-version.js "${{ needs.validate-release.outputs.version }}"
- name: 安装 Rust 工具链
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform.rust_target }}
- name: Rust 编译缓存
uses: swatinem/rust-cache@v2
with:
workspaces: src-tauri -> target
key: ${{ matrix.platform.name }}
save-if: false
- name: 安装 Linux 系统依赖
if: runner.os == '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: 构建 Tauri 应用
if: matrix.platform.name != 'Windows (x64) 完整包'
run: npx tauri build ${{ matrix.platform.args }} -- --locked
- name: 上传平台构建产物
if: matrix.platform.name != 'Windows (x64) 完整包'
uses: actions/upload-artifact@v4
with:
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 完整包模式
if: matrix.platform.name == 'Windows (x64) 完整包'
shell: bash
run: |
node -e "
const fs = require('fs');
const f = 'src-tauri/tauri.conf.json';
const c = JSON.parse(fs.readFileSync(f, 'utf8'));
c.bundle.windows.webviewInstallMode = { type: 'offlineInstaller', silent: true };
fs.writeFileSync(f, JSON.stringify(c, null, 2));
"
- name: 构建 Windows 完整包
if: matrix.platform.name == 'Windows (x64) 完整包'
shell: bash
run: npx tauri build -- --locked
- name: 重命名 Windows 完整包
if: matrix.platform.name == 'Windows (x64) 完整包'
shell: bash
run: |
VERSION="${{ needs.validate-release.outputs.version }}"
BUNDLE_DIR="src-tauri/target/release/bundle"
mv "${BUNDLE_DIR}/nsis/ClawPanel_${VERSION}_x64-setup.exe" \
"${BUNDLE_DIR}/nsis/ClawPanel_${VERSION}_x64-setup-full.exe"
mv "${BUNDLE_DIR}/msi/ClawPanel_${VERSION}_x64_en-US.msi" \
"${BUNDLE_DIR}/msi/ClawPanel_${VERSION}_x64-full_en-US.msi"
- 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
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: 构建前端
run: npm run build
- name: 打包 Web 热更新产物
shell: bash
run: |
VERSION="${{ needs.validate-release.outputs.version }}"
cd dist
zip -r "../web-${VERSION}.zip" .
cd ..
- 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 ────────────────────────────────
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 }}
steps:
- name: 签出发布代码
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 下载全部 workflow artifacts
uses: actions/download-artifact@v4
with:
pattern: release-*
path: release-assets
merge-multiple: true
- name: 生成 Release Notes
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_RESULT: ${{ needs.build.result }}
REPO: ${{ github.repository }}
run: |
DL="https://github.com/${REPO}/releases/download/${TAG_NAME}"
# ── 生成分类 Changelog ──
PREV_TAG=$(git tag --sort=-v:refname | grep -E '^v' | sed -n '2p' || echo "")
if [ -n "$PREV_TAG" ]; then
RANGE="${PREV_TAG}..HEAD"
CHANGELOG_HEADER="自 ${PREV_TAG} 以来的变更"
else
RANGE=""
CHANGELOG_HEADER="主要变更"
fi
# 优先使用人工整理的 CHANGELOG.md 当前版本段,避免 Release Notes 只显示 release commit。
CHANGELOG_BODY=$(python3 - "$VERSION" <<'PY'
import pathlib
import re
import sys
version = sys.argv[1]
path = pathlib.Path("CHANGELOG.md")
if not path.exists():
sys.exit(0)
text = path.read_text(encoding="utf-8")
current = re.search(rf"^## \[{re.escape(version)}\](?:\s+-\s+.+)?\s*$", text, re.MULTILINE)
if not current:
sys.exit(0)
rest = text[current.end():]
next_version = re.search(r"^## \[", rest, re.MULTILINE)
section = rest[:next_version.start()] if next_version else rest
print(section.strip())
PY
)
if [ -z "$CHANGELOG_BODY" ]; then
# CHANGELOG.md 缺失时,退回 conventional commit 前缀分类。
get_logs() {
local prefix="$1"
if [ -n "$RANGE" ]; then
git log "$RANGE" --pretty=format:"%s" --no-merges | { grep -iE "^${prefix}" || true; } | sed "s/^${prefix}[:(] */- /" | head -20
else
git log --pretty=format:"%s" --no-merges -30 | { grep -iE "^${prefix}" || true; } | sed "s/^${prefix}[:(] */- /" | head -20
fi
}
FEATS=$(get_logs "feat")
FIXES=$(get_logs "fix")
OTHERS=$(
if [ -n "$RANGE" ]; then
git log "$RANGE" --pretty=format:"%s" --no-merges | { grep -ivE "^(feat|fix|style|chore|ci|docs|test|build)" || true; } | sed 's/^/- /' | head -10
else
git log --pretty=format:"%s" --no-merges -30 | { grep -ivE "^(feat|fix|style|chore|ci|docs|test|build)" || true; } | sed 's/^/- /' | head -10
fi
)
if [ -n "$FEATS" ]; then
CHANGELOG_BODY="${CHANGELOG_BODY}"$'\n### ✨ 新功能\n'"${FEATS}"$'\n'
fi
if [ -n "$FIXES" ]; then
CHANGELOG_BODY="${CHANGELOG_BODY}"$'\n### 🐛 修复\n'"${FIXES}"$'\n'
fi
if [ -n "$OTHERS" ]; then
CHANGELOG_BODY="${CHANGELOG_BODY}"$'\n### 📦 其他\n'"${OTHERS}"$'\n'
fi
fi
if [ -z "$CHANGELOG_BODY" ]; then
CHANGELOG_BODY=$'\n- 常规更新与优化\n'
fi
# ── 构建状态 ──
if [ "$BUILD_RESULT" = "success" ]; then
STATUS_BADGE="✅ 全部平台构建成功"
else
STATUS_BADGE="⚠️ 部分平台构建失败,请查看 [Actions 日志](https://github.com/${REPO}/actions)"
fi
# ── 写入 Release Notes ──
{
echo "${STATUS_BADGE}"
echo ""
echo "## 📥 下载安装"
echo ""
echo "根据你的操作系统选择对应安装包,点击文件名即可下载:"
echo ""
echo "### macOS"
echo "| 芯片 | 安装包 |"
echo "|------|--------|"
echo "| Apple Silicon (M1/M2/M3/M4) | [ClawPanel_${VERSION}_aarch64.dmg](${DL}/ClawPanel_${VERSION}_aarch64.dmg) |"
echo "| Intel | [ClawPanel_${VERSION}_x64.dmg](${DL}/ClawPanel_${VERSION}_x64.dmg) |"
echo ""
echo '> **⚠️ 首次打开提示"无法验证开发者"** 在终端执行:`sudo xattr -rd com.apple.quarantine /Applications/ClawPanel.app`,或前往 **系统设置 → 隐私与安全性** 点击「仍要打开」。'
echo ""
echo "### Windows"
echo "| 格式 | 安装包 | 说明 |"
echo "|------|--------|------|"
echo "| EXE 安装器(推荐) | [ClawPanel_${VERSION}_x64-setup.exe](${DL}/ClawPanel_${VERSION}_x64-setup.exe) | 轻量版 ~10 MB |"
echo "| EXE 完整包 | [ClawPanel_${VERSION}_x64-setup-full.exe](${DL}/ClawPanel_${VERSION}_x64-setup-full.exe) | 含 WebView2 ~200 MB |"
echo "| MSI 安装器 | [ClawPanel_${VERSION}_x64_en-US.msi](${DL}/ClawPanel_${VERSION}_x64_en-US.msi) | 轻量版 ~10 MB |"
echo "| MSI 完整包 | [ClawPanel_${VERSION}_x64-full_en-US.msi](${DL}/ClawPanel_${VERSION}_x64-full_en-US.msi) | 含 WebView2 ~200 MB |"
echo ""
echo "> **💡 轻量版 vs 完整包**Win10 1803+ 和 Win11 已预装 WebView2推荐下载轻量版。如果是内网/断网环境,选择完整包。"
echo ""
echo "### Linux"
echo "| 格式 | 安装包 |"
echo "|------|--------|"
echo "| AppImage免安装 | [ClawPanel_${VERSION}_amd64.AppImage](${DL}/ClawPanel_${VERSION}_amd64.AppImage) |"
echo "| DEBDebian/Ubuntu | [ClawPanel_${VERSION}_amd64.deb](${DL}/ClawPanel_${VERSION}_amd64.deb) |"
echo "| RPMFedora/RHEL | [ClawPanel-${VERSION}-1.x86_64.rpm](${DL}/ClawPanel-${VERSION}-1.x86_64.rpm) |"
echo ""
echo "---"
echo ""
echo "## 🚀 首次使用"
echo ""
echo "1. 安装并打开 ClawPanel"
echo "2. 首次运行会自动检测 Node.js 环境和 OpenClaw CLI"
echo "3. 如未安装 OpenClaw按提示一键安装即可"
echo "4. 安装完成后自动跳转仪表盘,开始使用"
echo ""
echo '> **系统要求**ClawPanel Web 后端需要 Node.js 18+;运行 OpenClaw Gateway 会按当前 OpenClaw 的 engines.node 自动检测,建议 Node.js 22.19.0+。'
echo ""
echo "---"
echo ""
echo "## ${CHANGELOG_HEADER}"
echo "${CHANGELOG_BODY}"
echo "---"
echo ""
echo "📖 [项目主页](https://github.com/${REPO}) · 💬 [反馈问题](https://github.com/${REPO}/issues) · 📣 [QQ 群](https://qt.cool/c/OpenClaw) · Telegram: https://t.me/clawpanel"
} > 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