main
yml 127 lines 5.01 KB
Raw
1 name: Release SDK Rust (crates.io)
2
3 on:
4 push:
5 tags:
6 - "v[0-9]+.[0-9]+.[0-9]+"
7 workflow_dispatch:
8 inputs:
9 publish:
10 description: "Publish the selected existing release tag to crates.io"
11 type: boolean
12 default: false
13 release_tag:
14 description: "Existing vX.Y.Z tag to validate or publish on manual runs"
15 type: string
16 required: false
17
18 permissions:
19 contents: read
20
21 jobs:
22 preflight:
23 runs-on: macos-26
24 defaults:
25 run:
26 working-directory: ed
27 steps:
28 - uses: actions/checkout@v6
29 with:
30 path: ed
31 ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref }}
32 - name: Read Rust toolchain
33 run: |
34 rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
35 test -n "$rust_toolchain"
36 echo "RUST_TOOLCHAIN=$rust_toolchain" >> "$GITHUB_ENV"
37 - uses: dtolnay/rust-toolchain@stable
38 with:
39 toolchain: ${{ env.RUST_TOOLCHAIN }}
40 components: rustfmt, clippy
41 - name: Validate release tag matches workspace version
42 if: startsWith(github.ref, 'refs/tags/') || inputs.publish
43 env:
44 INPUT_RELEASE_TAG: ${{ inputs.release_tag }}
45 run: |
46 if [ "$GITHUB_REF_TYPE" = tag ]; then
47 release_tag="$GITHUB_REF_NAME"
48 else
49 release_tag="$INPUT_RELEASE_TAG"
50 fi
51 test -n "$release_tag"
52 [[ "$release_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
53 git show-ref --verify --quiet "refs/tags/$release_tag"
54 test "$(git rev-list -n 1 "$release_tag")" = "$(git rev-parse HEAD)"
55 version="$(cargo metadata --no-deps --format-version 1 | python3 -c 'import json,sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "ed-agent"))')"
56 test "${release_tag#v}" = "$version"
57 - run: cargo fmt --all -- --check
58 - run: cargo test --locked --workspace
59 - run: cargo clippy --locked --workspace --all-targets -- -D warnings
60 # Publishing Ed means publishing two crates in dependency order, and plain
61 # `cargo publish --dry-run` cannot cover the second one: packaging
62 # rewrites ed-agent-tauri's path dependency on ed-agent into a registry
63 # requirement, and `^1.0.0` cannot resolve until ed-agent is actually on
64 # crates.io. A [patch] does not help, since packaging ignores it by
65 # design. cargo-workspaces knows the order and its dry run warns about
66 # the unpublished sibling rather than failing on it, so ed-agent still
67 # gets a real packaging and verification pass here.
68 - name: Install cargo-workspaces
69 run: cargo install cargo-workspaces --version 0.4.2 --locked
70 - name: Dry-run the workspace publish
71 run: cargo workspaces publish --publish-as-is --dry-run --locked --yes
72
73 publish:
74 needs: preflight
75 if: startsWith(github.ref, 'refs/tags/') || inputs.publish
76 runs-on: macos-26
77 permissions:
78 contents: read
79 id-token: write
80 defaults:
81 run:
82 working-directory: ed
83 steps:
84 - uses: actions/checkout@v6
85 with:
86 path: ed
87 ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref }}
88 - name: Read Rust toolchain
89 run: |
90 rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
91 test -n "$rust_toolchain"
92 echo "RUST_TOOLCHAIN=$rust_toolchain" >> "$GITHUB_ENV"
93 - uses: dtolnay/rust-toolchain@stable
94 with:
95 toolchain: ${{ env.RUST_TOOLCHAIN }}
96 - name: Install cargo-workspaces
97 run: cargo install cargo-workspaces --version 0.4.2 --locked
98 # `plan --skip-published` is what makes a re-run safe: it lists only the
99 # crates still missing from crates.io, so a run that dies after ed-agent
100 # went up publishes just ed-agent-tauri the second time.
101 - name: Plan the publish
102 id: plan
103 run: |
104 cargo workspaces plan --skip-published --json > plan.json
105 cat plan.json
106 count="$(python3 -c 'import json; print(len(json.load(open("plan.json"))))')"
107 echo "count=$count" >> "$GITHUB_OUTPUT"
108 - name: Authenticate to crates.io
109 if: steps.plan.outputs.count != '0'
110 id: auth
111 uses: rust-lang/crates-io-auth-action@v1
112 - name: Publish workspace crates
113 if: steps.plan.outputs.count != '0'
114 env:
115 TOKEN: ${{ steps.auth.outputs.token }}
116 run: |
117 # --publish-interval gives the sparse index time to serve ed-agent
118 # before ed-agent-tauri's verification build tries to resolve it.
119 cargo workspaces publish \
120 --publish-as-is \
121 --yes \
122 --locked \
123 --publish-interval 15 \
124 --token "$TOKEN"
125 - name: Nothing to publish
126 if: steps.plan.outputs.count == '0'
127 run: echo "Every publishable crate at this version is already on crates.io."