chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
# CI/CD Flows
|
||||
|
||||
## PR Quality Gates (ci.yml)
|
||||
|
||||
Trigger: pull_request to develop or master
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ PR opened │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌────────▼─────────┐
|
||||
│ fmt --all │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌───────────▼──────────┐
|
||||
│ clippy --all-targets │
|
||||
└───┬───┬───┬───┬───┬──┘
|
||||
│ │ │ │ │
|
||||
┌───────────────┘ │ │ │ └────────────────┐
|
||||
│ ┌───────────┘ │ └───────────┐ │
|
||||
▼ ▼ ▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌───────────┐ ┌─────────┐ ┌──────────┐
|
||||
│ test │ │ security │ │ semgrep │ │benchmark│ │ doc │
|
||||
│ ubuntu │ │ cargo │ │ AST-aware │ │ >=80% │ │ review │
|
||||
│ windows │ │ audit │ │ diff-only │ │ savings │ │ ai agent │
|
||||
│ macos │ │ patterns │ │ │ │ │ │ │
|
||||
└────┬─────┘ └────┬─────┘ └─────┬─────┘ └────┬────┘ └────┬─────┘
|
||||
│ │ │ │ │
|
||||
└────────────┴─────────┬───┴─────────────┴────────────┘
|
||||
│
|
||||
┌──────────▼─────────┐
|
||||
│ All must pass │
|
||||
│ to merge │
|
||||
└────────────────────┘
|
||||
|
||||
+ DCO check (independent, develop PRs only)
|
||||
+ Dependabot (weekly: Cargo deps + GitHub Actions)
|
||||
```
|
||||
|
||||
## Merge to develop — pre-release (cd.yml)
|
||||
|
||||
Trigger: push to develop | workflow_dispatch (not master) | Concurrency: cancel-in-progress
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ push to develop │
|
||||
│ OR dispatch │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌────────▼──────────────────┐
|
||||
│ pre-release │
|
||||
│ compute next version │
|
||||
│ from conventional commits │
|
||||
│ tag = v{next}-rc.{run} │
|
||||
└────────┬──────────────────┘
|
||||
│
|
||||
┌────────▼──────────────────┐
|
||||
│ release.yml │
|
||||
│ prerelease = true │
|
||||
└────────┬──────────────────┘
|
||||
│
|
||||
┌────────▼──────────────────┐
|
||||
│ Build │
|
||||
│ 5 platforms + DEB + RPM │
|
||||
└────────┬──────────────────┘
|
||||
│
|
||||
┌────────▼──────────────────┐
|
||||
│ GitHub Release │
|
||||
│ (pre-release badge) │
|
||||
│ │
|
||||
│ Discord: SKIPPED │
|
||||
│ Homebrew: SKIPPED │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
## Merge to master — stable release (cd.yml)
|
||||
|
||||
Trigger: push to master (only) | Concurrency: never cancelled
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ push to master │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌────────▼──────────────────┐
|
||||
│ release-please │
|
||||
│ analyze conventional │
|
||||
│ commits │
|
||||
└────────┬──────────────────┘
|
||||
│
|
||||
┌────┴────────────────┐
|
||||
│ │
|
||||
no release release created
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌───────────────────────┐
|
||||
│ create/update│ │ release.yml │
|
||||
│ release PR │ │ prerelease = false │
|
||||
└──────────────┘ └───────────┬───────────┘
|
||||
│
|
||||
┌────────────▼────────────┐
|
||||
│ Build │
|
||||
│ 5 platforms + DEB + RPM │
|
||||
└────────────┬────────────┘
|
||||
│
|
||||
┌────────────▼────────────┐
|
||||
│ GitHub Release │
|
||||
│ (stable, "Latest" badge) │
|
||||
└──┬─────────┬─────────┬──┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Discord Homebrew latest
|
||||
notify tap update tag
|
||||
```
|
||||
|
||||
## Manual release (release.yml)
|
||||
|
||||
Trigger: workflow_dispatch
|
||||
|
||||
```
|
||||
┌────────────────────────┐
|
||||
│ workflow_dispatch │
|
||||
│ inputs: tag, prerelease │
|
||||
└───────────┬────────────┘
|
||||
│
|
||||
┌───────────▼────────────┐
|
||||
│ Full build pipeline │
|
||||
│ 5 platforms + DEB + RPM │
|
||||
└───────────┬────────────┘
|
||||
│
|
||||
┌──────┴──────┐
|
||||
│ │
|
||||
prerelease=false prerelease=true
|
||||
│ │
|
||||
▼ ▼
|
||||
Discord pre-release
|
||||
Homebrew badge only
|
||||
latest tag
|
||||
```
|
||||
@@ -0,0 +1,156 @@
|
||||
name: CD
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [develop, master]
|
||||
|
||||
concurrency:
|
||||
group: cd-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
# ═══════════════════════════════════════════════
|
||||
# DEVELOP PATH: Pre-release
|
||||
# ═══════════════════════════════════════════════
|
||||
|
||||
pre-release:
|
||||
if: >-
|
||||
github.ref == 'refs/heads/develop'
|
||||
|| (github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/master')
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Compute version from commits like release please
|
||||
id: tag
|
||||
run: |
|
||||
LATEST_TAG=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-version:refname | grep -v '-' | head -1)
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
echo "::error::No stable release tag found"
|
||||
exit 1
|
||||
fi
|
||||
LATEST_VERSION="${LATEST_TAG#v}"
|
||||
echo "Latest release: $LATEST_TAG"
|
||||
|
||||
# ── Analyse conventional commits since that tag ──
|
||||
COMMITS=$(git log "${LATEST_TAG}..HEAD" --format="%s")
|
||||
HAS_BREAKING=$(echo "$COMMITS" | grep -cE '^[a-z]+(\(.+\))?!:' || true)
|
||||
HAS_FEAT=$(echo "$COMMITS" | grep -cE '^feat(\(.+\))?:' || true)
|
||||
HAS_FIX=$(echo "$COMMITS" | grep -cE '^fix(\(.+\))?:' || true)
|
||||
echo "Commits since ${LATEST_TAG} — breaking=$HAS_BREAKING feat=$HAS_FEAT fix=$HAS_FIX"
|
||||
|
||||
# ── Compute next version (matches release-please observed behaviour) ──
|
||||
# Pre-1.0 with bump-minor-pre-major: breaking → minor, feat → minor, fix → patch
|
||||
IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST_VERSION"
|
||||
if [ "$MAJOR" -eq 0 ]; then
|
||||
if [ "$HAS_BREAKING" -gt 0 ] || [ "$HAS_FEAT" -gt 0 ]; then
|
||||
MINOR=$((MINOR + 1)); PATCH=0 # breaking or feat → minor
|
||||
else
|
||||
PATCH=$((PATCH + 1)) # fix only → patch
|
||||
fi
|
||||
else
|
||||
if [ "$HAS_BREAKING" -gt 0 ]; then
|
||||
MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 # breaking → major
|
||||
elif [ "$HAS_FEAT" -gt 0 ]; then
|
||||
MINOR=$((MINOR + 1)); PATCH=0 # feat → minor
|
||||
else
|
||||
PATCH=$((PATCH + 1)) # fix → patch
|
||||
fi
|
||||
fi
|
||||
VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
||||
TAG="dev-${VERSION}-rc.${{ github.run_number }}"
|
||||
|
||||
echo "Next version: $VERSION (from $LATEST_VERSION)"
|
||||
echo "Pre-release tag: $TAG"
|
||||
|
||||
# Safety: fail if this exact tag already exists
|
||||
if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q .; then
|
||||
echo "::error::Tag ${TAG} already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
build-prerelease:
|
||||
name: Build pre-release
|
||||
needs: pre-release
|
||||
if: needs.pre-release.outputs.tag != ''
|
||||
uses: ./.github/workflows/release.yml
|
||||
with:
|
||||
tag: ${{ needs.pre-release.outputs.tag }}
|
||||
prerelease: true
|
||||
permissions:
|
||||
contents: write
|
||||
secrets: inherit
|
||||
|
||||
# ═══════════════════════════════════════════════
|
||||
# MASTER PATH: Full release
|
||||
# ═══════════════════════════════════════════════
|
||||
|
||||
release-please:
|
||||
if: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_created: ${{ steps.release.outputs.release_created }}
|
||||
tag_name: ${{ steps.release.outputs.tag_name }}
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
client-id: ${{ secrets.APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
- uses: googleapis/release-please-action@v4
|
||||
id: release
|
||||
with:
|
||||
release-type: rust
|
||||
package-name: rtk
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
target-branch: ${{ github.ref_name }}
|
||||
|
||||
build-release:
|
||||
name: Build and upload release assets
|
||||
needs: release-please
|
||||
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
||||
uses: ./.github/workflows/release.yml
|
||||
with:
|
||||
tag: ${{ needs.release-please.outputs.tag_name }}
|
||||
permissions:
|
||||
contents: write
|
||||
secrets: inherit
|
||||
|
||||
update-latest-tag:
|
||||
name: Update 'latest' tag
|
||||
needs: [release-please, build-release]
|
||||
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
client-id: ${{ secrets.APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Update latest tag
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -fa latest -m "Latest stable release (${{ needs.release-please.outputs.tag_name }})"
|
||||
git push origin latest --force
|
||||
@@ -0,0 +1,389 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop, master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
# ─── Fast gates (fail early, save CI minutes) ───
|
||||
|
||||
check-test-presence:
|
||||
name: test presence
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Check filter modules have tests
|
||||
run: |
|
||||
git fetch origin "${{ github.base_ref }}" --depth=1 || true
|
||||
bash scripts/check-test-presence.sh "origin/${{ github.base_ref }}"
|
||||
|
||||
fmt:
|
||||
name: fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
name: clippy
|
||||
needs: fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo clippy --all-targets
|
||||
|
||||
# ─── Parallel gates (all need code to compile) ───
|
||||
|
||||
test:
|
||||
name: test (${{ matrix.os }})
|
||||
needs: clippy
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo test --all
|
||||
|
||||
security:
|
||||
name: Security Scan
|
||||
needs: clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
|
||||
- name: Cargo Audit (CVE check)
|
||||
run: |
|
||||
echo "## Security Scan Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Dependency Vulnerabilities" >> $GITHUB_STEP_SUMMARY
|
||||
if cargo audit 2>&1 | tee audit.log; then
|
||||
echo "No known vulnerabilities detected" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Vulnerabilities found:" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
cat audit.log >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "::warning::Dependency vulnerabilities detected - review required"
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Critical files check
|
||||
run: |
|
||||
echo "### Critical Files Modified" >> $GITHUB_STEP_SUMMARY
|
||||
CRITICAL=$(git diff --name-only origin/master...HEAD | grep -E "(runner|summary|tracking|init|pnpm_cmd|container)\.rs|Cargo\.toml|workflows/.*\.yml" || true)
|
||||
if [ -n "$CRITICAL" ]; then
|
||||
echo "**HIGH RISK**: The following critical files were modified:" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$CRITICAL" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Required Actions:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [ ] Manual security review by 2 maintainers" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [ ] Verify no shell injection vectors" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [ ] Check input validation remains intact" >> $GITHUB_STEP_SUMMARY
|
||||
echo "::warning::Critical RTK files modified - enhanced review required"
|
||||
else
|
||||
echo "No critical files modified" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Dangerous patterns scan
|
||||
run: |
|
||||
echo "### Dangerous Code Patterns" >> $GITHUB_STEP_SUMMARY
|
||||
PATTERNS=$(git diff origin/master...HEAD | grep -E "Command::new\(\"sh\"|Command::new\(\"bash\"|\.env\(\"LD_PRELOAD|\.env\(\"PATH|reqwest::|std::net::|TcpStream|UdpSocket|unsafe \{|\.unwrap\(\) |panic!\(|todo!\(|unimplemented!\(" || true)
|
||||
if [ -n "$PATTERNS" ]; then
|
||||
echo "**Potentially dangerous patterns detected:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$PATTERNS" | head -30 >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Security Concerns:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$PATTERNS" | grep -q "Command::new" && echo "- Shell command execution detected" >> $GITHUB_STEP_SUMMARY || true
|
||||
echo "$PATTERNS" | grep -q "\.env\(\"" && echo "- Environment variable manipulation" >> $GITHUB_STEP_SUMMARY || true
|
||||
echo "$PATTERNS" | grep -q "reqwest::\|std::net::\|TcpStream\|UdpSocket" && echo "- Network operations added" >> $GITHUB_STEP_SUMMARY || true
|
||||
echo "$PATTERNS" | grep -q "unsafe" && echo "- Unsafe code blocks" >> $GITHUB_STEP_SUMMARY || true
|
||||
echo "$PATTERNS" | grep -q "\.unwrap\(\)\|panic!\(" && echo "- Panic-inducing code" >> $GITHUB_STEP_SUMMARY || true
|
||||
echo "::warning::Dangerous code patterns detected - manual review required"
|
||||
else
|
||||
echo "No dangerous patterns detected" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: New dependencies check
|
||||
run: |
|
||||
echo "### Dependencies Changes" >> $GITHUB_STEP_SUMMARY
|
||||
if git diff origin/master...HEAD Cargo.toml | grep -E "^\+.*=" | grep -v "^\+\+\+" > new_deps.txt; then
|
||||
echo "**New dependencies added:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```toml' >> $GITHUB_STEP_SUMMARY
|
||||
cat new_deps.txt >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Required Actions:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [ ] Audit each new dependency on crates.io" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [ ] Check maintainer reputation and download counts" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [ ] Verify no typosquatting (e.g., 'reqwest' vs 'request')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "::warning::New dependencies require supply chain audit"
|
||||
else
|
||||
echo "No new dependencies added" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Clippy security lints
|
||||
run: |
|
||||
echo "### Clippy Security Lints" >> $GITHUB_STEP_SUMMARY
|
||||
if cargo clippy --all-targets -- -W clippy::unwrap_used -W clippy::panic -W clippy::expect_used 2>&1 | tee clippy.log | grep -E "warning:|error:"; then
|
||||
echo "Security-related lints triggered:" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
grep -E "warning:|error:" clippy.log | head -20 >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "::warning::Clippy security lints failed"
|
||||
else
|
||||
echo "All security lints passed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Summary verdict
|
||||
run: |
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Security Review Verdict" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**This is an automated security scan. A human maintainer must:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "1. Review all warnings above" >> $GITHUB_STEP_SUMMARY
|
||||
echo "2. Verify PR intent matches actual code changes" >> $GITHUB_STEP_SUMMARY
|
||||
echo "3. Check for subtle backdoors or logic bombs" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**For high-risk PRs (critical files modified):**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Require approval from 2 maintainers" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Test in isolated environment before merge" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
semgrep:
|
||||
name: semgrep security scan
|
||||
needs: clippy
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: semgrep/semgrep
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: semgrep scan --config .semgrep.yml --baseline-commit ${{ github.event.pull_request.base.sha }} --error
|
||||
|
||||
benchmark:
|
||||
name: benchmark
|
||||
needs: clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Build rtk
|
||||
run: cargo build --release
|
||||
|
||||
- name: Install system tools
|
||||
run: sudo apt-get install -y tree
|
||||
|
||||
- name: Install Python tools
|
||||
run: pip install ruff pytest mypy
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "stable"
|
||||
|
||||
- name: Install Go tools
|
||||
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
- name: Run benchmark
|
||||
run: ./scripts/benchmark.sh
|
||||
|
||||
# ─── AI Doc Review: develop PRs only ───
|
||||
|
||||
doc-review:
|
||||
name: doc review
|
||||
if: github.base_ref == 'develop'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Gather PR context
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR_NUM=${{ github.event.pull_request.number }}
|
||||
gh pr diff "$PR_NUM" --name-only > changed_files.txt
|
||||
gh pr diff "$PR_NUM" | head -c 12000 > diff.txt
|
||||
gh pr view "$PR_NUM" --json title,body --jq '"PR Title: \(.title)\nPR Description: \(.body)"' > pr_info.txt
|
||||
|
||||
- name: Build prompt files
|
||||
run: |
|
||||
# System prompt
|
||||
cat <<'EOF' > system_prompt.txt
|
||||
You are a documentation reviewer for the RTK project.
|
||||
You will receive the project's CONTRIBUTING.md (which contains the documentation rules), the PR info, changed files, and diff.
|
||||
Your job: based ONLY on the documentation rules in CONTRIBUTING.md, decide if the PR includes the required documentation updates.
|
||||
|
||||
IMPORTANT:
|
||||
- CI/CD changes, test-only changes, and refactors with no user-facing impact do NOT require doc updates.
|
||||
- Be practical, not pedantic. Small obvious fixes don't need CHANGELOG entries.
|
||||
- Only flag missing docs when there is a clear user-facing change.
|
||||
EOF
|
||||
|
||||
# User prompt: concatenate files (no printf, no variable expansion issues)
|
||||
{
|
||||
cat pr_info.txt
|
||||
echo ""
|
||||
echo "---"
|
||||
echo "CONTRIBUTING.md:"
|
||||
cat CONTRIBUTING.md
|
||||
echo ""
|
||||
echo "---"
|
||||
echo "Changed files:"
|
||||
cat changed_files.txt
|
||||
echo ""
|
||||
echo "---"
|
||||
echo "Diff (may be truncated):"
|
||||
cat diff.txt
|
||||
} > user_prompt.txt
|
||||
|
||||
- name: AI documentation review
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.RTK_DOCS_ANTHROPIC_KEY }}
|
||||
run: |
|
||||
echo "## Documentation Review (AI)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -z "$ANTHROPIC_API_KEY" ]; then
|
||||
echo "::warning::ANTHROPIC_API_KEY not configured — skipping AI doc review"
|
||||
echo "Skipped: ANTHROPIC_API_KEY secret not configured." >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::group::Preparing API request"
|
||||
echo "System prompt: $(wc -c < system_prompt.txt) bytes"
|
||||
echo "User prompt: $(wc -c < user_prompt.txt) bytes"
|
||||
SYSTEM_JSON=$(jq -Rs . < system_prompt.txt)
|
||||
USER_JSON=$(jq -Rs . < user_prompt.txt)
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Calling Claude API (claude-sonnet-4-6)"
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" https://api.anthropic.com/v1/messages \
|
||||
-H "content-type: application/json" \
|
||||
-H "x-api-key: $ANTHROPIC_API_KEY" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-d "{
|
||||
\"model\": \"claude-sonnet-4-6\",
|
||||
\"max_tokens\": 1024,
|
||||
\"messages\": [{\"role\": \"user\", \"content\": $USER_JSON}],
|
||||
\"system\": $SYSTEM_JSON,
|
||||
\"output_config\": {
|
||||
\"format\": {
|
||||
\"type\": \"json_schema\",
|
||||
\"schema\": {
|
||||
\"type\": \"object\",
|
||||
\"properties\": {
|
||||
\"status\": {\"type\": \"string\", \"enum\": [\"PASS\", \"FAIL\"]},
|
||||
\"reasoning\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}},
|
||||
\"files_to_update\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}
|
||||
},
|
||||
\"required\": [\"status\", \"reasoning\", \"files_to_update\"],
|
||||
\"additionalProperties\": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}")
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
||||
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||
echo "HTTP status: $HTTP_CODE"
|
||||
echo "::endgroup::"
|
||||
|
||||
if [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "::warning::Claude API returned HTTP $HTTP_CODE — skipping doc review"
|
||||
echo "Skipped: API error (HTTP $HTTP_CODE)" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$BODY" | head -10 >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse structured JSON response
|
||||
REVIEW_JSON=$(echo "$BODY" | jq -r '.content[0].text // empty')
|
||||
|
||||
if [ -z "$REVIEW_JSON" ]; then
|
||||
echo "::warning::Empty response from Claude API — skipping doc review"
|
||||
echo "Skipped: empty API response" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Raw response:"
|
||||
echo "$BODY" | head -20
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::group::AI Review Result"
|
||||
echo "$REVIEW_JSON" | jq .
|
||||
echo "::endgroup::"
|
||||
|
||||
STATUS=$(echo "$REVIEW_JSON" | jq -r '.status')
|
||||
REASONING=$(echo "$REVIEW_JSON" | jq -r '.reasoning[]' 2>/dev/null)
|
||||
FILES=$(echo "$REVIEW_JSON" | jq -r '.files_to_update[]' 2>/dev/null)
|
||||
|
||||
echo "### Verdict: ${STATUS}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -n "$REASONING" ]; then
|
||||
echo "**Reasoning:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$REASONING" | while IFS= read -r line; do
|
||||
echo "- $line" >> $GITHUB_STEP_SUMMARY
|
||||
done
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "$STATUS" = "FAIL" ] && [ -n "$FILES" ]; then
|
||||
echo "**Files to update:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$FILES" | while IFS= read -r f; do
|
||||
echo "- \`$f\`" >> $GITHUB_STEP_SUMMARY
|
||||
done
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "$STATUS" = "PASS" ]; then
|
||||
echo "Documentation review passed."
|
||||
elif [ "$STATUS" = "FAIL" ]; then
|
||||
echo "::error::Documentation review failed — see summary for details"
|
||||
exit 1
|
||||
else
|
||||
echo "::warning::Unexpected status '${STATUS}' — skipping"
|
||||
echo "Unexpected AI response status: ${STATUS}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -0,0 +1,126 @@
|
||||
name: Update Next Release PR
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
branches: [develop]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-next-release:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update Next Release PR
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
REPO: ${{ github.repository }}
|
||||
ALLOWED_REPOS: "rtk-ai/rtk"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
URL_PATTERN=""
|
||||
for repo in $ALLOWED_REPOS; do
|
||||
URL_PATTERN="${URL_PATTERN}|https://github\\.com/${repo}/issues/[0-9]+"
|
||||
done
|
||||
URL_PATTERN="${URL_PATTERN#|}"
|
||||
|
||||
if printf '%s' "$PR_TITLE" | grep -qiE '^feat'; then
|
||||
SECTION="Feats"
|
||||
elif printf '%s' "$PR_TITLE" | grep -qiE '^fix'; then
|
||||
SECTION="Fix"
|
||||
else
|
||||
SECTION="Other"
|
||||
fi
|
||||
|
||||
ISSUE_REFS=""
|
||||
if [ -n "$PR_BODY" ]; then
|
||||
ISSUE_REFS=$(echo "$PR_BODY" \
|
||||
| grep -oiE "(closes|fixes|resolves):?\s+#[0-9]+|${URL_PATTERN}" \
|
||||
| grep -oE '#[0-9]+|issues/[0-9]+' \
|
||||
| sed 's|issues/|#|' \
|
||||
| sort -u \
|
||||
|| true)
|
||||
fi
|
||||
|
||||
ENTRY="- ${PR_TITLE} [#${PR_NUMBER}](${PR_URL})"
|
||||
if [ -n "$ISSUE_REFS" ]; then
|
||||
CLOSES_PARTS=""
|
||||
while IFS= read -r ref; do
|
||||
[ -z "$ref" ] && continue
|
||||
NUM="${ref#\#}"
|
||||
ISSUE_URL="https://github.com/${REPO}/issues/${NUM}"
|
||||
if [ -n "$CLOSES_PARTS" ]; then
|
||||
CLOSES_PARTS="${CLOSES_PARTS}, [${ref}](${ISSUE_URL}) (to verify)"
|
||||
else
|
||||
CLOSES_PARTS="Closes [${ref}](${ISSUE_URL}) (to verify)"
|
||||
fi
|
||||
done <<< "$ISSUE_REFS"
|
||||
ENTRY="${ENTRY} — ${CLOSES_PARTS}"
|
||||
fi
|
||||
|
||||
NEXT_PR=$(gh pr list \
|
||||
--repo "$REPO" \
|
||||
--label next-release \
|
||||
--base master \
|
||||
--head develop \
|
||||
--state open \
|
||||
--json number,body \
|
||||
--jq '.[0] // empty')
|
||||
|
||||
NEXT_PR_NUMBER=""
|
||||
if [ -n "$NEXT_PR" ]; then
|
||||
NEXT_PR_NUMBER=$(echo "$NEXT_PR" | jq -r '.number')
|
||||
fi
|
||||
|
||||
if [ -z "$NEXT_PR_NUMBER" ]; then
|
||||
TEMPLATE="### Feats
|
||||
|
||||
### Fix
|
||||
|
||||
### Other"
|
||||
|
||||
PR_CREATE_URL=$(gh pr create \
|
||||
--repo "$REPO" \
|
||||
--base master \
|
||||
--head develop \
|
||||
--title "Next Release" \
|
||||
--label next-release \
|
||||
--body "$TEMPLATE")
|
||||
|
||||
NEXT_PR_NUMBER=$(echo "$PR_CREATE_URL" | grep -oE '/pull/[0-9]+' | grep -oE '[0-9]+')
|
||||
CURRENT_BODY="$TEMPLATE"
|
||||
else
|
||||
CURRENT_BODY=$(echo "$NEXT_PR" | jq -r '.body')
|
||||
fi
|
||||
|
||||
SECTION_HEADER="### ${SECTION}"
|
||||
export ENTRY
|
||||
if echo "$CURRENT_BODY" | grep -qF "$SECTION_HEADER"; then
|
||||
UPDATED_BODY=$(echo "$CURRENT_BODY" | awk -v section="$SECTION_HEADER" '
|
||||
$0 == section {
|
||||
print
|
||||
print ENVIRON["ENTRY"]
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
')
|
||||
else
|
||||
UPDATED_BODY="${CURRENT_BODY}
|
||||
|
||||
${SECTION_HEADER}
|
||||
${ENTRY}"
|
||||
fi
|
||||
|
||||
gh pr edit "$NEXT_PR_NUMBER" \
|
||||
--repo "$REPO" \
|
||||
--body "$UPDATED_BODY"
|
||||
|
||||
echo "Updated Next Release PR #${NEXT_PR_NUMBER} — added entry to ### ${SECTION}"
|
||||
@@ -0,0 +1,48 @@
|
||||
name: PR Target Branch Check
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
check-target:
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
# Skip develop→master PRs (maintainer releases)
|
||||
if: >-
|
||||
github.event.pull_request.base.ref == 'master' &&
|
||||
github.event.pull_request.head.ref != 'develop'
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
id: app-token
|
||||
with:
|
||||
client-id: ${{ secrets.APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Add wrong-base label and comment
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
|
||||
// Add label
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: ['wrong-base']
|
||||
});
|
||||
|
||||
// Post comment
|
||||
const body = `Automatic message from CI checks : It seems like this branch is targeting the wrong branch, any contribution should target develop branch.
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/rtk-ai/rtk/blob/master/CONTRIBUTING.md) for details.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: body
|
||||
});
|
||||
@@ -0,0 +1,370 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to release'
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: 'Mark as pre-release'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to release (e.g., v0.1.0)'
|
||||
required: true
|
||||
prerelease:
|
||||
description: 'Mark as pre-release'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# macOS
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
archive: tar.gz
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
archive: tar.gz
|
||||
# Linux
|
||||
- target: x86_64-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
archive: tar.gz
|
||||
musl: true
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
archive: tar.gz
|
||||
cross: true
|
||||
# Windows
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
archive: zip
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Install cross-compilation tools
|
||||
if: matrix.cross
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
|
||||
|
||||
- name: Install musl tools
|
||||
if: matrix.musl
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y musl-tools
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --target ${{ matrix.target }}
|
||||
env:
|
||||
RTK_TELEMETRY_URL: ${{ vars.RTK_TELEMETRY_URL }}
|
||||
RTK_TELEMETRY_TOKEN: ${{ secrets.RTK_TELEMETRY_TOKEN }}
|
||||
|
||||
- name: Package (Unix)
|
||||
if: matrix.os != 'windows-latest'
|
||||
run: |
|
||||
cd target/${{ matrix.target }}/release
|
||||
tar -czvf ../../../rtk-${{ matrix.target }}.${{ matrix.archive }} rtk
|
||||
cd ../../..
|
||||
|
||||
- name: Package (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
cd target/${{ matrix.target }}/release
|
||||
7z a ../../../rtk-${{ matrix.target }}.${{ matrix.archive }} rtk.exe
|
||||
cd ../../..
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rtk-${{ matrix.target }}
|
||||
path: rtk-${{ matrix.target }}.${{ matrix.archive }}
|
||||
|
||||
build-deb:
|
||||
name: Build DEB package
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
|
||||
- name: Build DEB
|
||||
run: cargo deb
|
||||
env:
|
||||
RTK_TELEMETRY_URL: ${{ vars.RTK_TELEMETRY_URL }}
|
||||
RTK_TELEMETRY_TOKEN: ${{ secrets.RTK_TELEMETRY_TOKEN }}
|
||||
|
||||
- name: Upload DEB
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rtk-deb
|
||||
path: target/debian/*.deb
|
||||
|
||||
build-rpm:
|
||||
name: Build RPM package
|
||||
runs-on: ubuntu-latest
|
||||
container: fedora:latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
dnf install -y rust cargo rpm-build
|
||||
|
||||
- name: Install cargo-generate-rpm
|
||||
run: cargo install cargo-generate-rpm
|
||||
|
||||
- name: Build release
|
||||
run: cargo build --release
|
||||
env:
|
||||
RTK_TELEMETRY_URL: ${{ vars.RTK_TELEMETRY_URL }}
|
||||
RTK_TELEMETRY_TOKEN: ${{ secrets.RTK_TELEMETRY_TOKEN }}
|
||||
|
||||
- name: Generate RPM
|
||||
run: cargo generate-rpm
|
||||
|
||||
- name: Upload RPM
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rtk-rpm
|
||||
path: target/generate-rpm/*.rpm
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: [build, build-deb, build-rpm]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
client-id: ${{ secrets.APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
env:
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
TAG="$INPUT_TAG"
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="$RELEASE_TAG"
|
||||
fi
|
||||
echo "version=$TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Flatten artifacts
|
||||
run: |
|
||||
mkdir -p release
|
||||
find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" -o -name "*.deb" -o -name "*.rpm" \) -exec cp {} release/ \;
|
||||
|
||||
- name: Create version-agnostic package names
|
||||
run: |
|
||||
cd release
|
||||
for f in *.deb; do
|
||||
[ -f "$f" ] && cp "$f" "rtk_amd64.deb"
|
||||
done
|
||||
for f in *.rpm; do
|
||||
[ -f "$f" ] && cp "$f" "rtk.x86_64.rpm"
|
||||
done
|
||||
|
||||
- name: Create checksums
|
||||
run: |
|
||||
cd release
|
||||
sha256sum * > checksums.txt
|
||||
|
||||
- name: Upload Release Assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.version }}
|
||||
files: release/*
|
||||
prerelease: ${{ inputs.prerelease }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
notify-discord:
|
||||
name: Notify Discord
|
||||
needs: [release]
|
||||
if: ${{ !inputs.prerelease }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get version
|
||||
id: version
|
||||
env:
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
TAG="$INPUT_TAG"
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="$RELEASE_TAG"
|
||||
fi
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Send Discord notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.RTK_DISCORD_RELEASE }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
TAG="${{ steps.version.outputs.tag }}"
|
||||
RELEASE_URL="https://github.com/rtk-ai/rtk/releases/tag/${TAG}"
|
||||
|
||||
# Fetch release notes from GitHub API
|
||||
NOTES=$(gh api "repos/rtk-ai/rtk/releases/tags/${TAG}" --jq '.body' 2>/dev/null | head -c 1800 || echo "")
|
||||
DESC=$(echo "${NOTES:-No release notes}" | jq -Rs .)
|
||||
|
||||
jq -n \
|
||||
--arg title "RTK ${TAG} released" \
|
||||
--arg url "$RELEASE_URL" \
|
||||
--argjson desc "$DESC" \
|
||||
'{embeds: [{title: $title, url: $url, description: $desc, color: 5814783, footer: {text: "Rust Token Killer"}}]}' \
|
||||
| curl -sf -H "Content-Type: application/json" -d @- "$DISCORD_WEBHOOK"
|
||||
|
||||
homebrew:
|
||||
name: Update Homebrew formula
|
||||
needs: [release]
|
||||
if: ${{ !inputs.prerelease }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get version
|
||||
id: version
|
||||
env:
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
TAG="$INPUT_TAG"
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="$RELEASE_TAG"
|
||||
fi
|
||||
VERSION="${TAG#v}"
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download checksums
|
||||
run: |
|
||||
gh release download "${{ steps.version.outputs.tag }}" \
|
||||
--repo rtk-ai/rtk \
|
||||
--pattern checksums.txt
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Parse checksums
|
||||
id: sha
|
||||
run: |
|
||||
echo "mac_arm=$(grep aarch64-apple-darwin.tar.gz checksums.txt | head -1 | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||
echo "mac_intel=$(grep x86_64-apple-darwin.tar.gz checksums.txt | head -1 | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||
echo "linux_arm=$(grep aarch64-unknown-linux-gnu.tar.gz checksums.txt | head -1 | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||
echo "linux_intel=$(grep x86_64-unknown-linux-musl.tar.gz checksums.txt | head -1 | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate formula
|
||||
run: |
|
||||
cat > rtk.rb << 'FORMULA'
|
||||
class Rtk < Formula
|
||||
desc "Rust Token Killer - High-performance CLI proxy to minimize LLM token consumption"
|
||||
homepage "https://www.rtk-ai.app"
|
||||
version "VERSION_PLACEHOLDER"
|
||||
license "Apache 2.0"
|
||||
|
||||
if OS.mac? && Hardware::CPU.arm?
|
||||
url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-aarch64-apple-darwin.tar.gz"
|
||||
sha256 "SHA_MAC_ARM_PLACEHOLDER"
|
||||
elsif OS.mac? && Hardware::CPU.intel?
|
||||
url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-x86_64-apple-darwin.tar.gz"
|
||||
sha256 "SHA_MAC_INTEL_PLACEHOLDER"
|
||||
elsif OS.linux? && Hardware::CPU.arm?
|
||||
url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-aarch64-unknown-linux-gnu.tar.gz"
|
||||
sha256 "SHA_LINUX_ARM_PLACEHOLDER"
|
||||
elsif OS.linux? && Hardware::CPU.intel?
|
||||
url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-x86_64-unknown-linux-musl.tar.gz"
|
||||
sha256 "SHA_LINUX_INTEL_PLACEHOLDER"
|
||||
end
|
||||
|
||||
def install
|
||||
bin.install "rtk"
|
||||
end
|
||||
|
||||
def caveats
|
||||
<<~EOS
|
||||
rtk is installed! Get started:
|
||||
|
||||
# Initialize for Claude Code
|
||||
rtk init -g # Global hook-first setup (recommended)
|
||||
rtk init # Add to ./CLAUDE.md (this project only)
|
||||
|
||||
# See all commands
|
||||
rtk --help
|
||||
|
||||
# Measure your token savings
|
||||
rtk gain
|
||||
|
||||
Full documentation: https://www.rtk-ai.app
|
||||
EOS
|
||||
end
|
||||
|
||||
test do
|
||||
system "#{bin}/rtk", "--version"
|
||||
end
|
||||
end
|
||||
FORMULA
|
||||
sed -i "s/VERSION_PLACEHOLDER/${{ steps.version.outputs.version }}/g" rtk.rb
|
||||
sed -i "s/TAG_PLACEHOLDER/${{ steps.version.outputs.tag }}/g" rtk.rb
|
||||
sed -i "s/SHA_MAC_ARM_PLACEHOLDER/${{ steps.sha.outputs.mac_arm }}/g" rtk.rb
|
||||
sed -i "s/SHA_MAC_INTEL_PLACEHOLDER/${{ steps.sha.outputs.mac_intel }}/g" rtk.rb
|
||||
sed -i "s/SHA_LINUX_ARM_PLACEHOLDER/${{ steps.sha.outputs.linux_arm }}/g" rtk.rb
|
||||
sed -i "s/SHA_LINUX_INTEL_PLACEHOLDER/${{ steps.sha.outputs.linux_intel }}/g" rtk.rb
|
||||
# Remove leading spaces from heredoc
|
||||
sed -i 's/^ //' rtk.rb
|
||||
|
||||
- name: Push to homebrew-tap
|
||||
run: |
|
||||
CONTENT=$(base64 -w 0 rtk.rb)
|
||||
SHA=$(gh api repos/rtk-ai/homebrew-tap/contents/Formula/rtk.rb --jq '.sha' 2>/dev/null || echo "")
|
||||
if [ -n "$SHA" ]; then
|
||||
gh api -X PUT repos/rtk-ai/homebrew-tap/contents/Formula/rtk.rb \
|
||||
-f message="rtk ${{ steps.version.outputs.version }}" \
|
||||
-f content="$CONTENT" \
|
||||
-f sha="$SHA"
|
||||
else
|
||||
gh api -X PUT repos/rtk-ai/homebrew-tap/contents/Formula/rtk.rb \
|
||||
-f message="rtk ${{ steps.version.outputs.version }}" \
|
||||
-f content="$CONTENT"
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||
Reference in New Issue
Block a user