name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
id-token: write
packages: write
env:
CARGO_TERM_COLOR: always
jobs:
# Build CLI for all platforms
build-cli:
name: Build CLI ${{ matrix.target }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-apple-darwin
os: macos-latest
artifact: reticle-cli-darwin-x86_64
- target: aarch64-apple-darwin
os: macos-latest
artifact: reticle-cli-darwin-aarch64
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
artifact: reticle-cli-linux-x86_64
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
artifact: reticle-cli-linux-aarch64
- target: x86_64-pc-windows-msvc
os: windows-latest
artifact: reticle-cli-windows-x86_64
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install cross-compilation tools (Linux ARM64)
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# Configure cargo for cross-compilation
mkdir -p ~/.cargo
cat >> ~/.cargo/config.toml << EOF
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
EOF
- name: Build CLI
run: |
cd crates/reticle-cli
cargo build --release --target ${{ matrix.target }}
- name: Package (Unix)
if: runner.os != 'Windows'
run: |
mkdir -p dist
cp crates/reticle-cli/target/${{ matrix.target }}/release/reticle dist/
cd dist
tar -czvf ${{ matrix.artifact }}.tar.gz reticle
shasum -a 256 ${{ matrix.artifact }}.tar.gz > ${{ matrix.artifact }}.tar.gz.sha256
- name: Package (Windows)
if: runner.os == 'Windows'
run: |
mkdir dist
cp crates/reticle-cli/target/${{ matrix.target }}/release/reticle.exe dist/
cd dist
7z a ${{ matrix.artifact }}.zip reticle.exe
certutil -hashfile ${{ matrix.artifact }}.zip SHA256 > ${{ matrix.artifact }}.zip.sha256
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: dist/*
# Build GUI app for all platforms using Tauri
build-gui:
name: Build GUI ${{ matrix.platform }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- platform: macos-x86_64
os: macos-latest
target: x86_64-apple-darwin
artifact: reticle-app-darwin-x86_64
- platform: macos-aarch64
os: macos-latest
target: aarch64-apple-darwin
artifact: reticle-app-darwin-aarch64
- platform: linux-x86_64
os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
artifact: reticle-app-linux-x86_64
- platform: windows-x86_64
os: windows-latest
target: x86_64-pc-windows-msvc
artifact: reticle-app-windows-x86_64
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install frontend dependencies
run: |
cd frontend
npm ci
- name: Build frontend
run: |
cd frontend
npm run build
- name: Install Tauri CLI
run: cargo install tauri-cli --version "^2.0"
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
projectPath: src-tauri
tauriScript: cargo tauri
args: --target ${{ matrix.target }} --config '{"build":{"beforeBuildCommand":""}}'
- name: Package macOS (DMG + standalone binary)
if: runner.os == 'macOS'
run: |
mkdir -p dist
# Copy DMG
cp src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg dist/${{ matrix.artifact }}.dmg 2>/dev/null || true
# Copy standalone binary
cp src-tauri/target/${{ matrix.target }}/release/reticle-app dist/reticle-app
cd dist
tar -czvf ${{ matrix.artifact }}.tar.gz reticle-app
shasum -a 256 ${{ matrix.artifact }}.tar.gz > ${{ matrix.artifact }}.tar.gz.sha256
if [ -f "${{ matrix.artifact }}.dmg" ]; then
shasum -a 256 ${{ matrix.artifact }}.dmg > ${{ matrix.artifact }}.dmg.sha256
fi
- name: Package Linux (AppImage + standalone binary)
if: runner.os == 'Linux'
run: |
mkdir -p dist
# Copy AppImage
cp src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage dist/${{ matrix.artifact }}.AppImage 2>/dev/null || true
# Copy deb
cp src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb dist/${{ matrix.artifact }}.deb 2>/dev/null || true
# Copy standalone binary
cp src-tauri/target/${{ matrix.target }}/release/reticle-app dist/reticle-app
cd dist
tar -czvf ${{ matrix.artifact }}.tar.gz reticle-app
sha256sum ${{ matrix.artifact }}.tar.gz > ${{ matrix.artifact }}.tar.gz.sha256
if [ -f "${{ matrix.artifact }}.AppImage" ]; then
sha256sum ${{ matrix.artifact }}.AppImage > ${{ matrix.artifact }}.AppImage.sha256
fi
if [ -f "${{ matrix.artifact }}.deb" ]; then
sha256sum ${{ matrix.artifact }}.deb > ${{ matrix.artifact }}.deb.sha256
fi
- name: Package Windows (MSI + standalone binary)
if: runner.os == 'Windows'
run: |
mkdir dist
# Copy MSI
Copy-Item src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi dist/${{ matrix.artifact }}.msi -ErrorAction SilentlyContinue
# Copy NSIS installer
Copy-Item src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*.exe dist/${{ matrix.artifact }}-setup.exe -ErrorAction SilentlyContinue
# Copy standalone binary
Copy-Item src-tauri/target/${{ matrix.target }}/release/reticle-app.exe dist/reticle-app.exe
cd dist
7z a ${{ matrix.artifact }}.zip reticle-app.exe
certutil -hashfile ${{ matrix.artifact }}.zip SHA256 > ${{ matrix.artifact }}.zip.sha256
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: dist/*
release:
name: Create Release
needs: [build-cli, build-gui]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare release assets
run: |
mkdir release
# Copy all CLI and GUI artifacts
find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" -o -name "*.sha256" -o -name "*.dmg" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.msi" -o -name "*-setup.exe" \) -exec cp {} release/ \;
# Copy install script
cp scripts/install.sh release/ 2>/dev/null || true
ls -la release/
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: release/*
generate_release_notes: true
draft: false
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
publish-npm:
name: Publish to npm
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Download CLI artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: reticle-cli-*
- name: Prepare npm packages
run: |
VERSION=${GITHUB_REF#refs/tags/v}
# Update versions in all package.json files
for pkg in packages/npm packages/npm-binaries/*; do
if [ -f "$pkg/package.json" ]; then
jq ".version = \"$VERSION\"" "$pkg/package.json" > tmp.json && mv tmp.json "$pkg/package.json"
fi
done
# Copy binaries to platform packages
for artifact_dir in artifacts/reticle-cli-*; do
artifact_name=$(basename "$artifact_dir")
case "$artifact_name" in
reticle-cli-darwin-aarch64)
mkdir -p packages/npm-binaries/darwin-arm64/bin
cp "$artifact_dir/reticle" packages/npm-binaries/darwin-arm64/bin/
;;
reticle-cli-darwin-x86_64)
mkdir -p packages/npm-binaries/darwin-x64/bin
cp "$artifact_dir/reticle" packages/npm-binaries/darwin-x64/bin/
;;
reticle-cli-linux-x86_64)
mkdir -p packages/npm-binaries/linux-x64/bin
cp "$artifact_dir/reticle" packages/npm-binaries/linux-x64/bin/
;;
reticle-cli-linux-aarch64)
mkdir -p packages/npm-binaries/linux-arm64/bin
cp "$artifact_dir/reticle" packages/npm-binaries/linux-arm64/bin/
;;
reticle-cli-windows-x86_64)
mkdir -p packages/npm-binaries/win32-x64/bin
cp "$artifact_dir/reticle.exe" packages/npm-binaries/win32-x64/bin/
;;
esac
done
- name: Publish platform packages
run: |
for pkg in packages/npm-binaries/*; do
if [ -d "$pkg" ] && [ -f "$pkg/package.json" ]; then
cd "$pkg"
npm publish --access public || echo "Failed to publish $(basename $pkg), may already exist"
cd -
fi
done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish main package
run: |
cd packages/npm
npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish to GitHub Packages
run: |
# Configure npm for GitHub Packages
echo "@labterminal:registry=https://npm.pkg.github.com" > ~/.npmrc
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc
# Update package name with scope for GitHub Packages
cd packages/npm
jq '.name = "@labterminal/mcp-reticle"' package.json > tmp.json && mv tmp.json package.json
# Publish to GitHub Packages
npm publish --access public || echo "GitHub Packages publish failed or package exists"
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-pypi:
name: Publish to PyPI
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Download CLI artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: reticle-cli-*
- name: Prepare Python package
run: |
VERSION=${GITHUB_REF#refs/tags/v}
# Update version in pyproject.toml
sed -i "s/version = \".*\"/version = \"$VERSION\"/" packages/python/pyproject.toml
sed -i "s/__version__ = \".*\"/__version__ = \"$VERSION\"/" packages/python/mcp_reticle/__init__.py
# Copy binaries into package structure
mkdir -p packages/python/mcp_reticle/bin
for artifact_dir in artifacts/reticle-cli-*; do
artifact_name=$(basename "$artifact_dir")
case "$artifact_name" in
reticle-cli-darwin-aarch64)
mkdir -p packages/python/mcp_reticle/bin/darwin-aarch64
cp "$artifact_dir/reticle" packages/python/mcp_reticle/bin/darwin-aarch64/
;;
reticle-cli-darwin-x86_64)
mkdir -p packages/python/mcp_reticle/bin/darwin-x86_64
cp "$artifact_dir/reticle" packages/python/mcp_reticle/bin/darwin-x86_64/
;;
reticle-cli-linux-x86_64)
mkdir -p packages/python/mcp_reticle/bin/linux-x86_64
cp "$artifact_dir/reticle" packages/python/mcp_reticle/bin/linux-x86_64/
;;
reticle-cli-linux-aarch64)
mkdir -p packages/python/mcp_reticle/bin/linux-aarch64
cp "$artifact_dir/reticle" packages/python/mcp_reticle/bin/linux-aarch64/
;;
reticle-cli-windows-x86_64)
mkdir -p packages/python/mcp_reticle/bin/windows-x86_64
cp "$artifact_dir/reticle.exe" packages/python/mcp_reticle/bin/windows-x86_64/
;;
esac
done
- name: Build wheel
run: |
pip install build
cd packages/python
python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: packages/python/dist/
password: ${{ secrets.PYPI_TOKEN }}
update-homebrew:
name: Update Homebrew
needs: release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download SHA256 files
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: reticle-cli-*
- name: Update Homebrew formula
run: |
VERSION=${GITHUB_REF#refs/tags/v}
# Read SHA256 hashes
DARWIN_ARM64_SHA=$(cat artifacts/reticle-cli-darwin-aarch64/reticle-cli-darwin-aarch64.tar.gz.sha256 | awk '{print $1}')
DARWIN_X64_SHA=$(cat artifacts/reticle-cli-darwin-x86_64/reticle-cli-darwin-x86_64.tar.gz.sha256 | awk '{print $1}')
LINUX_ARM64_SHA=$(cat artifacts/reticle-cli-linux-aarch64/reticle-cli-linux-aarch64.tar.gz.sha256 | awk '{print $1}')
LINUX_X64_SHA=$(cat artifacts/reticle-cli-linux-x86_64/reticle-cli-linux-x86_64.tar.gz.sha256 | awk '{print $1}')
# Update formula
sed -i "s/version \".*\"/version \"$VERSION\"/" Formula/mcp-reticle.rb
sed -i "s/PLACEHOLDER_SHA256_DARWIN_ARM64/$DARWIN_ARM64_SHA/" Formula/mcp-reticle.rb
sed -i "s/PLACEHOLDER_SHA256_DARWIN_X64/$DARWIN_X64_SHA/" Formula/mcp-reticle.rb
sed -i "s/PLACEHOLDER_SHA256_LINUX_ARM64/$LINUX_ARM64_SHA/" Formula/mcp-reticle.rb
sed -i "s/PLACEHOLDER_SHA256_LINUX_X64/$LINUX_X64_SHA/" Formula/mcp-reticle.rb
echo "Updated formula:"
cat Formula/mcp-reticle.rb
- name: Commit updated formula
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git fetch origin main
git checkout -B main origin/main
git add Formula/mcp-reticle.rb
git commit -m "Update Homebrew formula for ${{ github.ref_name }}"
git push origin main