1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# Release: publish agora-agentkit to crates.io.
#
# **Releasing is merging a version bump.** On every push to `main` the
# `version` job reads the crate version; if tag `v<version>` does not exist
# yet, it tags that commit and this same run publishes. No hand tagging, no
# publishing from a laptop. (It has to be one run: a tag pushed with the
# workflow's GITHUB_TOKEN deliberately triggers no further workflows.) Pushing
# a `v*` tag by hand still works and must match the version. A main push that
# doesn't change the version does nothing here.
#
# The release runs `just check` itself before publishing: CI's run on the
# same commit starts at the same moment, and a publish must not outrun it.
#
# No secrets. publish uses crates.io trusted publishing (GitHub OIDC -> a
# short-lived crates.io token, revoked when the job ends). One-time setup on
# crates.io for `agora-agentkit`: Settings -> Trusted Publishing -> GitHub,
# owner `mdegans`, repository `agora-agentkit`, workflow `release.yaml`.
name: Release
on:
push:
branches:
tags:
# Two merges in quick succession must not race to tag and publish.
concurrency:
group: release
cancel-in-progress: false
permissions:
jobs:
# Decide whether this run releases:
# - a `v*` tag push releases iff the tag matches the version exactly;
# - a push to main releases iff `v<version>` doesn't exist yet, and tags
# the commit first.
version:
runs-on: ubuntu-latest
permissions:
# Pushing the release tag.
contents: write
outputs:
version: ${{ steps.meta.outputs.version }}
release: ${{ steps.decide.outputs.release }}
steps:
- uses: actions/checkout@v4
- name: Read crate version
id: meta
run: |
VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)
[ -n "$VERSION" ] || { echo "::error::no version in Cargo.toml"; exit 1; }
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Decide whether this run releases (and tag it if from main)
id: decide
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
TAG="v$VERSION"
case "$GITHUB_EVENT_NAME:$GITHUB_REF_TYPE" in
push:tag)
[ "$GITHUB_REF_NAME" = "$TAG" ] || {
echo "::error::tag $GITHUB_REF_NAME != $TAG (crate version)"
exit 1
}
echo "release=true" >> "$GITHUB_OUTPUT"
;;
push:branch)
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null; then
echo "$TAG already exists; nothing to release."
echo "release=false" >> "$GITHUB_OUTPUT"
else
git tag "$TAG" "$GITHUB_SHA"
git push origin "refs/tags/$TAG"
echo "Tagged $TAG at $GITHUB_SHA; releasing."
echo "release=true" >> "$GITHUB_OUTPUT"
fi
;;
*)
echo "release=false" >> "$GITHUB_OUTPUT"
;;
esac
# The same gate as CI's `check` job, on the commit being released.
check:
needs: version
if: needs.version.outputs.release == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@just
- name: check
run: just check
publish:
needs:
if: needs.version.outputs.release == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
# The OIDC token crates.io exchanges for a short-lived publish token.
id-token: write
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Authenticate with crates.io
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish