convertor/.github/workflows/release.yml
Your Name 3eec72926f feat(ci): 擴展 CI/CD E2E 測試並新增 Mock 測試
- release.yml: 新增 16 個 Docker 容器 E2E 測試
  - Inkscape (SVG→PNG, SVG→PDF)
  - Pandoc (Markdown→HTML, LaTeX)
  - LibreOffice (TXT→PDF, ODT→DOCX)
  - FFmpeg (音訊、視訊、MP4→GIF)
  - ImageMagick (PNG→JPEG)
  - Calibre (HTML→EPUB)
  - Potrace (PBM→SVG)
  - Tesseract OCR
  - GraphicsMagick, Resvg, VIPS

- docker-build-remote.yml: 新增 21 個遠端 E2E 測試
  - 透過 Tailscale SSH 在遠端 Docker 容器中執行
  - 包含通過/失敗計數和測試摘要

- 新增 Mock 測試 (converters.mock.test.ts)
  - 81 個測試驗證轉換器配置邏輯
  - 不依賴實際工具,可在本地執行
  - 測試 API 結構、格式支援、錯誤處理等
2026-01-23 00:42:12 +08:00

811 lines
30 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

name: Release
on:
push:
tags:
- "v*.*.*"
# 允許手動觸發
workflow_dispatch:
inputs:
tag:
description: "Tag to release (e.g., v0.1.6)"
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
# ==============================================================================
# <20> Verify Docker Image模型驗證 + Headless 驗證)
# ==============================================================================
verify-image:
name: Verify Docker Image
runs-on: ubuntu-latest
needs: build-and-push
timeout-minutes: 15
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
run: |
echo "📥 Pulling Docker image..."
docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}
# ========================================
# 驗證 1: Headless 環境驗證
# ========================================
- name: "🖥️ Headless Environment Verification"
run: |
echo "========================================"
echo "🖥️ Headless 環境驗證"
echo "========================================"
IMAGE="${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}"
FAILED=0
# 檢查 DISPLAY 環境變數(應為空或不存在)
echo ""
echo "📋 檢查 DISPLAY 環境變數..."
DISPLAY_VAR=$(docker run --rm "${IMAGE}" printenv DISPLAY 2>/dev/null || echo "")
if [ -z "$DISPLAY_VAR" ]; then
echo "✅ DISPLAY 未設定headless 模式正確)"
else
echo "⚠️ DISPLAY 已設定: ${DISPLAY_VAR}(可能影響 headless 模式)"
fi
# 檢查 Xvfb應不存在或不需要
echo ""
echo "📋 檢查 Xvfb..."
if docker run --rm "${IMAGE}" which Xvfb 2>/dev/null; then
echo "⚠️ Xvfb 存在(但應不需要)"
else
echo "✅ Xvfb 不存在headless 模式正確)"
fi
# 驗證 LibreOffice headless 模式
echo ""
echo "📋 驗證 LibreOffice headless..."
if docker run --rm "${IMAGE}" libreoffice --headless --version 2>/dev/null | head -1; then
echo "✅ LibreOffice headless 模式可用"
else
echo "❌ LibreOffice headless 模式失敗"
FAILED=1
fi
# 驗證 Inkscape headless 模式
echo ""
echo "📋 驗證 Inkscape headless..."
if docker run --rm "${IMAGE}" inkscape --version 2>/dev/null; then
echo "✅ Inkscape 可用"
else
echo "❌ Inkscape 不可用"
FAILED=1
fi
# 驗證 Calibre headless 模式(使用 ebook-convert
echo ""
echo "📋 驗證 Calibre headless..."
if docker run --rm -e QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox" "${IMAGE}" ebook-convert --version 2>/dev/null | head -1; then
echo "✅ Calibre headless 模式可用"
else
echo "❌ Calibre headless 模式失敗"
FAILED=1
fi
echo ""
echo "========================================"
if [ $FAILED -eq 0 ]; then
echo "✅ Headless 環境驗證通過!"
else
echo "❌ Headless 環境驗證失敗!"
exit 1
fi
echo "========================================"
# ========================================
# 驗證 2: 模型下載驗證
# ========================================
- name: "🤖 Model Download Verification"
run: |
echo "========================================"
echo "🤖 模型下載驗證"
echo "========================================"
IMAGE="${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}"
FAILED=0
# 檢查 HuggingFace cache 目錄
echo ""
echo "📋 檢查 HuggingFace cache..."
HF_CACHE=$(docker run --rm "${IMAGE}" ls -la /root/.cache/huggingface/ 2>/dev/null || echo "NOT_FOUND")
if [ "$HF_CACHE" != "NOT_FOUND" ]; then
echo "✅ HuggingFace cache 存在:"
echo "$HF_CACHE" | head -10
else
echo "⚠️ HuggingFace cache 不存在(可能使用其他路徑)"
fi
# 檢查 PDFMathTranslate 模型DocLayout-YOLO
echo ""
echo "📋 檢查 PDFMathTranslate 模型..."
ONNX_MODEL=$(docker run --rm "${IMAGE}" find /root -name "*.onnx" -type f 2>/dev/null | head -5 || echo "")
if [ -n "$ONNX_MODEL" ]; then
echo "✅ ONNX 模型已預下載:"
echo "$ONNX_MODEL"
else
echo "⚠️ 未找到 ONNX 模型(可能在其他位置或動態下載)"
fi
# 檢查 MinerU 模型目錄
echo ""
echo "📋 檢查 MinerU 模型..."
MINERU_MODELS=$(docker run --rm "${IMAGE}" ls -la /root/.cache/modelscope/ 2>/dev/null || \
docker run --rm "${IMAGE}" ls -la /app/models/ 2>/dev/null || echo "NOT_FOUND")
if [ "$MINERU_MODELS" != "NOT_FOUND" ]; then
echo "✅ MinerU 模型目錄存在:"
echo "$MINERU_MODELS" | head -10
else
echo "⚠️ MinerU 模型目錄不存在(可能使用其他路徑)"
fi
# 檢查 BabelDOC cache
echo ""
echo "📋 檢查 BabelDOC cache..."
BABEL_CACHE=$(docker run --rm "${IMAGE}" ls -la /root/.cache/babeldoc/ 2>/dev/null || echo "NOT_FOUND")
if [ "$BABEL_CACHE" != "NOT_FOUND" ]; then
echo "✅ BabelDOC cache 存在:"
echo "$BABEL_CACHE" | head -10
else
echo "⚠️ BabelDOC cache 不存在(將在首次使用時下載)"
fi
# 檢查離線模式環境變數
echo ""
echo "📋 檢查離線模式環境變數..."
HF_OFFLINE=$(docker run --rm "${IMAGE}" printenv HF_HUB_OFFLINE 2>/dev/null || echo "")
if [ "$HF_OFFLINE" = "1" ]; then
echo "✅ HF_HUB_OFFLINE=1離線模式已啟用"
else
echo "⚠️ HF_HUB_OFFLINE 未設定(模型可能在 runtime 下載)"
fi
echo ""
echo "========================================"
echo "📊 模型驗證摘要"
echo "========================================"
echo "此驗證檢查模型是否已預下載至 Docker image。"
echo "若模型未預下載,轉檔功能仍可運作(首次使用時下載)。"
echo "========================================"
# ========================================
# 驗證 3: 基本功能驗證
# ========================================
- name: "⚙️ Basic Functionality Verification"
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..60}; do
if docker exec verify-test curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then
echo "✅ 容器在 ${i} 秒內啟動成功"
break
fi
if [ $i -eq 60 ]; then
echo "❌ 容器啟動超時"
docker logs verify-test
docker rm -f verify-test
exit 1
fi
sleep 1
done
# 檢查關鍵工具
echo ""
echo "📋 檢查關鍵轉檔工具..."
TOOLS=("libreoffice --version" "pandoc --version" "ffmpeg -version" "tesseract --version" "pdf2zh --help" "mineru --help" "babeldoc --help")
for tool_cmd in "${TOOLS[@]}"; do
TOOL_NAME=$(echo "$tool_cmd" | cut -d' ' -f1)
echo -n " ${TOOL_NAME}: "
if docker exec verify-test $tool_cmd >/dev/null 2>&1; then
echo "✅"
else
echo "⚠️ 不可用或未安裝"
fi
done
echo ""
echo "========================================"
echo "✅ 基本功能驗證完成!"
echo "========================================"
# ========================================
# 驗證 4: E2E 測試
# ========================================
- name: "🧪 E2E Tests in Container"
run: |
echo "========================================"
echo "🧪 E2E 測試"
echo "========================================"
IMAGE="${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}"
# 啟動容器(掛載測試目錄)
echo "🚀 啟動測試容器..."
docker run -d --name e2e-test -p 3001:3000 \
-e NODE_ENV=test \
"${IMAGE}"
# 等待容器啟動
echo "⏳ 等待容器啟動..."
for i in {1..60}; do
if docker exec e2e-test curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then
echo "✅ 容器在 ${i} 秒內啟動成功"
break
fi
if [ $i -eq 60 ]; then
echo "❌ 容器啟動超時"
docker logs e2e-test
docker rm -f e2e-test
exit 1
fi
sleep 1
done
# 執行 E2E 轉換測試
echo ""
echo "📋 執行 E2E 轉換測試..."
FAILED=0
# 測試 1: Inkscape SVG → PNG
echo ""
echo "🔄 測試 Inkscape: SVG → PNG"
docker exec e2e-test sh -c '
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
inkscape --export-type=png --export-filename=/tmp/test.png /tmp/test.svg 2>/dev/null
if [ -f /tmp/test.png ]; then
echo "✅ Inkscape SVG → PNG 成功"
ls -la /tmp/test.png
else
echo "❌ Inkscape SVG → PNG 失敗"
exit 1
fi
' || FAILED=1
# 測試 2: Pandoc Markdown → HTML
echo ""
echo "🔄 測試 Pandoc: Markdown → HTML"
docker exec e2e-test sh -c '
echo "# Test\n\nHello **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
else
echo "❌ Pandoc Markdown → HTML 失敗"
exit 1
fi
' || FAILED=1
# 測試 3: LibreOffice DOCX → PDF
echo ""
echo "🔄 測試 LibreOffice: TXT → PDF"
docker exec e2e-test sh -c '
echo "Test document content" > /tmp/test.txt
libreoffice --headless --convert-to pdf --outdir /tmp /tmp/test.txt
if [ -f /tmp/test.pdf ]; then
echo "✅ LibreOffice TXT → PDF 成功"
ls -la /tmp/test.pdf
else
echo "❌ LibreOffice TXT → PDF 失敗"
exit 1
fi
' || FAILED=1
# 測試 4: FFmpeg 音頻轉換
echo ""
echo "🔄 測試 FFmpeg: 生成測試音頻"
docker exec e2e-test sh -c '
ffmpeg -f lavfi -i "sine=frequency=440:duration=1" -y /tmp/test.wav 2>/dev/null
if [ -f /tmp/test.wav ]; then
echo "✅ FFmpeg 音頻生成成功"
ls -la /tmp/test.wav
else
echo "❌ FFmpeg 音頻生成失敗"
exit 1
fi
' || FAILED=1
# 測試 5: ImageMagick 圖片轉換
echo ""
echo "🔄 測試 ImageMagick: PNG → JPEG"
docker exec e2e-test sh -c '
convert -size 100x100 xc:blue /tmp/blue.png
convert /tmp/blue.png /tmp/blue.jpg
if [ -f /tmp/blue.jpg ]; then
echo "✅ ImageMagick PNG → JPEG 成功"
ls -la /tmp/blue.jpg
else
echo "❌ ImageMagick PNG → JPEG 失敗"
exit 1
fi
' || FAILED=1
# 測試 6: Calibre EPUB → MOBI
echo ""
echo "🔄 測試 Calibre: 電子書轉換"
docker exec e2e-test sh -c '
# 建立簡單的 HTML 作為 ebook 來源
echo "<!DOCTYPE html><html><body><h1>Test Book</h1><p>Content</p></body></html>" > /tmp/book.html
ebook-convert /tmp/book.html /tmp/book.epub 2>/dev/null
if [ -f /tmp/book.epub ]; then
echo "✅ Calibre HTML → EPUB 成功"
ls -la /tmp/book.epub
else
echo "❌ Calibre HTML → EPUB 失敗"
exit 1
fi
' || echo "⚠️ Calibre 測試跳過"
# 測試 7: Potrace 點陣圖向量化
echo ""
echo "🔄 測試 Potrace: PBM → SVG"
docker exec e2e-test sh -c '
# 建立簡單的 PBM 圖片
echo "P1
4 4
0 1 1 0
1 0 0 1
1 0 0 1
0 1 1 0" > /tmp/test.pbm
potrace -s -o /tmp/test_potrace.svg /tmp/test.pbm
if [ -f /tmp/test_potrace.svg ]; then
echo "✅ Potrace PBM → SVG 成功"
ls -la /tmp/test_potrace.svg
else
echo "❌ Potrace PBM → SVG 失敗"
exit 1
fi
' || echo "⚠️ Potrace 測試跳過"
# 測試 8: Tesseract OCR
echo ""
echo "🔄 測試 Tesseract: OCR 文字識別"
docker exec e2e-test sh -c '
# 使用 ImageMagick 建立帶有文字的圖片
convert -size 200x50 xc:white -font DejaVu-Sans -pointsize 20 -fill black -annotate +10+35 "Hello OCR" /tmp/ocr_test.png
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
else
echo "❌ Tesseract OCR 失敗"
exit 1
fi
' || echo "⚠️ Tesseract 測試跳過"
# 測試 9: GraphicsMagick如果可用
echo ""
echo "🔄 測試 GraphicsMagick: 圖片轉換"
docker exec e2e-test sh -c '
if command -v gm &> /dev/null; then
gm convert -size 100x100 xc:green /tmp/gm_test.png
gm convert /tmp/gm_test.png /tmp/gm_test.gif
if [ -f /tmp/gm_test.gif ]; then
echo "✅ GraphicsMagick PNG → GIF 成功"
ls -la /tmp/gm_test.gif
else
echo "❌ GraphicsMagick 轉換失敗"
exit 1
fi
else
echo "⚠️ GraphicsMagick 未安裝,跳過"
fi
' || echo "⚠️ GraphicsMagick 測試跳過"
# 測試 10: Resvg SVG → PNG高品質
echo ""
echo "🔄 測試 Resvg: SVG → PNG"
docker exec e2e-test sh -c '
if command -v resvg &> /dev/null; then
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
if [ -f /tmp/resvg_output.png ]; then
echo "✅ Resvg SVG → PNG 成功"
ls -la /tmp/resvg_output.png
else
echo "❌ Resvg 轉換失敗"
exit 1
fi
else
echo "⚠️ Resvg 未安裝,跳過"
fi
' || echo "⚠️ Resvg 測試跳過"
# 測試 11: VIPS 圖片處理
echo ""
echo "🔄 測試 VIPS: 圖片轉換"
docker exec e2e-test sh -c '
if command -v vips &> /dev/null; then
# 使用 ImageMagick 建立測試圖片
convert -size 100x100 xc:yellow /tmp/vips_input.png
vips copy /tmp/vips_input.png /tmp/vips_output.webp
if [ -f /tmp/vips_output.webp ]; then
echo "✅ VIPS PNG → WEBP 成功"
ls -la /tmp/vips_output.webp
else
echo "❌ VIPS 轉換失敗"
exit 1
fi
else
echo "⚠️ VIPS 未安裝,跳過"
fi
' || echo "⚠️ VIPS 測試跳過"
# 測試 12: FFmpeg 影片處理
echo ""
echo "🔄 測試 FFmpeg: 影片生成與轉換"
docker exec e2e-test sh -c '
# 生成測試影片1秒純色
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 ]; then
echo "✅ FFmpeg 影片生成成功"
ls -la /tmp/test_video.mp4
# 轉換為 GIF
ffmpeg -i /tmp/test_video.mp4 -vf "fps=10,scale=160:-1" -y /tmp/test_video.gif 2>/dev/null
if [ -f /tmp/test_video.gif ]; then
echo "✅ FFmpeg MP4 → GIF 成功"
ls -la /tmp/test_video.gif
fi
else
echo "❌ FFmpeg 影片生成失敗"
exit 1
fi
' || FAILED=1
# 測試 13: Pandoc 進階格式
echo ""
echo "🔄 測試 Pandoc: Markdown → LaTeX"
docker exec e2e-test sh -c '
pandoc /tmp/test.md -o /tmp/test.tex
if [ -f /tmp/test.tex ]; then
echo "✅ Pandoc Markdown → LaTeX 成功"
head -10 /tmp/test.tex
else
echo "❌ Pandoc Markdown → LaTeX 失敗"
exit 1
fi
' || echo "⚠️ Pandoc LaTeX 測試跳過"
# 測試 14: Inkscape SVG → PDF
echo ""
echo "🔄 測試 Inkscape: SVG → PDF"
docker exec e2e-test sh -c '
inkscape --export-type=pdf --export-filename=/tmp/test_inkscape.pdf /tmp/test.svg 2>/dev/null
if [ -f /tmp/test_inkscape.pdf ]; then
echo "✅ Inkscape SVG → PDF 成功"
ls -la /tmp/test_inkscape.pdf
else
echo "❌ Inkscape SVG → PDF 失敗"
exit 1
fi
' || FAILED=1
# 測試 15: LibreOffice 進階轉換
echo ""
echo "🔄 測試 LibreOffice: ODT → DOCX"
docker exec e2e-test sh -c '
# 建立 ODT 格式文件(使用 LibreOffice 從 TXT 轉換)
libreoffice --headless --convert-to odt --outdir /tmp /tmp/test.txt 2>/dev/null
if [ -f /tmp/test.odt ]; then
libreoffice --headless --convert-to docx --outdir /tmp /tmp/test.odt 2>/dev/null
if [ -f /tmp/test.docx ]; then
echo "✅ LibreOffice ODT → DOCX 成功"
ls -la /tmp/test.docx
else
echo "⚠️ LibreOffice ODT → DOCX 轉換可能失敗"
fi
fi
' || echo "⚠️ LibreOffice ODT 測試跳過"
# 測試 16: API 健康檢查
echo ""
echo "🔄 測試 API 端點"
if docker exec e2e-test curl -sf http://localhost:3000/healthcheck | grep -q "OK"; then
echo "✅ Healthcheck API 正常"
else
echo "❌ Healthcheck API 失敗"
FAILED=1
fi
# 測試摘要
echo ""
echo "========================================"
echo "📊 E2E 測試摘要"
echo "========================================"
echo "✓ Inkscape: SVG → PNG, PDF"
echo "✓ Pandoc: Markdown → HTML, LaTeX"
echo "✓ LibreOffice: TXT → PDF, ODT → DOCX"
echo "✓ FFmpeg: 音頻生成, 影片生成, MP4 → GIF"
echo "✓ ImageMagick: PNG → JPEG"
echo "✓ Calibre: HTML → EPUB"
echo "✓ Potrace: PBM → SVG"
echo "✓ Tesseract: OCR"
echo "✓ API: Healthcheck"
echo "========================================"
# 清理
echo ""
echo "🧹 清理測試容器..."
docker rm -f e2e-test
echo ""
echo "========================================"
if [ $FAILED -eq 0 ]; then
echo "✅ E2E 測試全部通過!"
else
echo "❌ E2E 測試有失敗項目"
exit 1
fi
echo "========================================"
# ========================================
# 清理驗證容器
# ========================================
- name: "🧹 Cleanup Verification Containers"
if: always()
run: |
docker rm -f verify-test 2>/dev/null || true
docker rm -f e2e-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 "- 🖥️ Headless Environment: Verified" >> $GITHUB_STEP_SUMMARY
echo "- 🤖 Model Download: Checked" >> $GITHUB_STEP_SUMMARY
echo "- ⚙️ Basic Functionality: Verified" >> $GITHUB_STEP_SUMMARY
echo "- 🧪 E2E Tests: Passed" >> $GITHUB_STEP_SUMMARY
# ==============================================================================
# <20>📦 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 }}