main
yml 133 lines 4.58 KB
Raw
1 name: Publish to crates.io
2
3 on:
4 release:
5 types: [published]
6 workflow_dispatch:
7 inputs:
8 tag:
9 description: "Release tag (e.g. v0.1.0)"
10 required: true
11 dry_run:
12 description: "Dry run (do not publish)"
13 type: boolean
14 default: false
15
16 permissions:
17 contents: read
18 # Required for crates.io trusted publishing — allows the job to request
19 # a GitHub OIDC token and exchange it for a short-lived crates.io token.
20 id-token: write
21
22 env:
23 CARGO_TERM_COLOR: always
24
25 jobs:
26 publish:
27 name: Publish to crates.io
28 runs-on: ubuntu-latest
29
30 steps:
31 - name: Resolve tag and version
32 id: release
33 shell: bash
34 run: |
35 if [[ "${{ github.event_name }}" == "release" ]]; then
36 TAG="${{ github.event.release.tag_name }}"
37 else
38 TAG="${{ github.event.inputs.tag }}"
39 fi
40 VERSION="${TAG#v}"
41
42 echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
43 echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
44
45 - name: Checkout
46 uses: actions/checkout@v6
47 with:
48 ref: ${{ steps.release.outputs.tag }}
49
50 - name: Install Rust toolchain
51 uses: dtolnay/rust-toolchain@stable
52
53 - name: Setup Rust cache
54 uses: Swatinem/rust-cache@v2
55 with:
56 key: publish
57
58 - name: Verify Cargo.toml version matches tag
59 shell: bash
60 run: |
61 CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 \
62 | jq -r '.packages[] | select(.name == "proses") | .version')
63 TAG_VERSION="${{ steps.release.outputs.version }}"
64
65 echo "Cargo.toml version : ${CARGO_VERSION}"
66 echo "Release tag version: ${TAG_VERSION}"
67
68 if [[ "${CARGO_VERSION}" != "${TAG_VERSION}" ]]; then
69 echo "::error::Version mismatch — bump version in Cargo.toml before releasing."
70 exit 1
71 fi
72
73 - name: Run tests
74 shell: bash
75 run: cargo test --locked
76
77 # ── Trusted publishing ────────────────────────────────────────────────
78 # 1. Request a GitHub OIDC token scoped to crates.io.
79 # 2. Exchange it with the crates.io trusted-publishing endpoint for a
80 # short-lived API token (valid for one publish operation only).
81 # 3. Mask the token immediately so it never appears in logs.
82 #
83 # Prerequisites (one-time setup):
84 # • On crates.io → your crate → "Trusted Publishing" tab, add a publisher:
85 # Repository owner : smbcloudXYZ
86 # Repository name : proses
87 # Workflow filename: release-crate.yml
88 # Environment name : (leave blank, or match the `environment:` key below)
89 # • No repository secret is required — the OIDC handshake replaces it.
90 - name: Obtain crates.io publish token via OIDC
91 id: crates-token
92 shell: bash
93 run: |
94 # Request a GitHub OIDC JWT scoped to crates.io.
95 OIDC_TOKEN=$(curl --silent --fail \
96 -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
97 "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=crates.io" \
98 | jq -r '.value')
99
100 if [[ -z "${OIDC_TOKEN}" || "${OIDC_TOKEN}" == "null" ]]; then
101 echo "::error::Failed to obtain OIDC token — ensure id-token: write is set."
102 exit 1
103 fi
104
105 # Exchange the OIDC JWT for a short-lived crates.io publish token.
106 RESPONSE=$(curl --silent --fail \
107 -X POST "https://crates.io/api/v1/trusted_publishing/tokens" \
108 -H "Content-Type: application/json" \
109 -d "{\"jwt\": \"${OIDC_TOKEN}\"}")
110
111 CRATES_TOKEN=$(echo "${RESPONSE}" | jq -r '.token')
112
113 if [[ -z "${CRATES_TOKEN}" || "${CRATES_TOKEN}" == "null" ]]; then
114 echo "::error::Failed to exchange OIDC token — check your trusted publisher config on crates.io."
115 echo "Response: ${RESPONSE}"
116 exit 1
117 fi
118
119 # Mask the token so it is redacted in all subsequent log output.
120 echo "::add-mask::${CRATES_TOKEN}"
121 echo "token=${CRATES_TOKEN}" >> "$GITHUB_OUTPUT"
122
123 - name: Publish
124 shell: bash
125 env:
126 CARGO_REGISTRY_TOKEN: ${{ steps.crates-token.outputs.token }}
127 run: |
128 if [[ "${{ github.event.inputs.dry_run }}" == "true" ]]; then
129 echo "Dry run — package will not be published."
130 cargo publish --locked --dry-run
131 else
132 cargo publish --locked
133 fi