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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# Release workflow — publishes `tympan-apo` to crates.io.
#
# Trigger: a `v*.*.*` tag pushed to the repository. The tag's
# numeric portion must match the framework crate's
# `Cargo.toml#package.version`; a mismatch fails the preflight step
# below before any side effects run.
#
# Authentication: crates.io Trusted Publishing (OIDC). The
# `rust-lang/crates-io-auth-action@v1` step exchanges this
# workflow's GitHub-issued OIDC ID token for a short-lived
# (~15 min) crates.io access token, scoped to a single publish.
# The token is auto-revoked when the job ends, so no long-lived
# API token is stored as a GitHub secret.
#
# One-time setup (before the first release):
#
# crates.io does not yet support "pending publishers" — a Trusted
# Publisher configuration can only be attached to a crate that
# already exists. Per RFC 3691 (§ Guide-level explanation):
# "A Trusted Publisher Configuration can only be created after an
# initial manual publishing of a crate." The very first release
# therefore needs a manual `cargo publish` with an API token;
# subsequent releases are what this workflow automates.
#
# 1. crates.io → Account Settings → API Tokens → New Token
# Name: something descriptive ("tympan-apo bootstrap")
# Scopes: publish-new (least privilege)
# Expiry: short — e.g. 1 day. The token is single-use here.
#
# 2. From a clean `main` checkout on a Windows host, publish
# v0.1.0 locally:
#
# $env:CARGO_REGISTRY_TOKEN = 'cio_xxxxx...'
# cargo publish -p tympan-apo
# Remove-Item Env:\CARGO_REGISTRY_TOKEN
#
# Or, equivalently from a POSIX shell that can cross-compile
# (rare for an APO project — typically a Windows host is
# used):
#
# CARGO_REGISTRY_TOKEN='cio_xxxxx...' \
# cargo publish -p tympan-apo
#
# The env-var form is preferred over `cargo login`: it leaves
# no trace in shell history or `~/.cargo/credentials.toml`.
#
# 3. Once the crate exists on crates.io, configure the Trusted
# Publisher:
#
# https://crates.io/crates/tympan-apo/settings
# → Trusted Publishers section → Add
#
# Fields:
# Repository owner penta2himajin
# Repository name tympan-apo
# Workflow filename release.yml
# Environment (leave blank)
#
# Only direct crate owners can access this page. If you are a
# member of an owner team but not listed as an individual owner,
# see rust-lang/crates.io#12710 for the current workaround.
#
# 4. Revoke the bootstrap API token from
# crates.io → Account Settings → API Tokens → (token) → Revoke.
# The workflow below authenticates via OIDC from this point on;
# no long-lived token is needed.
#
# 5. Subsequent releases (v0.2.0 and beyond) only need a tag push —
# see "Release procedure" below.
#
# Note: do not push a `v0.1.0` tag *after* the manual first publish.
# This workflow's preflight would pass, the dry-run would pass, and
# the real publish would then fail with `crate version `0.1.0`
# already uploaded`. Either start tagging from `v0.2.0`, or push
# `v0.1.0` only for git-history purposes while accepting that the
# `cargo publish` step will fail harmlessly.
#
# Release procedure:
#
# 1. Update `Cargo.toml#package.version` and commit on a branch.
# 2. Merge the bump PR to `main`.
# 3. From a clean `main` checkout:
# git tag vX.Y.Z
# git push origin vX.Y.Z
# 4. This workflow fires; on success the new version is live on
# crates.io.
name: Release
on:
push:
tags:
- 'v*.*.*'
permissions:
contents: read
# Required for the OIDC token exchange with crates.io. Scoped to
# this workflow only; no other workflows in this repo request an
# ID token.
id-token: write
concurrency:
# Serialise releases — two simultaneous `cargo publish` runs from
# the same crate would race and the loser would fail with
# `crate already uploaded`. The release tag is the natural lock
# key.
group: release-${{ github.ref }}
cancel-in-progress: false
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: short
jobs:
publish:
name: Publish tympan-apo to crates.io
# `windows-latest` matches the framework's Tier 1/2/3 runners so
# the publish-time `cargo publish --dry-run` (which compiles
# the packaged crate from scratch) exercises the Windows-only
# COM bridge in `src/raw/` and the `[target.'cfg(windows)']`
# `windows` / `windows-core` dependencies. Compiling on Linux
# would skip all of that.
runs-on: windows-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust toolchain (stable)
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: release
- name: Verify tag matches Cargo.toml version
# The release tag's numeric portion (`v0.1.0` → `0.1.0`)
# must equal the framework crate's manifest version. A
# mismatch is almost always either a missing version bump
# or a typo in the tag, and either way the release should
# not proceed.
shell: bash
run: |
set -euo pipefail
TAG="${GITHUB_REF#refs/tags/v}"
VERSION=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.name=="tympan-apo") | .version')
if [ "$TAG" != "$VERSION" ]; then
echo "::error::tag v$TAG does not match Cargo.toml version $VERSION"
exit 1
fi
echo "Releasing tympan-apo $VERSION"
- name: cargo publish --dry-run
# Exercises the full publish path (manifest validation,
# `.crate` packaging, dependency resolution against
# crates.io's index, full compile of the packaged crate
# against the Windows SDK) without uploading. If this
# fails the real publish would fail too, and failing here
# costs nothing.
run: cargo publish -p tympan-apo --dry-run
- name: Obtain crates.io access token via Trusted Publishing
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: cargo publish
env:
# The action's output is a short-lived crates.io token;
# cargo reads it from this environment variable. The
# token is automatically revoked at end-of-job by the
# action's post step.
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish -p tympan-apo