name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
version:
description: 'Version to release'
required: true
type: string
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
permissions:
contents: write
packages: write
jobs:
# Pre-release validation
validate:
name: Pre-release Validation
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ubuntu-validation-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Run tests
run: cargo test --all-features
env:
RUSTFLAGS: "--cfg ci"
- name: Security audit
run: |
cargo install cargo-audit
cargo audit
# Create GitHub release
create-release:
name: Create Release
needs: validate
runs-on: ubuntu-latest
outputs:
version: ${{ steps.extract_version.outputs.version }}
release_id: ${{ steps.create_release.outputs.id }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Extract version
id: extract_version
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
else
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi
- name: Create Release
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.extract_version.outputs.version }}"
PRERELEASE=""
if [[ "$VERSION" == *"alpha"* ]] || [[ "$VERSION" == *"beta"* ]] || [[ "$VERSION" == *"rc"* ]]; then
PRERELEASE="--prerelease"
fi
# Check if release already exists
if gh release view "$VERSION" > /dev/null 2>&1; then
echo "Release $VERSION already exists, getting its ID"
RELEASE_ID=$(gh release view "$VERSION" --json id -q '.id')
echo "Using existing release ID: $RELEASE_ID"
else
echo "Creating new release $VERSION"
RELEASE_ID=$(gh release create "$VERSION" \
--title "HT-MCP $VERSION" \
--notes "## What's Changed
See [CHANGELOG.md](https://github.com/memextech/ht-mcp/blob/main/CHANGELOG.md) for detailed changes.
## Installation
### Homebrew
\`\`\`bash
brew tap memextech/tap
brew install ht-mcp
\`\`\`
### Cargo
\`\`\`bash
cargo install ht-mcp
\`\`\`
### Pre-built Binaries
Download the appropriate binary for your platform below:
- **macOS**: \`ht-mcp-x86_64-apple-darwin\` (Intel) or \`ht-mcp-aarch64-apple-darwin\` (Apple Silicon)
- **Linux**: \`ht-mcp-x86_64-unknown-linux-gnu\` (glibc), \`ht-mcp-x86_64-unknown-linux-musl\` (static), or \`ht-mcp-aarch64-unknown-linux-gnu\` (ARM64)
- **Windows**: \`ht-mcp-x86_64-pc-windows-msvc\` (64-bit)
This release includes experimental Windows support." \
$PRERELEASE \
--generate-notes)
fi
echo "id=$RELEASE_ID" >> $GITHUB_OUTPUT
# Build and upload binaries
build-release:
name: Build Release
needs: create-release
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
cross: false
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
cross: true
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
cross: true
- os: macos-13 # Intel
target: x86_64-apple-darwin
cross: false
- os: macos-14 # Apple Silicon
target: aarch64-apple-darwin
cross: false
- os: windows-latest # Windows
target: x86_64-pc-windows-msvc
cross: false
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ matrix.target }}-release-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install cross
if: matrix.cross
run: cargo install cross --git https://github.com/cross-rs/cross
- name: Build release binary
shell: bash
run: |
if [[ "${{ matrix.cross }}" == "true" ]]; then
cross build --release --locked --target ${{ matrix.target }}
else
cargo build --release --locked --target ${{ matrix.target }}
fi
- name: Prepare binary for upload (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
cd target/${{ matrix.target }}/release
BINARY_NAME="ht-mcp"
ASSET_NAME="ht-mcp-${{ matrix.target }}"
# Strip binary to reduce size
strip $BINARY_NAME || true
# Create checksum
shasum -a 256 $BINARY_NAME | sed "s/$BINARY_NAME/$ASSET_NAME/" > $ASSET_NAME.sha256
# Rename binary
mv $BINARY_NAME $ASSET_NAME
- name: Prepare binary for upload (Windows)
if: runner.os == 'Windows'
shell: powershell
run: |
cd target/${{ matrix.target }}/release
$BINARY_NAME = "ht-mcp.exe"
$ASSET_NAME = "ht-mcp-${{ matrix.target }}.exe"
# Create checksum
$hash = Get-FileHash -Algorithm SHA256 $BINARY_NAME
$hashString = $hash.Hash.ToLower() + " " + $ASSET_NAME
$hashString | Out-File -Encoding ASCII "$ASSET_NAME.sha256"
# Remove destination file if it exists (for re-runs)
if (Test-Path $ASSET_NAME) {
Remove-Item $ASSET_NAME -Force
}
# Rename binary
Rename-Item $BINARY_NAME $ASSET_NAME
- name: Upload Release Assets (Unix)
if: runner.os != 'Windows'
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd target/${{ matrix.target }}/release
VERSION="${{ needs.create-release.outputs.version }}"
ASSET_NAME="ht-mcp-${{ matrix.target }}"
# Upload binary and checksum (--clobber overwrites existing assets)
gh release upload "$VERSION" "$ASSET_NAME" "$ASSET_NAME.sha256" --clobber
- name: Upload Release Assets (Windows)
if: runner.os == 'Windows'
shell: powershell
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd target/${{ matrix.target }}/release
$VERSION = "${{ needs.create-release.outputs.version }}"
$ASSET_NAME = "ht-mcp-${{ matrix.target }}.exe"
# Upload binary and checksum (--clobber overwrites existing assets)
gh release upload $VERSION $ASSET_NAME "$ASSET_NAME.sha256" --clobber
# Publish to crates.io - DISABLED until ht-core publishes to crates.io
# publish-crate:
# name: Publish to Crates.io
# needs: [create-release, build-release]
# runs-on: ubuntu-latest
# if: startsWith(github.ref, 'refs/tags/v') && !contains(needs.create-release.outputs.version, 'alpha') && !contains(needs.create-release.outputs.version, 'beta') && !contains(needs.create-release.outputs.version, 'rc')
#
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
# with:
# submodules: recursive
#
# - name: Install Rust toolchain
# uses: dtolnay/rust-toolchain@stable
#
# - name: Cache dependencies
# uses: actions/cache@v3
# with:
# path: |
# ~/.cargo/registry/index/
# ~/.cargo/registry/cache/
# ~/.cargo/git/db/
# target/
# key: ubuntu-publish-cargo-${{ hashFiles('**/Cargo.lock') }}
#
# - name: Publish to crates.io
# env:
# CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
# run: cargo publish --token $CARGO_REGISTRY_TOKEN