Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 316 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
name: Release

on:
workflow_dispatch:
inputs:
package:
description: Language package to release
required: true
type: choice
options:
- js
- python
- go
version:
description: Stable SemVer version without a leading v, for example 0.3.1
required: true
type: string
dry_run:
description: Validate release without publishing or creating a tag
required: true
type: boolean
default: true

permissions:
contents: read

concurrency:
group: release-${{ inputs.package }}-${{ inputs.version }}
cancel-in-progress: false

jobs:
validate:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.meta.outputs.tag }}
title: ${{ steps.meta.outputs.title }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Validate release inputs
id: meta
env:
PACKAGE: ${{ inputs.package }}
VERSION: ${{ inputs.version }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail

if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Version must be a stable SemVer value like 0.3.1"
exit 1
fi

if [[ "$DRY_RUN" != "true" && "$GITHUB_REF" != "refs/heads/main" && "$GITHUB_REF" != "refs/heads/master" ]]; then
echo "Real releases must be run from main or master"
exit 1
fi

case "$PACKAGE" in
js) TAG="js/v$VERSION" ;;
python) TAG="python/v$VERSION" ;;
go) TAG="go/v$VERSION" ;;
*)
echo "Unknown package: $PACKAGE"
exit 1
;;
esac

if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
echo "Tag already exists: $TAG"
exit 1
fi

{
echo "tag=$TAG"
echo "title=$PACKAGE v$VERSION"
} >> "$GITHUB_OUTPUT"

js-check:
needs: validate
if: ${{ inputs.package == 'js' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: pnpm/action-setup@v4
with:
version: 8

- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Check JS package version
env:
VERSION: ${{ inputs.version }}
run: |
node -e "const pkg = require('./js/package.json'); if (pkg.version !== process.env.VERSION) { throw new Error(`js/package.json version ${pkg.version} does not match ${process.env.VERSION}`); }"

- run: pnpm run verify:fixtures
- run: pnpm -C js test
- run: pnpm -C js build
- run: npm pack --dry-run
working-directory: js

js-publish:
needs:
- validate
- js-check
if: ${{ inputs.package == 'js' && inputs.dry_run == false }}
runs-on: ubuntu-latest
environment: npm
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: pnpm/action-setup@v4
with:
version: 8

- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org

- run: pnpm install --frozen-lockfile
- run: pnpm -C js build
- run: npm publish --access public
working-directory: js

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.validate.outputs.tag }}
TITLE: ${{ needs.validate.outputs.title }}
run: |
gh release create "$TAG" --target "$GITHUB_SHA" --title "$TITLE" --notes "Published $TITLE to npm."

python-check:
needs: validate
if: ${{ inputs.package == 'python' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: pnpm/action-setup@v4
with:
version: 8

- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- run: pnpm install --frozen-lockfile
- run: pnpm run verify:fixtures

- name: Check Python package version
env:
VERSION: ${{ inputs.version }}
run: |
python - <<'PY'
import os
import pathlib
import tomllib

pyproject = tomllib.loads(pathlib.Path("python/pyproject.toml").read_text())
actual = pyproject["project"]["version"]
expected = os.environ["VERSION"]
if actual != expected:
raise SystemExit(f"python/pyproject.toml version {actual} does not match {expected}")
PY

- run: python -m pip install -e 'python[dev]'
- run: python -m pytest python/tests
- run: python -m ruff check python/src python/tests
- run: python -m build python
- run: python -m twine check python/dist/*

- uses: actions/upload-artifact@v4
with:
name: python-dist
path: python/dist/*
if-no-files-found: error

python-publish:
needs:
- validate
- python-check
if: ${{ inputs.package == 'python' && inputs.dry_run == false }}
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/download-artifact@v4
with:
name: python-dist
path: python/dist

- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: python/dist

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.validate.outputs.tag }}
TITLE: ${{ needs.validate.outputs.title }}
run: |
gh release create "$TAG" --target "$GITHUB_SHA" --title "$TITLE" --notes "Published $TITLE to PyPI."

go-check:
needs: validate
if: ${{ inputs.package == 'go' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: pnpm/action-setup@v4
with:
version: 8

- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm

- uses: actions/setup-go@v5
with:
go-version: "1.22"
cache-dependency-path: go/go.mod

- run: pnpm install --frozen-lockfile
- run: pnpm run verify:fixtures

- run: go test ./...
working-directory: go
- run: go vet ./...
working-directory: go
- run: go mod tidy
working-directory: go
- run: git diff --exit-code -- go/go.mod go/go.sum
- run: test -z "$(git status --porcelain -- go/go.mod go/go.sum)"

go-release:
needs:
- validate
- go-check
if: ${{ inputs.package == 'go' && inputs.dry_run == false }}
runs-on: ubuntu-latest
environment: go-release
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-go@v5
with:
go-version: "1.22"

- name: Push Go module tag
env:
TAG: ${{ needs.validate.outputs.tag }}
run: |
git tag "$TAG" "$GITHUB_SHA"
git push origin "$TAG"

- name: Request Go module indexing
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
for attempt in {1..6}; do
if GOPROXY=proxy.golang.org go list -m "github.com/algoux/standard-ranklist-utils/go@v$VERSION"; then
exit 0
fi
echo "Go proxy has not indexed the module yet; retry $attempt/6"
sleep 10
done
GOPROXY=proxy.golang.org go list -m "github.com/algoux/standard-ranklist-utils/go@v$VERSION"

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.validate.outputs.tag }}
TITLE: ${{ needs.validate.outputs.title }}
run: |
gh release create "$TAG" --title "$TITLE" --notes "Published $TITLE as a Go module tag."
60 changes: 60 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Test

on:
push:
pull_request:

jobs:
js:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run sync:fixtures
- run: git diff --exit-code -- testdata/contract-fixtures.json python/tests/fixtures/contract-fixtures.json go/testdata/fixtures/contract-fixtures.json
- run: pnpm -C js test
- run: pnpm -C js build
- run: npm pack --dry-run
working-directory: js

python:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: python -m pip install -e 'python[dev]'
- run: python -m pytest python/tests
- run: python -m ruff check python/src python/tests
- run: python -m build python
if: matrix.python-version == '3.12'
- run: python -m twine check python/dist/*
if: matrix.python-version == '3.12'

go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
cache-dependency-path: go/go.mod
- run: go test ./...
working-directory: go
- run: go vet ./...
working-directory: go
- run: go mod tidy
working-directory: go
- run: git diff --exit-code -- go/go.mod go/go.sum
- run: test -z "$(git status --porcelain -- go/go.mod go/go.sum)"
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
.DS_Store
.idea
node_modules
/dist
dist
.pnpm-debug.log
npm-debug.log*
.pytest_cache
.ruff_cache
.mypy_cache
__pycache__
*.egg-info
python/dist
python/build
python/.venv
go/testdata/*.tmp
go/.gocache
Loading
Loading