Bump version to 0.14.0 (#280) #41
Workflow file for this run
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: Release Crate and Wheels | |
| on: | |
| push: | |
| tags: ['v*'] | |
| workflow_dispatch: | |
| inputs: | |
| publish: | |
| description: "Publish to PyPI and crates.io" | |
| type: boolean | |
| required: false | |
| default: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| version-check: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - name: Verify version consistency | |
| run: | | |
| set -euo pipefail | |
| EVENT_NAME="${GITHUB_EVENT_NAME}" | |
| REF_NAME="${GITHUB_REF_NAME}" | |
| TAG_VERSION="" | |
| if [ "$EVENT_NAME" = "push" ] && [[ "$REF_NAME" == v* ]]; then | |
| TAG_VERSION="${REF_NAME#v}" # drop 'v' prefix if present | |
| fi | |
| echo "Event: $EVENT_NAME" | |
| echo "Ref name: $REF_NAME" | |
| echo "Tag version: ${TAG_VERSION:-<none>}" | |
| python - <<'PY' | |
| import os, sys | |
| try: | |
| import tomllib # Python 3.11+ | |
| except Exception as e: # pragma: no cover | |
| print("Python 3.11+ with tomllib is required", file=sys.stderr) | |
| raise | |
| def read_toml(path: str): | |
| with open(path, 'rb') as f: | |
| return tomllib.load(f) | |
| tag = os.environ.get('TAG_VERSION', '') | |
| cargo = read_toml('Cargo.toml') | |
| pyproj = read_toml('pyproject.toml') | |
| with open('package.json', 'r') as f: | |
| import json | |
| pkg_json = json.load(f) | |
| cargo_version = cargo.get('package', {}).get('version') | |
| py_version = pyproj.get('project', {}).get('version') | |
| npm_version = pkg_json.get('version') | |
| print(f"Cargo.toml version: {cargo_version}") | |
| print(f"pyproject.toml version: {py_version}") | |
| print(f"package.json version: {npm_version}") | |
| ok = True | |
| # Always ensure all three match each other | |
| if cargo_version != py_version: | |
| print("❌ Cargo.toml and pyproject.toml versions differ", file=sys.stderr) | |
| ok = False | |
| if cargo_version != npm_version: | |
| print("❌ Cargo.toml and package.json versions differ", file=sys.stderr) | |
| ok = False | |
| # On tag builds, also enforce equality with the tag | |
| if tag: | |
| if cargo_version != tag: | |
| print("❌ Cargo.toml version does not match tag", file=sys.stderr) | |
| ok = False | |
| if py_version != tag: | |
| print("❌ pyproject.toml version does not match tag", file=sys.stderr) | |
| ok = False | |
| if npm_version != tag: | |
| print("❌ package.json version does not match tag", file=sys.stderr) | |
| ok = False | |
| if not ok: | |
| sys.exit(1) | |
| print("✅ Versions are consistent") | |
| PY | |
| preflight-release-guard: | |
| name: Preflight release guard | |
| needs: [version-check] | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| runs-on: ubuntu-latest | |
| environment: automation | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Ensure existing release (if any) is a draft | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RELEASE_TAG: ${{ github.ref_name }} | |
| run: | | |
| set -euo pipefail | |
| release_query_err_file="$(mktemp)" | |
| if release_is_draft="$(gh release view "${RELEASE_TAG}" -R "${GITHUB_REPOSITORY}" --json isDraft --jq '.isDraft' 2>"${release_query_err_file}")"; then | |
| : | |
| elif grep -Eq '404|not[[:space:]]+found' "${release_query_err_file}"; then | |
| echo "No release exists for ${RELEASE_TAG}; continuing." | |
| exit 0 | |
| else | |
| echo "Failed to query release ${RELEASE_TAG}." >&2 | |
| cat "${release_query_err_file}" >&2 | |
| exit 1 | |
| fi | |
| if [ "${release_is_draft}" = "true" ]; then | |
| echo "Draft release ${RELEASE_TAG} already exists; continuing for rerun safety." | |
| exit 0 | |
| fi | |
| echo "Published release ${RELEASE_TAG} already exists; refusing to continue." >&2 | |
| exit 1 | |
| linux: | |
| needs: [version-check] | |
| runs-on: ubuntu-22.04 | |
| strategy: | |
| matrix: | |
| target: [x86_64, aarch64, armv7, i686, ppc64le, s390x] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - name: Build manylinux wheels | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b | |
| with: | |
| target: ${{ matrix.target }} | |
| args: --release --out dist | |
| manylinux: auto | |
| sccache: ${{ github.event_name != 'release' }} | |
| - name: Upload wheels (manylinux) | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: wheels-manylinux-${{ matrix.target }} | |
| path: dist | |
| smoke-manylinux: | |
| name: Smoke Test (manylinux) | |
| needs: [linux] | |
| runs-on: ubuntu-22.04 | |
| strategy: | |
| matrix: | |
| python: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| pattern: wheels-manylinux-* | |
| merge-multiple: true | |
| path: dist | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b | |
| with: | |
| save-cache: false | |
| - name: Install wheel and run CLI | |
| run: | | |
| set -euxo pipefail | |
| uv python install ${{ matrix.python }} | |
| uv venv -p ${{ matrix.python }} .venv | |
| uv pip install -p .venv --no-index --find-links dist ryl | |
| uv run -p .venv ryl --version | |
| musllinux: | |
| needs: [version-check] | |
| runs-on: ubuntu-22.04 | |
| strategy: | |
| matrix: | |
| target: [x86_64, aarch64] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - name: Build musllinux wheels | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b | |
| with: | |
| target: ${{ matrix.target }} | |
| args: --release --out dist | |
| manylinux: musllinux_1_2 | |
| sccache: ${{ github.event_name != 'release' }} | |
| - name: Upload wheels (musllinux) | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: wheels-musllinux-${{ matrix.target }} | |
| path: dist | |
| smoke-musllinux: | |
| name: Smoke Test (musllinux) | |
| needs: [musllinux] | |
| runs-on: ubuntu-22.04 | |
| strategy: | |
| matrix: | |
| python_image: ["3.10-alpine", "3.11-alpine", "3.12-alpine", "3.13-alpine", "3.14-alpine"] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| pattern: wheels-musllinux-* | |
| merge-multiple: true | |
| path: dist | |
| - name: Install wheel and run CLI in Alpine | |
| run: | | |
| set -euxo pipefail | |
| docker run --rm -v "$PWD/dist":/dist python:${{ matrix.python_image }} \ | |
| sh -euxc "python --version; python -m pip install --no-index --find-links /dist ryl; ryl --version" | |
| windows: | |
| needs: [version-check] | |
| runs-on: windows-latest | |
| strategy: | |
| matrix: | |
| target: [x86_64, aarch64] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - name: Build Windows wheels | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b | |
| with: | |
| target: ${{ matrix.target }} | |
| args: --release --out dist | |
| sccache: ${{ github.event_name != 'release' }} | |
| - name: Upload wheels (Windows) | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: wheels-windows-${{ matrix.target }} | |
| path: dist | |
| smoke-windows: | |
| name: Smoke Test (Windows) | |
| needs: [windows] | |
| runs-on: windows-latest | |
| strategy: | |
| matrix: | |
| python: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| pattern: wheels-windows-* | |
| merge-multiple: true | |
| path: dist | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b | |
| with: | |
| save-cache: false | |
| - name: Install wheel and run CLI | |
| shell: bash | |
| run: | | |
| set -euxo pipefail | |
| uv python install ${{ matrix.python }} | |
| uv venv -p ${{ matrix.python }} .venv | |
| uv pip install -p .venv --no-index --find-links dist ryl | |
| uv run -p .venv ryl --version | |
| macos: | |
| needs: [version-check] | |
| strategy: | |
| matrix: | |
| include: | |
| - runner: macos-latest | |
| target: aarch64 | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - name: Build macOS wheels | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b | |
| with: | |
| target: ${{ matrix.target }} | |
| args: --release --out dist | |
| sccache: ${{ github.event_name != 'release' }} | |
| - name: Upload wheels (macOS) | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: wheels-macos-${{ matrix.target }} | |
| path: dist | |
| smoke-macos: | |
| name: Smoke Test (macOS) | |
| needs: [macos] | |
| strategy: | |
| matrix: | |
| runner: [macos-latest] | |
| python: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| pattern: wheels-macos-* | |
| merge-multiple: true | |
| path: dist | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b | |
| with: | |
| save-cache: false | |
| - name: Install wheel and run CLI | |
| run: | | |
| set -euxo pipefail | |
| uv python install ${{ matrix.python }} | |
| uv venv -p ${{ matrix.python }} .venv | |
| uv pip install -p .venv --no-index --find-links dist ryl | |
| uv run -p .venv ryl --version | |
| sdist: | |
| needs: [version-check] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - name: Build sdist | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b | |
| with: | |
| command: sdist | |
| args: --out dist | |
| - name: Upload sdist | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: wheels-sdist | |
| path: dist | |
| upload-release-binaries: | |
| name: Upload release binaries | |
| needs: | |
| - smoke-manylinux | |
| - smoke-musllinux | |
| - smoke-windows | |
| - smoke-macos | |
| - sdist | |
| - preflight-release-guard | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| - runner: ubuntu-22.04 | |
| target: aarch64-unknown-linux-gnu | |
| - runner: ubuntu-22.04 | |
| target: armv7-unknown-linux-gnueabihf | |
| - runner: ubuntu-22.04 | |
| target: i686-unknown-linux-gnu | |
| - runner: ubuntu-22.04 | |
| target: powerpc64le-unknown-linux-gnu | |
| - runner: ubuntu-22.04 | |
| target: s390x-unknown-linux-gnu | |
| - runner: ubuntu-22.04 | |
| target: x86_64-unknown-linux-musl | |
| - runner: ubuntu-22.04 | |
| target: aarch64-unknown-linux-musl | |
| - runner: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| - runner: windows-latest | |
| target: aarch64-pc-windows-msvc | |
| - runner: macos-latest | |
| target: aarch64-apple-darwin | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - name: Build binaries for ${{ matrix.target }} | |
| uses: taiki-e/upload-rust-binary-action@f0d45ae91ee7b8ee928de7a9d04d893a08bcbec6 | |
| with: | |
| bin: ryl | |
| target: ${{ matrix.target }} | |
| locked: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| dry-run: true | |
| - name: Collect release assets for ${{ matrix.target }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| mkdir -p release-assets | |
| assets=(ryl-${{ matrix.target }}*) | |
| for asset in "${assets[@]}"; do | |
| if [ -f "$asset" ]; then | |
| cp "$asset" release-assets/ | |
| fi | |
| done | |
| if [ -z "$(ls -A release-assets)" ]; then | |
| echo "No release assets found for target ${{ matrix.target }}" >&2 | |
| exit 1 | |
| fi | |
| - name: Upload release assets for ${{ matrix.target }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: release-binaries-${{ matrix.target }} | |
| path: release-assets/* | |
| draft-github-release: | |
| name: Draft GitHub release | |
| needs: | |
| - upload-release-binaries | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| pattern: release-binaries-* | |
| merge-multiple: true | |
| path: release-assets | |
| - name: Generate release asset attestation | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 | |
| with: | |
| subject-path: 'release-assets/*' | |
| - name: Ensure draft tag release exists | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RELEASE_TAG: ${{ github.ref_name }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then | |
| echo "Ref '${GITHUB_REF}' is not a release tag (refs/tags/v*)." >&2 | |
| exit 1 | |
| fi | |
| release_query_err_file="$(mktemp)" | |
| if release_is_draft="$(gh release view "${RELEASE_TAG}" -R "${GITHUB_REPOSITORY}" --json isDraft --jq '.isDraft' 2>"${release_query_err_file}")"; then | |
| : | |
| elif grep -Eq '404|not[[:space:]]+found' "${release_query_err_file}"; then | |
| gh release create "${RELEASE_TAG}" -R "${GITHUB_REPOSITORY}" --verify-tag --title "${RELEASE_TAG}" --draft --generate-notes | |
| exit 0 | |
| else | |
| echo "Failed to query release ${RELEASE_TAG}." >&2 | |
| cat "${release_query_err_file}" >&2 | |
| exit 1 | |
| fi | |
| if [ "${release_is_draft}" = "true" ]; then | |
| echo "Reusing existing draft release ${RELEASE_TAG}." | |
| exit 0 | |
| fi | |
| echo "Published release ${RELEASE_TAG} already exists; refusing to continue." >&2 | |
| exit 1 | |
| - name: Upload release assets | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RELEASE_TAG: ${{ github.ref_name }} | |
| run: gh release upload "$RELEASE_TAG" -R "${GITHUB_REPOSITORY}" release-assets/* --clobber | |
| publish-crates: | |
| name: Publish to crates.io | |
| runs-on: ubuntu-latest | |
| needs: | |
| - draft-github-release | |
| - smoke-manylinux | |
| - smoke-musllinux | |
| - smoke-windows | |
| - smoke-macos | |
| - sdist | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - name: Read crate metadata | |
| id: crate_metadata | |
| run: | | |
| set -euo pipefail | |
| crate_name="$(python - <<'PY' | |
| import tomllib | |
| with open("Cargo.toml", "rb") as f: | |
| data = tomllib.load(f) | |
| print(data["package"]["name"]) | |
| PY | |
| )" | |
| crate_version="$(python - <<'PY' | |
| import tomllib | |
| with open("Cargo.toml", "rb") as f: | |
| data = tomllib.load(f) | |
| print(data["package"]["version"]) | |
| PY | |
| )" | |
| echo "name=${crate_name}" >> "${GITHUB_OUTPUT}" | |
| echo "version=${crate_version}" >> "${GITHUB_OUTPUT}" | |
| - name: Check if crate version already exists | |
| id: crate_exists | |
| env: | |
| CRATE_NAME: ${{ steps.crate_metadata.outputs.name }} | |
| CRATE_VERSION: ${{ steps.crate_metadata.outputs.version }} | |
| HTTP_USER_AGENT: ryl-release-workflow/1.0 (+https://github.com/${{ github.repository }}) | |
| run: | | |
| set -euo pipefail | |
| url="https://crates.io/api/v1/crates/${CRATE_NAME}/${CRATE_VERSION}" | |
| http_status="$(curl -sS -A "${HTTP_USER_AGENT}" -o /dev/null -w "%{http_code}" "${url}")" | |
| if [ "${http_status}" = "200" ]; then | |
| echo "already_published=true" >> "${GITHUB_OUTPUT}" | |
| echo "Crate ${CRATE_NAME} ${CRATE_VERSION} already published; skipping cargo publish." | |
| exit 0 | |
| fi | |
| if [ "${http_status}" = "404" ]; then | |
| echo "already_published=false" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| echo "Unexpected crates.io response (${http_status}) for ${CRATE_NAME} ${CRATE_VERSION}." >&2 | |
| exit 1 | |
| - name: Authenticate with crates.io (Trusted Publishing) | |
| if: steps.crate_exists.outputs.already_published != 'true' | |
| id: crates_io_auth | |
| uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe | |
| - name: Publish to crates.io | |
| if: steps.crate_exists.outputs.already_published != 'true' | |
| run: cargo publish | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ steps.crates_io_auth.outputs.token }} | |
| publish-pypi: | |
| name: Publish to PyPI | |
| runs-on: ubuntu-latest | |
| needs: | |
| - draft-github-release | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| permissions: | |
| id-token: write | |
| contents: read | |
| attestations: write | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| pattern: wheels-* | |
| merge-multiple: true | |
| path: dist | |
| - name: Read package metadata | |
| id: package_metadata | |
| run: | | |
| set -euo pipefail | |
| package_name="$(python - <<'PY' | |
| import tomllib | |
| with open("pyproject.toml", "rb") as f: | |
| data = tomllib.load(f) | |
| print(data["project"]["name"]) | |
| PY | |
| )" | |
| package_version="$(python - <<'PY' | |
| import tomllib | |
| with open("pyproject.toml", "rb") as f: | |
| data = tomllib.load(f) | |
| print(data["project"]["version"]) | |
| PY | |
| )" | |
| echo "name=${package_name}" >> "${GITHUB_OUTPUT}" | |
| echo "version=${package_version}" >> "${GITHUB_OUTPUT}" | |
| - name: Check if PyPI version already exists | |
| id: pypi_exists | |
| env: | |
| PACKAGE_NAME: ${{ steps.package_metadata.outputs.name }} | |
| PACKAGE_VERSION: ${{ steps.package_metadata.outputs.version }} | |
| HTTP_USER_AGENT: ryl-release-workflow/1.0 (+https://github.com/${{ github.repository }}) | |
| run: | | |
| set -euo pipefail | |
| url="https://pypi.org/pypi/${PACKAGE_NAME}/${PACKAGE_VERSION}/json" | |
| http_status="$(curl -sS -A "${HTTP_USER_AGENT}" -o /dev/null -w "%{http_code}" "${url}")" | |
| if [ "${http_status}" = "200" ]; then | |
| echo "already_published=true" >> "${GITHUB_OUTPUT}" | |
| echo "PyPI package ${PACKAGE_NAME} ${PACKAGE_VERSION} already published; skipping upload." | |
| exit 0 | |
| fi | |
| if [ "${http_status}" = "404" ]; then | |
| echo "already_published=false" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| echo "Unexpected PyPI response (${http_status}) for ${PACKAGE_NAME} ${PACKAGE_VERSION}." >&2 | |
| exit 1 | |
| - name: Generate artifact attestation | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 | |
| with: | |
| subject-path: 'dist/*' | |
| - name: Publish to PyPI (Trusted Publishing) | |
| if: steps.pypi_exists.outputs.already_published != 'true' | |
| uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b | |
| with: | |
| print-hash: true | |
| skip-existing: true | |
| stage_npm_packages: | |
| name: Stage npm packages | |
| runs-on: ubuntu-latest | |
| needs: | |
| - draft-github-release | |
| - upload-release-binaries | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| outputs: | |
| platform_matrix: ${{ steps.platform_matrix.outputs.value }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| pattern: release-binaries-* | |
| merge-multiple: true | |
| path: release-assets | |
| - name: Build npm package directories | |
| run: python3 scripts/build_npm_packages.py --assets-dir release-assets --out-dir dist/npm | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: '24' | |
| - name: Pack npm packages | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist/npm-tarballs | |
| for package_dir in dist/npm/*; do | |
| [ -d "${package_dir}" ] || continue | |
| ( | |
| cd "${package_dir}" | |
| npm pack --pack-destination "${GITHUB_WORKSPACE}/dist/npm-tarballs" | |
| ) | |
| done | |
| - name: Compute npm platform matrix | |
| id: platform_matrix | |
| run: | | |
| set -euo pipefail | |
| matrix_json="$(python3 - <<'PY' | |
| import json | |
| with open("npm-platforms.json", "r", encoding="utf-8") as handle: | |
| platforms = json.load(handle)["platforms"] | |
| matrix = [ | |
| {"folder_name": platform["folderName"], "package_name": platform["packageName"]} | |
| for platform in platforms | |
| ] | |
| print(json.dumps(matrix, separators=(",", ":"))) | |
| PY | |
| )" | |
| echo "value=${matrix_json}" >> "${GITHUB_OUTPUT}" | |
| - name: Upload packed npm packages | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: npm-packages | |
| path: dist/npm-tarballs/*.tgz | |
| publish_npm_platforms: | |
| name: Publish npm platform package | |
| runs-on: ubuntu-latest | |
| needs: | |
| - stage_npm_packages | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.stage_npm_packages.outputs.platform_matrix) }} | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: '24' | |
| registry-url: 'https://registry.npmjs.org' | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: npm-packages | |
| path: dist/npm-tarballs | |
| - name: Verify Node and npm versions for Trusted Publishing | |
| run: | | |
| set -euo pipefail | |
| node --version | |
| npm --version | |
| python - <<'PY' | |
| import os | |
| import subprocess | |
| import sys | |
| def parse(version: str) -> tuple[int, int, int]: | |
| version = version.strip().removeprefix("v") | |
| parts = version.split(".") | |
| return tuple(int(part) for part in parts[:3]) | |
| node_version = subprocess.check_output(["node", "--version"], text=True).strip() | |
| npm_version = subprocess.check_output(["npm", "--version"], text=True).strip() | |
| min_node = (22, 14, 0) | |
| min_npm = (11, 5, 1) | |
| if parse(node_version) < min_node: | |
| print( | |
| f"Node {node_version} is too old for npm Trusted Publishing; " | |
| f"need >= {'.'.join(map(str, min_node))}.", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| if parse(npm_version) < min_npm: | |
| print( | |
| f"npm {npm_version} is too old for npm Trusted Publishing; " | |
| f"need >= {'.'.join(map(str, min_npm))}.", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| PY | |
| - name: Read package metadata | |
| id: package_metadata | |
| run: | | |
| set -euo pipefail | |
| tarball_path=$(find dist/npm-tarballs -maxdepth 1 -type f -name "*${{ matrix.folder_name }}-*.tgz" | head -n 1) | |
| [ -n "${tarball_path}" ] | |
| readarray -t package_metadata < <(python3 - "${tarball_path}" <<'PY' | |
| import json | |
| import sys | |
| import tarfile | |
| with tarfile.open(sys.argv[1], "r:gz") as archive: | |
| package_json = json.load(archive.extractfile("package/package.json")) | |
| print(package_json["name"]) | |
| print(package_json["version"]) | |
| PY | |
| ) | |
| package_name="${package_metadata[0]}" | |
| package_version="${package_metadata[1]}" | |
| echo "name=${package_name}" >> "${GITHUB_OUTPUT}" | |
| echo "version=${package_version}" >> "${GITHUB_OUTPUT}" | |
| echo "tarball_path=${tarball_path}" >> "${GITHUB_OUTPUT}" | |
| - name: Check if NPM version already exists | |
| id: npm_exists | |
| env: | |
| PACKAGE_NAME: ${{ steps.package_metadata.outputs.name }} | |
| PACKAGE_VERSION: ${{ steps.package_metadata.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version > /dev/null 2>&1; then | |
| echo "already_published=true" >> "${GITHUB_OUTPUT}" | |
| echo "NPM package ${PACKAGE_NAME} ${PACKAGE_VERSION} already published; skipping upload." | |
| else | |
| echo "already_published=false" >> "${GITHUB_OUTPUT}" | |
| fi | |
| - name: Publish npm platform package (Trusted Publishing) | |
| if: steps.npm_exists.outputs.already_published != 'true' | |
| run: npm publish --provenance --access public "${{ steps.package_metadata.outputs.tarball_path }}" | |
| publish_npm_meta: | |
| name: Publish npm meta-package | |
| runs-on: ubuntu-latest | |
| needs: | |
| - stage_npm_packages | |
| - publish_npm_platforms | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: '24' | |
| registry-url: 'https://registry.npmjs.org' | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c | |
| with: | |
| name: npm-packages | |
| path: dist/npm-tarballs | |
| - name: Verify Node and npm versions for Trusted Publishing | |
| run: | | |
| set -euo pipefail | |
| node --version | |
| npm --version | |
| python - <<'PY' | |
| import subprocess | |
| import sys | |
| def parse(version: str) -> tuple[int, int, int]: | |
| version = version.strip().removeprefix("v") | |
| parts = version.split(".") | |
| return tuple(int(part) for part in parts[:3]) | |
| node_version = subprocess.check_output(["node", "--version"], text=True).strip() | |
| npm_version = subprocess.check_output(["npm", "--version"], text=True).strip() | |
| min_node = (22, 14, 0) | |
| min_npm = (11, 5, 1) | |
| if parse(node_version) < min_node: | |
| print( | |
| f"Node {node_version} is too old for npm Trusted Publishing; " | |
| f"need >= {'.'.join(map(str, min_node))}.", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| if parse(npm_version) < min_npm: | |
| print( | |
| f"npm {npm_version} is too old for npm Trusted Publishing; " | |
| f"need >= {'.'.join(map(str, min_npm))}.", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| PY | |
| - name: Read package metadata | |
| id: package_metadata | |
| run: | | |
| set -euo pipefail | |
| tarball_path="dist/npm-tarballs/owenlamont-ryl-${GITHUB_REF_NAME#v}.tgz" | |
| [ -f "${tarball_path}" ] | |
| readarray -t package_metadata < <(python3 - "${tarball_path}" <<'PY' | |
| import json | |
| import sys | |
| import tarfile | |
| with tarfile.open(sys.argv[1], "r:gz") as archive: | |
| package_json = json.load(archive.extractfile("package/package.json")) | |
| print(package_json["name"]) | |
| print(package_json["version"]) | |
| PY | |
| ) | |
| package_name="${package_metadata[0]}" | |
| package_version="${package_metadata[1]}" | |
| echo "name=${package_name}" >> "${GITHUB_OUTPUT}" | |
| echo "version=${package_version}" >> "${GITHUB_OUTPUT}" | |
| echo "tarball_path=${tarball_path}" >> "${GITHUB_OUTPUT}" | |
| - name: Check if NPM version already exists | |
| id: npm_exists | |
| env: | |
| PACKAGE_NAME: ${{ steps.package_metadata.outputs.name }} | |
| PACKAGE_VERSION: ${{ steps.package_metadata.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version > /dev/null 2>&1; then | |
| echo "already_published=true" >> "${GITHUB_OUTPUT}" | |
| echo "NPM package ${PACKAGE_NAME} ${PACKAGE_VERSION} already published; skipping upload." | |
| else | |
| echo "already_published=false" >> "${GITHUB_OUTPUT}" | |
| fi | |
| - name: Publish npm meta-package (Trusted Publishing) | |
| if: steps.npm_exists.outputs.already_published != 'true' | |
| run: npm publish --provenance --access public "${{ steps.package_metadata.outputs.tarball_path }}" | |
| finalize-github-release: | |
| name: Finalize GitHub release | |
| needs: | |
| - publish-crates | |
| - publish-pypi | |
| - publish_npm_meta | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Publish GitHub release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RELEASE_TAG: ${{ github.ref_name }} | |
| run: gh release edit "$RELEASE_TAG" -R "${GITHUB_REPOSITORY}" --draft=false | |
| sync-schemastore: | |
| name: Sync SchemaStore | |
| needs: | |
| - finalize-github-release | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| uses: ./.github/workflows/sync-schemastore.yml | |
| with: | |
| source_ref: ${{ github.sha }} | |
| secrets: | |
| AUTH_APP_CLIENT_ID: ${{ secrets.AUTH_APP_CLIENT_ID }} | |
| AUTH_APP_PRIVATE_KEY: ${{ secrets.AUTH_APP_PRIVATE_KEY }} | |
| notify-ryl-pre-commit: | |
| name: Notify ryl-pre-commit | |
| runs-on: ubuntu-latest | |
| environment: automation | |
| needs: | |
| - finalize-github-release | |
| if: >- | |
| startsWith(github.ref, 'refs/tags/v') && | |
| (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish == true)) | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Generate token for cross-repository dispatch | |
| id: app-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 | |
| with: | |
| app-id: ${{ secrets.AUTH_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.AUTH_APP_PRIVATE_KEY }} | |
| owner: owenlamont | |
| repositories: ryl-pre-commit | |
| permission-contents: write | |
| - name: Dispatch pypi_release event to ryl-pre-commit | |
| env: | |
| APP_TOKEN: ${{ steps.app-token.outputs.token }} | |
| RELEASE_TAG: ${{ github.ref_name }} | |
| run: | | |
| set -euo pipefail | |
| version="${RELEASE_TAG#v}" | |
| curl --fail -X POST \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Authorization: Bearer ${APP_TOKEN}" \ | |
| https://api.github.com/repos/owenlamont/ryl-pre-commit/dispatches \ | |
| -d "{\"event_type\":\"pypi_release\",\"client_payload\":{\"version\":\"${version}\"}}" |