656 lines
25 KiB
YAML
656 lines
25 KiB
YAML
name: Release
|
||
|
||
on:
|
||
push:
|
||
tags:
|
||
- "v*.*.*"
|
||
# 允許手動觸發
|
||
workflow_dispatch:
|
||
inputs:
|
||
tag:
|
||
description: "Tag to release (e.g., v0.1.13)"
|
||
required: true
|
||
type: string
|
||
|
||
env:
|
||
DOCKER_IMAGE: convertx/convertx-cn
|
||
|
||
jobs:
|
||
# ==============================================================================
|
||
# 🐳 Build and Push Docker Image(關鍵流程)
|
||
# ==============================================================================
|
||
build-and-push:
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
contents: read
|
||
packages: write
|
||
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
|
||
# ========================================
|
||
# 清理磁碟空間(解決 no space left on device)
|
||
# ========================================
|
||
# 🔥 GitHub Actions Runner 只有約 14GB 可用空間
|
||
# 大型模型 + Multi-Arch build 需要清理更多預裝軟體
|
||
# ========================================
|
||
- name: Free disk space (aggressive)
|
||
run: |
|
||
echo "🧹 Aggressive disk cleanup for large model builds..."
|
||
echo "========================================"
|
||
echo "📊 初始磁碟空間:"
|
||
df -h /
|
||
echo "========================================"
|
||
|
||
# ========================================
|
||
# 移除大型預裝軟體(約可釋放 30-40GB)
|
||
# ========================================
|
||
echo "🗑️ 移除預裝開發工具..."
|
||
|
||
# .NET SDK(約 2-3GB)
|
||
sudo rm -rf /usr/share/dotnet || true
|
||
|
||
# Android SDK(約 10-15GB)- 最大宗!
|
||
sudo rm -rf /usr/local/lib/android || true
|
||
|
||
# Haskell/GHC(約 1GB)
|
||
sudo rm -rf /opt/ghc || true
|
||
|
||
# CodeQL(約 1GB)
|
||
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
|
||
|
||
# Boost(約 500MB)
|
||
sudo rm -rf /usr/local/share/boost || true
|
||
|
||
# Swift(約 1.5GB)
|
||
sudo rm -rf /usr/share/swift || true
|
||
|
||
# Hosted Tool Cache(約 5-8GB)
|
||
sudo rm -rf /opt/hostedtoolcache || true
|
||
|
||
# Azure CLI(約 500MB)
|
||
sudo rm -rf /usr/share/az_* || true
|
||
sudo rm -rf /opt/az || true
|
||
|
||
# Google Cloud SDK(約 500MB)
|
||
sudo rm -rf /usr/lib/google-cloud-sdk || true
|
||
|
||
# PowerShell(約 200MB)
|
||
sudo rm -rf /usr/local/share/powershell || true
|
||
|
||
# Miniconda(約 500MB)
|
||
sudo rm -rf /usr/share/miniconda || true
|
||
|
||
# ========================================
|
||
# 清理系統 cache
|
||
# ========================================
|
||
echo "🗑️ 清理系統 cache..."
|
||
|
||
# apt cache
|
||
sudo apt-get clean || true
|
||
sudo apt-get autoremove -y || true
|
||
sudo rm -rf /var/lib/apt/lists/* || true
|
||
|
||
# npm/yarn global cache
|
||
sudo rm -rf /usr/local/share/.cache || true
|
||
|
||
# ========================================
|
||
# 清理 Docker 舊資料
|
||
# ========================================
|
||
echo "🗑️ 清理 Docker cache..."
|
||
docker system prune -af --volumes || true
|
||
|
||
# 清理 BuildKit cache
|
||
sudo rm -rf /var/lib/docker/buildkit || true
|
||
|
||
echo "========================================"
|
||
echo "📊 清理後磁碟空間:"
|
||
df -h /
|
||
echo "========================================"
|
||
|
||
- name: Get tag name
|
||
id: tag
|
||
run: |
|
||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||
echo "version=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
- name: Set up QEMU
|
||
uses: docker/setup-qemu-action@v3
|
||
|
||
# ========================================
|
||
# 設定 Docker Buildx(針對大型模型優化)
|
||
# ========================================
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
with:
|
||
# 🔥 使用 docker-container driver 以獲得更好的 cache 控制
|
||
driver-opts: |
|
||
image=moby/buildkit:latest
|
||
network=host
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@v3
|
||
with:
|
||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: Login to GitHub Container Registry
|
||
uses: docker/login-action@v3
|
||
with:
|
||
registry: ghcr.io
|
||
username: ${{ github.actor }}
|
||
password: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
# ========================================
|
||
# Build and Push(針對大型模型優化)
|
||
# ========================================
|
||
# 🔥 注意事項:
|
||
# - 使用 registry cache 而非 GHA cache(避免 10GB 限制)
|
||
# - 啟用 zstd compression 減少傳輸量和儲存空間
|
||
# - Multi-arch build 需要更多磁碟空間,已在上方清理
|
||
# ========================================
|
||
- name: Build and push Docker image
|
||
uses: docker/build-push-action@v6
|
||
with:
|
||
context: .
|
||
push: true
|
||
tags: |
|
||
${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}
|
||
${{ env.DOCKER_IMAGE }}:latest
|
||
platforms: linux/amd64,linux/arm64
|
||
# 🔥 使用 registry cache 取代 GHA cache
|
||
# registry cache 沒有 10GB 限制,更適合大型模型
|
||
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache
|
||
cache-to: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache,mode=max,compression=zstd
|
||
# 啟用壓縮減少 image 大小
|
||
outputs: type=image,compression=zstd,compression-level=3
|
||
# 設定 build 參數
|
||
build-args: |
|
||
BUILDKIT_INLINE_CACHE=1
|
||
|
||
- name: Docker Build Summary
|
||
run: |
|
||
echo "## 🐳 Docker Image Published" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "**Repository:** \`${{ env.DOCKER_IMAGE }}\`" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
|
||
echo "- \`${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
|
||
echo "- \`${{ env.DOCKER_IMAGE }}:latest\`" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "**Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
|
||
|
||
# ==============================================================================
|
||
# 🔍 Verify Docker Image(模型驗證 + Headless 驗證)
|
||
# ==============================================================================
|
||
verify-image:
|
||
name: Verify Docker Image
|
||
runs-on: ubuntu-latest
|
||
needs: build-and-push
|
||
timeout-minutes: 45
|
||
|
||
steps:
|
||
- name: Get tag name
|
||
id: tag
|
||
run: |
|
||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||
echo "version=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
- name: Pull Docker image
|
||
timeout-minutes: 25
|
||
run: |
|
||
echo "📥 Pulling Docker image(大型 image,可能需要 10-20 分鐘)..."
|
||
echo "Image size: ~8-12 GB(含預下載模型)"
|
||
docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}
|
||
|
||
# ========================================
|
||
# 驗證 1: 工具存在性 + 版本檢查(使用 xvfb 解決 GUI 問題)
|
||
# ========================================
|
||
- name: "🔍 Tools & Model Verification"
|
||
timeout-minutes: 8
|
||
continue-on-error: true # 🔥 版本檢查失敗不應阻擋後續 E2E 測試
|
||
run: |
|
||
echo "========================================"
|
||
echo "🔍 工具存在性 + 模型驗證"
|
||
echo "========================================"
|
||
|
||
IMAGE="${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}"
|
||
|
||
# 🔥 關鍵修復:
|
||
# 1. 為每個 GUI 工具版本檢查添加 timeout 防止無限掛起
|
||
# 2. 使用 timeout 命令限制每個工具的執行時間
|
||
# 3. 增加整體 timeout-minutes 從 5 到 8 分鐘
|
||
docker run --rm "${IMAGE}" sh -c '
|
||
echo ""
|
||
echo "========================================"
|
||
echo "🔧 CLI 工具版本檢查"
|
||
echo "========================================"
|
||
|
||
# 純 CLI 工具 - 直接執行
|
||
echo "ConvertX $(cat /app/package.json 2>/dev/null | grep version | head -1 | cut -d\" -f4)"
|
||
echo "Bun $(bun --version 2>/dev/null || echo "N/A")"
|
||
pandoc --version 2>/dev/null | head -1 || echo "pandoc: N/A"
|
||
ffmpeg -version 2>/dev/null | head -1 || echo "ffmpeg: N/A"
|
||
tesseract --version 2>&1 | head -1 || echo "tesseract: N/A"
|
||
vips --version 2>/dev/null || echo "vips: N/A"
|
||
gm -version 2>/dev/null | head -1 || echo "GraphicsMagick: N/A"
|
||
resvg --version 2>/dev/null || echo "resvg: N/A"
|
||
potrace --version 2>/dev/null | head -1 || echo "potrace: N/A"
|
||
dasel --version 2>/dev/null || echo "dasel: N/A"
|
||
djxl --version 2>&1 | head -1 || echo "djxl: N/A"
|
||
deark -version 2>/dev/null | head -1 || echo "deark: N/A"
|
||
|
||
echo ""
|
||
echo "========================================"
|
||
echo "🖥️ GUI 工具版本檢查(使用 xvfb-run + timeout)"
|
||
echo "========================================"
|
||
|
||
# GUI 工具 - 使用 timeout + xvfb-run 防止掛起
|
||
# timeout 60 秒,超過就跳過該工具
|
||
echo -n "Inkscape: "
|
||
timeout 60 xvfb-run -a --server-args="-screen 0 1024x768x24" inkscape --version 2>/dev/null || echo "N/A (timeout or error)"
|
||
|
||
echo -n "Calibre: "
|
||
timeout 60 xvfb-run -a --server-args="-screen 0 1024x768x24" ebook-convert --version 2>/dev/null | head -1 || echo "N/A (timeout or error)"
|
||
|
||
echo -n "LibreOffice: "
|
||
timeout 60 xvfb-run -a --server-args="-screen 0 1024x768x24" libreoffice --version 2>/dev/null || echo "N/A (timeout or error)"
|
||
|
||
echo ""
|
||
echo "========================================"
|
||
echo "🤖 模型目錄檢查"
|
||
echo "========================================"
|
||
|
||
echo -n " HuggingFace cache: "
|
||
[ -d "/root/.cache/huggingface" ] && echo "✅ 存在" || echo "⚠️ 不存在"
|
||
|
||
echo -n " ModelScope cache: "
|
||
[ -d "/root/.cache/modelscope" ] && echo "✅ 存在" || echo "⚠️ 不存在"
|
||
|
||
echo -n " BabelDOC cache: "
|
||
[ -d "/root/.cache/babeldoc" ] && echo "✅ 存在" || echo "⚠️ 不存在"
|
||
|
||
echo ""
|
||
echo "========================================"
|
||
echo "✅ 工具驗證完成!"
|
||
echo "========================================"
|
||
'
|
||
|
||
# ========================================
|
||
# 驗證 2: 啟動容器並檢查健康狀態
|
||
# ========================================
|
||
- name: "⚙️ Start Container & Health Check"
|
||
timeout-minutes: 3
|
||
run: |
|
||
echo "========================================"
|
||
echo "⚙️ 啟動容器並檢查健康狀態"
|
||
echo "========================================"
|
||
|
||
IMAGE="${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}"
|
||
|
||
# 啟動容器
|
||
echo "🚀 啟動容器..."
|
||
docker run -d --name verify-test -p 3000:3000 "${IMAGE}"
|
||
|
||
# 等待啟動
|
||
echo "⏳ 等待容器啟動..."
|
||
for i in {1..90}; do
|
||
if docker exec verify-test curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then
|
||
echo "✅ 容器在 ${i} 秒內啟動成功"
|
||
break
|
||
fi
|
||
if [ $i -eq 90 ]; then
|
||
echo "❌ 容器啟動超時"
|
||
docker logs verify-test
|
||
docker rm -f verify-test
|
||
exit 1
|
||
fi
|
||
sleep 1
|
||
done
|
||
|
||
# 顯示容器日誌
|
||
echo ""
|
||
echo "📋 容器啟動日誌:"
|
||
docker logs verify-test 2>&1 | tail -20
|
||
|
||
echo ""
|
||
echo "========================================"
|
||
echo "✅ 容器健康檢查通過!"
|
||
echo "========================================"
|
||
|
||
# ========================================
|
||
# 驗證 3: E2E 轉換測試(使用 xvfb-run 解決 GUI 問題)
|
||
# ========================================
|
||
- name: "🧪 E2E Tests in Container"
|
||
timeout-minutes: 10
|
||
run: |
|
||
echo "========================================"
|
||
echo "🧪 E2E 轉換測試"
|
||
echo "========================================"
|
||
|
||
# 使用已啟動的 verify-test 容器
|
||
# 🔥 關鍵:GUI 工具使用 xvfb-run 包裹,設置 DISPLAY 環境變數
|
||
|
||
FAILED=0
|
||
INKSCAPE_RESULT="❌"
|
||
LIBREOFFICE_RESULT="❌"
|
||
CALIBRE_RESULT="❌"
|
||
PANDOC_RESULT="❌"
|
||
FFMPEG_RESULT="❌"
|
||
TESSERACT_RESULT="❌"
|
||
RESVG_RESULT="❌"
|
||
VIDEO_RESULT="❌"
|
||
API_RESULT="❌"
|
||
|
||
# 測試 1: Inkscape SVG → PNG(使用 xvfb-run,設置虛擬顯示)
|
||
echo ""
|
||
echo "🔄 測試 Inkscape: SVG → PNG"
|
||
if docker exec verify-test bash -c '
|
||
export DISPLAY=:99
|
||
echo "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100\" height=\"100\"><circle cx=\"50\" cy=\"50\" r=\"40\" fill=\"red\"/></svg>" > /tmp/test.svg
|
||
# 啟動 Xvfb 並執行 inkscape
|
||
Xvfb :99 -screen 0 1024x768x24 &
|
||
XVFB_PID=$!
|
||
sleep 2
|
||
timeout 60 inkscape --export-type=png --export-filename=/tmp/test.png /tmp/test.svg 2>&1 || true
|
||
kill $XVFB_PID 2>/dev/null || true
|
||
if [ -f /tmp/test.png ] && [ -s /tmp/test.png ]; then
|
||
echo "✅ Inkscape SVG → PNG 成功"
|
||
ls -la /tmp/test.png
|
||
exit 0
|
||
else
|
||
echo "❌ Inkscape SVG → PNG 失敗"
|
||
exit 1
|
||
fi
|
||
'; then
|
||
INKSCAPE_RESULT="✓"
|
||
else
|
||
FAILED=1
|
||
fi
|
||
|
||
# 測試 2: Pandoc Markdown → HTML(純 CLI,不需要 xvfb)
|
||
echo ""
|
||
echo "🔄 測試 Pandoc: Markdown → HTML"
|
||
if docker exec verify-test sh -c '
|
||
echo "# Test" > /tmp/test.md
|
||
echo "" >> /tmp/test.md
|
||
echo "Hello **world**" >> /tmp/test.md
|
||
pandoc /tmp/test.md -o /tmp/test.html
|
||
if [ -f /tmp/test.html ]; then
|
||
echo "✅ Pandoc Markdown → HTML 成功"
|
||
cat /tmp/test.html
|
||
exit 0
|
||
else
|
||
echo "❌ Pandoc Markdown → HTML 失敗"
|
||
exit 1
|
||
fi
|
||
'; then
|
||
PANDOC_RESULT="✓"
|
||
else
|
||
FAILED=1
|
||
fi
|
||
|
||
# 測試 3: LibreOffice TXT → PDF(使用 Xvfb)
|
||
echo ""
|
||
echo "🔄 測試 LibreOffice: TXT → PDF"
|
||
if docker exec verify-test bash -c '
|
||
export DISPLAY=:99
|
||
echo "Test document content" > /tmp/test.txt
|
||
# 啟動 Xvfb 並執行 libreoffice
|
||
Xvfb :99 -screen 0 1024x768x24 &
|
||
XVFB_PID=$!
|
||
sleep 2
|
||
timeout 60 libreoffice --headless --convert-to pdf --outdir /tmp /tmp/test.txt 2>&1 || true
|
||
kill $XVFB_PID 2>/dev/null || true
|
||
if [ -f /tmp/test.pdf ] && [ -s /tmp/test.pdf ]; then
|
||
echo "✅ LibreOffice TXT → PDF 成功"
|
||
ls -la /tmp/test.pdf
|
||
exit 0
|
||
else
|
||
echo "❌ LibreOffice TXT → PDF 失敗"
|
||
exit 1
|
||
fi
|
||
'; then
|
||
LIBREOFFICE_RESULT="✓"
|
||
else
|
||
FAILED=1
|
||
fi
|
||
|
||
# 測試 4: FFmpeg 音頻轉換(純 CLI)
|
||
echo ""
|
||
echo "🔄 測試 FFmpeg: 生成測試音頻"
|
||
if docker exec verify-test sh -c '
|
||
ffmpeg -f lavfi -i "sine=frequency=440:duration=1" -y /tmp/test.wav 2>/dev/null
|
||
if [ -f /tmp/test.wav ] && [ -s /tmp/test.wav ]; then
|
||
echo "✅ FFmpeg 音頻生成成功"
|
||
ls -la /tmp/test.wav
|
||
exit 0
|
||
else
|
||
echo "❌ FFmpeg 音頻生成失敗"
|
||
exit 1
|
||
fi
|
||
'; then
|
||
FFMPEG_RESULT="✓"
|
||
else
|
||
FAILED=1
|
||
fi
|
||
|
||
# 測試 5: Calibre HTML → EPUB(使用 Xvfb)
|
||
echo ""
|
||
echo "🔄 測試 Calibre: HTML → EPUB"
|
||
if docker exec verify-test bash -c '
|
||
export DISPLAY=:99
|
||
echo "<!DOCTYPE html><html><body><h1>Test Book</h1><p>Content</p></body></html>" > /tmp/book.html
|
||
# 啟動 Xvfb 並執行 ebook-convert
|
||
Xvfb :99 -screen 0 1024x768x24 &
|
||
XVFB_PID=$!
|
||
sleep 2
|
||
timeout 60 ebook-convert /tmp/book.html /tmp/book.epub 2>&1 || true
|
||
kill $XVFB_PID 2>/dev/null || true
|
||
if [ -f /tmp/book.epub ] && [ -s /tmp/book.epub ]; then
|
||
echo "✅ Calibre HTML → EPUB 成功"
|
||
ls -la /tmp/book.epub
|
||
exit 0
|
||
else
|
||
echo "❌ Calibre HTML → EPUB 失敗"
|
||
exit 1
|
||
fi
|
||
'; then
|
||
CALIBRE_RESULT="✓"
|
||
else
|
||
FAILED=1
|
||
fi
|
||
|
||
# 測試 6: Tesseract OCR(純 CLI)
|
||
echo ""
|
||
echo "🔄 測試 Tesseract: OCR 文字識別"
|
||
if docker exec verify-test sh -c '
|
||
convert -size 200x50 xc:white -font DejaVu-Sans -pointsize 20 -fill black -annotate +10+35 "Hello OCR" /tmp/ocr_test.png 2>/dev/null
|
||
tesseract /tmp/ocr_test.png /tmp/ocr_output 2>/dev/null
|
||
if [ -f /tmp/ocr_output.txt ]; then
|
||
echo "✅ Tesseract OCR 成功"
|
||
cat /tmp/ocr_output.txt
|
||
exit 0
|
||
else
|
||
echo "❌ Tesseract OCR 失敗"
|
||
exit 1
|
||
fi
|
||
'; then
|
||
TESSERACT_RESULT="✓"
|
||
else
|
||
FAILED=1
|
||
fi
|
||
|
||
# 測試 7: Resvg SVG → PNG(純 CLI)
|
||
echo ""
|
||
echo "🔄 測試 Resvg: SVG → PNG"
|
||
if docker exec verify-test sh -c '
|
||
echo "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"50\" height=\"50\"><rect fill=\"purple\" width=\"50\" height=\"50\"/></svg>" > /tmp/resvg_test.svg
|
||
resvg /tmp/resvg_test.svg /tmp/resvg_output.png 2>/dev/null
|
||
if [ -f /tmp/resvg_output.png ] && [ -s /tmp/resvg_output.png ]; then
|
||
echo "✅ Resvg SVG → PNG 成功"
|
||
ls -la /tmp/resvg_output.png
|
||
exit 0
|
||
else
|
||
echo "❌ Resvg SVG → PNG 失敗"
|
||
exit 1
|
||
fi
|
||
'; then
|
||
RESVG_RESULT="✓"
|
||
else
|
||
FAILED=1
|
||
fi
|
||
|
||
# 測試 8: FFmpeg 影片轉換(純 CLI)
|
||
echo ""
|
||
echo "🔄 測試 FFmpeg: 影片生成"
|
||
if docker exec verify-test sh -c '
|
||
ffmpeg -f lavfi -i color=c=red:s=320x240:d=1 -y /tmp/test_video.mp4 2>/dev/null
|
||
if [ -f /tmp/test_video.mp4 ] && [ -s /tmp/test_video.mp4 ]; then
|
||
echo "✅ FFmpeg 影片生成成功"
|
||
ls -la /tmp/test_video.mp4
|
||
exit 0
|
||
else
|
||
echo "❌ FFmpeg 影片生成失敗"
|
||
exit 1
|
||
fi
|
||
'; then
|
||
VIDEO_RESULT="✓"
|
||
else
|
||
FAILED=1
|
||
fi
|
||
|
||
# 測試 9: API 健康檢查(直接從 host 測試,因為容器已映射端口)
|
||
echo ""
|
||
echo "🔄 測試 API 端點"
|
||
# 先檢查容器是否還在運行
|
||
if docker ps | grep -q verify-test; then
|
||
HEALTH_RESPONSE=$(curl -sf http://localhost:3000/healthcheck 2>/dev/null || echo "")
|
||
if [ -n "$HEALTH_RESPONSE" ]; then
|
||
echo "✅ Healthcheck API 正常"
|
||
echo "Response: $HEALTH_RESPONSE"
|
||
API_RESULT="✓"
|
||
else
|
||
# 嘗試容器內部測試
|
||
HEALTH_RESPONSE=$(docker exec verify-test curl -sf http://localhost:3000/healthcheck 2>/dev/null || echo "")
|
||
if [ -n "$HEALTH_RESPONSE" ]; then
|
||
echo "✅ Healthcheck API 正常 (內部)"
|
||
echo "Response: $HEALTH_RESPONSE"
|
||
API_RESULT="✓"
|
||
else
|
||
echo "❌ Healthcheck API 失敗"
|
||
echo "容器狀態:"
|
||
docker ps -a | grep verify-test || true
|
||
echo "容器日誌:"
|
||
docker logs verify-test 2>&1 | tail -30 || true
|
||
FAILED=1
|
||
fi
|
||
fi
|
||
else
|
||
echo "❌ 容器已停止運行"
|
||
docker logs verify-test 2>&1 | tail -50 || true
|
||
FAILED=1
|
||
fi
|
||
|
||
# 測試摘要
|
||
echo ""
|
||
echo "========================================"
|
||
echo "📊 E2E 測試摘要"
|
||
echo "========================================"
|
||
echo "${INKSCAPE_RESULT} Inkscape: SVG → PNG"
|
||
echo "${PANDOC_RESULT} Pandoc: Markdown → HTML"
|
||
echo "${LIBREOFFICE_RESULT} LibreOffice: TXT → PDF"
|
||
echo "${FFMPEG_RESULT} FFmpeg: 音頻"
|
||
echo "${CALIBRE_RESULT} Calibre: HTML → EPUB"
|
||
echo "${TESSERACT_RESULT} Tesseract: OCR"
|
||
echo "${RESVG_RESULT} Resvg: SVG → PNG"
|
||
echo "${VIDEO_RESULT} FFmpeg: 影片"
|
||
echo "${API_RESULT} API: Healthcheck"
|
||
echo "========================================"
|
||
|
||
if [ $FAILED -eq 0 ]; then
|
||
echo "✅ E2E 測試全部通過!"
|
||
else
|
||
echo "❌ E2E 測試有失敗項目"
|
||
exit 1
|
||
fi
|
||
|
||
# ========================================
|
||
# 清理驗證容器
|
||
# ========================================
|
||
- name: "🧹 Cleanup Verification Containers"
|
||
if: always()
|
||
run: |
|
||
docker rm -f verify-test 2>/dev/null || true
|
||
|
||
- name: Verification Summary
|
||
run: |
|
||
echo "## 🔍 Image Verification Results" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "**Image:** \`${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "### ✅ Verification Passed" >> $GITHUB_STEP_SUMMARY
|
||
echo "" >> $GITHUB_STEP_SUMMARY
|
||
echo "- <20> Tools & Model: Verified" >> $GITHUB_STEP_SUMMARY
|
||
echo "- ⚙️ Basic Functionality: Verified" >> $GITHUB_STEP_SUMMARY
|
||
echo "- 🧪 E2E Tests: Passed" >> $GITHUB_STEP_SUMMARY
|
||
|
||
# ==============================================================================
|
||
# 📦 Create GitHub Release
|
||
# ==============================================================================
|
||
create-release:
|
||
runs-on: ubuntu-latest
|
||
needs: [build-and-push, verify-image]
|
||
permissions:
|
||
contents: write
|
||
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Get tag name
|
||
id: tag
|
||
run: |
|
||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||
echo "version=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
- name: Generate changelog
|
||
id: changelog
|
||
run: |
|
||
# Get previous tag
|
||
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||
|
||
if [ -z "$PREVIOUS_TAG" ]; then
|
||
# No previous tag, get all commits
|
||
CHANGELOG=$(git log --pretty=format:"- %s (%h)" HEAD)
|
||
else
|
||
# Get commits between previous tag and current
|
||
CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${PREVIOUS_TAG}..HEAD)
|
||
fi
|
||
|
||
# Write to file to preserve newlines
|
||
echo "$CHANGELOG" > changelog.txt
|
||
|
||
- name: Create GitHub Release
|
||
uses: softprops/action-gh-release@v2
|
||
with:
|
||
name: ${{ steps.tag.outputs.version }}
|
||
tag_name: ${{ steps.tag.outputs.version }}
|
||
body_path: changelog.txt
|
||
generate_release_notes: true
|
||
draft: false
|
||
prerelease: false
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|