name: Release π
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag_name:
description: 'Tag name for release (e.g., v4.0.0)'
required: true
default: 'v4.0.0'
type: string
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
# Set the tag name based on trigger type
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.ref_name }}
jobs:
build:
name: Build ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Linux builds
- os: ubuntu-latest
name: Linux x86_64
target: x86_64-unknown-linux-gnu
archive: tar.gz
extension: ''
- os: ubuntu-latest
name: Linux ARM64
target: aarch64-unknown-linux-gnu
archive: tar.gz
extension: ''
cross: true
# macOS builds
- os: macos-latest
name: macOS Intel
target: x86_64-apple-darwin
archive: tar.gz
extension: ''
- os: macos-latest
name: macOS Apple Silicon
target: aarch64-apple-darwin
archive: tar.gz
extension: ''
# Windows builds
- os: windows-latest
name: Windows x86_64
target: x86_64-pc-windows-msvc
archive: zip
extension: .exe
steps:
- name: Checkout repository (with submodules)
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install Rust π¦
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache cargo registry π
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-registry-
- name: Cache cargo index π
uses: actions/cache@v4
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-index-
- name: Install cross-compilation tools π οΈ
if: matrix.cross == true
uses: taiki-e/install-action@v2
with:
tool: cross
- name: Build (native) π¨
if: matrix.cross != true
run: cargo build --release --target ${{ matrix.target }}
- name: Build (cross) π§
if: matrix.cross == true
run: cross build --release --target ${{ matrix.target }}
# - name: Run tests π§ͺ
# if: ${{ matrix.cross != true && matrix.os != 'windows-latest' }}
# run: cargo test --release --target ${{ matrix.target }}
- name: Strip binary (Linux/macOS) ποΈ
if: matrix.os != 'windows-latest' && matrix.cross != true
run: |
strip target/${{ matrix.target }}/release/st
- name: Strip binary (cross-compiled) ποΈ
if: matrix.cross == true
run: |
# For cross-compiled binaries, we need the target-specific strip command
# or skip stripping as it's optional for release builds
echo "Skipping strip for cross-compiled binary ${{ matrix.target }}"
# Alternatively, install and use the correct strip tool:
# For ARM64 Linux: sudo apt-get install -y gcc-aarch64-linux-gnu
# Then use: aarch64-linux-gnu-strip target/${{ matrix.target }}/release/st
# But for simplicity, we'll skip it as Rust already optimizes release builds
- name: Create archive π¦
shell: bash
run: |
cd target/${{ matrix.target }}/release
# Set binary name with extension
BINARY_NAME="st${{ matrix.extension }}"
# Set archive names - both versioned and unversioned for compatibility
VERSIONED_ARCHIVE="st-${{ env.RELEASE_TAG }}-${{ matrix.target }}"
UNVERSIONED_ARCHIVE="st-${{ matrix.target }}"
# Copy documentation files
cp ../../../README.md . || true
cp ../../../LICENSE . || true
cp ../../../SMART_TREE_CHEET_SHEET.md . || true
# Create archive based on type
if [[ "${{ matrix.archive }}" == "zip" ]]; then
# Windows ZIP - versioned
7z a "../../../${VERSIONED_ARCHIVE}.zip" "$BINARY_NAME" README.md LICENSE SMART_TREE_CHEET_SHEET.md
# Also create unversioned for backward compatibility
cp "../../../${VERSIONED_ARCHIVE}.zip" "../../../${UNVERSIONED_ARCHIVE}.zip"
else
# Unix tar.gz - versioned
tar czf "../../../${VERSIONED_ARCHIVE}.tar.gz" "$BINARY_NAME" README.md LICENSE SMART_TREE_CHEET_SHEET.md
# Also create unversioned for backward compatibility
cp "../../../${VERSIONED_ARCHIVE}.tar.gz" "../../../${UNVERSIONED_ARCHIVE}.tar.gz"
fi
cd ../../..
- name: Generate checksums π
shell: bash
run: |
# Generate checksums for both versioned and unversioned archives
VERSIONED_ARCHIVE="st-${{ env.RELEASE_TAG }}-${{ matrix.target }}.${{ matrix.archive }}"
UNVERSIONED_ARCHIVE="st-${{ matrix.target }}.${{ matrix.archive }}"
if [[ -f "$VERSIONED_ARCHIVE" ]]; then
sha256sum "$VERSIONED_ARCHIVE" > "${VERSIONED_ARCHIVE}.sha256"
fi
if [[ -f "$UNVERSIONED_ARCHIVE" ]]; then
sha256sum "$UNVERSIONED_ARCHIVE" > "${UNVERSIONED_ARCHIVE}.sha256"
fi
- name: Upload artifact π€
uses: actions/upload-artifact@v4
with:
name: st-${{ matrix.target }}
path: |
st-${{ env.RELEASE_TAG }}-${{ matrix.target }}.${{ matrix.archive }}
st-${{ env.RELEASE_TAG }}-${{ matrix.target }}.${{ matrix.archive }}.sha256
st-${{ matrix.target }}.${{ matrix.archive }}
st-${{ matrix.target }}.${{ matrix.archive }}.sha256
if-no-files-found: error
build-dxt:
name: Build DXT Package π¦
runs-on: ubuntu-latest
steps:
- name: Checkout code π¦
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install dependencies π οΈ
run: |
sudo apt-get update
sudo apt-get install -y zip jq
- name: Build DXT package π
run: |
if [[ -d "dxt" && -f "dxt/build-dxt.sh" ]]; then
cd dxt
chmod +x build-dxt.sh
./build-dxt.sh
cd ..
# Generate checksum if file exists
if [[ -f "dxt/smart-tree.dxt" ]]; then
sha256sum dxt/smart-tree.dxt > dxt/smart-tree.dxt.sha256
fi
else
echo "No DXT directory found, skipping DXT build"
mkdir -p dxt
touch dxt/smart-tree.dxt
fi
- name: Upload DXT artifact π€
uses: actions/upload-artifact@v4
with:
name: smart-tree-dxt
path: |
dxt/smart-tree.dxt
dxt/smart-tree.dxt.sha256
if-no-files-found: warn
release:
name: Create Release π
needs: [build, build-dxt]
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- name: Checkout code π¦
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Download artifacts π₯
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare release assets π
shell: bash
run: |
# Move all artifacts to release directory
mkdir -p release
find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" -o -name "*.dxt" -o -name "*.sha256" \) -exec mv {} release/ \; 2>/dev/null || true
# List all release files
echo "Release files:"
ls -la release/ || echo "No release files found"
- name: Generate release notes π
shell: bash
run: |
TAG_NAME="${{ env.RELEASE_TAG }}"
# Try to get previous tag
PREVIOUS_TAG=""
if git describe --tags --abbrev=0 "${TAG_NAME}^" 2>/dev/null; then
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${TAG_NAME}^" 2>/dev/null || echo "")
fi
# Start release notes
echo "## πΈ Smart Tree ${TAG_NAME} Release" > release_notes.md
echo "" >> release_notes.md
# Add link to full release notes if they exist
if [[ -f "RELEASE_NOTES_${TAG_NAME}.md" ]]; then
echo "π **[Full Release Notes](https://github.com/${{ github.repository }}/blob/main/RELEASE_NOTES_${TAG_NAME}.md)**" >> release_notes.md
echo "" >> release_notes.md
fi
# Generate changelog if we have a previous tag
if [[ -n "$PREVIOUS_TAG" ]]; then
echo "### π Changes since $PREVIOUS_TAG" >> release_notes.md
echo "" >> release_notes.md
git log "${PREVIOUS_TAG}..${TAG_NAME}" --pretty=format:"- %s (%an)" >> release_notes.md || true
echo "" >> release_notes.md
echo "" >> release_notes.md
fi
# Add installation instructions
cat >> release_notes.md << 'EOF'
## β‘ Quick Installation
### Linux/macOS/WSL
```bash
curl -sSL https://raw.githubusercontent.com/8b-is/smart-tree/main/scripts/install.sh | bash
```
### Claude Desktop MCP Configuration
```json
{
"mcpServers": {
"smart-tree": {
"command": "/usr/local/bin/st",
"args": ["--mcp"],
"env": {
"AI_TOOLS": "1"
}
}
}
}
```
## π¦ Download Options
| Platform | Architecture | File |
|----------|--------------|------|
| Linux | x86_64 | `st-${{ env.RELEASE_TAG }}-x86_64-unknown-linux-gnu.tar.gz` |
| Linux | ARM64 | `st-${{ env.RELEASE_TAG }}-aarch64-unknown-linux-gnu.tar.gz` |
| macOS | Intel | `st-${{ env.RELEASE_TAG }}-x86_64-apple-darwin.tar.gz` |
| macOS | Apple Silicon | `st-${{ env.RELEASE_TAG }}-aarch64-apple-darwin.tar.gz` |
| Windows | x86_64 | `st-${{ env.RELEASE_TAG }}-x86_64-pc-windows-msvc.zip` |
| Claude Desktop | All | `smart-tree.dxt` |
---
πΈ **Pro Tip**: Life's too short for slow directory traversal!
Built with π by the Smart Tree Team: Hue, Aye, Trisha, and The Cheet
Aye, Aye! π’
EOF
- name: Create GitHub Release π
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.RELEASE_TAG }}
name: Smart Tree ${{ env.RELEASE_TAG }} π³
body_path: release_notes.md
files: release/*
draft: false
prerelease: false
fail_on_unmatched_files: false
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}