From c8470cf6032a4e85869d2f9e3b63250471674d24 Mon Sep 17 00:00:00 2001 From: Vaica <94612053+zqlit@users.noreply.github.com> Date: Thu, 25 Jun 2026 13:17:45 +0800 Subject: [PATCH] 1317 --- .github/workflows/deploy.yml | 227 ++++++++++++++++-- .../subset-font-safe.cpython-312.pyc | Bin 0 -> 17031 bytes 2 files changed, 204 insertions(+), 23 deletions(-) create mode 100644 scripts/__pycache__/subset-font-safe.cpython-312.pyc diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 59380d2c..897c9efd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -100,7 +100,8 @@ jobs: needs: pre-check if: | needs.pre-check.outputs.can_deploy == 'true' && - inputs.skip_font_subset != 'true' + inputs.skip_font_subset != 'true' && + (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') outputs: font_updated: ${{ steps.update.outputs.font_updated }} char_count: ${{ steps.stats.outputs.char_count }} @@ -326,6 +327,7 @@ jobs: name: hugo-public path: public/ retention-days: 1 + compression-level: 0 - name: Push Image Optimizations continue-on-error: true @@ -358,30 +360,75 @@ jobs: permissions: contents: read needs: build + outputs: + deployed: ${{ steps.check.outputs.deployed }} steps: - name: Start timing id: start-edgeone run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT + - name: Check build hash cache + id: cache-check + uses: actions/cache/restore@v4 + with: + path: .last_deploy_hash + key: ${{ runner.os }}-deploy-edgeone-${{ needs.build.outputs.build_hash }} + restore-keys: | + ${{ runner.os }}-deploy-edgeone- + + - name: Check if deployment is needed + id: check + run: | + CURRENT_HASH="${{ needs.build.outputs.build_hash }}" + echo "当前 build_hash: $CURRENT_HASH" + + if [ -f .last_deploy_hash ]; then + LAST_HASH=$(cat .last_deploy_hash) + echo "上次部署 hash: $LAST_HASH" + if [ "$CURRENT_HASH" = "$LAST_HASH" ]; then + echo "✅ 构建产物无变化,跳过 EdgeOne 部署" + echo "deployed=false" >> $GITHUB_OUTPUT + echo "skip_reason=hash_match" >> $GITHUB_OUTPUT + exit 0 + fi + fi + + echo "🔄 构建产物有变化,需要部署到 EdgeOne" + echo "deployed=true" >> $GITHUB_OUTPUT + - name: Checkout code (for scripts) + if: steps.check.outputs.deployed == 'true' uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download build artifact + if: steps.check.outputs.deployed == 'true' uses: actions/download-artifact@v4 with: name: hugo-public path: public/ - name: Deploy to EdgeOne Pages + if: steps.check.outputs.deployed == 'true' env: EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }} run: | chmod +x scripts/deploy_edgeone.sh ./scripts/deploy_edgeone.sh hugo-blog ./public overseas + - name: Save deploy hash + if: steps.check.outputs.deployed == 'true' + run: echo "${{ needs.build.outputs.build_hash }}" > .last_deploy_hash + + - name: Save build hash cache + if: steps.check.outputs.deployed == 'true' + uses: actions/cache/save@v4 + with: + path: .last_deploy_hash + key: ${{ runner.os }}-deploy-edgeone-${{ needs.build.outputs.build_hash }} + - name: End timing id: end-edgeone run: | @@ -389,7 +436,12 @@ jobs: start_time=${{ steps.start-edgeone.outputs.start_time }} duration=$((end_time - start_time)) echo "duration=${duration}" >> $GITHUB_OUTPUT - echo "## ⏱️ Deploy EdgeOne 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.check.outputs.deployed }}" = "true" ]; then + echo "## ⏱️ Deploy EdgeOne 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY + else + echo "## EdgeOne 部署已跳过(构建产物无变化)" >> $GITHUB_STEP_SUMMARY + echo "总耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY + fi # ============================================================ # Job 4: Deploy to UpYun (并行) @@ -400,19 +452,72 @@ jobs: permissions: contents: read needs: build + outputs: + deployed: ${{ steps.check.outputs.deployed }} steps: - name: Start timing id: start-upyun run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT + - name: Check build hash cache + id: cache-check + uses: actions/cache/restore@v4 + with: + path: .last_deploy_hash + key: ${{ runner.os }}-deploy-upyun-${{ needs.build.outputs.build_hash }} + restore-keys: | + ${{ runner.os }}-deploy-upyun- + + - name: Check if deployment is needed + id: check + run: | + CURRENT_HASH="${{ needs.build.outputs.build_hash }}" + echo "当前 build_hash: $CURRENT_HASH" + + if [ -f .last_deploy_hash ]; then + LAST_HASH=$(cat .last_deploy_hash) + echo "上次部署 hash: $LAST_HASH" + if [ "$CURRENT_HASH" = "$LAST_HASH" ]; then + echo "✅ 构建产物无变化,跳过 UpYun 部署" + echo "deployed=false" >> $GITHUB_OUTPUT + echo "skip_reason=hash_match" >> $GITHUB_OUTPUT + exit 0 + fi + fi + + echo "🔄 构建产物有变化,需要部署到 UpYun" + echo "deployed=true" >> $GITHUB_OUTPUT + + - name: Start artifact download timing + if: steps.check.outputs.deployed == 'true' + id: start-upyun-download + run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT + - name: Download build artifact + if: steps.check.outputs.deployed == 'true' uses: actions/download-artifact@v4 with: name: hugo-public path: public/ + - name: End artifact download timing + if: steps.check.outputs.deployed == 'true' + id: end-upyun-download + run: | + end_time=$(date +%s) + start_time=${{ steps.start-upyun-download.outputs.start_time }} + duration=$((end_time - start_time)) + echo "duration=${duration}" >> $GITHUB_OUTPUT + echo "## UpYun artifact download duration: ${duration}s" >> $GITHUB_STEP_SUMMARY + + - name: Start UpYun upload timing + if: steps.check.outputs.deployed == 'true' + id: start-upyun-upload + run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT + - name: Deploy to UpYun + if: steps.check.outputs.deployed == 'true' uses: her-cat/upyun-deployer@v1.0.4 with: bucket: ${{ secrets.UPYUN_BUCKET }} @@ -421,24 +526,46 @@ jobs: dir: 'public' publish_dir: '/' + - name: Save deploy hash + if: steps.check.outputs.deployed == 'true' + run: echo "${{ needs.build.outputs.build_hash }}" > .last_deploy_hash + + - name: Save build hash cache + if: steps.check.outputs.deployed == 'true' + uses: actions/cache/save@v4 + with: + path: .last_deploy_hash + key: ${{ runner.os }}-deploy-upyun-${{ needs.build.outputs.build_hash }} + - name: End timing id: end-upyun run: | end_time=$(date +%s) - start_time=${{ steps.start-upyun.outputs.start_time }} - duration=$((end_time - start_time)) - echo "duration=${duration}" >> $GITHUB_OUTPUT - echo "## ⏱️ Deploy UpYun 耗时: ${duration}秒" >> $GITHUB_STEP_SUMMARY + total_start_time=${{ steps.start-upyun.outputs.start_time }} + total_duration=$((end_time - total_start_time)) + echo "duration=${total_duration}" >> $GITHUB_OUTPUT + + if [ "${{ steps.check.outputs.deployed }}" = "true" ]; then + upload_start_time=${{ steps.start-upyun-upload.outputs.start_time }} + upload_duration=$((end_time - upload_start_time)) + echo "upload_duration=${upload_duration}" >> $GITHUB_OUTPUT + echo "## Deploy UpYun total duration: ${total_duration}s" >> $GITHUB_STEP_SUMMARY + echo "## UpYun upload duration: ${upload_duration}s" >> $GITHUB_STEP_SUMMARY + else + echo "## UpYun 部署已跳过(构建产物无变化)" >> $GITHUB_STEP_SUMMARY + echo "总耗时: ${total_duration}s" >> $GITHUB_STEP_SUMMARY + fi # ============================================================ - # Job 5: CDN Refresh & Notifications (在所有部署完成后) + # Job 5: CDN Refresh & Notifications (after all deployments) # ============================================================ finalize: runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read - needs: [deploy-edgeone, deploy-upyun] + needs: [pre-check, subset-fonts, build, deploy-edgeone, deploy-upyun] + if: always() && needs.build.result == 'success' && needs.deploy-edgeone.result == 'success' && needs.deploy-upyun.result == 'success' steps: - name: Start timing @@ -459,7 +586,23 @@ jobs: - name: Install Node.js dependencies run: npm ci + - name: Determine deployment status + id: deploy-status + run: | + EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" + UPYUN_DEPLOYED="${{ needs.deploy-upyun.outputs.deployed }}" + + echo "EdgeOne deployed: $EDGEONE_DEPLOYED" + echo "UpYun deployed: $UPYUN_DEPLOYED" + + if [ "$EDGEONE_DEPLOYED" = "true" ] || [ "$UPYUN_DEPLOYED" = "true" ]; then + echo "any_deployed=true" >> $GITHUB_OUTPUT + else + echo "any_deployed=false" >> $GITHUB_OUTPUT + fi + - name: Refresh DogeCloud CDN + if: steps.deploy-status.outputs.any_deployed == 'true' env: DOGECLOUD_ACCESS_KEY: ${{ secrets.DOGECLOUD_ACCESS_KEY }} DOGECLOUD_SECRET_KEY: ${{ secrets.DOGECLOUD_SECRET_KEY }} @@ -467,6 +610,7 @@ jobs: run: node scripts/refresh_cdn.js - name: Generate Notification Report + if: steps.deploy-status.outputs.any_deployed == 'true' continue-on-error: true run: node scripts/generate_notification_report.js @@ -480,7 +624,17 @@ jobs: exit 0 fi - MESSAGE="✅ **部署成功** + EDGEONE_STATUS="${{ needs.deploy-edgeone.outputs.deployed == 'true' && '✅' || '⏭️ 跳过' }}" + UPYUN_STATUS="${{ needs.deploy-upyun.outputs.deployed == 'true' && '✅' || '⏭️ 跳过' }}" + CDN_STATUS="${{ steps.deploy-status.outputs.any_deployed == 'true' && '✅' || '⏭️ 跳过' }}" + + if [ "${{ steps.deploy-status.outputs.any_deployed }}" = "true" ]; then + TITLE="✅ **部署成功**" + else + TITLE="ℹ️ **无变化,跳过部署**" + fi + + MESSAGE="${TITLE} 📦 项目: ${{ github.repository }} 🌿 分支: ${{ github.ref_name }} @@ -488,9 +642,9 @@ jobs: 🕐 时间: $(date +'%Y-%m-%d %H:%M:%S') 📊 部署目标: - - EdgeOne Pages ✓ - - UpYun ✓ - - CDN 刷新 ✓" + - EdgeOne Pages ${EDGEONE_STATUS} + - UpYun ${UPYUN_STATUS} + - CDN 刷新 ${CDN_STATUS}" curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d chat_id="${TELEGRAM_CHAT_ID}" \ @@ -506,21 +660,33 @@ jobs: exit 0 fi + EDGEONE_STATUS="${{ needs.deploy-edgeone.outputs.deployed == 'true' && '✅' || '⏭️ 跳过' }}" + UPYUN_STATUS="${{ needs.deploy-upyun.outputs.deployed == 'true' && '✅' || '⏭️ 跳过' }}" + CDN_STATUS="${{ steps.deploy-status.outputs.any_deployed == 'true' && '✅' || '⏭️ 跳过' }}" + + if [ "${{ steps.deploy-status.outputs.any_deployed }}" = "true" ]; then + TITLE="✅ **部署成功**" + else + TITLE="ℹ️ **无变化,跳过部署**" + fi + CURRENT_TIME=$(date +'%Y-%m-%d %H:%M:%S') curl -s -X POST "${FEISHU_WEBHOOK_URL}" \ -H "Content-Type: application/json" \ -d '{ "msg_type": "text", "content": { - "text": "✅ **部署成功**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n🔗 Commit: ${{ github.sha }}\n🕐 时间: '"${CURRENT_TIME}"'\n\n📊 部署目标:\n- EdgeOne Pages ✓\n- UpYun ✓\n- CDN 刷新 ✓" + "text": "'"${TITLE}"'\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n🔗 Commit: ${{ github.sha }}\n🕐 时间: '"${CURRENT_TIME}"'\n\n📊 部署目标:\n- EdgeOne Pages '"${EDGEONE_STATUS}"'\n- UpYun '"${UPYUN_STATUS}"'\n- CDN 刷新 '"${CDN_STATUS}"'" } }' - name: Get current date (CN) + if: steps.deploy-status.outputs.any_deployed == 'true' id: date run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT - name: Send deployment notification + if: steps.deploy-status.outputs.any_deployed == 'true' uses: dawidd6/action-send-mail@v3 with: server_address: smtp.qq.com @@ -541,18 +707,33 @@ jobs: start_time=${{ steps.start-finalize.outputs.start_time }} finalize_duration=$((end_time - start_time)) + EDGEONE_DEPLOYED="${{ needs.deploy-edgeone.outputs.deployed }}" + UPYUN_DEPLOYED="${{ needs.deploy-upyun.outputs.deployed }}" + + if [ "$EDGEONE_DEPLOYED" = "true" ]; then + EDGEONE_DISPLAY="✅ 已部署" + else + EDGEONE_DISPLAY="⏭️ 跳过(无变化)" + fi + + if [ "$UPYUN_DEPLOYED" = "true" ]; then + UPYUN_DISPLAY="✅ 已部署" + else + UPYUN_DISPLAY="⏭️ 跳过(无变化)" + fi + echo "## ⏱️ 部署耗时统计" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "| 阶段 | 耗时 |" >> $GITHUB_STEP_SUMMARY + echo "| 阶段 | 状态 |" >> $GITHUB_STEP_SUMMARY echo "|------|------|" >> $GITHUB_STEP_SUMMARY - echo "| Pre-check | ${{ needs.pre-check.result == 'success' && '✅' || '⏭️' }} |" >> $GITHUB_STEP_SUMMARY - echo "| Subset-fonts | ${{ needs.build.result == 'success' && '✅' || '⏭️' }} |" >> $GITHUB_STEP_SUMMARY - echo "| Build | ${{ needs.build.result == 'success' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY - echo "| Deploy EdgeOne | ${{ needs.deploy-edgeone.result == 'success' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY - echo "| Deploy UpYun | ${{ needs.deploy-upyun.result == 'success' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY - echo "| Finalize | ✅ (${finalize_duration}秒) |" >> $GITHUB_STEP_SUMMARY + echo "| Pre-check | ${{ needs.pre-check.result == 'success' && '✅ 成功' || needs.pre-check.result == 'skipped' && '⏭️ 跳过' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Subset-fonts | ${{ needs.subset-fonts.result == 'success' && '✅ 成功' || needs.subset-fonts.result == 'skipped' && '⏭️ 跳过' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Build | ${{ needs.build.result == 'success' && '✅ 成功' || '❌ 失败' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Deploy EdgeOne | ${EDGEONE_DISPLAY} |" >> $GITHUB_STEP_SUMMARY + echo "| Deploy UpYun | ${UPYUN_DISPLAY} |" >> $GITHUB_STEP_SUMMARY + echo "| Finalize | ✅ 成功 (${finalize_duration}秒) |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "**总计耗时:** ${finalize_duration}秒 (finalize 阶段)" >> $GITHUB_STEP_SUMMARY + echo "**Finalize 阶段耗时:** ${finalize_duration}秒" >> $GITHUB_STEP_SUMMARY # ============================================================ # Job 6: Failure Notification (失败通知) @@ -562,8 +743,8 @@ jobs: timeout-minutes: 3 permissions: contents: read - needs: [build, deploy-edgeone, deploy-upyun, finalize] - if: failure() + needs: [pre-check, subset-fonts, build, deploy-edgeone, deploy-upyun, finalize] + if: always() && (needs.pre-check.result == 'failure' || needs.build.result == 'failure' || needs.deploy-edgeone.result == 'failure' || needs.deploy-upyun.result == 'failure' || needs.finalize.result == 'failure') steps: - name: Send Telegram failure notification diff --git a/scripts/__pycache__/subset-font-safe.cpython-312.pyc b/scripts/__pycache__/subset-font-safe.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb77221447392cf76f8c9001200324d909901886 GIT binary patch literal 17031 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