name: Release Python package
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
dry_run:
description: "Build and upload artifacts without publishing"
required: true
type: boolean
default: true
permissions:
contents: read
concurrency:
group: release-pypi-${{ github.ref }}
cancel-in-progress: false
jobs:
metadata:
name: validate package versions
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 with:
python-version: "3.12"
- name: Validate release metadata
shell: bash
run: |
set -euo pipefail
python - <<'PY'
import json
import os
import pathlib
import tomllib
root = pathlib.Path(".")
cargo_lock = tomllib.loads((root / "Cargo.lock").read_text())
cargo_versions = {
package["name"]: package["version"]
for package in cargo_lock["package"]
if package["name"] in {"rustwright-core", "rustwright-node"}
}
node_lock = json.loads((root / "node/package-lock.json").read_text())
versions = {
"pyproject.toml": tomllib.loads((root / "pyproject.toml").read_text())["project"]["version"],
"Cargo.toml": tomllib.loads((root / "Cargo.toml").read_text())["package"]["version"],
"node/Cargo.toml": tomllib.loads((root / "node/Cargo.toml").read_text())["package"]["version"],
"node/package.json": json.loads((root / "node/package.json").read_text())["version"],
"Cargo.lock rustwright-core": cargo_versions["rustwright-core"],
"Cargo.lock rustwright-node": cargo_versions["rustwright-node"],
"node/package-lock.json": node_lock["version"],
'node/package-lock.json packages[""]': node_lock["packages"][""]["version"],
}
expected = next(iter(versions.values()))
if any(version != expected for version in versions.values()):
raise SystemExit(f"Package versions do not match: {versions}")
# Match the npm workflow, which rejects stable versions: a single tag
# must publish to both registries or neither. This alpha project ships
# prereleases only (e.g. 0.2.0-alpha.1), so reject a stable version here
# too rather than let PyPI publish something npm would refuse.
if "-" not in expected:
raise SystemExit(
f"Refusing to publish stable version {expected!r}: this project "
f"publishes prereleases only (e.g. 0.2.0-alpha.1), matching release-npm.yml."
)
if os.environ["GITHUB_REF_TYPE"] == "tag":
tag_version = os.environ["GITHUB_REF_NAME"].removeprefix("v")
if tag_version != expected:
raise SystemExit(
f"Tag version {tag_version!r} does not match package version {expected!r}"
)
print(f"Validated package version {expected}")
PY
wheels:
name: wheel - ${{ matrix.platform.target }}
needs: metadata
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
platform:
- runner: macos-latest
target: aarch64-apple-darwin
manylinux: auto
extra_args: ""
- runner: macos-latest
target: x86_64-apple-darwin
manylinux: auto
extra_args: ""
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
manylinux: "2_17"
extra_args: ""
- runner: ubuntu-latest
target: aarch64-unknown-linux-gnu
manylinux: "2_17"
extra_args: "--zig"
- runner: windows-latest
target: x86_64-pc-windows-msvc
manylinux: auto
extra_args: ""
steps:
- name: Check out repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 with:
python-version: "3.12"
- name: Build abi3 wheel
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b with:
command: build
maturin-version: v1.11.5
target: ${{ matrix.platform.target }}
manylinux: ${{ matrix.platform.manylinux }}
rust-toolchain: stable
args: --release --locked --compatibility pypi --out dist ${{ matrix.platform.extra_args }}
- name: Upload wheel
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: pypi-wheel-${{ matrix.platform.target }}
path: dist/*.whl
if-no-files-found: error
sdist:
name: source distribution
needs: metadata
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- name: Build source distribution
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b with:
command: sdist
maturin-version: v1.11.5
rust-toolchain: stable
args: --out dist
- name: Upload source distribution
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: pypi-sdist
path: dist/*.tar.gz
if-no-files-found: error
publish:
name: publish to PyPI
needs:
- wheels
- sdist
if: >-
${{
github.repository == 'Skyvern-AI/rustwright' &&
github.ref_type == 'tag' &&
(
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && !inputs.dry_run)
)
}}
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/rustwright/
permissions:
contents: read
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
pattern: pypi-*
path: dist
merge-multiple: true
- name: Publish with PyPI Trusted Publishing
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b