d4ebdbf6a9
- Add `fonts download` and `fonts verify` subcommands - Implement Rust-based downloader (ureq + tar + flate2) with pinned sources - Verify SHA-256 for Liberation and Noto Sans TTFs for reproducibility - Keep binary behind `build-bin` feature; library build unaffected
490 lines
15 KiB
YAML
490 lines
15 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*.*.*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: 'Version to release (e.g., v1.0.0)'
|
|
required: true
|
|
type: string
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
RUST_BACKTRACE: 1
|
|
|
|
jobs:
|
|
validate-release:
|
|
name: Validate Release
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.get-version.outputs.version }}
|
|
tag: ${{ steps.get-version.outputs.tag }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Get version from tag or input
|
|
id: get-version
|
|
run: |
|
|
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
|
VERSION="${{ github.event.inputs.version }}"
|
|
echo "version=${VERSION#v}" >> $GITHUB_OUTPUT
|
|
echo "tag=${VERSION}" >> $GITHUB_OUTPUT
|
|
else
|
|
VERSION="${GITHUB_REF#refs/tags/}"
|
|
echo "version=${VERSION#v}" >> $GITHUB_OUTPUT
|
|
echo "tag=${VERSION}" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Validate version format
|
|
run: |
|
|
VERSION="${{ steps.get-version.outputs.version }}"
|
|
if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
|
|
echo "❌ Invalid version format: $VERSION"
|
|
echo "Expected format: X.Y.Z or X.Y.Z-suffix"
|
|
exit 1
|
|
fi
|
|
echo "✅ Valid version format: $VERSION"
|
|
|
|
- name: Check if version matches Cargo.toml
|
|
run: |
|
|
CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
|
|
INPUT_VERSION="${{ steps.get-version.outputs.version }}"
|
|
if [ "$CARGO_VERSION" != "$INPUT_VERSION" ]; then
|
|
echo "❌ Version mismatch:"
|
|
echo " Cargo.toml: $CARGO_VERSION"
|
|
echo " Tag/Input: $INPUT_VERSION"
|
|
exit 1
|
|
fi
|
|
echo "✅ Version matches Cargo.toml: $INPUT_VERSION"
|
|
|
|
test:
|
|
name: Run Tests
|
|
runs-on: ${{ matrix.os }}
|
|
needs: validate-release
|
|
strategy:
|
|
matrix:
|
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
rust: [stable]
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@master
|
|
with:
|
|
toolchain: ${{ matrix.rust }}
|
|
|
|
- name: Cache Cargo registry
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
target
|
|
key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}
|
|
|
|
- name: Install system dependencies (Ubuntu)
|
|
if: matrix.os == 'ubuntu-latest'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
build-essential \
|
|
pkg-config \
|
|
libssl-dev \
|
|
libfontconfig1-dev \
|
|
libfreetype6-dev \
|
|
libjpeg-dev \
|
|
libpng-dev
|
|
|
|
- name: Install system dependencies (macOS)
|
|
if: matrix.os == 'macos-latest'
|
|
run: |
|
|
brew update
|
|
brew install pkg-config freetype jpeg libpng
|
|
|
|
- name: Run tests (library only)
|
|
run: cargo test --verbose --lib
|
|
|
|
build:
|
|
name: Build Release Artifacts
|
|
runs-on: ${{ matrix.job.os }}
|
|
needs: [validate-release, test]
|
|
strategy:
|
|
matrix:
|
|
job:
|
|
- { os: ubuntu-latest, target: x86_64-unknown-linux-gnu, use-cross: false }
|
|
- { os: ubuntu-latest, target: x86_64-unknown-linux-musl, use-cross: true }
|
|
- { os: ubuntu-latest, target: aarch64-unknown-linux-gnu, use-cross: true }
|
|
- { os: ubuntu-latest, target: aarch64-unknown-linux-musl, use-cross: true }
|
|
- { os: windows-latest, target: x86_64-pc-windows-msvc, use-cross: false }
|
|
- { os: macos-latest, target: x86_64-apple-darwin, use-cross: false }
|
|
- { os: macos-latest, target: aarch64-apple-darwin, use-cross: false }
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.job.target }}
|
|
|
|
- name: Install cross compilation tools
|
|
if: matrix.job.use-cross
|
|
run: |
|
|
cargo install cross --git https://github.com/cross-rs/cross
|
|
|
|
- name: Cache Cargo registry
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
target
|
|
key: ${{ runner.os }}-${{ matrix.job.target }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}
|
|
|
|
- name: Install system dependencies (Ubuntu)
|
|
if: matrix.job.os == 'ubuntu-latest' && !matrix.job.use-cross
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
build-essential \
|
|
pkg-config \
|
|
libssl-dev \
|
|
libfontconfig1-dev \
|
|
libfreetype6-dev \
|
|
libjpeg-dev \
|
|
libpng-dev
|
|
|
|
- name: Install system dependencies (macOS)
|
|
if: matrix.job.os == 'macos-latest'
|
|
run: |
|
|
brew update
|
|
brew install pkg-config freetype jpeg libpng
|
|
|
|
- name: Build release binary
|
|
run: |
|
|
if [ "${{ matrix.job.use-cross }}" = "true" ]; then
|
|
cross build --release --target ${{ matrix.job.target }}
|
|
else
|
|
cargo build --release --target ${{ matrix.job.target }}
|
|
fi
|
|
|
|
- name: Prepare release archive
|
|
shell: bash
|
|
run: |
|
|
VERSION="${{ needs.validate-release.outputs.version }}"
|
|
TARGET="${{ matrix.job.target }}"
|
|
|
|
# Create staging directory
|
|
mkdir -p staging
|
|
|
|
# Copy binary
|
|
if [[ "${{ matrix.job.os }}" == "windows-latest" ]]; then
|
|
cp "target/${TARGET}/release/docx-mcp.exe" staging/
|
|
BINARY="docx-mcp.exe"
|
|
else
|
|
cp "target/${TARGET}/release/docx-mcp" staging/
|
|
BINARY="docx-mcp"
|
|
fi
|
|
|
|
# Copy additional files
|
|
cp README.md staging/
|
|
cp LICENSE staging/
|
|
|
|
# Create archive name
|
|
ARCHIVE="docx-mcp-${VERSION}-${TARGET}"
|
|
|
|
# Create archive
|
|
cd staging
|
|
if [[ "${{ matrix.job.os }}" == "windows-latest" ]]; then
|
|
7z a "../${ARCHIVE}.zip" *
|
|
echo "ARCHIVE=${ARCHIVE}.zip" >> $GITHUB_ENV
|
|
else
|
|
tar czf "../${ARCHIVE}.tar.gz" *
|
|
echo "ARCHIVE=${ARCHIVE}.tar.gz" >> $GITHUB_ENV
|
|
fi
|
|
cd ..
|
|
|
|
# Generate checksums
|
|
if [[ "${{ matrix.job.os }}" == "windows-latest" ]]; then
|
|
certutil -hashfile "${ARCHIVE}.zip" SHA256 > "${ARCHIVE}.zip.sha256"
|
|
else
|
|
shasum -a 256 "${ARCHIVE}.tar.gz" > "${ARCHIVE}.tar.gz.sha256"
|
|
fi
|
|
|
|
echo "BINARY=${BINARY}" >> $GITHUB_ENV
|
|
|
|
- name: Upload release artifacts
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: release-${{ matrix.job.target }}
|
|
path: |
|
|
${{ env.ARCHIVE }}
|
|
${{ env.ARCHIVE }}.sha256
|
|
|
|
create-release:
|
|
name: Create GitHub Release
|
|
runs-on: ubuntu-latest
|
|
needs: [validate-release, build]
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Prepare release assets
|
|
run: |
|
|
mkdir -p release-assets
|
|
find artifacts -type f -name "*.tar.gz" -o -name "*.zip" -o -name "*.sha256" | \
|
|
xargs -I {} cp {} release-assets/
|
|
ls -la release-assets/
|
|
|
|
- name: Generate changelog
|
|
id: changelog
|
|
run: |
|
|
VERSION="${{ needs.validate-release.outputs.version }}"
|
|
TAG="${{ needs.validate-release.outputs.tag }}"
|
|
|
|
# Get previous tag
|
|
PREV_TAG=$(git tag --sort=-version:refname | grep -v "^${TAG}$" | head -1)
|
|
|
|
echo "Generating changelog from ${PREV_TAG} to ${TAG}"
|
|
|
|
# Generate changelog
|
|
if [ -n "$PREV_TAG" ]; then
|
|
CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges ${PREV_TAG}..HEAD)
|
|
else
|
|
CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges)
|
|
fi
|
|
|
|
# Create release notes
|
|
cat > release-notes.md << EOF
|
|
## What's Changed
|
|
|
|
${CHANGELOG}
|
|
|
|
## Installation
|
|
|
|
### Pre-built Binaries
|
|
|
|
Download the appropriate binary for your system:
|
|
|
|
- **Linux x86_64**: \`docx-mcp-${VERSION}-x86_64-unknown-linux-gnu.tar.gz\`
|
|
- **Linux x86_64 (musl)**: \`docx-mcp-${VERSION}-x86_64-unknown-linux-musl.tar.gz\`
|
|
- **Linux ARM64**: \`docx-mcp-${VERSION}-aarch64-unknown-linux-gnu.tar.gz\`
|
|
- **macOS Intel**: \`docx-mcp-${VERSION}-x86_64-apple-darwin.tar.gz\`
|
|
- **macOS Apple Silicon**: \`docx-mcp-${VERSION}-aarch64-apple-darwin.tar.gz\`
|
|
- **Windows x86_64**: \`docx-mcp-${VERSION}-x86_64-pc-windows-msvc.zip\`
|
|
|
|
### From Source
|
|
|
|
\`\`\`bash
|
|
cargo install --git https://github.com/hongkongkiwi/docx-mcp --tag ${TAG}
|
|
\`\`\`
|
|
|
|
### Verification
|
|
|
|
All binaries are provided with SHA256 checksums for verification:
|
|
|
|
\`\`\`bash
|
|
# Linux/macOS
|
|
shasum -a 256 -c docx-mcp-${VERSION}-your-target.tar.gz.sha256
|
|
|
|
# Windows
|
|
certutil -hashfile docx-mcp-${VERSION}-x86_64-pc-windows-msvc.zip SHA256
|
|
\`\`\`
|
|
|
|
## Full Changelog
|
|
|
|
**Full Changelog**: https://github.com/hongkongkiwi/docx-mcp/compare/${PREV_TAG}...${TAG}
|
|
EOF
|
|
|
|
echo "CHANGELOG_FILE=release-notes.md" >> $GITHUB_OUTPUT
|
|
|
|
- name: Create Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: ${{ needs.validate-release.outputs.tag }}
|
|
name: Release ${{ needs.validate-release.outputs.tag }}
|
|
body_path: ${{ steps.changelog.outputs.CHANGELOG_FILE }}
|
|
files: release-assets/*
|
|
draft: false
|
|
prerelease: ${{ contains(needs.validate-release.outputs.version, '-') }}
|
|
generate_release_notes: false
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
publish-crate:
|
|
name: Publish to crates.io
|
|
runs-on: ubuntu-latest
|
|
needs: [validate-release, create-release]
|
|
if: ${{ !contains(needs.validate-release.outputs.version, '-') }} # Only publish stable releases
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Install system dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
build-essential \
|
|
pkg-config \
|
|
libssl-dev \
|
|
libfontconfig1-dev \
|
|
libfreetype6-dev \
|
|
libjpeg-dev \
|
|
libpng-dev
|
|
|
|
- name: Cache Cargo registry
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
target
|
|
key: ubuntu-cargo-publish-${{ hashFiles('**/Cargo.lock') }}
|
|
|
|
- name: Verify package
|
|
run: cargo package --dry-run
|
|
|
|
- name: Publish to crates.io
|
|
run: cargo publish
|
|
env:
|
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
|
|
|
docker-release:
|
|
name: Build and Push Docker Images
|
|
runs-on: ubuntu-latest
|
|
needs: [validate-release, create-release]
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Login to GitHub Container Registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Login to Docker Hub
|
|
if: ${{ secrets.DOCKERHUB_USERNAME && secrets.DOCKERHUB_TOKEN }}
|
|
uses: docker/login-action@v3
|
|
with:
|
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
- name: Extract metadata
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: |
|
|
ghcr.io/${{ github.repository }}
|
|
${{ secrets.DOCKERHUB_USERNAME && format('{0}/docx-mcp', secrets.DOCKERHUB_USERNAME) || '' }}
|
|
tags: |
|
|
type=ref,event=tag
|
|
type=semver,pattern={{version}}
|
|
type=semver,pattern={{major}}.{{minor}}
|
|
type=semver,pattern={{major}}
|
|
type=raw,value=latest,enable={{is_default_branch}}
|
|
|
|
- name: Build and push Docker image
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: .
|
|
file: ./Dockerfile
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
update-docs:
|
|
name: Update Documentation
|
|
runs-on: ubuntu-latest
|
|
needs: [validate-release, create-release]
|
|
permissions:
|
|
contents: write
|
|
pages: write
|
|
id-token: write
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Install system dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
build-essential \
|
|
pkg-config \
|
|
libssl-dev \
|
|
libfontconfig1-dev \
|
|
libfreetype6-dev \
|
|
libjpeg-dev \
|
|
libpng-dev
|
|
|
|
- name: Generate documentation
|
|
run: |
|
|
cargo doc --all-features --no-deps
|
|
echo '<meta http-equiv="refresh" content="0; url=docx_mcp">' > target/doc/index.html
|
|
|
|
- name: Deploy to GitHub Pages
|
|
uses: peaceiris/actions-gh-pages@v4
|
|
with:
|
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
publish_dir: ./target/doc
|
|
cname: docs.example.com # Replace with your custom domain if you have one
|
|
|
|
notify-success:
|
|
name: Notify Release Success
|
|
runs-on: ubuntu-latest
|
|
needs: [create-release, publish-crate, docker-release, update-docs]
|
|
if: success()
|
|
steps:
|
|
- name: Success notification
|
|
run: |
|
|
echo "🎉 Release ${{ needs.validate-release.outputs.tag }} completed successfully!"
|
|
echo "- ✅ GitHub release created"
|
|
echo "- ✅ Binaries built for all platforms"
|
|
echo "- ✅ Published to crates.io"
|
|
echo "- ✅ Docker images pushed"
|
|
echo "- ✅ Documentation updated"
|
|
|
|
notify-failure:
|
|
name: Notify Release Failure
|
|
runs-on: ubuntu-latest
|
|
needs: [validate-release, test, build, create-release, publish-crate, docker-release, update-docs]
|
|
if: failure()
|
|
steps:
|
|
- name: Failure notification
|
|
run: |
|
|
echo "❌ Release ${{ needs.validate-release.outputs.tag }} failed!"
|
|
echo "Please check the workflow logs for details."
|
|
exit 1 |