Add comprehensive release infrastructure and tooling

- Add automated GitHub Actions workflow for multi-platform releases
- Add release script with version management and validation
- Add Docker container support with multi-stage builds
- Add comprehensive release documentation and templates
- Update Cargo.toml with complete package metadata
- Add command-line argument parsing with security options
- Update README with detailed configuration examples

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andy
2025-08-11 14:40:53 +08:00
parent 39e94c1b13
commit 705e8bfa91
11 changed files with 1819 additions and 30 deletions
+58
View File
@@ -0,0 +1,58 @@
---
name: Release Checklist
about: Checklist for preparing a new release
title: 'Release v[VERSION]'
labels: 'release'
assignees: ''
---
## Pre-release Checklist
- [ ] All planned features and fixes are merged
- [ ] All tests are passing on main branch
- [ ] Documentation is updated
- [ ] CHANGELOG.md is updated (if maintained separately)
- [ ] Version is updated in Cargo.toml
- [ ] No critical security vulnerabilities in dependencies
## Release Process
- [ ] Run `./scripts/release.sh [patch|minor|major|version X.Y.Z]`
- [ ] Verify all CI checks pass
- [ ] Tag is created and pushed
- [ ] GitHub release is created automatically
- [ ] Binaries are built for all platforms
- [ ] Crate is published to crates.io (for stable releases)
- [ ] Docker images are pushed
## Post-release Tasks
- [ ] Verify release artifacts are available
- [ ] Test installation from released binaries
- [ ] Update any dependent projects
- [ ] Announce release (if applicable)
## Release Notes
<!--
Add release notes here:
- New features
- Bug fixes
- Breaking changes
- Performance improvements
- Security fixes
-->
## Verification Commands
```bash
# Test the release script (dry run)
./scripts/release.sh patch --dry-run
# Run pre-release checks
./scripts/release.sh check
# Create the actual release
./scripts/release.sh patch # or minor/major/version X.Y.Z
```
+490
View File
@@ -0,0 +1,490 @@
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
run: cargo test --all-features --verbose
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 }} --all-features
else
cargo build --release --target ${{ matrix.job.target }} --all-features
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