Skip to content

Repository files navigation

maintainer_tools

Reusable GitHub Actions for Calysto packages. All actions are available via the v1 floating tag:

uses: calysto/maintainer_tools/actions/<name>@v1

The v1 tag always points to the latest stable commit and is updated automatically on each stable release.


Actions

build

Builds and inspects the Python package using hynek/build-and-inspect-python-package and exports supported Python versions derived from the package classifiers. Includes free-threaded builds by default.

Inputs

Name Required Default Description
include-free-threaded No "true" Whether to include free-threaded Python builds.

Outputs

Name Description
python-versions JSON array of supported Python versions derived from classifiers (e.g. ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]).

Usage

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      python-versions: ${{ steps.build.outputs.python-versions }}
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/build@v1
        id: build

base-setup

Installs Python, Poetry (with OS-keyed cache), just, and project dependencies. This action should be the first step in any job that needs to build or test the package.

Inputs

Name Required Default Description
python-version No "" Python version to use. Defaults to the minimum version from pyproject.toml.
poetry-version No "pre-commit" Poetry version to install with pipx. The default resolves the version pinned by the poetry hook in .pre-commit-config.yaml (falling back to the latest Poetry when no such hook exists), keeping the lock update in sync with the poetry-check hook. Pass an explicit version to pin it, or an empty string to skip installing Poetry.

Usage

- uses: actions/checkout@v6
- uses: calysto/maintainer_tools/actions/base-setup@v1
  with:
    python-version: "3.12"

A full test matrix workflow using the build action to derive the supported Python versions:

jobs:
  build:
    name: Build & inspect package
    runs-on: ubuntu-latest
    outputs:
      python-versions: ${{ steps.build.outputs.python-versions }}
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/build@v1
        id: build

  test:
    name: Test (Python ${{ matrix.python-version }})
    needs: [build]
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ${{ fromJSON(needs.build.outputs.python-versions) }}
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/base-setup@v1
        with:
          python-version: ${{ matrix.python-version }}
      - run: just test

pre-commit-autoupdate

Installs prek, runs prek auto-update with a configurable cooldown, and opens a pull request with the changes. Optionally generates a GitHub App token for authenticated pushes.

The poetry hook is always excluded — in whatever URL form the config uses (https, ssh, with or without .git). Its version is coupled to poetry.lock (via base-setup), so it is bumped by poetry-lock-update instead — that keeps the toolchain and the lock in sync and avoids autoupdate PRs that fail the poetry-lock hook. Pass exclude-repos to leave any additional repositories untouched.

Inputs

Name Required Default Description
app-id No "" GitHub App ID for authenticated pushes. Falls back to github.token if not provided.
app-private-key No "" GitHub App private key for authenticated pushes.
cooldown-days No "7" Minimum release age in days before updating to a new version.
exclude-repos No "" Space-separated list of additional hook repositories to leave untouched (passed through as --exclude-repo). The configured poetry hook is always excluded, in whatever URL form it uses.
branch No "pre-commit-autoupdate" Branch name for the autoupdate pull request.
labels No "maintenance" Labels to apply to the pull request.
dry-run No "false" If "true", passes --dry-run to gh pr create (no PR is actually opened).

Usage

- uses: actions/checkout@v6
  with:
    persist-credentials: false
- uses: calysto/maintainer_tools/actions/pre-commit-autoupdate@v1
  with:
    app-id: ${{ vars.APP_ID }}
    app-private-key: ${{ secrets.APP_PRIVATE_KEY }}

Typically used in a scheduled workflow:

on:
  schedule:
    - cron: '0 9 * * 1'  # Every Monday at 9am

permissions:
  pull-requests: write

jobs:
  autoupdate:
    runs-on: ubuntu-latest
    environment: release
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/pre-commit-autoupdate@v1
        with:
          app-id: ${{ vars.APP_ID }}
          app-private-key: ${{ secrets.APP_PRIVATE_KEY }}

poetry-lock-update

Runs poetry update under a minimum-release-age cooldown and opens a pull request with the poetry.lock changes — or, if a lock-update PR is already open, force-pushes the refreshed lock file to it instead of opening a duplicate. Requires base-setup to run before this action. Optionally generates a GitHub App token for authenticated pushes.

This action also owns the Poetry version: it bumps the poetry hook in .pre-commit-config.yaml to the newest release that satisfies the cooldown (via prek update --repo), reinstalls that Poetry, and regenerates the lock with it. The version bump and the lock change therefore land together in one PR, because Poetry stamps its own version into poetry.lock and base-setup derives CI's Poetry from the same hook. pre-commit-autoupdate leaves the poetry hook alone so the two never diverge.

If the cooldown blocks every version satisfying a dependency constraint, the resolve retries with the cooldown waived for just the blocking packages, up to 5 attempts. A genuine dependency conflict still fails the run. Waivers are listed in a ## Cooldown waived section of the pull request body; those packages are locked at versions that have not met the cooldown, so review them accordingly.

The pull request body lists the Poetry version bump (if any) under a ## Poetry version heading.

Inputs

Name Required Default Description
app-id No "" GitHub App ID for authenticated pushes. Falls back to github.token if not provided.
app-private-key No "" GitHub App private key for authenticated pushes.
min-release-age-days No "7" Minimum release age in days before Poetry's resolver will consider a version.
branch No "poetry-lock-update" Branch name for the update pull request. Reused across runs — an existing open PR on this branch is updated in place rather than duplicated. This branch is exclusively managed by this action and force-pushed on every run, so manual commits pushed to it will not survive.
labels No "maintenance" Labels to apply to the pull request.
dry-run No "false" If "true", no push or PR mutation happens: gh pr create runs with --dry-run when no PR is open, or a "would update" message prints when one is.

Usage

- uses: actions/checkout@v6
  with:
    persist-credentials: false
- uses: calysto/maintainer_tools/actions/base-setup@v1
- uses: calysto/maintainer_tools/actions/poetry-lock-update@v1
  with:
    app-id: ${{ vars.APP_ID }}
    app-private-key: ${{ secrets.APP_PRIVATE_KEY }}

Typically used in a scheduled workflow:

on:
  schedule:
    - cron: '0 6 * * 1'  # Every Monday at 6am

permissions:
  pull-requests: write

jobs:
  lock-update:
    runs-on: ubuntu-latest
    environment: release
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/base-setup@v1
      - uses: calysto/maintainer_tools/actions/poetry-lock-update@v1
        with:
          app-id: ${{ vars.APP_ID }}
          app-private-key: ${{ secrets.APP_PRIVATE_KEY }}

enforce-label

Enforces that every PR has at least one of the required labels: bug, enhancement, dependencies, maintenance, documentation.

Inputs

None.

Usage

- uses: actions/checkout@v6
- uses: calysto/maintainer_tools/actions/enforce-label@v1

Typically used in a workflow triggered on pull_request events:

on:
  pull_request:
    types: [labeled, unlabeled, opened, edited, synchronize]

jobs:
  enforce-label:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: calysto/maintainer_tools/actions/enforce-label@v1

pre-commit-run

Installs prek and runs pre-commit hooks with environment caching.

Inputs

Name Required Default Description
extra-args No "--all-files --hook-stage manual" Extra arguments passed to prek run.

Usage

- uses: actions/checkout@v6
  with:
    persist-credentials: false
- uses: calysto/maintainer_tools/actions/pre-commit-run@v1

Typically used in a workflow triggered on push and pull_request events:

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/pre-commit-run@v1

Pass extra-args to run only on changed files (e.g. in a PR context):

- uses: calysto/maintainer_tools/actions/pre-commit-run@v1
  with:
    extra-args: "--from-ref ${{ github.event.pull_request.base.sha }} --to-ref HEAD"

release

Bumps the package version, updates CHANGELOG.md, commits the changes, creates a GitHub release, then bumps to the next .dev version. Supports dry-run mode for testing. Requires base-setup to run before this action.

Inputs

Name Required Default Description
version Yes — Version to release: a version number (e.g. 1.0.0rc4) or one of: patch, minor, major, prepatch, preminor, premajor, prerelease.
dry_run No "false" If "true", creates a draft release then deletes it and does not push changes.
app_id No "" GitHub App ID for authenticated pushes (not required for dry runs).
app_private_key No "" GitHub App private key (not required for dry runs).
changelog_body No "" Custom release notes to use instead of the auto-generated changelog. See Providing custom release notes.
ref Yes — Branch to push commits back to (ignored when dry_run is "true").

Outputs

Name Description
tag The release tag created (e.g. v0.3.5), or the commit SHA on a dry run.

Usage

- uses: actions/checkout@v6
- uses: calysto/maintainer_tools/actions/base-setup@v1
- uses: calysto/maintainer_tools/actions/release@v1
  with:
    version: ${{ inputs.version }}
    dry_run: "false"
    app_id: ${{ vars.APP_ID }}
    app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
    ref: ${{ github.ref_name }}

A full release workflow with build and PyPI publish steps:

jobs:
  release:
    runs-on: ubuntu-latest
    environment: release
    permissions:
      contents: write
    outputs:
      tag: ${{ steps.release.outputs.tag }}
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/base-setup@v1
      - uses: calysto/maintainer_tools/actions/release@v1
        id: release
        with:
          version: ${{ inputs.version }}
          dry_run: ${{ inputs.dry_run }}
          app_id: ${{ vars.APP_ID }}
          app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
          ref: ${{ github.ref_name }}

  build:
    name: Build & verify package
    needs: [release]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.release.outputs.tag }}
          fetch-depth: 0
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/build@v1

  publish:
    needs: [build]
    runs-on: ubuntu-latest
    environment: release
    permissions:
      id-token: write
      attestations: write
    steps:
      - name: Download packages built by build-and-inspect-python-package
        uses: actions/download-artifact@v4
        with:
          name: Packages
          path: dist
      - name: Upload package to Test PyPI
        uses: pypa/gh-action-pypi-publish@v1
        with:
          repository-url: https://test.pypi.org/legacy/
          skip-existing: ${{ inputs.dry_run }}
      - name: Upload package to PyPI
        if: ${{ !inputs.dry_run }}
        uses: pypa/gh-action-pypi-publish@v1

Providing custom release notes

By default the action auto-generates the changelog from PR titles and labels. Pass changelog_body to supply your own release notes instead.

The GitHub Actions web UI only provides a single-line text field, so pasting multi-line markdown directly will have its newlines stripped by the browser. There are two ways to work around this:

  • Recommended — use the CLI. gh workflow run accepts real newlines:

    gh workflow run release.yml \
      -f version=patch \
      -f changelog_body="## Highlights
    
    MetaKernel 1.0 is a major release.
    
    ## New Features
    
    - DisplayData() for raw MIME bundle display (#211)"
  • Use \n escape sequences in the web UI. The action converts literal \n strings to real newlines, so you can type or paste a single-line string:

    ## Highlights\n\nMetaKernel 1.0 is a major release.\n\n## New Features\n\n- DisplayData() for raw MIME bundle display (#211)
    

test-minimum-versions

Pins all dependencies to their minimum allowed versions (as declared in pyproject.toml) and runs the test suite. Requires base-setup to run before this action.

Inputs

Name Required Default Description
command No "just test" Command to run the test suite.

Usage

- uses: actions/checkout@v6
- uses: calysto/maintainer_tools/actions/base-setup@v1
- uses: calysto/maintainer_tools/actions/test-minimum-versions@v1
  with:
    command: "just test"

test-sdist

Downloads the Packages artifact produced by hynek/build-and-inspect-python-package, unpacks the sdist, and runs the test suite from within it. Requires base-setup to run before this action.

Inputs

Name Required Default Description
command No "just test" Command to run the test suite from within the unpacked sdist directory.

Usage

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/build@v1

  test-sdist:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: calysto/maintainer_tools/actions/base-setup@v1
      - uses: calysto/maintainer_tools/actions/test-sdist@v1
        with:
          command: "just test"

codeql

Initializes CodeQL and performs security analysis for a given language. Wraps github/codeql-action/init and github/codeql-action/analyze.

Inputs

Name Required Default Description
language Yes — Language to analyze (e.g. python, actions, javascript-typescript).
build-mode No "none" Build mode: none, autobuild, or manual.

Usage

- uses: actions/checkout@v6
  with:
    persist-credentials: false
- uses: calysto/maintainer_tools/actions/codeql@v1
  with:
    language: python
    build-mode: none

Typically used in a scheduled CodeQL workflow with a language matrix:

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]
  schedule:
    - cron: '44 18 * * 3'

jobs:
  analyze:
    name: Analyze (${{ matrix.language }})
    if: github.event_name != 'schedule' || github.repository == 'your-org/your-repo'
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      packages: read
      actions: read
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - language: actions
            build-mode: none
          - language: python
            build-mode: none
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/codeql@v1
        with:
          language: ${{ matrix.language }}
          build-mode: ${{ matrix.build-mode }}

zizmor

Runs zizmor GitHub Actions security analysis. If the calling repository does not have a .github/zizmor.yml config file, a bundled default config is used that pins actions/* and calysto/maintainer_tools/* references to a version tag.

Inputs

None.

Usage

- uses: actions/checkout@v6
  with:
    persist-credentials: false
- uses: calysto/maintainer_tools/actions/zizmor@v1

Typically used in a workflow triggered on push and pull_request events:

jobs:
  zizmor:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v6
        with:
          persist-credentials: false
      - uses: calysto/maintainer_tools/actions/zizmor@v1

To customize the rules, add a .github/zizmor.yml to your repository — it will be used instead of the bundled default.


Tag Management

The v1 floating tag is updated automatically as part of the release workflow. After a stable release (any version without pre-release markers like a, b, rc, or dev), the update-v1-tag job will:

  1. Delete the existing v1 tag locally and remotely
  2. Re-create v1 at the release commit
  3. Push the updated tag

Pre-release versions (e.g. 1.0.0a1, 1.0.0rc2) will not update the v1 tag.

About

GitHub Actions and other tools used by Calysto packages

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages