Skip to content

Bump version to 0.14.0 (#280) #41

Bump version to 0.14.0 (#280)

Bump version to 0.14.0 (#280) #41

Workflow file for this run

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}\"}}"