Skip to content

Commit c6fdf6a

Browse files
committed
Merge remote-tracking branch 'origin/main' into worktree-rollback-scan-duality
# Conflicts: # CHANGELOG.md # crates/socket-patch-cli/tests/e2e_hosted_production.rs
2 parents 0b975cb + 3a2b06d commit c6fdf6a

15 files changed

Lines changed: 1379 additions & 414 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ jobs:
159159
done
160160
161161
- name: Shell — shellcheck the release scripts
162-
run: shellcheck scripts/version-sync.sh scripts/bump-version.sh scripts/release-lint.sh
162+
run: shellcheck scripts/version-sync.sh scripts/bump-version.sh scripts/release-lint.sh scripts/dispatch-publish.sh
163163

164164
# Release-readiness gate (scripts/release-lint.sh — the same checks the
165165
# Release workflow's `version` job runs before publishing anything):
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
name: Publish crates.io
2+
run-name: "Publish crates.io ${{ inputs.version }}${{ inputs.distinct-id != '' && format(' [{0}]', inputs.distinct-id) || '' }}"
3+
4+
# Publishes socket-patch-core + socket-patch-cli to crates.io for an existing
5+
# v<version> tag. Dispatched two ways, both as a plain workflow_dispatch run:
6+
# - by release.yml (scripts/dispatch-publish.sh), as one leg of the
7+
# single-dispatch release fan-out — distinct-id carries the release
8+
# run's correlation id into this run's name;
9+
# - manually (Actions → Publish crates.io → Run workflow), to retry just
10+
# this registry after a mid-release failure: fix the cause, enter the
11+
# release version, leave distinct-id blank.
12+
# Every run checks out refs/tags/v<version> and publishes from source, so a
13+
# manual retry publishes exactly what the release run would have.
14+
#
15+
# Idempotent: already-published versions are probed and skipped, so re-runs
16+
# and retries after a partial publish (core landed, cli didn't) are safe.
17+
#
18+
# OIDC trusted publishing: the crates.io trusted publisher for both crates is
19+
# keyed on this repo + THIS file's name (publish-cargo.yml). Because this
20+
# workflow only ever runs as its own top-level workflow_dispatch run (never
21+
# as a called reusable workflow), the OIDC token's workflow_ref and
22+
# job_workflow_ref claims both name this file — one publisher registration
23+
# covers every path, regardless of which claim the registry matches.
24+
25+
on:
26+
workflow_dispatch:
27+
inputs:
28+
version:
29+
description: 'Release version (X.Y.Z; the tag v<version> must exist)'
30+
required: true
31+
type: string
32+
distinct-id:
33+
description: 'Correlation id set by release.yml to track its dispatched run — leave blank for manual runs'
34+
required: false
35+
default: ''
36+
type: string
37+
38+
# Serialize same-version runs: the already-published probes below are
39+
# check-then-act, so two CONCURRENT runs for one version could both pass a
40+
# probe and the loser would hard-fail on the registry. Serialized, a
41+
# duplicate (e.g. re-dispatched by a release-run watcher whose `gh run
42+
# watch` timed out while this run was still going) waits behind the live
43+
# run and then no-ops. NOTE: the group holds at most ONE waiting run — a
44+
# further same-version dispatch displaces (cancels) the waiting duplicate,
45+
# and a watcher following the displaced run reports that as a failure; the
46+
# publish itself is unaffected (the surviving runs no-op or publish).
47+
concurrency:
48+
group: publish-cargo-${{ inputs.version }}
49+
50+
permissions: {}
51+
52+
jobs:
53+
cargo-publish:
54+
runs-on: ubuntu-latest
55+
# Bounds how long a wedged run (hung registry call) can hold this
56+
# workflow's per-version concurrency group before retries can proceed.
57+
timeout-minutes: 45
58+
permissions:
59+
contents: read
60+
id-token: write
61+
steps:
62+
- name: Validate version input
63+
env:
64+
VERSION: ${{ inputs.version }}
65+
run: |
66+
if ! printf '%s' "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
67+
echo "::error::'${VERSION}' is not a plain X.Y.Z release version"
68+
exit 1
69+
fi
70+
71+
- name: Checkout release tag
72+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
73+
with:
74+
ref: refs/tags/v${{ inputs.version }}
75+
persist-credentials: false
76+
77+
- name: Verify tag carries the requested version
78+
env:
79+
VERSION: ${{ inputs.version }}
80+
run: |
81+
CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
82+
if [ "$CARGO_VERSION" != "$VERSION" ]; then
83+
echo "::error::tag v${VERSION} carries workspace version ${CARGO_VERSION} — refusing to publish"
84+
exit 1
85+
fi
86+
87+
- name: Install Rust
88+
# rustup is pre-installed on GitHub-hosted runners. `rustup show`
89+
# reads rust-toolchain.toml in the repo root, then installs the
90+
# pinned channel + listed components if missing.
91+
run: rustup show
92+
93+
- name: Probe crates.io for already-published versions
94+
id: published
95+
env:
96+
VERSION: ${{ inputs.version }}
97+
run: |
98+
# crates.io returns HTTP 200 for a published version and 404
99+
# otherwise; its crawler policy requires a User-Agent identifying
100+
# the caller. Anything but a definite 200 (including transient
101+
# network errors) falls through to publishing, where `cargo publish`
102+
# gives the authoritative error.
103+
probe() {
104+
curl -fsSL -o /dev/null \
105+
-H 'User-Agent: socket-patch-release-workflow (https://github.com/SocketDev/socket-patch)' \
106+
"https://crates.io/api/v1/crates/${1}/${VERSION}"
107+
}
108+
if probe socket-patch-core; then
109+
echo "socket-patch-core ${VERSION} already on crates.io; skipping its publish."
110+
echo "core=true" >> "$GITHUB_OUTPUT"
111+
else
112+
echo "core=false" >> "$GITHUB_OUTPUT"
113+
fi
114+
if probe socket-patch-cli; then
115+
echo "socket-patch-cli ${VERSION} already on crates.io; skipping its publish."
116+
echo "cli=true" >> "$GITHUB_OUTPUT"
117+
else
118+
echo "cli=false" >> "$GITHUB_OUTPUT"
119+
fi
120+
121+
- name: Authenticate with crates.io
122+
id: crates-io-auth
123+
uses: rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec # v1.0.3
124+
125+
- name: Publish socket-patch-core
126+
if: steps.published.outputs.core != 'true'
127+
run: cargo publish -p socket-patch-core
128+
env:
129+
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}
130+
131+
- name: Wait for crates.io index update
132+
if: steps.published.outputs.core != 'true'
133+
run: sleep 30
134+
135+
- name: Copy README for CLI crate
136+
run: cp README.md crates/socket-patch-cli/README.md
137+
138+
- name: Publish socket-patch-cli
139+
if: steps.published.outputs.cli != 'true'
140+
run: cargo publish -p socket-patch-cli
141+
env:
142+
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}

0 commit comments

Comments
 (0)