1 name: Crates.io CLI Release
2
3 on:
4 push:
5 tags:
6 - "v*.*.*"
7 workflow_dispatch:
8 inputs:
9 tag:
10 description: "Release tag (e.g. v0.3.39)"
11 required: true
12
13 permissions:
14 contents: read
15 actions: write
16
17 env:
18 CARGO_TERM_COLOR: always
19 CLI_CLIENT_SECRET: ${{ secrets.CLI_CLIENT_SECRET }}
20
21 jobs:
22 publish:
23 name: Publish workspace crates to crates.io
24 runs-on: ubuntu-latest
25 outputs:
26 tag: ${{ steps.release.outputs.tag }}
27 published_count: ${{ steps.plan.outputs.count }}
28 publish_cli_sdk_pypi: ${{ steps.downstream.outputs.publish_cli_sdk_pypi }}
29 publish_cli_sdk_npm: ${{ steps.downstream.outputs.publish_cli_sdk_npm }}
30 publish_cli_sdk_gem: ${{ steps.downstream.outputs.publish_cli_sdk_gem }}
31
32 steps:
33 - name: Checkout
34 uses: actions/checkout@v6
35 with:
36 fetch-depth: 0
37 ref: ${{ github.event.inputs.tag || github.ref }}
38
39 - name: Set the release version
40 id: release
41 shell: bash
42 run: |
43 if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
44 release_tag="${GITHUB_REF_NAME}"
45 release_version="${GITHUB_REF_NAME#v}"
46 else
47 release_tag="${{ github.event.inputs.tag }}"
48 release_tag="${release_tag#refs/tags/}"
49 release_version="${release_tag#v}"
50 fi
51
52 echo "RELEASE_TAG=${release_tag}" >> "$GITHUB_ENV"
53 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
54 echo "tag=${release_tag}" >> "$GITHUB_OUTPUT"
55 echo "version=${release_version}" >> "$GITHUB_OUTPUT"
56
57 - name: Read Rust toolchain
58 shell: bash
59 run: |
60 rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
61 if [ -z "$rust_toolchain" ]; then
62 echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2
63 exit 1
64 fi
65
66 echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV"
67
68 - name: Install toolkit
69 uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
70 with:
71 toolchain: ${{ env.RUST_TOOLCHAIN }}
72
73 - name: Setup Rust cache
74 uses: Swatinem/rust-cache@v2
75 with:
76 key: publish
77
78 - name: Install cargo-workspaces
79 shell: bash
80 run: cargo install cargo-workspaces --version 0.4.2 --locked
81
82 - name: Verify workspace crate versions match tag
83 shell: bash
84 run: |
85 cargo metadata --no-deps --format-version 1 > metadata.json
86
87 crate_count="$(jq '[.packages[] | select(.manifest_path | contains("/crates/"))] | length' metadata.json)"
88 if [[ "$crate_count" == "0" ]]; then
89 echo "::error::No workspace crates found under /crates."
90 exit 1
91 fi
92
93 mismatches="$({
94 jq -r --arg release_version "${RELEASE_VERSION}" '
95 .packages[]
96 | select(.manifest_path | contains("/crates/"))
97 | select(.version != $release_version)
98 | "\(.name) \(.version)"' metadata.json
99 } || true)"
100
101 if [[ -n "$mismatches" ]]; then
102 echo "::error::These crates do not match tag version ${RELEASE_VERSION}:"
103 echo "$mismatches"
104 exit 1
105 fi
106
107 - name: Build unpublished crate plan
108 id: plan
109 shell: bash
110 env:
111 CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
112 run: |
113 if [[ -z "${CARGO_REGISTRY_TOKEN}" ]]; then
114 echo "::error::CARGO_REGISTRY_TOKEN is required."
115 exit 1
116 fi
117
118 cargo workspaces plan --skip-published --json --token "${CARGO_REGISTRY_TOKEN}" > plan.json
119 cat plan.json
120
121 plan_count="$(jq 'length' plan.json)"
122 echo "count=${plan_count}" >> "$GITHUB_OUTPUT"
123
124 - name: Derive downstream workflow triggers
125 id: downstream
126 shell: bash
127 run: |
128 publish_cli_sdk_pypi="$(jq -r 'map(.name) | any(. == "smbcloud-auth-sdk-py" or . == "smbcloud-auth-sdk" or . == "smbcloud-model" or . == "smbcloud-network")' plan.json)"
129 publish_cli_sdk_npm="$(jq -r 'map(.name) | any(. == "smbcloud-auth-sdk-wasm" or . == "smbcloud-auth-sdk" or . == "smbcloud-network" or . == "smbcloud-networking")' plan.json)"
130 publish_cli_sdk_gem="$(jq -r 'map(.name) | any(. == "smbcloud-auth-sdk" or . == "smbcloud-model" or . == "smbcloud-network")' plan.json)"
131
132 echo "publish_cli_sdk_pypi=${publish_cli_sdk_pypi}" >> "$GITHUB_OUTPUT"
133 echo "publish_cli_sdk_npm=${publish_cli_sdk_npm}" >> "$GITHUB_OUTPUT"
134 echo "publish_cli_sdk_gem=${publish_cli_sdk_gem}" >> "$GITHUB_OUTPUT"
135
136 echo "SDK PyPI trigger: ${publish_cli_sdk_pypi}"
137 echo "SDK npm trigger: ${publish_cli_sdk_npm}"
138 echo "SDK gem trigger: ${publish_cli_sdk_gem}"
139
140 - name: Publish workspace crates to crates.io
141 if: steps.plan.outputs.count != '0'
142 shell: bash
143 env:
144 CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
145 run: |
146 cargo workspaces publish \
147 --publish-as-is \
148 --yes \
149 --locked \
150 --allow-branch "*" \
151 --publish-interval 15 \
152 --token "${CARGO_REGISTRY_TOKEN}"
153
154 - name: Nothing to publish
155 if: steps.plan.outputs.count == '0'
156 run: echo "All workspace crate versions in this tag are already published."
157
158 dispatch-distributions:
159 name: Trigger CLI distribution workflows
160 needs: publish
161 runs-on: ubuntu-latest
162
163 steps:
164 - name: Trigger GitHub CLI release
165 uses: actions/github-script@v7
166 with:
167 script: |
168 await github.rest.actions.createWorkflowDispatch({
169 owner: context.repo.owner,
170 repo: context.repo.repo,
171 workflow_id: 'release-github.yml',
172 ref: '${{ needs.publish.outputs.tag }}',
173 inputs: {
174 tag: '${{ needs.publish.outputs.tag }}'
175 }
176 })
177
178 - name: Trigger npm CLI release
179 uses: actions/github-script@v7
180 with:
181 script: |
182 await github.rest.actions.createWorkflowDispatch({
183 owner: context.repo.owner,
184 repo: context.repo.repo,
185 workflow_id: 'release-npm.yml',
186 ref: '${{ needs.publish.outputs.tag }}',
187 inputs: {
188 tag: '${{ needs.publish.outputs.tag }}'
189 }
190 })
191
192 - name: Trigger NuGet CLI release
193 uses: actions/github-script@v7
194 with:
195 script: |
196 await github.rest.actions.createWorkflowDispatch({
197 owner: context.repo.owner,
198 repo: context.repo.repo,
199 workflow_id: 'release-nuget.yml',
200 ref: '${{ needs.publish.outputs.tag }}',
201 inputs: {
202 tag: '${{ needs.publish.outputs.tag }}'
203 }
204 })
205
206 - name: Trigger PyPI CLI release
207 uses: actions/github-script@v7
208 with:
209 script: |
210 await github.rest.actions.createWorkflowDispatch({
211 owner: context.repo.owner,
212 repo: context.repo.repo,
213 workflow_id: 'release-pypi.yml',
214 ref: '${{ needs.publish.outputs.tag }}',
215 inputs: {
216 tag: '${{ needs.publish.outputs.tag }}'
217 }
218 })
219
220 - name: Trigger npm XCRS release
221 uses: actions/github-script@v7
222 with:
223 script: |
224 await github.rest.actions.createWorkflowDispatch({
225 owner: context.repo.owner,
226 repo: context.repo.repo,
227 workflow_id: 'release-xcrs-npm.yml',
228 ref: '${{ needs.publish.outputs.tag }}',
229 inputs: {
230 tag: '${{ needs.publish.outputs.tag }}'
231 }
232 })
233
234 - name: Trigger NuGet XCRS release
235 uses: actions/github-script@v7
236 with:
237 script: |
238 await github.rest.actions.createWorkflowDispatch({
239 owner: context.repo.owner,
240 repo: context.repo.repo,
241 workflow_id: 'release-xcrs-nuget.yml',
242 ref: '${{ needs.publish.outputs.tag }}',
243 inputs: {
244 tag: '${{ needs.publish.outputs.tag }}'
245 }
246 })
247
248 - name: Trigger PyPI XCRS release
249 uses: actions/github-script@v7
250 with:
251 script: |
252 await github.rest.actions.createWorkflowDispatch({
253 owner: context.repo.owner,
254 repo: context.repo.repo,
255 workflow_id: 'release-xcrs-pypi.yml',
256 ref: '${{ needs.publish.outputs.tag }}',
257 inputs: {
258 tag: '${{ needs.publish.outputs.tag }}'
259 }
260 })
261
262 dispatch-sdk-pypi:
263 name: Trigger SDK PyPI release
264 needs: publish
265 if: needs.publish.outputs.publish_cli_sdk_pypi == 'true'
266 runs-on: ubuntu-latest
267
268 steps:
269 - name: Trigger SDK PyPI release workflow
270 uses: actions/github-script@v7
271 with:
272 script: |
273 await github.rest.actions.createWorkflowDispatch({
274 owner: context.repo.owner,
275 repo: context.repo.repo,
276 workflow_id: 'release-sdk-pypi.yml',
277 ref: '${{ needs.publish.outputs.tag }}',
278 inputs: {
279 tag: '${{ needs.publish.outputs.tag }}'
280 }
281 })
282
283 dispatch-sdk-npm:
284 name: Trigger SDK npm release
285 needs: publish
286 if: needs.publish.outputs.publish_cli_sdk_npm == 'true'
287 runs-on: ubuntu-latest
288
289 steps:
290 - name: Trigger SDK npm release workflow
291 uses: actions/github-script@v7
292 with:
293 script: |
294 await github.rest.actions.createWorkflowDispatch({
295 owner: context.repo.owner,
296 repo: context.repo.repo,
297 workflow_id: 'release-sdk-npm.yml',
298 ref: '${{ needs.publish.outputs.tag }}',
299 inputs: {
300 tag: '${{ needs.publish.outputs.tag }}'
301 }
302 })
303
304 dispatch-sdk-gem:
305 name: Trigger SDK Ruby gem release
306 needs: publish
307 if: needs.publish.outputs.publish_cli_sdk_gem == 'true'
308 runs-on: ubuntu-latest
309
310 steps:
311 - name: Trigger SDK Ruby gem release workflow
312 uses: actions/github-script@v7
313 with:
314 script: |
315 await github.rest.actions.createWorkflowDispatch({
316 owner: context.repo.owner,
317 repo: context.repo.repo,
318 workflow_id: 'release-sdk-gem.yml',
319 ref: '${{ needs.publish.outputs.tag }}',
320 inputs: {
321 tag: '${{ needs.publish.outputs.tag }}'
322 }
323 })