mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-11 06:39:00 +08:00
Refactored the release workflow to add semantic version validation, improved commit and file diff collection, and enhanced release note generation with more context and formatting. Updated release note and default documentation prompts for clarity, conciseness, and better user guidance. Fixed owner typo in workflow and improved error handling for missing tags.
428 lines
16 KiB
YAML
428 lines
16 KiB
YAML
name: Release NapCat
|
||
|
||
on:
|
||
workflow_dispatch:
|
||
push:
|
||
tags:
|
||
- 'v*'
|
||
|
||
permissions: write-all
|
||
|
||
env:
|
||
OPENROUTER_API_URL: https://91vip.futureppo.top/v1/chat/completions
|
||
OPENROUTER_MODEL: "Antigravity/gemini-3-flash-preview"
|
||
RELEASE_NAME: "NapCat"
|
||
|
||
jobs:
|
||
# 验证版本号格式
|
||
validate-version:
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
valid: ${{ steps.check.outputs.valid }}
|
||
version: ${{ steps.check.outputs.version }}
|
||
steps:
|
||
- name: Validate semantic version
|
||
id: check
|
||
run: |
|
||
TAG="${GITHUB_REF#refs/tags/}"
|
||
echo "Checking tag: $TAG"
|
||
|
||
# 语义化版本正则表达式
|
||
# 支持: v1.0.0, v1.0.0-beta, v1.0.0-rc.1, v1.0.0-alpha.1+build.123
|
||
SEMVER_REGEX="^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?$"
|
||
|
||
if [[ "$TAG" =~ $SEMVER_REGEX ]]; then
|
||
echo "✅ Valid semantic version: $TAG"
|
||
echo "valid=true" >> $GITHUB_OUTPUT
|
||
echo "version=$TAG" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "❌ Invalid version format: $TAG"
|
||
echo "Expected format: vX.Y.Z or vX.Y.Z-prerelease"
|
||
echo "Examples: v1.0.0, v1.2.3-beta, v2.0.0-rc.1"
|
||
echo "valid=false" >> $GITHUB_OUTPUT
|
||
exit 1
|
||
fi
|
||
|
||
Build-Framework:
|
||
needs: validate-version
|
||
if: needs.validate-version.outputs.valid == 'true'
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Clone Main Repository
|
||
uses: actions/checkout@v4
|
||
- name: Use Node.js 20.X
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 20.x
|
||
- name: Build NapCat.Framework
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
run: |
|
||
npm i -g pnpm
|
||
pnpm i
|
||
pnpm --filter napcat-webui-frontend run build || exit 1
|
||
pnpm run build:framework
|
||
mv packages/napcat-framework/dist framework-dist
|
||
cd framework-dist
|
||
npm install --omit=dev
|
||
rm ./package-lock.json || exit 0
|
||
- name: Upload Artifact
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: NapCat.Framework
|
||
path: framework-dist
|
||
|
||
Build-Shell:
|
||
needs: validate-version
|
||
if: needs.validate-version.outputs.valid == 'true'
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Clone Main Repository
|
||
uses: actions/checkout@v4
|
||
- name: Use Node.js 20.X
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 20.x
|
||
- name: Build NapCat.Shell
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
run: |
|
||
npm i -g pnpm
|
||
pnpm i
|
||
pnpm --filter napcat-webui-frontend run build || exit 1
|
||
pnpm run build:shell
|
||
mv packages/napcat-shell/dist shell-dist
|
||
cd shell-dist
|
||
npm install --omit=dev
|
||
rm ./package-lock.json || exit 0
|
||
- name: Upload Artifact
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: NapCat.Shell
|
||
path: shell-dist
|
||
Download-QNX64:
|
||
needs: Build-Shell
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Download Artifacts
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
path: ./artifacts
|
||
|
||
- name: Setup tools
|
||
run: |
|
||
sudo apt update
|
||
sudo apt install -y aria2 unzip zip p7zip-full curl jq
|
||
|
||
- name: Download QQ x64, Node.js and Assemble NapCat.Shell.Windows.Node.zip
|
||
run: |
|
||
set -euo pipefail
|
||
TMPDIR=$(mktemp -d)
|
||
cd "$TMPDIR"
|
||
|
||
# -----------------------------
|
||
# 1) 下载 QQ x64
|
||
# -----------------------------
|
||
# JS_URL="https://cdn-go.cn/qq-web/im.qq.com_new/latest/rainbow/windowsConfig.js"
|
||
# JS_URL="https://slave.docadan488.workers.dev/proxy?url=https://cdn-go.cn/qq-web/im.qq.com_new/latest/rainbow/windowsConfig.js"
|
||
# NT_URL=$(curl -fsSL "$JS_URL" | grep -oP '"ntDownloadX64Url"\s*:\s*"\K[^"]+')
|
||
NT_URL="https://dldir1v6.qq.com/qqfile/qq/QQNT/eb263b35/QQ9.9.23.42086_x64.exe"
|
||
QQ_ZIP="$(basename "$NT_URL")"
|
||
aria2c -x16 -s16 -k1M -o "$QQ_ZIP" "$NT_URL"
|
||
|
||
QQ_EXTRACT="$TMPDIR/qq_extracted"
|
||
mkdir -p "$QQ_EXTRACT"
|
||
7z x -y -o"$QQ_EXTRACT" "$QQ_ZIP" >/dev/null
|
||
|
||
# -----------------------------
|
||
# 2) 下载 Node.js Windows x64 zip 22.11.0
|
||
# -----------------------------
|
||
NODE_VER="22.11.0"
|
||
NODE_URL="https://nodejs.org/dist/v$NODE_VER/node-v$NODE_VER-win-x64.zip"
|
||
NODE_ZIP="node-v$NODE_VER-win-x64.zip"
|
||
aria2c -x1 -s1 -k1M -o "$NODE_ZIP" "$NODE_URL"
|
||
|
||
NODE_EXTRACT="$TMPDIR/node_extracted"
|
||
mkdir -p "$NODE_EXTRACT"
|
||
unzip -q "$NODE_ZIP" -d "$NODE_EXTRACT"
|
||
|
||
# -----------------------------
|
||
# 3) 创建输出目录
|
||
# -----------------------------
|
||
OUT_DIR="$GITHUB_WORKSPACE/NapCat.Shell.Windows.Node"
|
||
mkdir -p "$OUT_DIR/NapCat.Shell.Windows.Node"
|
||
|
||
# -----------------------------
|
||
# 4) 解压 NapCat.Shell.zip 到 napcat
|
||
# -----------------------------
|
||
cp -a "$GITHUB_WORKSPACE/artifacts/NapCat.Shell/." "$OUT_DIR/napcat/"
|
||
|
||
# -----------------------------
|
||
# 5) 拷贝 QQ 文件到 NapCat.Shell.Windows.Node
|
||
# -----------------------------
|
||
QQ_TARGETS=("avif_convert.dll" "broadcast_ipc.dll" "config.json" "libglib-2.0-0.dll" "libgobject-2.0-0.dll" "libvips-42.dll" "ncnn.dll" "opencv.dll" "package.json" "QBar.dll" "wrapper.node")
|
||
for name in "${QQ_TARGETS[@]}"; do
|
||
find "$QQ_EXTRACT" -iname "$name" -exec cp -a {} "$OUT_DIR" \; || true
|
||
done
|
||
|
||
# -----------------------------
|
||
# 6) 拷贝仓库文件 napcat.bat 和 index.js
|
||
# -----------------------------
|
||
cp -a "$GITHUB_WORKSPACE/packages/napcat-develop/napcat.bat" "$OUT_DIR/" || true
|
||
cp -a "$GITHUB_WORKSPACE/packages/napcat-develop/index.js" "$OUT_DIR/" || true
|
||
cp -a "$GITHUB_WORKSPACE/packages/napcat-develop/QQNT.dll" "$OUT_DIR/" || true
|
||
# -----------------------------
|
||
# 7) 拷贝 Node.exe 到 NapCat.Shell.Windows.Node
|
||
# -----------------------------
|
||
cp -a "$NODE_EXTRACT/node-v$NODE_VER-win-x64/node.exe" "$OUT_DIR/" || true
|
||
|
||
- name: Upload Artifact
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: NapCat.Shell.Windows.Node
|
||
path: NapCat.Shell.Windows.Node
|
||
|
||
release-napcat:
|
||
needs: [Build-Framework, Build-Shell, Download-QNX64]
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Download Artifacts
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
path: ./artifacts
|
||
|
||
- name: Zip Artifacts
|
||
run: |
|
||
cd artifacts
|
||
[ -d NapCat.Framework ] && (cd NapCat.Framework && zip -qr ../../NapCat.Framework.zip .)
|
||
[ -d NapCat.Shell ] && (cd NapCat.Shell && zip -qr ../../NapCat.Shell.zip .)
|
||
[ -d NapCat.Shell.Windows.Node ] && (cd NapCat.Shell.Windows.Node && zip -qr ../../NapCat.Shell.Windows.Node.zip .)
|
||
cd ..
|
||
|
||
- name: Generate release note via OpenRouter
|
||
env:
|
||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||
OPENROUTER_API_URL: ${{ env.OPENROUTER_API_URL }}
|
||
OPENROUTER_MODEL: ${{ env.OPENROUTER_MODEL }}
|
||
GITHUB_OWNER: "NapNeko" # 替换成你的 repo owner
|
||
GITHUB_REPO: "NapCatQQ" # 替换成你的 repo 名
|
||
run: |
|
||
set -euo pipefail
|
||
|
||
# 当前 tag
|
||
CURRENT_TAG="${GITHUB_REF#refs/tags/}"
|
||
echo "Current tag: $CURRENT_TAG"
|
||
|
||
# 从 GitHub API 获取 tag 列表
|
||
TAGS_JSON=$(curl -s "https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/tags?per_page=100")
|
||
TAGS=( $(echo "$TAGS_JSON" | jq -r '.[].name' | sort -V) )
|
||
|
||
# 找到上一个 tag
|
||
PREV_TAG=""
|
||
for i in "${!TAGS[@]}"; do
|
||
if [ "${TAGS[$i]}" = "$CURRENT_TAG" ]; then
|
||
if [ $i -gt 0 ]; then
|
||
PREV_TAG="${TAGS[$((i-1))]}"
|
||
fi
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [ -z "$PREV_TAG" ]; then
|
||
echo "⚠️ Could not find previous tag for $CURRENT_TAG, using first commit"
|
||
PREV_TAG=$(git rev-list --max-parents=0 HEAD | head -1)
|
||
fi
|
||
|
||
echo "Previous tag: $PREV_TAG"
|
||
|
||
# 强制拉取上一个 tag 和当前 tag
|
||
git fetch origin "refs/tags/$PREV_TAG:refs/tags/$PREV_TAG" --force || true
|
||
git fetch origin "refs/tags/$CURRENT_TAG:refs/tags/$CURRENT_TAG" --force || true
|
||
|
||
# 获取 commit,使用更清晰的格式
|
||
# 格式: <type>: <subject> (<hash>)
|
||
COMMITS=$(git log --pretty=format:'- %s (%h)' "$PREV_TAG".."$CURRENT_TAG" 2>/dev/null || git log --pretty=format:'- %s (%h)' -20)
|
||
|
||
echo "Commit list from $PREV_TAG to $CURRENT_TAG:"
|
||
echo "$COMMITS"
|
||
|
||
# 获取文件变化统计
|
||
echo "Getting file change statistics..."
|
||
FILE_STATS=$(git diff --stat "$PREV_TAG".."$CURRENT_TAG" 2>/dev/null || echo "")
|
||
|
||
# 获取总体统计(最后一行)
|
||
SUMMARY_LINE=$(echo "$FILE_STATS" | tail -1)
|
||
echo "Summary: $SUMMARY_LINE"
|
||
|
||
# 获取每个文件的变化(去掉最后一行汇总)
|
||
# 截断过长的输出(最多50个文件,每行最多80字符)
|
||
FILE_CHANGES=$(echo "$FILE_STATS" | head -n -1 | head -50 | cut -c1-80)
|
||
|
||
# 如果文件变化太多,进一步精简:只保留主要目录的变化
|
||
FILE_COUNT=$(echo "$FILE_STATS" | head -n -1 | wc -l)
|
||
if [ "$FILE_COUNT" -gt 50 ]; then
|
||
echo "Too many files ($FILE_COUNT), grouping by directory..."
|
||
# 按目录分组统计
|
||
DIR_STATS=$(git diff --stat "$PREV_TAG".."$CURRENT_TAG" 2>/dev/null | head -n -1 | \
|
||
sed 's/|.*//g' | \
|
||
awk -F'/' '{if(NF>1) print $1"/"$2; else print $1}' | \
|
||
sort | uniq -c | sort -rn | head -20)
|
||
FILE_CHANGES="[按目录分组统计 - 共 $FILE_COUNT 个文件变更]
|
||
$DIR_STATS"
|
||
fi
|
||
|
||
echo "File changes:"
|
||
echo "$FILE_CHANGES"
|
||
|
||
# 获取具体代码变化(关键文件的diff)
|
||
echo "Getting code diff for key files..."
|
||
|
||
# 定义关键目录(优先展示这些目录的变化)
|
||
KEY_DIRS="packages/napcat-core packages/napcat-onebot packages/napcat-webui-backend"
|
||
|
||
# 获取变更的关键文件列表(排除测试、配置等)
|
||
KEY_FILES=$(git diff --name-only "$PREV_TAG".."$CURRENT_TAG" 2>/dev/null | \
|
||
grep -E "^packages/napcat-(core|onebot|webui-backend|shell)/" | \
|
||
grep -E "\.(ts|js)$" | \
|
||
grep -v -E "(test|spec|\.d\.ts|config)" | \
|
||
head -15)
|
||
|
||
CODE_DIFF=""
|
||
DIFF_CHAR_LIMIT=6000 # 总diff字符限制
|
||
CURRENT_CHARS=0
|
||
|
||
for file in $KEY_FILES; do
|
||
if [ "$CURRENT_CHARS" -ge "$DIFF_CHAR_LIMIT" ]; then
|
||
CODE_DIFF="$CODE_DIFF
|
||
[... 更多文件变化已截断 ...]"
|
||
break
|
||
fi
|
||
|
||
# 获取单个文件的diff,限制每个文件最多50行
|
||
FILE_DIFF=$(git diff "$PREV_TAG".."$CURRENT_TAG" -- "$file" 2>/dev/null | head -50)
|
||
FILE_DIFF_LEN=${#FILE_DIFF}
|
||
|
||
# 如果单个文件diff超过1500字符,截断
|
||
if [ "$FILE_DIFF_LEN" -gt 1500 ]; then
|
||
FILE_DIFF=$(echo "$FILE_DIFF" | head -c 1500)
|
||
FILE_DIFF="$FILE_DIFF
|
||
[... 文件 $file 变化已截断 ...]"
|
||
fi
|
||
|
||
if [ -n "$FILE_DIFF" ]; then
|
||
CODE_DIFF="$CODE_DIFF
|
||
|
||
### $file
|
||
\`\`\`diff
|
||
$FILE_DIFF
|
||
\`\`\`"
|
||
CURRENT_CHARS=$((CURRENT_CHARS + FILE_DIFF_LEN))
|
||
fi
|
||
done
|
||
|
||
# 如果没有关键文件变化,获取前5个变更文件的diff
|
||
if [ -z "$CODE_DIFF" ]; then
|
||
echo "No key files changed, getting top changed files..."
|
||
TOP_FILES=$(git diff --name-only "$PREV_TAG".."$CURRENT_TAG" 2>/dev/null | \
|
||
grep -E "\.(ts|js)$" | head -5)
|
||
|
||
for file in $TOP_FILES; do
|
||
FILE_DIFF=$(git diff "$PREV_TAG".."$CURRENT_TAG" -- "$file" 2>/dev/null | head -30)
|
||
if [ -n "$FILE_DIFF" ] && [ ${#FILE_DIFF} -lt 1000 ]; then
|
||
CODE_DIFF="$CODE_DIFF
|
||
|
||
### $file
|
||
\`\`\`diff
|
||
$FILE_DIFF
|
||
\`\`\`"
|
||
fi
|
||
done
|
||
fi
|
||
|
||
echo "Code diff preview:"
|
||
echo "$CODE_DIFF" | head -50
|
||
|
||
# 读取 prompt
|
||
PROMPT_FILE=".github/prompt/release_note_prompt.txt"
|
||
SYSTEM_PROMPT=$(<"$PROMPT_FILE")
|
||
|
||
# 构建用户内容,传递更多上下文(包含文件变化和代码diff)
|
||
USER_CONTENT="当前版本: $CURRENT_TAG
|
||
上一版本: $PREV_TAG
|
||
|
||
## 提交列表
|
||
$COMMITS
|
||
|
||
## 文件变化统计
|
||
$SUMMARY_LINE
|
||
|
||
## 变更文件列表
|
||
$FILE_CHANGES
|
||
|
||
## 关键代码变化
|
||
$CODE_DIFF"
|
||
|
||
# 构建请求 JSON,增加 max_tokens 以获取更完整的输出
|
||
BODY=$(jq -n \
|
||
--arg system "$SYSTEM_PROMPT" \
|
||
--arg user "$USER_CONTENT" \
|
||
--arg model "$OPENROUTER_MODEL" \
|
||
'{model: $model, messages:[{role:"system", content:$system},{role:"user", content:$user}], temperature:0.2, max_tokens:1500}')
|
||
|
||
echo "=== OpenRouter request body ==="
|
||
echo "$BODY" | jq .
|
||
|
||
# 调用 OpenRouter
|
||
if RESPONSE=$(curl -s -X POST "$OPENROUTER_API_URL" \
|
||
-H "Authorization: Bearer $OPENAI_KEY" \
|
||
-H "Content-Type: application/json" \
|
||
-d "$BODY"); then
|
||
echo "=== raw response ==="
|
||
echo "$RESPONSE"
|
||
echo "=== OpenRouter raw response ==="
|
||
if echo "$RESPONSE" | jq . >/dev/null 2>&1; then
|
||
echo "$RESPONSE" | jq .
|
||
else
|
||
echo "jq failed to parse response"
|
||
fi
|
||
|
||
# 提取生成内容
|
||
RELEASE_BODY=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // .choices[0].text // ""' 2>/dev/null || echo "")
|
||
|
||
if [ -z "$RELEASE_BODY" ]; then
|
||
echo "❌ OpenRouter failed to generate release note, using default.md"
|
||
# 替换默认模板中的版本占位符
|
||
sed "s/{VERSION}/$CURRENT_TAG/g" .github/prompt/default.md > CHANGELOG.md
|
||
else
|
||
# 后处理:确保版本号正确,并添加比较链接
|
||
echo -e "$RELEASE_BODY" > CHANGELOG.md
|
||
# 替换可能的占位符
|
||
sed -i "s/{VERSION}/$CURRENT_TAG/g" CHANGELOG.md
|
||
sed -i "s/{PREV_VERSION}/$PREV_TAG/g" CHANGELOG.md
|
||
fi
|
||
else
|
||
echo "❌ Curl failed, using default.md"
|
||
sed "s/{VERSION}/$CURRENT_TAG/g" .github/prompt/default.md > CHANGELOG.md
|
||
fi
|
||
echo "=== generated release note ==="
|
||
cat CHANGELOG.md
|
||
|
||
- name: Create Release Draft and Upload Artifacts
|
||
uses: softprops/action-gh-release@v1
|
||
with:
|
||
name: NapCat ${{ github.ref_name }}
|
||
token: ${{ secrets.GITHUB_TOKEN }}
|
||
body_path: CHANGELOG.md
|
||
files: |
|
||
NapCat.Shell.Windows.Node.zip
|
||
NapCat.Framework.zip
|
||
NapCat.Shell.zip
|
||
draft: true
|