Publish Crates #12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish Crates | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| crate_list: | |
| description: Space-separated crate package names to publish in order | |
| required: true | |
| type: string | |
| dry_run: | |
| description: Run cargo publish --dry-run only | |
| required: false | |
| default: "true" | |
| type: choice | |
| options: ["true", "false"] | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| publish: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Strip private-registry refs for crates.io publish | |
| run: | | |
| python3 - <<'PY' | |
| import re, pathlib | |
| # The Gitea `terraphim` registry mirrors crates.io for these deps. | |
| # crates.io rejects manifests whose deps pin a non-default registry, | |
| # so drop the `registry = "terraphim"` key everywhere and remove any | |
| # [patch.crates-io] entry that pins to it (a patch needs a source). | |
| root = pathlib.Path('Cargo.toml') | |
| lines = root.read_text().splitlines() | |
| out, inp = [], False | |
| for ln in lines: | |
| if ln.strip() == '[patch.crates-io]': | |
| inp = True; out.append(ln); continue | |
| if ln.startswith('['): | |
| inp = False | |
| if inp and 'registry = "terraphim"' in ln: | |
| continue | |
| out.append(ln) | |
| root.write_text("\n".join(out) + "\n") | |
| for f in pathlib.Path('.').rglob('Cargo.toml'): | |
| s = f.read_text() | |
| s = re.sub(r',\s*registry = "terraphim"', '', s) | |
| s = re.sub(r'registry = "terraphim",\s*', '', s) | |
| f.write_text(s) | |
| leftover = [str(p) for p in pathlib.Path('.').rglob('Cargo.toml') | |
| if 'registry = "terraphim"' in p.read_text()] | |
| assert not leftover, f"registry refs remain: {leftover}" | |
| print("Stripped terraphim-registry refs") | |
| PY | |
| - name: Publish crates in dependency order | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| CRATE_LIST: ${{ inputs.crate_list }} | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| run: | | |
| set -euo pipefail | |
| for crate in $CRATE_LIST; do | |
| manifest="crates/$crate/Cargo.toml" | |
| if [ ! -f "$manifest" ]; then | |
| echo "Missing manifest for $crate at $manifest" >&2 | |
| exit 1 | |
| fi | |
| # Resolve the real version via cargo metadata so that | |
| # `version.workspace = true` is expanded to the workspace value. | |
| # (A naive `grep '^version'` yields the literal "true".) | |
| version=$(cargo metadata --no-deps --format-version=1 \ | |
| | jq -r --arg manifest "$manifest" \ | |
| '.packages[] | select(.manifest_path | endswith($manifest)) | .version') | |
| if [ -z "$version" ]; then | |
| echo "ERROR: could not resolve version for $crate from $manifest" >&2 | |
| exit 1 | |
| fi | |
| # Check crates.io with auth to avoid rate limits | |
| if curl -fsS -H "Authorization: ${CARGO_REGISTRY_TOKEN}" "https://crates.io/api/v1/crates/$crate/$version" >/dev/null 2>&1; then | |
| echo "$crate $version already exists on crates.io; skipping" | |
| continue | |
| fi | |
| if [ "$DRY_RUN" = "true" ]; then | |
| cargo publish --manifest-path "$manifest" --dry-run --allow-dirty | |
| else | |
| set +e | |
| output=$(cargo publish --manifest-path "$manifest" --allow-dirty 2>&1) | |
| exit_code=$? | |
| set -e | |
| if [ "$exit_code" -ne 0 ]; then | |
| if echo "$output" | grep -qi "already exists"; then | |
| echo "$crate $version already exists on crates.io; skipping" | |
| continue | |
| fi | |
| echo "$output" >&2 | |
| echo "ERROR: Failed to publish $crate" >&2 | |
| exit 1 | |
| fi | |
| fi | |
| done |