Skip to content

fix: 修复 COS action 文件读取错误 #48

fix: 修复 COS action 文件读取错误

fix: 修复 COS action 文件读取错误 #48

name: Build and Publish Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
publish-linux:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Debug environment
run: |
echo "GitHub ref: ${{ github.ref }}"
echo "Tag name: ${{ github.ref_name }}"
echo "Repository: ${{ github.repository }}"
echo "Is tag push: ${{ startsWith(github.ref, 'refs/tags/') }}"
echo "GITHUB_TOKEN exists: ${{ secrets.GITHUB_TOKEN != '' }}"
node --version
npm --version
- name: Publish Linux (AppImage and deb)
run: npm run publish:linux
env:
GH_TOKEN: ${{ secrets.atriordsa }}
DEBUG: electron-builder
publish-windows:
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Debug environment
run: |
echo "GitHub ref: ${{ github.ref }}"
echo "Tag name: ${{ github.ref_name }}"
echo "Repository: ${{ github.repository }}"
echo "Is tag push: ${{ startsWith(github.ref, 'refs/tags/') }}"
echo "GITHUB_TOKEN exists: ${{ secrets.GITHUB_TOKEN != '' }}"
node --version
npm --version
- name: Publish Windows
run: npm run publish:win
env:
GH_TOKEN: ${{ secrets.atriordsa }}
DEBUG: electron-builder
publish-macos:
runs-on: macos-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Debug environment
run: |
echo "GitHub ref: ${{ github.ref }}"
echo "Tag name: ${{ github.ref_name }}"
echo "Repository: ${{ github.repository }}"
echo "Is tag push: ${{ startsWith(github.ref, 'refs/tags/') }}"
echo "GITHUB_TOKEN exists: ${{ secrets.GITHUB_TOKEN != '' }}"
node --version
npm --version
- name: Publish macOS
run: npm run publish:mac
env:
GH_TOKEN: ${{ secrets.atriordsa }}
DEBUG: electron-builder
cleanup-release:
runs-on: ubuntu-latest
needs: [publish-linux, publish-windows, publish-macos]
if: always() && (needs.publish-linux.result == 'success' || needs.publish-windows.result == 'success' || needs.publish-macos.result == 'success')
permissions:
contents: write
actions: read
steps:
- name: Debug GitHub token and permissions
run: |
echo "🔍 Debugging GitHub environment..."
echo "Repository: ${{ github.repository }}"
echo "Actor: ${{ github.actor }}"
echo "Token exists: ${{ secrets.atriordsa != '' }}"
echo "Token length: ${#GITHUB_TOKEN}"
# 尝试不同的 API 调用
echo "🧪 Testing different API endpoints..."
# 测试基本用户信息
echo "Testing /user endpoint:"
curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user | jq -r '.login // "Failed"' || echo "curl failed"
# 测试仓库信息
echo "Testing repository endpoint:"
curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${{ github.repository }} | jq -r '.name // "Failed"' || echo "curl failed"
env:
GITHUB_TOKEN: ${{ secrets.atriordsa }}
- name: Clean unwanted files from release
run: |
set -e # 启用错误时退出
echo "🧹 Starting release cleanup for tag: ${{ github.ref_name }}"
# 直接使用 curl 而不是 gh CLI,因为 gh CLI 可能有权限问题
echo "🔗 Testing GitHub API with curl..."
# 验证环境变量
if [ -z "$GITHUB_TOKEN" ]; then
echo "❌ GITHUB_TOKEN not set"
exit 1
fi
# 测试 API 连接
USER_RESPONSE=$(curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user)
if ! echo "$USER_RESPONSE" | jq -e '.login' > /dev/null 2>&1; then
echo "❌ Cannot authenticate with GitHub API"
echo "Response: $USER_RESPONSE"
exit 1
fi
echo "✅ GitHub API authentication successful"
# 等待确保所有上传完成
echo "⏰ Waiting 30 seconds for uploads to complete..."
sleep 30
echo "📡 Getting release for tag: ${{ github.ref_name }}"
# 首先尝试通过标签获取 release,如果失败则从所有 releases 中查找
RELEASE_RESPONSE=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ github.ref_name }})
# 如果直接通过标签获取失败,则从所有 releases 中查找
if ! echo "$RELEASE_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then
echo "⚠️ Direct tag lookup failed, searching in all releases..."
echo "Direct response: $RELEASE_RESPONSE"
ALL_RELEASES=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases)
RELEASE_RESPONSE=$(echo "$ALL_RELEASES" | jq ".[] | select(.tag_name == \"${{ github.ref_name }}\")")
if ! echo "$RELEASE_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then
echo "❌ Could not find release for tag ${{ github.ref_name }}"
echo "Available releases:"
echo "$ALL_RELEASES" | jq -r '.[] | "- \(.tag_name) (ID: \(.id))"' | head -10
exit 1
fi
fi
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
echo "✅ Found release ID: $RELEASE_ID"
# 验证 RELEASE_ID 不为空且为数字
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ] || ! echo "$RELEASE_ID" | grep -q '^[0-9]\+$'; then
echo "❌ Invalid release ID: '$RELEASE_ID'"
exit 1
fi
# 获取 assets,添加超时和重试
echo "📁 Getting release assets..."
for attempt in 1 2 3; do
echo " Attempt $attempt/3..."
ASSETS_RESPONSE=$(curl -s --connect-timeout 30 --max-time 60 \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets)
if echo "$ASSETS_RESPONSE" | jq -e '. | length' > /dev/null 2>&1; then
echo "✅ Successfully retrieved $(echo "$ASSETS_RESPONSE" | jq length) assets"
break
else
echo "⚠️ Attempt $attempt failed. Response: $ASSETS_RESPONSE"
if [ $attempt -lt 3 ]; then
echo " Waiting 10 seconds before retry..."
sleep 10
fi
fi
done
# 最终验证assets获取是否成功
if ! echo "$ASSETS_RESPONSE" | jq -e '. | length' > /dev/null 2>&1; then
echo "❌ Failed to get assets after 3 attempts"
echo "Final response: $ASSETS_RESPONSE"
exit 1
fi
echo "📁 Current release assets:"
echo "$ASSETS_RESPONSE" | jq -r '.[] | "- \(.name) (ID: \(.id))"'
# 查找并删除不需要的文件
echo "🔍 Looking for unwanted assets..."
# 删除 .yml, .yaml, .blockmap 文件
echo "Finding .yml, .yaml, .blockmap files..."
YML_ASSETS=$(echo "$ASSETS_RESPONSE" | jq -r 'if type == "array" then .[] | select(.name | test("\\.(yml|yaml|blockmap)$")) | .id else empty end' 2>/dev/null || echo "")
# 删除不带架构标识的 Windows 文件
echo "Finding Windows files without architecture identifiers..."
WINDOWS_NO_ARCH_ASSETS=$(echo "$ASSETS_RESPONSE" | jq -r 'if type == "array" then .[] | select(.name | test("windows.*\\.(exe)$") and (.name | test("_(x64|arm64)_") | not)) | .id else empty end' 2>/dev/null || echo "")
# 合并所有要删除的 assets,过滤空行
ALL_UNWANTED_ASSETS=""
if [ -n "$YML_ASSETS" ]; then
ALL_UNWANTED_ASSETS="$YML_ASSETS"
fi
if [ -n "$WINDOWS_NO_ARCH_ASSETS" ]; then
if [ -n "$ALL_UNWANTED_ASSETS" ]; then
ALL_UNWANTED_ASSETS="$ALL_UNWANTED_ASSETS\n$WINDOWS_NO_ARCH_ASSETS"
else
ALL_UNWANTED_ASSETS="$WINDOWS_NO_ARCH_ASSETS"
fi
fi
if [ -n "$ALL_UNWANTED_ASSETS" ]; then
echo "🎯 Found unwanted assets to delete:"
# 显示要删除的文件列表
echo "$ASSETS_RESPONSE" | jq -r 'if type == "array" then .[] | select((.name | test("\\.(yml|yaml|blockmap)$")) or (.name | test("windows.*\\.(exe)$") and (.name | test("_(x64|arm64)_") | not))) | "- \(.name) (ID: \(.id))" else empty end' 2>/dev/null || echo "Error displaying file list"
echo -e "$ALL_UNWANTED_ASSETS" | while read -r asset_id; do
if [ -n "$asset_id" ] && [ "$asset_id" != "null" ] && [ "$asset_id" != "" ]; then
ASSET_NAME=$(echo "$ASSETS_RESPONSE" | jq -r "if type == \"array\" then .[] | select(.id == $asset_id) | .name else \"unknown\" end" 2>/dev/null)
echo "🗑️ Deleting: $ASSET_NAME (ID: $asset_id)"
DELETE_RESPONSE=$(curl -s -w "%{http_code}" -o /dev/null \
-X DELETE \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases/assets/$asset_id)
if [ "$DELETE_RESPONSE" = "204" ]; then
echo "✅ Successfully deleted: $ASSET_NAME"
else
echo "❌ Failed to delete: $ASSET_NAME (HTTP: $DELETE_RESPONSE)"
fi
fi
done
else
echo "🎉 No unwanted files found!"
fi
# 检查并发布 draft release
RELEASE_DRAFT=$(echo "$RELEASE_RESPONSE" | jq -r '.draft')
if [ "$RELEASE_DRAFT" = "true" ]; then
echo "📤 Publishing draft release..."
PUBLISH_RESPONSE=$(curl -s -w "%{http_code}" -o /dev/null \
-X PATCH \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"draft": false}' \
https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID)
if [ "$PUBLISH_RESPONSE" = "200" ]; then
echo "✅ Successfully published release"
else
echo "⚠️ Failed to publish release (HTTP: $PUBLISH_RESPONSE)"
echo " This may affect subsequent download steps"
fi
else
echo "✅ Release is already published"
fi
echo "🏁 Cleanup completed successfully!"
env:
GITHUB_TOKEN: ${{ secrets.atriordsa }}
upload-to-cos:
runs-on: ubuntu-latest
needs: [cleanup-release]
if: always() && needs.cleanup-release.result == 'success'
steps:
- name: Get release info
id: release
run: |
set -e
echo "🔍 Getting release info for tag: ${{ github.ref_name }}"
# 验证环境变量
if [ -z "$GITHUB_TOKEN" ]; then
echo "❌ GITHUB_TOKEN not set"
exit 1
fi
# 等待确保 cleanup 完成
echo "⏰ Waiting 15 seconds for cleanup to complete..."
sleep 15
# 首先尝试通过标签获取 release(与 cleanup-release 相同的方法)
RELEASE_RESPONSE=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ github.ref_name }})
# 如果直接通过标签获取失败,则从所有 releases 中查找
if ! echo "$RELEASE_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then
echo "⚠️ Direct tag lookup failed, searching in all releases..."
echo "Direct response: $RELEASE_RESPONSE"
ALL_RELEASES=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases)
RELEASE_RESPONSE=$(echo "$ALL_RELEASES" | jq ".[] | select(.tag_name == \"${{ github.ref_name }}\")")
if ! echo "$RELEASE_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then
echo "❌ Could not find release for tag ${{ github.ref_name }}"
echo "Available releases:"
echo "$ALL_RELEASES" | jq -r '.[] | "- \(.tag_name) (ID: \(.id))"' | head -10
exit 1
fi
fi
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
TAG_NAME=$(echo "$RELEASE_RESPONSE" | jq -r '.tag_name')
ASSETS=$(echo "$RELEASE_RESPONSE" | jq -c '.assets')
echo "✅ Found release: $TAG_NAME (ID: $RELEASE_ID)"
echo "📦 Assets count: $(echo "$ASSETS" | jq length)"
# 检查是否是 untagged release
if echo "$ASSETS" | jq -r '.[0].browser_download_url' | grep -q "untagged"; then
echo "⚠️ WARNING: Found untagged release URLs!"
echo " This may indicate a draft release or tagging issue."
echo " Expected tag: ${{ github.ref_name }}"
echo " Found in URL: $(echo "$ASSETS" | jq -r '.[0].browser_download_url' | sed 's|.*/download/||' | sed 's|/.*||')"
fi
# 输出到 GitHub Actions outputs
echo "release_id=$RELEASE_ID" >> $GITHUB_OUTPUT
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
echo "assets<<EOF" >> $GITHUB_OUTPUT
echo "$ASSETS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "✅ Release info prepared for COS upload"
env:
GITHUB_TOKEN: ${{ secrets.atriordsa }}
- name: Download release assets
id: download
run: |
echo "📥 Starting download of release assets..."
# 创建下载目录
mkdir -p ./downloads
cd ./downloads
# 解析 assets JSON
echo '${{ steps.release.outputs.assets }}' > assets.json
# 使用 jq 处理每个 asset
jq -r '.[] | "\(.name)|\(.browser_download_url)"' assets.json | while IFS='|' read -r name url; do
echo "📥 Downloading: $name"
echo " URL: $url"
# 验证文件名不为空且符合预期格式
if [ -z "$name" ] || [ "$name" = "null" ]; then
echo "❌ Invalid file name: '$name'"
continue
fi
# 下载文件,使用认证
if curl -L -H "Authorization: token ${{ secrets.atriordsa }}" \
-H "Accept: application/octet-stream" \
--fail --show-error --silent \
-o "$name" "$url"; then
# 验证下载的文件大小
if [ -s "$name" ]; then
echo "✅ Downloaded: $name ($(du -h "$name" | cut -f1))"
else
echo "⚠️ Downloaded but file is empty: $name"
rm -f "$name"
fi
else
echo "❌ Failed to download: $name"
# 清理可能的空文件
rm -f "$name"
fi
done
echo "📂 Downloaded files:"
ls -la
# 验证下载的文件
echo "🔍 Verifying downloaded files..."
for file in *; do
if [ -f "$file" ] && [ "$file" != "assets.json" ]; then
echo "✅ $file: $(du -h "$file" | cut -f1) - $(file "$file" | cut -d: -f2-)"
fi
done
# 检查是否有文件名问题
if ls -1 | grep -q "^[^S].*AppImage$\|^[^S].*\.deb$\|^[^S].*\.dmg$\|^[^S].*\.exe$"; then
echo "⚠️ WARNING: Found files with potentially truncated names:"
ls -1 | grep "^[^S].*AppImage$\|^[^S].*\.deb$\|^[^S].*\.dmg$\|^[^S].*\.exe$" || true
fi
- name: Pre-upload file verification
run: |
echo "🔍 Final file verification before COS upload..."
cd ./downloads
# 移除 assets.json 文件以避免上传
rm -f assets.json
echo "Files to be uploaded:"
ls -la
echo ""
echo "File count: $(ls -1 | wc -l)"
echo ""
echo "Detailed file analysis:"
for file in *; do
if [ -f "$file" ]; then
echo "📄 $file:"
echo " Size: $(du -h "$file" | cut -f1)"
echo " Type: $(file "$file" | cut -d: -f2-)"
echo " Permissions: $(ls -l "$file" | cut -d' ' -f1)"
# 检查文件名是否包含特殊字符
if echo "$file" | grep -q '[^a-zA-Z0-9._-]'; then
echo " ⚠️ Contains special characters"
fi
fi
done
# 修复可能被截断的文件名
echo ""
echo "🔧 Checking for truncated filenames..."
fixed_count=0
for file in *; do
if [ -f "$file" ]; then
# 检查是否是被截断的文件名(缺少开头的S)
if echo "$file" | grep -q "^DUTOJCompetitionSideClient"; then
new_name="S$file"
echo "🔧 Renaming: $file -> $new_name"
mv "$file" "$new_name"
fixed_count=$((fixed_count + 1))
fi
fi
done
if [ $fixed_count -gt 0 ]; then
echo "✅ Fixed $fixed_count filename(s)"
echo ""
echo "Files after fixing:"
ls -la
else
echo "✅ No filename issues found"
fi
- name: Configure Tencent Cloud COS
uses: TencentCloud/cos-action@v1
with:
secret_id: ${{ secrets.COS_SECRET_ID }}
secret_key: ${{ secrets.COS_SECRET_KEY }}
cos_bucket: ${{ secrets.COS_BUCKET }}
cos_region: ${{ secrets.COS_REGION }}
local_path: ./downloads
remote_path: /releases/${{ steps.release.outputs.tag_name }}
clean: false
- name: Create release index
uses: actions/github-script@v7
with:
github-token: ${{ secrets.atriordsa }}
script: |
const fs = require('fs');
const path = require('path');
const tagName = '${{ steps.release.outputs.tag_name }}';
const cdnUrl = '${{ secrets.CDN_URL }}';
const versionPath = `releases/${tagName}`;
console.log('📋 Creating release index...');
// 读取下载的文件信息
const downloadDir = './downloads';
const files = [];
if (fs.existsSync(downloadDir)) {
const fileList = fs.readdirSync(downloadDir);
for (const fileName of fileList) {
if (fileName !== 'assets.json') {
const filePath = path.join(downloadDir, fileName);
const stats = fs.statSync(filePath);
const safeKey = `${versionPath}/${fileName}`.replace(/ /g, '_');
const cdnFileUrl = `${cdnUrl.replace(/\/$/, '')}/${safeKey}`;
files.push({
name: fileName,
cos_key: safeKey,
cdn_url: cdnFileUrl,
size: stats.size
});
console.log(`📄 ${fileName} -> ${cdnFileUrl}`);
}
}
}
// 创建索引数据
const indexData = {
version: tagName,
upload_time: Math.floor(Date.now() / 1000),
files: files,
total_files: files.length,
repository: context.repo.owner + '/' + context.repo.repo
};
// 保存索引文件
const indexFile = path.join(downloadDir, 'index.json');
fs.writeFileSync(indexFile, JSON.stringify(indexData, null, 2));
console.log(`✅ Created index with ${files.length} files`);
console.log(`📁 Version directory: ${cdnUrl.replace(/\/$/, '')}/${versionPath}/`);
- name: Upload index to COS
uses: TencentCloud/cos-action@v1
with:
secret_id: ${{ secrets.COS_SECRET_ID }}
secret_key: ${{ secrets.COS_SECRET_KEY }}
cos_bucket: ${{ secrets.COS_BUCKET }}
cos_region: ${{ secrets.COS_REGION }}
local_path: ./downloads/index.json
remote_path: /releases/${{ steps.release.outputs.tag_name }}/index.json
clean: false
- name: Display upload summary
run: |
echo "🎉 COS upload completed successfully!"
echo "📁 Version: ${{ steps.release.outputs.tag_name }}"
echo "🌐 CDN Base URL: ${{ secrets.CDN_URL }}"
echo "📂 Release Directory: ${{ secrets.CDN_URL }}/releases/${{ steps.release.outputs.tag_name }}/"
echo "📋 Index File: ${{ secrets.CDN_URL }}/releases/${{ steps.release.outputs.tag_name }}/index.json"