name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
BINARY_NAME: tsift
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
verify:
name: verify release inputs
runs-on: ubuntu-latest
defaults:
run:
working-directory: tsift
steps:
- uses: actions/checkout@v4
with:
path: tsift
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Install ripgrep
run: sudo apt-get update && sudo apt-get install -y ripgrep
- name: Validate tag matches crate version
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: |
tag_version="${GITHUB_REF_NAME#v}"
while IFS= read -r manifest; do
crate_name="$(sed -n 's/^name = "\(.*\)"/\1/p' "$manifest" | head -n 1)"
crate_version="$(sed -n 's/^version = "\(.*\)"/\1/p' "$manifest" | head -n 1)"
if [ -z "$crate_name" ] || [ -z "$crate_version" ]; then
echo "failed to read package name/version from $manifest" >&2
exit 1
fi
if [ "$tag_version" != "$crate_version" ]; then
echo "tag version v$tag_version does not match $crate_name version $crate_version in $manifest" >&2
exit 1
fi
done < <(find . -name Cargo.toml -not -path './target/*' | sort)
- name: Full CI suite
run: make ci-full
- name: Crate package file check
shell: bash
run: |
for package in \
tsift-core \
tsift-graph \
tsift-sqlite \
tsift-algorithms \
tsift-resolution \
tsift-tokensave \
tsift-libsql \
tsift-index \
tsift-summarize \
tsift-quality \
tsift-agent-doc \
tsift-digest \
tsift-search \
tsift-status \
tsift-session \
tsift-memory \
tsift-surrealdb \
tsift-cli \
tsift \
tsift-sim-world
do
cargo package -p "$package" --locked --allow-dirty --list > "/tmp/${package}.package-files"
done
- name: OpenCode plugin publish dry run
run: npm run publish:check
working-directory: tsift/packages/opencode-tsift
build:
name: build ${{ matrix.target }}
runs-on: ${{ matrix.os }}
needs: verify
defaults:
run:
working-directory: tsift
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
asset_name: tsift-x86_64-unknown-linux-gnu.tar.gz
- os: macos-14
target: aarch64-apple-darwin
asset_name: tsift-aarch64-apple-darwin.tar.gz
- os: windows-latest
target: x86_64-pc-windows-msvc
asset_name: tsift-x86_64-pc-windows-msvc.zip
steps:
- uses: actions/checkout@v4
with:
path: tsift
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Build release binary
run: cargo build -p tsift --release --locked --target ${{ matrix.target }}
- name: Package Unix archive
if: runner.os != 'Windows'
shell: bash
run: |
stage_dir="dist/${BINARY_NAME}-${{ matrix.target }}"
mkdir -p "$stage_dir"
cp "target/${{ matrix.target }}/release/${BINARY_NAME}" "$stage_dir/"
cp LICENSE "$stage_dir/"
tar -C dist -czf "dist/${{ matrix.asset_name }}" "${BINARY_NAME}-${{ matrix.target }}"
shasum -a 256 "dist/${{ matrix.asset_name }}" > "dist/${{ matrix.asset_name }}.sha256"
- name: Package Windows archive
if: runner.os == 'Windows'
shell: pwsh
run: |
$stageDir = "dist/${env:BINARY_NAME}-${{ matrix.target }}"
New-Item -ItemType Directory -Force -Path $stageDir | Out-Null
Copy-Item "target/${{ matrix.target }}/release/${env:BINARY_NAME}.exe" "$stageDir/"
Copy-Item "LICENSE" "$stageDir/"
Compress-Archive -Path "$stageDir/*" -DestinationPath "dist/${{ matrix.asset_name }}"
$hash = (Get-FileHash "dist/${{ matrix.asset_name }}" -Algorithm SHA256).Hash.ToLower()
"$hash ${{ matrix.asset_name }}" | Out-File -Encoding ascii "dist/${{ matrix.asset_name }}.sha256"
- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.target }}
path: tsift/dist/*
if-no-files-found: error
github-release:
name: publish GitHub release
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Download packaged artifacts
uses: actions/download-artifact@v4
with:
pattern: release-*
merge-multiple: true
path: dist
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
files: dist/*
fail_on_unmatched_files: true
generate_release_notes: true
publish-crate:
name: publish crates.io package
runs-on: ubuntu-latest
defaults:
run:
working-directory: tsift
needs:
- verify
- build
if: startsWith(github.ref, 'refs/tags/v') && vars.TSIFT_ENABLE_CRATES_PUBLISH == 'true'
steps:
- uses: actions/checkout@v4
with:
path: tsift
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Publish crate
shell: bash
run: |
release_version="${GITHUB_REF_NAME#v}"
max_attempts=12
crate_version_exists() {
local package="$1"
cargo info --registry crates-io "$package@$release_version" >/dev/null 2>&1
}
publish_package() {
local package="$1"
local attempt=1
local output
local status
if crate_version_exists "$package"; then
echo "$package v$release_version already exists on crates.io; skipping"
return 0
fi
while true; do
cargo publish -p "$package" --locked --dry-run
set +e
output="$(cargo publish -p "$package" --locked 2>&1)"
status=$?
set -e
printf '%s\n' "$output"
if [ "$status" -eq 0 ]; then
sleep 20
return 0
fi
if crate_version_exists "$package"; then
echo "$package v$release_version is visible on crates.io after publish attempt; continuing"
sleep 20
return 0
fi
if ! printf '%s\n' "$output" | grep -Eiq 'Too Many Requests|rate limit|try again after'; then
return "$status"
fi
if [ "$attempt" -ge "$max_attempts" ]; then
echo "crates.io rate limit did not clear for $package after $max_attempts attempts" >&2
return "$status"
fi
echo "crates.io rate-limited $package publish attempt $attempt/$max_attempts; retrying in 180 seconds"
sleep 180
attempt=$((attempt + 1))
done
}
for package in \
tsift-core \
tsift-graph \
tsift-sqlite \
tsift-algorithms \
tsift-resolution \
tsift-tokensave \
tsift-libsql \
tsift-index \
tsift-summarize \
tsift-quality \
tsift-agent-doc \
tsift-digest \
tsift-search \
tsift-status \
tsift-session \
tsift-memory \
tsift-surrealdb \
tsift-cli \
tsift \
tsift-sim-world
do
publish_package "$package"
done
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
publish-opencode-plugin:
name: publish OpenCode npm plugin
runs-on: ubuntu-latest
needs:
- verify
- build
if: startsWith(github.ref, 'refs/tags/v') && vars.TSIFT_ENABLE_NPM_PUBLISH == 'true'
steps:
- uses: actions/checkout@v4
with:
path: tsift
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
- name: Publish OpenCode plugin package
run: npm publish --access public
working-directory: tsift/packages/opencode-tsift
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}