replace Python SDK with JavaScript SDK for COS upload #82
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | |
# 获取 assets | |
echo "📁 Getting release assets..." | |
ASSETS_RESPONSE=$(curl -s -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 "❌ Failed to get assets" | |
echo "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 | |
echo "🏁 Cleanup completed successfully!" | |
env: | |
GITHUB_TOKEN: ${{ secrets.atriordsa }} | |
upload-to-cos: | |
runs-on: ubuntu-latest | |
timeout-minutes: 180 # 3小时超时,适合大文件上传 | |
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)" | |
# 输出到 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 | |
env: | |
GITHUB_TOKEN: ${{ secrets.atriordsa }} | |
run: | | |
echo "📥 Starting download of release assets..." | |
# 验证环境变量 | |
if [ -z "$GITHUB_TOKEN" ]; then | |
echo "❌ GITHUB_TOKEN not set" | |
exit 1 | |
fi | |
# 创建下载目录 | |
mkdir -p ./downloads | |
cd ./downloads | |
# 解析 assets JSON | |
echo '${{ steps.release.outputs.assets }}' > assets.json | |
# 检查是否有assets | |
ASSET_COUNT=$(jq 'length' assets.json) | |
echo "📦 Found $ASSET_COUNT assets to download" | |
if [ "$ASSET_COUNT" -eq 0 ]; then | |
echo "❌ No assets found to download" | |
exit 1 | |
fi | |
# 直接从tagged release构建正确的URL | |
TAG_NAME="${{ steps.release.outputs.tag_name }}" | |
REPO="${{ github.repository }}" | |
# 使用 jq 处理每个 asset,但构建正确的URL | |
jq -r '.[] | .name' assets.json | while read -r name; do | |
echo "📥 Downloading: $name" | |
# 构建正确的下载URL(使用tag而不是untagged) | |
CORRECT_URL="https://github.com/${REPO}/releases/download/${TAG_NAME}/${name}" | |
echo " URL: $CORRECT_URL" | |
# 下载文件,使用认证和更详细的错误处理 | |
if curl -L -H "Authorization: token $GITHUB_TOKEN" \ | |
-H "Accept: application/octet-stream" \ | |
--fail --show-error --silent \ | |
--connect-timeout 30 \ | |
--max-time 300 \ | |
-o "$name" "$CORRECT_URL"; then | |
echo "✅ Downloaded: $name ($(du -h "$name" | cut -f1))" | |
# 验证文件不为空 | |
if [ ! -s "$name" ]; then | |
echo "❌ Downloaded file is empty: $name" | |
exit 1 | |
fi | |
else | |
echo "❌ Failed to download: $name" | |
echo "❌ curl exit code: $?" | |
# 尝试无认证下载(对于公开文件) | |
echo "🔄 Trying without authentication..." | |
if curl -L --fail --show-error --silent \ | |
--connect-timeout 30 \ | |
--max-time 300 \ | |
-o "$name" "$CORRECT_URL"; then | |
echo "✅ Downloaded without auth: $name ($(du -h "$name" | cut -f1))" | |
else | |
echo "❌ Failed even without authentication" | |
echo "❌ Trying alternative URL construction..." | |
# 最后尝试:使用GitHub API下载 | |
ASSET_ID=$(echo '${{ steps.release.outputs.assets }}' | jq -r ".[] | select(.name == \"$name\") | .id") | |
if [ -n "$ASSET_ID" ] && [ "$ASSET_ID" != "null" ]; then | |
echo "🔄 Trying GitHub API download (Asset ID: $ASSET_ID)..." | |
if curl -L -H "Authorization: token $GITHUB_TOKEN" \ | |
-H "Accept: application/octet-stream" \ | |
--fail --show-error --silent \ | |
--connect-timeout 30 \ | |
--max-time 300 \ | |
-o "$name" \ | |
"https://api.github.com/repos/${REPO}/releases/assets/${ASSET_ID}"; then | |
echo "✅ Downloaded via API: $name ($(du -h "$name" | cut -f1))" | |
else | |
echo "❌ API download also failed" | |
exit 1 | |
fi | |
else | |
echo "❌ Could not find asset ID" | |
exit 1 | |
fi | |
fi | |
fi | |
done | |
echo "📄 Downloaded files:" | |
ls -la | |
- 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); | |
// CDN URL 应该匹配实际的上传路径 | |
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 all files to COS using JavaScript SDK | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.atriordsa }} | |
script: | | |
const fs = require('fs'); | |
const path = require('path'); | |
console.log('� Setting up Tencent Cloud COS upload with JavaScript SDK...'); | |
// 安装腾讯云COS JavaScript SDK | |
console.log('� Installing Tencent Cloud COS JavaScript SDK...'); | |
const { execSync } = require('child_process'); | |
try { | |
execSync('npm install cos-nodejs-sdk-v5', { stdio: 'inherit' }); | |
console.log('✅ COS JavaScript SDK installed successfully'); | |
} catch (error) { | |
console.error('❌ Failed to install COS SDK:', error.message); | |
process.exit(1); | |
} | |
// 导入COS SDK | |
console.log('📦 Importing COS SDK...'); | |
const COS = require('cos-nodejs-sdk-v5'); | |
console.log('✅ COS SDK imported successfully'); | |
// 从环境变量获取配置 | |
console.log('🔍 Reading environment variables...'); | |
const secretId = process.env.COS_SECRET_ID; | |
const secretKey = process.env.COS_SECRET_KEY; | |
const region = process.env.COS_REGION; | |
const bucket = process.env.COS_BUCKET; | |
const tagName = process.env.TAG_NAME; | |
if (!secretId || !secretKey || !region || !bucket || !tagName) { | |
console.error('❌ Missing required environment variables'); | |
process.exit(1); | |
} | |
console.log(`🔧 Configuring COS client for region: ${region}, bucket: ${bucket}`); | |
// 创建COS客户端 | |
const cos = new COS({ | |
SecretId: secretId, | |
SecretKey: secretKey, | |
Timeout: 1800000, // 30分钟超时,适合大文件上传 | |
}); | |
console.log('✅ COS client created successfully'); | |
// 工具函数:格式化文件大小 | |
function formatFileSize(bytes) { | |
if (bytes === 0) return '0B'; | |
const k = 1024; | |
const sizes = ['B', 'KB', 'MB', 'GB']; | |
const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + sizes[i]; | |
} | |
// 检查下载目录 | |
console.log('📁 Checking downloads directory...'); | |
const downloadsDir = './downloads'; | |
if (!fs.existsSync(downloadsDir)) { | |
console.error('❌ Downloads directory not found'); | |
process.exit(1); | |
} | |
// 扫描文件 | |
console.log(`📁 Scanning directory: ${downloadsDir}`); | |
const allFiles = fs.readdirSync(downloadsDir); | |
const files = allFiles.filter(f => { | |
const filePath = path.join(downloadsDir, f); | |
return fs.statSync(filePath).isFile() && f !== 'assets.json'; | |
}); | |
console.log(`📦 Found ${files.length} files to upload`); | |
// 计算总大小 | |
let totalSize = 0; | |
const fileSizes = {}; | |
for (const filename of files) { | |
const filePath = path.join(downloadsDir, filename); | |
const size = fs.statSync(filePath).size; | |
fileSizes[filename] = size; | |
totalSize += size; | |
} | |
console.log(`📊 Total size to upload: ${formatFileSize(totalSize)}`); | |
// 上传文件函数 | |
function uploadFile(filePath, key) { | |
return new Promise((resolve, reject) => { | |
const startTime = Date.now(); | |
cos.uploadFile({ | |
Bucket: bucket, | |
Region: region, | |
Key: key, | |
FilePath: filePath, | |
onProgress: function(progressData) { | |
// 可选:显示上传进度 | |
// console.log(`Progress: ${Math.round(progressData.percent * 100)}%`); | |
} | |
}, function(err, data) { | |
const uploadTime = (Date.now() - startTime) / 1000; | |
if (err) { | |
console.error(`❌ Upload failed: ${err.message}`); | |
reject(err); | |
} else { | |
const fileSize = fileSizes[path.basename(filePath)]; | |
const speed = uploadTime > 0 ? fileSize / uploadTime : 0; | |
console.log(`✅ Successfully uploaded: ${path.basename(filePath)}`); | |
console.log(` Time: ${uploadTime.toFixed(1)}s, Speed: ${formatFileSize(speed)}/s`); | |
resolve(data); | |
} | |
}); | |
}); | |
} | |
// 开始上传 | |
const uploadedFiles = []; | |
let uploadedSize = 0; | |
let success = true; | |
for (let i = 0; i < files.length; i++) { | |
const filename = files[i]; | |
const filePath = path.join(downloadsDir, filename); | |
const fileSize = fileSizes[filename]; | |
try { | |
const key = `atrior/oj-competition-side-client/releases/${tagName}/${filename}`; | |
console.log(`📤 [${i + 1}/${files.length}] Uploading: ${filename} (${formatFileSize(fileSize)})`); | |
console.log(` Local: ${filePath}`); | |
console.log(` Remote: ${key}`); | |
await uploadFile(filePath, key); | |
uploadedSize += fileSize; | |
const progress = (uploadedSize / totalSize * 100).toFixed(1); | |
console.log(` Progress: ${uploadedSize}/${totalSize} (${progress}%)`); | |
uploadedFiles.push(filename); | |
} catch (error) { | |
console.error(`❌ Failed to upload ${filename}: ${error.message}`); | |
success = false; | |
} | |
} | |
// 上传摘要 | |
console.log('\n📊 Upload Summary:'); | |
console.log(`✅ Successfully uploaded: ${uploadedFiles.length} files`); | |
if (uploadedFiles.length > 0) { | |
uploadedFiles.forEach(f => console.log(` - ${f}`)); | |
} | |
if (success) { | |
console.log(`\n🎉 All ${files.length} files uploaded successfully to COS!`); | |
console.log(`📂 Remote path: atrior/oj-competition-side-client/releases/${tagName}/`); | |
// 上传index.json | |
const indexFile = path.join(downloadsDir, 'index.json'); | |
if (fs.existsSync(indexFile)) { | |
try { | |
const indexSize = fs.statSync(indexFile).size; | |
const indexKey = `atrior/oj-competition-side-client/releases/${tagName}/index.json`; | |
console.log(`📋 Uploading index.json (${formatFileSize(indexSize)})...`); | |
await uploadFile(indexFile, indexKey); | |
console.log('✅ Successfully uploaded index.json'); | |
} catch (error) { | |
console.error(`❌ Failed to upload index.json: ${error.message}`); | |
} | |
} | |
} else { | |
console.error('\n❌ Some uploads failed'); | |
process.exit(1); | |
} | |
env: | |
COS_SECRET_ID: ${{ secrets.COS_SECRET_ID }} | |
COS_SECRET_KEY: ${{ secrets.COS_SECRET_KEY }} | |
COS_REGION: ${{ secrets.COS_REGION }} | |
COS_BUCKET: ${{ secrets.COS_BUCKET }} | |
TAG_NAME: ${{ steps.release.outputs.tag_name }} | |
- 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" |