Release #3
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 | |
| 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." |