760 lines
30 KiB
YAML
760 lines
30 KiB
YAML
name: Release (一般版)
|
||
|
||
on:
|
||
push:
|
||
tags:
|
||
# 只匹配一般版 tag(不含 -lite)
|
||
- "v[0-9]*.[0-9]*.[0-9]*"
|
||
- "!v*-lite"
|
||
# 允許手動觸發
|
||
workflow_dispatch:
|
||
inputs:
|
||
tag:
|
||
description: "Tag to release (e.g., v0.1.15) - 一般版"
|
||
required: true
|
||
type: string
|
||
|
||
env:
|
||
DOCKER_IMAGE: convertx/convertx-cn
|
||
|
||
jobs:
|
||
# ==============================================================================
|
||
# 🐳 Build AMD64 Image(分開構建解決空間不足問題)
|
||
# ==============================================================================
|
||
build-amd64:
|
||
runs-on: ubuntu-24.04
|
||
permissions:
|
||
contents: read
|
||
packages: write
|
||
|
||
steps:
|
||
# ========================================
|
||
# 🔥 極限磁碟清理(釋放 70GB+ 空間)
|
||
# ========================================
|
||
- name: Maximize disk space (before checkout)
|
||
run: |
|
||
echo "🧹 極限磁碟清理..."
|
||
echo "📊 初始磁碟空間:"
|
||
df -h /
|
||
|
||
# 停止服務
|
||
sudo systemctl stop docker containerd || true
|
||
|
||
# 移除大型預裝軟體(約 50-60GB)
|
||
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc || true
|
||
sudo rm -rf /opt/hostedtoolcache /usr/share/swift /opt/microsoft || true
|
||
sudo rm -rf /usr/local/aws-* /usr/local/julia* /usr/share/az_* /opt/az || true
|
||
sudo rm -rf /usr/lib/google-cloud-sdk /usr/local/share/powershell || true
|
||
sudo rm -rf /usr/share/miniconda /usr/local/go ~/go || true
|
||
sudo rm -rf /usr/share/rust ~/.rustup ~/.cargo /usr/share/R /usr/lib/R || true
|
||
sudo rm -rf /usr/local/share/boost /usr/local/graalvm /usr/local/.ghcup || true
|
||
sudo rm -rf /usr/local/share/chromium || true
|
||
|
||
# 移除資料庫
|
||
sudo rm -rf /var/lib/mysql /var/lib/postgresql /var/lib/mongodb || true
|
||
|
||
# 移除 snap/flatpak
|
||
sudo rm -rf /snap /var/snap /var/lib/snapd || true
|
||
|
||
# 移除文檔和 locale
|
||
sudo rm -rf /usr/share/doc /usr/share/man /usr/share/locale || true
|
||
sudo rm -rf /usr/share/icons /usr/share/fonts /usr/share/help || true
|
||
|
||
# 移除 imagegeneration(GitHub runner 專用)
|
||
sudo rm -rf /imagegeneration /opt/actionarchivecache || true
|
||
|
||
# 清理 cache
|
||
sudo apt-get clean && sudo apt-get autoremove -y || true
|
||
sudo rm -rf /var/lib/apt/lists/* /var/cache/* /tmp/* || true
|
||
|
||
# 完全重置 Docker
|
||
sudo rm -rf /var/lib/docker /var/lib/containerd || true
|
||
sudo mkdir -p /var/lib/docker /var/lib/containerd
|
||
sudo systemctl start containerd docker || true
|
||
|
||
echo "📊 清理後磁碟空間:"
|
||
df -h /
|
||
|
||
# 驗證空間
|
||
AVAIL_GB=$(df / | tail -1 | awk '{print int($4/1024/1024)}')
|
||
echo "可用空間: ${AVAIL_GB}GB"
|
||
[ "$AVAIL_GB" -ge 50 ] && echo "✅ 空間充足" || echo "⚠️ 空間可能不足"
|
||
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
|
||
- 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 Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
with:
|
||
driver-opts: |
|
||
image=moby/buildkit:latest
|
||
network=host
|
||
# 限制 BuildKit 使用的磁碟空間
|
||
buildkitd-config-inline: |
|
||
[worker.oci]
|
||
max-parallelism = 1
|
||
gc = true
|
||
gckeepstorage = 5000000000
|
||
[worker.containerd]
|
||
gc = true
|
||
gckeepstorage = 5000000000
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@v3
|
||
with:
|
||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: "📦 開始構建 AMD64 Image"
|
||
run: |
|
||
echo "========================================"
|
||
echo "📦 開始構建 AMD64 Docker Image..."
|
||
echo "========================================"
|
||
echo "Image: ${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}-amd64"
|
||
echo "Platform: linux/amd64"
|
||
echo ""
|
||
echo "⚠️ 注意:Image 大小約 10-14GB"
|
||
echo "⚠️ 構建 + 上傳可能需要 1-3 小時"
|
||
echo "⚠️ 'exporting layers' 階段是上傳到 Docker Hub,請耐心等待"
|
||
echo "========================================"
|
||
echo "開始時間: $(date)"
|
||
echo "========================================"
|
||
|
||
- name: "🐳 Build and push AMD64 image"
|
||
uses: docker/build-push-action@v6
|
||
with:
|
||
context: .
|
||
file: Dockerfile
|
||
push: true
|
||
tags: ${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}-amd64
|
||
platforms: linux/amd64
|
||
# 只使用 cache-from,不使用 cache-to(避免額外上傳時間)
|
||
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache-amd64
|
||
provenance: false
|
||
sbom: false
|
||
build-args: |
|
||
BUILDKIT_INLINE_CACHE=1
|
||
|
||
- name: "✅ AMD64 Image 構建完成"
|
||
run: |
|
||
echo "========================================"
|
||
echo "🚀 AMD64 Image 已成功推送到 Docker Hub"
|
||
echo "========================================"
|
||
echo "Image: ${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}-amd64"
|
||
echo "Platform: linux/amd64"
|
||
echo "Time: $(date)"
|
||
echo "========================================"
|
||
|
||
# ==============================================================================
|
||
# 🐳 Build ARM64 Image(分開構建解決空間不足問題)
|
||
# ==============================================================================
|
||
build-arm64:
|
||
runs-on: ubuntu-24.04
|
||
permissions:
|
||
contents: read
|
||
packages: write
|
||
|
||
steps:
|
||
# ========================================
|
||
# 🔥 極限磁碟清理(釋放 70GB+ 空間)
|
||
# ========================================
|
||
- name: Maximize disk space (before checkout)
|
||
run: |
|
||
echo "🧹 極限磁碟清理..."
|
||
echo "📊 初始磁碟空間:"
|
||
df -h /
|
||
|
||
# 停止服務
|
||
sudo systemctl stop docker containerd || true
|
||
|
||
# 移除大型預裝軟體(約 50-60GB)
|
||
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc || true
|
||
sudo rm -rf /opt/hostedtoolcache /usr/share/swift /opt/microsoft || true
|
||
sudo rm -rf /usr/local/aws-* /usr/local/julia* /usr/share/az_* /opt/az || true
|
||
sudo rm -rf /usr/lib/google-cloud-sdk /usr/local/share/powershell || true
|
||
sudo rm -rf /usr/share/miniconda /usr/local/go ~/go || true
|
||
sudo rm -rf /usr/share/rust ~/.rustup ~/.cargo /usr/share/R /usr/lib/R || true
|
||
sudo rm -rf /usr/local/share/boost /usr/local/graalvm /usr/local/.ghcup || true
|
||
sudo rm -rf /usr/local/share/chromium || true
|
||
|
||
# 移除資料庫
|
||
sudo rm -rf /var/lib/mysql /var/lib/postgresql /var/lib/mongodb || true
|
||
|
||
# 移除 snap/flatpak
|
||
sudo rm -rf /snap /var/snap /var/lib/snapd || true
|
||
|
||
# 移除文檔和 locale
|
||
sudo rm -rf /usr/share/doc /usr/share/man /usr/share/locale || true
|
||
sudo rm -rf /usr/share/icons /usr/share/fonts /usr/share/help || true
|
||
|
||
# 移除 imagegeneration(GitHub runner 專用)
|
||
sudo rm -rf /imagegeneration /opt/actionarchivecache || true
|
||
|
||
# 清理 cache
|
||
sudo apt-get clean && sudo apt-get autoremove -y || true
|
||
sudo rm -rf /var/lib/apt/lists/* /var/cache/* /tmp/* || true
|
||
|
||
# 完全重置 Docker
|
||
sudo rm -rf /var/lib/docker /var/lib/containerd || true
|
||
sudo mkdir -p /var/lib/docker /var/lib/containerd
|
||
sudo systemctl start containerd docker || true
|
||
|
||
echo "📊 清理後磁碟空間:"
|
||
df -h /
|
||
|
||
# 驗證空間
|
||
AVAIL_GB=$(df / | tail -1 | awk '{print int($4/1024/1024)}')
|
||
echo "可用空間: ${AVAIL_GB}GB"
|
||
[ "$AVAIL_GB" -ge 50 ] && echo "✅ 空間充足" || echo "⚠️ 空間可能不足"
|
||
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v4
|
||
|
||
- 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
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
with:
|
||
driver-opts: |
|
||
image=moby/buildkit:latest
|
||
network=host
|
||
# 限制 BuildKit 使用的磁碟空間
|
||
buildkitd-config-inline: |
|
||
[worker.oci]
|
||
max-parallelism = 1
|
||
gc = true
|
||
gckeepstorage = 5000000000
|
||
[worker.containerd]
|
||
gc = true
|
||
gckeepstorage = 5000000000
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@v3
|
||
with:
|
||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: "📦 開始構建 ARM64 Image"
|
||
run: |
|
||
echo "========================================"
|
||
echo "📦 開始構建 ARM64 Docker Image..."
|
||
echo "========================================"
|
||
echo "Image: ${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}-arm64"
|
||
echo "Platform: linux/arm64 (via QEMU emulation)"
|
||
echo ""
|
||
echo "⚠️ 注意:Image 大小約 10-14GB"
|
||
echo "⚠️ ARM64 使用 QEMU 模擬,構建時間更長"
|
||
echo "⚠️ 構建 + 上傳可能需要 2-4 小時"
|
||
echo "⚠️ 'exporting layers' 階段是上傳到 Docker Hub,請耐心等待"
|
||
echo "========================================"
|
||
echo "開始時間: $(date)"
|
||
echo "========================================"
|
||
|
||
- name: "🐳 Build and push ARM64 image"
|
||
uses: docker/build-push-action@v6
|
||
with:
|
||
context: .
|
||
file: Dockerfile
|
||
push: true
|
||
tags: ${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}-arm64
|
||
platforms: linux/arm64
|
||
# 只使用 cache-from,不使用 cache-to(避免額外上傳時間)
|
||
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache-arm64
|
||
provenance: false
|
||
sbom: false
|
||
build-args: |
|
||
BUILDKIT_INLINE_CACHE=1
|
||
- name: "✅ ARM64 Image 構建完成"
|
||
run: |
|
||
echo "========================================"
|
||
echo "🚀 ARM64 Image 已成功推送到 Docker Hub"
|
||
echo "========================================"
|
||
echo "Image: ${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}-arm64"
|
||
echo "Platform: linux/arm64"
|
||
echo "Time: $(date)"
|
||
echo "========================================"
|
||
|
||
# ==============================================================================
|
||
# 🐳 Create Multi-Arch Manifest(合併兩個架構)
|
||
# ==============================================================================
|
||
create-manifest:
|
||
runs-on: ubuntu-latest
|
||
needs: [build-amd64, build-arm64]
|
||
permissions:
|
||
contents: read
|
||
packages: write
|
||
|
||
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: Login to Docker Hub
|
||
uses: docker/login-action@v3
|
||
with:
|
||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: Create and push multi-arch manifest
|
||
run: |
|
||
VERSION="${{ steps.tag.outputs.version }}"
|
||
IMAGE="${{ env.DOCKER_IMAGE }}"
|
||
|
||
echo "🔄 創建 multi-arch manifest..."
|
||
|
||
# 創建版本 tag 的 manifest
|
||
docker buildx imagetools create -t ${IMAGE}:${VERSION} \
|
||
${IMAGE}:${VERSION}-amd64 \
|
||
${IMAGE}:${VERSION}-arm64
|
||
|
||
# 創建 latest tag 的 manifest
|
||
docker buildx imagetools create -t ${IMAGE}:latest \
|
||
${IMAGE}:${VERSION}-amd64 \
|
||
${IMAGE}:${VERSION}-arm64
|
||
|
||
echo "✅ Multi-arch manifest 創建完成"
|
||
|
||
# 驗證 manifest
|
||
echo "🔍 驗證 manifest..."
|
||
docker buildx imagetools inspect ${IMAGE}:${VERSION}
|
||
docker buildx imagetools inspect ${IMAGE}:latest
|
||
|
||
- 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-24.04
|
||
needs: create-manifest
|
||
timeout-minutes: 45
|
||
|
||
steps:
|
||
# ========================================
|
||
# 🔥 磁碟清理(拉取大型 image 需要空間)
|
||
# ========================================
|
||
- name: Free disk space for image pull
|
||
run: |
|
||
echo "🧹 清理磁碟空間以拉取大型 Docker image..."
|
||
df -h /
|
||
|
||
# 移除大型預裝軟體
|
||
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc || true
|
||
sudo rm -rf /opt/hostedtoolcache /usr/share/swift /opt/microsoft || true
|
||
sudo rm -rf /usr/local/aws-* /usr/local/julia* /usr/share/az_* || true
|
||
sudo rm -rf /usr/lib/google-cloud-sdk /usr/local/share/powershell || true
|
||
sudo rm -rf /usr/share/miniconda /usr/local/go ~/go || true
|
||
sudo rm -rf /usr/share/rust ~/.rustup ~/.cargo || true
|
||
|
||
# 清理 apt cache
|
||
sudo apt-get clean || true
|
||
sudo rm -rf /var/lib/apt/lists/* || true
|
||
|
||
echo "📊 清理後磁碟空間:"
|
||
df -h /
|
||
|
||
- 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 轉換測試(模擬真實用戶操作)
|
||
# ========================================
|
||
- name: "🧪 E2E Tests in Container"
|
||
timeout-minutes: 15
|
||
run: |
|
||
echo "========================================"
|
||
echo "🧪 E2E 轉換測試(模擬真實用戶操作)"
|
||
echo "========================================"
|
||
|
||
# 首先確認容器健康
|
||
echo "🔍 確認服務健康狀態..."
|
||
for i in {1..30}; do
|
||
if curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then
|
||
echo "✅ 服務健康"
|
||
break
|
||
fi
|
||
if [ $i -eq 30 ]; then
|
||
echo "❌ 服務不健康"
|
||
docker logs verify-test 2>&1 | tail -30
|
||
exit 1
|
||
fi
|
||
sleep 1
|
||
done
|
||
|
||
# 定義測試結果追蹤
|
||
TOTAL_TESTS=0
|
||
PASSED_TESTS=0
|
||
FAILED_TESTS=0
|
||
|
||
run_test() {
|
||
local name="$1"
|
||
local cmd="$2"
|
||
TOTAL_TESTS=$((TOTAL_TESTS + 1))
|
||
echo ""
|
||
echo "🔄 測試: $name"
|
||
if docker exec verify-test bash -c "$cmd" 2>&1; then
|
||
echo "✅ $name 成功"
|
||
PASSED_TESTS=$((PASSED_TESTS + 1))
|
||
return 0
|
||
else
|
||
echo "❌ $name 失敗"
|
||
FAILED_TESTS=$((FAILED_TESTS + 1))
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
echo ""
|
||
echo "========================================"
|
||
echo "📋 測試工具轉換功能"
|
||
echo "========================================"
|
||
|
||
# 1. Pandoc: Markdown → HTML (純 CLI)
|
||
run_test "Pandoc: Markdown → HTML" '
|
||
echo "# Test Document" > /tmp/test.md
|
||
echo "" >> /tmp/test.md
|
||
echo "Hello **world**" >> /tmp/test.md
|
||
pandoc /tmp/test.md -o /tmp/test.html
|
||
[ -s /tmp/test.html ] && cat /tmp/test.html
|
||
' || true
|
||
|
||
# 2. FFmpeg: 音頻生成 (純 CLI)
|
||
run_test "FFmpeg: 音頻生成" '
|
||
ffmpeg -f lavfi -i "sine=frequency=440:duration=1" -y /tmp/test.wav 2>/dev/null
|
||
[ -s /tmp/test.wav ] && ls -la /tmp/test.wav
|
||
' || true
|
||
|
||
# 3. FFmpeg: 影片生成 (純 CLI)
|
||
run_test "FFmpeg: 影片生成" '
|
||
ffmpeg -f lavfi -i color=c=blue:s=320x240:d=1 -y /tmp/test_video.mp4 2>/dev/null
|
||
[ -s /tmp/test_video.mp4 ] && ls -la /tmp/test_video.mp4
|
||
' || true
|
||
|
||
# 4. Resvg: SVG → PNG (純 CLI)
|
||
run_test "Resvg: SVG → PNG" '
|
||
echo "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"50\" height=\"50\"><rect fill=\"green\" width=\"50\" height=\"50\"/></svg>" > /tmp/resvg.svg
|
||
resvg /tmp/resvg.svg /tmp/resvg.png 2>/dev/null
|
||
[ -s /tmp/resvg.png ] && ls -la /tmp/resvg.png
|
||
' || true
|
||
|
||
# 5. Dasel: JSON → YAML (純 CLI)
|
||
run_test "Dasel: JSON → YAML" '
|
||
echo "{\"name\":\"test\",\"value\":123}" > /tmp/test.json
|
||
dasel -f /tmp/test.json -w yaml > /tmp/test.yaml 2>/dev/null
|
||
[ -s /tmp/test.yaml ] && cat /tmp/test.yaml
|
||
' || true
|
||
|
||
# 6. Tesseract: OCR (需要 ImageMagick 生成測試圖片)
|
||
run_test "Tesseract: OCR" '
|
||
convert -size 200x50 xc:white -font DejaVu-Sans -pointsize 20 -fill black -annotate +10+35 "Hello OCR" /tmp/ocr.png 2>/dev/null
|
||
tesseract /tmp/ocr.png /tmp/ocr 2>/dev/null
|
||
[ -s /tmp/ocr.txt ] && cat /tmp/ocr.txt
|
||
' || true
|
||
|
||
# 7. Inkscape: SVG → PNG (需要 xvfb)
|
||
run_test "Inkscape: SVG → PNG" '
|
||
echo "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100\" height=\"100\"><circle cx=\"50\" cy=\"50\" r=\"40\" fill=\"red\"/></svg>" > /tmp/ink.svg
|
||
timeout 60 xvfb-run -a --server-args="-screen 0 1024x768x24" inkscape --export-type=png --export-filename=/tmp/ink.png /tmp/ink.svg 2>&1 || true
|
||
[ -s /tmp/ink.png ] && ls -la /tmp/ink.png
|
||
' || true
|
||
|
||
# 8. LibreOffice: TXT → PDF (需要 xvfb)
|
||
run_test "LibreOffice: TXT → PDF" '
|
||
echo "Test document for LibreOffice conversion" > /tmp/libre.txt
|
||
timeout 60 xvfb-run -a --server-args="-screen 0 1024x768x24" libreoffice --headless --convert-to pdf --outdir /tmp /tmp/libre.txt 2>&1 || true
|
||
[ -s /tmp/libre.pdf ] && ls -la /tmp/libre.pdf
|
||
' || true
|
||
|
||
# 9. Calibre: HTML → EPUB (需要 xvfb)
|
||
run_test "Calibre: HTML → EPUB" '
|
||
echo "<!DOCTYPE html><html><body><h1>Test Book</h1><p>Content here</p></body></html>" > /tmp/book.html
|
||
timeout 60 xvfb-run -a --server-args="-screen 0 1024x768x24" ebook-convert /tmp/book.html /tmp/book.epub 2>&1 || true
|
||
[ -s /tmp/book.epub ] && ls -la /tmp/book.epub
|
||
' || true
|
||
|
||
echo ""
|
||
echo "========================================"
|
||
echo "📋 測試 API 端點"
|
||
echo "========================================"
|
||
|
||
# 10. Healthcheck API
|
||
run_test "API: Healthcheck" '
|
||
response=$(curl -sf http://localhost:3000/healthcheck)
|
||
echo "Response: $response"
|
||
[ -n "$response" ]
|
||
' || true
|
||
|
||
# 11. Converters API
|
||
run_test "API: Converters 列表" '
|
||
response=$(curl -sf http://localhost:3000/converters 2>/dev/null | head -c 500)
|
||
echo "Response (前500字符): $response"
|
||
[ -n "$response" ]
|
||
' || true
|
||
|
||
# 12. 主頁面
|
||
run_test "API: 主頁面載入" '
|
||
status=$(curl -sf -o /dev/null -w "%{http_code}" http://localhost:3000/)
|
||
echo "HTTP Status: $status"
|
||
[ "$status" = "200" ] || [ "$status" = "302" ]
|
||
' || true
|
||
|
||
# 測試摘要
|
||
echo ""
|
||
echo "========================================"
|
||
echo "📊 E2E 測試摘要"
|
||
echo "========================================"
|
||
echo "總測試數: $TOTAL_TESTS"
|
||
echo "通過: $PASSED_TESTS"
|
||
echo "失敗: $FAILED_TESTS"
|
||
echo "========================================"
|
||
|
||
# 計算通過率
|
||
if [ $TOTAL_TESTS -gt 0 ]; then
|
||
PASS_RATE=$((PASSED_TESTS * 100 / TOTAL_TESTS))
|
||
echo "通過率: ${PASS_RATE}%"
|
||
|
||
# 至少 70% 通過才算成功
|
||
if [ $PASS_RATE -ge 70 ]; then
|
||
echo "✅ E2E 測試通過 (>= 70% 通過率)"
|
||
else
|
||
echo "❌ E2E 測試失敗 (< 70% 通過率)"
|
||
exit 1
|
||
fi
|
||
else
|
||
echo "⚠️ 沒有執行任何測試"
|
||
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: [create-manifest, 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 }}
|