Skip to content

Commit 1da4e84

Browse files
tdiclaude
andcommitted
Add GitHub Action for crates.io publishing with manual approval
- Create publish.yml workflow for releasing to crates.io - Requires manual approval via 'crates-io' environment - Supports both manual trigger and release event trigger - Verifies version, runs tests, and validates package before publish - Uses CARGO_REGISTRY_TOKEN secret for authentication - Builds release artifacts for Linux and macOS after publishing - Add comprehensive setup documentation in docs/GITHUB_SETUP.md The workflow ensures safe publishing with: - Version verification - Test suite validation - Manual approval gate - Automatic binary builds - SHA256 checksums for releases 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f3fafc9 commit 1da4e84

2 files changed

Lines changed: 334 additions & 0 deletions

File tree

.github/workflows/publish.yml

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
name: Publish to crates.io
2+
3+
on:
4+
release:
5+
types: [created]
6+
workflow_dispatch:
7+
inputs:
8+
version:
9+
description: 'Version to publish (e.g., 0.1.0)'
10+
required: true
11+
type: string
12+
13+
permissions:
14+
contents: write
15+
16+
jobs:
17+
verify:
18+
name: Verify Release
19+
runs-on: ubuntu-latest
20+
outputs:
21+
version: ${{ steps.get_version.outputs.version }}
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Install Rust
26+
uses: dtolnay/rust-toolchain@stable
27+
28+
- name: Get version
29+
id: get_version
30+
run: |
31+
if [ "${{ github.event_name }}" == "release" ]; then
32+
VERSION="${{ github.event.release.tag_name }}"
33+
VERSION="${VERSION#v}" # Remove 'v' prefix if present
34+
else
35+
VERSION="${{ github.event.inputs.version }}"
36+
fi
37+
echo "version=$VERSION" >> $GITHUB_OUTPUT
38+
39+
# Verify version in Cargo.toml matches
40+
CARGO_VERSION=$(grep "^version" Cargo.toml | sed 's/version = "\(.*\)"/\1/')
41+
if [ "$CARGO_VERSION" != "$VERSION" ]; then
42+
echo "Error: Cargo.toml version ($CARGO_VERSION) doesn't match release version ($VERSION)"
43+
exit 1
44+
fi
45+
echo "✅ Version $VERSION confirmed"
46+
47+
- name: Check crate name availability
48+
run: |
49+
if curl -s https://crates.io/api/v1/crates/ccagents | grep -q '"name":"ccagents"'; then
50+
echo "✅ Crate already exists, will publish new version"
51+
else
52+
echo "✅ Crate name is available for first publish"
53+
fi
54+
55+
- name: Run tests
56+
run: |
57+
cargo test --all-features
58+
cargo clippy -- -D warnings
59+
cargo fmt -- --check
60+
61+
- name: Verify package
62+
run: |
63+
cargo publish --dry-run
64+
echo "✅ Package verification successful"
65+
66+
publish-crates:
67+
name: Publish to crates.io
68+
needs: verify
69+
runs-on: ubuntu-latest
70+
environment: crates-io
71+
steps:
72+
- uses: actions/checkout@v4
73+
74+
- name: Install Rust
75+
uses: dtolnay/rust-toolchain@stable
76+
77+
- name: Publish to crates.io
78+
env:
79+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
80+
run: |
81+
cargo publish --token "$CARGO_REGISTRY_TOKEN"
82+
echo "✅ Published version ${{ needs.verify.outputs.version }} to crates.io"
83+
84+
- name: Wait for crates.io
85+
run: |
86+
echo "Waiting for crates.io to index the new version..."
87+
sleep 30
88+
89+
- name: Verify publication
90+
run: |
91+
VERSION="${{ needs.verify.outputs.version }}"
92+
if curl -s https://crates.io/api/v1/crates/ccagents | grep -q "\"max_version\":\"$VERSION\""; then
93+
echo "✅ Version $VERSION successfully published to crates.io"
94+
else
95+
echo "⚠️ Version may still be indexing on crates.io"
96+
fi
97+
98+
create-release-artifacts:
99+
name: Create Release Artifacts
100+
needs: [verify, publish-crates]
101+
runs-on: ${{ matrix.os }}
102+
strategy:
103+
matrix:
104+
include:
105+
- os: ubuntu-latest
106+
target: x86_64-unknown-linux-gnu
107+
binary_name: ccagents
108+
asset_name: ccagents-linux-amd64
109+
110+
- os: ubuntu-latest
111+
target: x86_64-unknown-linux-musl
112+
binary_name: ccagents
113+
asset_name: ccagents-linux-musl-amd64
114+
115+
- os: ubuntu-latest
116+
target: aarch64-unknown-linux-gnu
117+
binary_name: ccagents
118+
asset_name: ccagents-linux-arm64
119+
120+
- os: macos-latest
121+
target: x86_64-apple-darwin
122+
binary_name: ccagents
123+
asset_name: ccagents-macos-amd64
124+
125+
- os: macos-latest
126+
target: aarch64-apple-darwin
127+
binary_name: ccagents
128+
asset_name: ccagents-macos-arm64
129+
130+
steps:
131+
- uses: actions/checkout@v4
132+
133+
- name: Install Rust
134+
uses: dtolnay/rust-toolchain@stable
135+
with:
136+
targets: ${{ matrix.target }}
137+
138+
- name: Install cross-compilation tools
139+
if: matrix.target == 'aarch64-unknown-linux-gnu'
140+
run: |
141+
sudo apt-get update
142+
sudo apt-get install -y gcc-aarch64-linux-gnu
143+
144+
- name: Install musl tools
145+
if: matrix.target == 'x86_64-unknown-linux-musl'
146+
run: |
147+
sudo apt-get update
148+
sudo apt-get install -y musl-tools
149+
150+
- name: Build
151+
run: cargo build --release --target ${{ matrix.target }}
152+
153+
- name: Strip binary (Linux/macOS)
154+
if: matrix.os != 'windows-latest'
155+
run: |
156+
strip target/${{ matrix.target }}/release/${{ matrix.binary_name }}
157+
158+
- name: Create archive
159+
run: |
160+
cd target/${{ matrix.target }}/release
161+
tar czf ../../../${{ matrix.asset_name }}.tar.gz ${{ matrix.binary_name }}
162+
cd ../../../
163+
sha256sum ${{ matrix.asset_name }}.tar.gz > ${{ matrix.asset_name }}.tar.gz.sha256
164+
165+
- name: Upload to release
166+
if: github.event_name == 'release'
167+
uses: softprops/action-gh-release@v1
168+
with:
169+
files: |
170+
${{ matrix.asset_name }}.tar.gz
171+
${{ matrix.asset_name }}.tar.gz.sha256
172+
173+
- name: Upload artifacts (manual trigger)
174+
if: github.event_name == 'workflow_dispatch'
175+
uses: actions/upload-artifact@v3
176+
with:
177+
name: binaries-${{ matrix.asset_name }}
178+
path: |
179+
${{ matrix.asset_name }}.tar.gz
180+
${{ matrix.asset_name }}.tar.gz.sha256
181+
182+
create-tag:
183+
name: Create Git Tag
184+
needs: [verify, publish-crates]
185+
runs-on: ubuntu-latest
186+
if: github.event_name == 'workflow_dispatch'
187+
steps:
188+
- uses: actions/checkout@v4
189+
with:
190+
fetch-depth: 0
191+
192+
- name: Configure Git
193+
run: |
194+
git config user.name "GitHub Actions"
195+
git config user.email "actions@github.com"
196+
197+
- name: Create and push tag
198+
run: |
199+
VERSION="${{ needs.verify.outputs.version }}"
200+
git tag -a "v$VERSION" -m "Release version $VERSION"
201+
git push origin "v$VERSION"
202+
echo "✅ Created tag v$VERSION"
203+
204+
- name: Create GitHub Release
205+
uses: softprops/action-gh-release@v1
206+
with:
207+
tag_name: v${{ needs.verify.outputs.version }}
208+
name: v${{ needs.verify.outputs.version }}
209+
body: |
210+
## 🚀 Released to crates.io
211+
212+
Version ${{ needs.verify.outputs.version }} has been published to [crates.io](https://crates.io/crates/ccagents).
213+
214+
### Installation
215+
216+
```bash
217+
cargo install ccagents
218+
```
219+
220+
### Changes
221+
222+
See [CHANGELOG.md](https://github.com/Bitropy/ccagents/blob/main/CHANGELOG.md) for details.
223+
draft: false
224+
prerelease: false

docs/GITHUB_SETUP.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# GitHub Repository Setup for Publishing
2+
3+
This guide explains how to configure your GitHub repository for automated publishing to crates.io.
4+
5+
## 1. Create the crates-io Environment
6+
7+
1. Go to your repository settings: `https://github.com/Bitropy/ccagents/settings/environments`
8+
2. Click **"New environment"**
9+
3. Name it exactly: `crates-io`
10+
4. Configure the environment:
11+
-**Required reviewers**: Add yourself or team members who should approve releases
12+
-**Deployment branches**: Restrict to `main` branch only
13+
5. Click **"Save protection rules"**
14+
15+
## 2. Add the CARGO_REGISTRY_TOKEN Secret
16+
17+
### Get your crates.io token:
18+
1. Login to [crates.io](https://crates.io) with your GitHub account
19+
2. Go to [Account Settings](https://crates.io/me)
20+
3. Click on **"API Tokens"**
21+
4. Click **"New Token"**
22+
5. Name it: `ccagents-github-actions`
23+
6. Copy the token (you won't see it again!)
24+
25+
### Add to GitHub:
26+
1. Go to `https://github.com/Bitropy/ccagents/settings/secrets/actions`
27+
2. Click **"New repository secret"**
28+
3. Name: `CARGO_REGISTRY_TOKEN`
29+
4. Value: Paste your crates.io token
30+
5. Click **"Add secret"**
31+
32+
## 3. How to Use the Workflows
33+
34+
### Option 1: Manual Release (Recommended for first release)
35+
36+
1. Go to the **Actions** tab
37+
2. Select **"Publish to crates.io"** workflow
38+
3. Click **"Run workflow"**
39+
4. Enter the version (e.g., `0.1.0`)
40+
5. Click **"Run workflow"**
41+
6. Wait for the `verify` job to complete
42+
7. Go to the workflow run and **approve** the `crates-io` environment
43+
8. The workflow will:
44+
- Publish to crates.io
45+
- Create a git tag
46+
- Create a GitHub release with binaries
47+
48+
### Option 2: Release via GitHub UI
49+
50+
1. Go to **Releases**
51+
2. Click **"Create a new release"**
52+
3. Create a new tag (e.g., `v0.1.0`)
53+
4. Fill in release notes
54+
5. Click **"Publish release"**
55+
6. The workflow will trigger automatically
56+
7. Approve the `crates-io` environment when prompted
57+
58+
## 4. Workflow Features
59+
60+
### The publish workflow (`publish.yml`):
61+
62+
- **Verify stage**:
63+
- Checks version consistency
64+
- Runs tests and clippy
65+
- Validates package with `cargo publish --dry-run`
66+
67+
- **Manual approval**:
68+
- Requires approval for the `crates-io` environment
69+
- Prevents accidental publishes
70+
71+
- **Publish stage**:
72+
- Publishes to crates.io
73+
- Only runs after approval
74+
75+
- **Release artifacts**:
76+
- Builds binaries for Linux (x64, ARM64) and macOS (x64, ARM64)
77+
- Creates tar.gz archives with SHA256 checksums
78+
- Uploads to GitHub release
79+
80+
## 5. Version Management
81+
82+
Before releasing:
83+
84+
1. Update version in `Cargo.toml`
85+
2. Update `CHANGELOG.md`
86+
3. Commit changes: `git commit -am "Bump version to 0.1.0"`
87+
4. Push to main: `git push origin main`
88+
5. Then trigger the release workflow
89+
90+
## 6. Troubleshooting
91+
92+
### "Environment not found"
93+
- Make sure you created the `crates-io` environment exactly as named
94+
95+
### "Secret not found"
96+
- Verify `CARGO_REGISTRY_TOKEN` is added as a repository secret
97+
98+
### "Version mismatch"
99+
- Ensure Cargo.toml version matches the version you're trying to release
100+
101+
### "Package already published"
102+
- You cannot republish the same version
103+
- Bump the version in Cargo.toml
104+
105+
## Security Notes
106+
107+
- Never commit the CARGO_REGISTRY_TOKEN
108+
- Limit the token's scope if possible
109+
- Rotate tokens periodically
110+
- Use environment protection rules to control who can publish

0 commit comments

Comments
 (0)