diff --git a/common.just b/common.just new file mode 100644 index 0000000..e786f5c --- /dev/null +++ b/common.just @@ -0,0 +1,12 @@ +# Print help +[group('help')] +help: + @echo Variables: + @just --evaluate + @echo + @just --list --unsorted + +# Check for secrets in git. +[group('security')] +secrets: + gitleaks detect --source . --no-git diff --git a/containers.just b/containers.just new file mode 100644 index 0000000..d94696a --- /dev/null +++ b/containers.just @@ -0,0 +1,83 @@ +# Container runtime. Defaults to podman. +container_engine := env("CONTAINER_ENGINE", "podman") + +# Space-separated list of registries. +# +# Examples: +# REGISTRIES="ghcr.io/battlesnake" +# REGISTRIES="ghcr.io/battlesnake quay.io/battlesnake registry.example.com/battlesnake" +registries := env("REGISTRIES", "") + +# Image name +image := env("IMAGE", "") + +# Use the exact Git tag when HEAD is tagged. +# Otherwise use the current Git commit SHA. +version := ``` + git describe --tags --exact-match HEAD 2>/dev/null || \ + git rev-parse --short HEAD 2>/dev/null || \ + echo dev + ``` + +# Local image reference. +image_ref := image + ":" + version + +# --------------------------------------------------------------------------- +# Help +# --------------------------------------------------------------------------- + +# Print version that would be used for artifacts. +[group('help')] +version: + @echo "{{ version }}" + +# Print current configuration. +[group('help')] +config: + @echo "Version: {{ version }}" + @echo "Container engine: {{ container_engine }}" + @echo "Image: {{ image_ref }}" + @echo "Registries:" + @if [ -z "{{ registries }}" ]; then \ + echo " (none)"; \ + else \ + for registry in {{ registries }}; do \ + echo " $registry"; \ + done; \ + fi + +# Check for environment issues. +[group('help')] +doctor: + @command -v {{ container_engine }} >/dev/null || { \ + echo "{{ container_engine }} is not installed"; \ + exit 1; \ + } + @{{ container_engine }} --version + @echo "Container environment looks good." + +# --------------------------------------------------------------------------- +# Containers +# --------------------------------------------------------------------------- + +# Build one immutable local image identified by Git tag or commit SHA. +[group('container')] +build: + {{ container_engine }} build \ + --pull \ + --tag {{ image_ref }} \ + . + +# Push the immutable image to every configured registry. +[group('container')] +push: build + @test -n "{{ registries }}" || { \ + echo "Set REGISTRIES before pushing"; \ + exit 1; \ + } + @for registry in {{ registries }}; do \ + target="$registry/{{ image_ref }}"; \ + echo "Pushing $target"; \ + {{ container_engine }} tag {{ image_ref }} "$target"; \ + {{ container_engine }} push "$target"; \ + done diff --git a/python.just b/python.just new file mode 100644 index 0000000..aa27f40 --- /dev/null +++ b/python.just @@ -0,0 +1,136 @@ +# Target for code coverage. Coverage task will fail if below this. +coverage_goal := env("COVERAGE_GOAL", "90") + +# --------------------------------------------------------------------------- +# Help +# --------------------------------------------------------------------------- + +# Check for environment issues. +[group('help')] +doctor: + @command -v uv >/dev/null || { echo "uv is not installed"; exit 1; } + @uv run ruff --version + @uv run mypy --version + @uv run pytest --version + @echo "Python environment looks good." + +# --------------------------------------------------------------------------- +# Python +# --------------------------------------------------------------------------- + +# Build Python package. +[group('python')] +build: + uv build --clear + +# Publish Python package. +[group('python')] +publish: + uv publish + +# --------------------------------------------------------------------------- +# Dependencies +# --------------------------------------------------------------------------- + +# Install dependencies. +[arg('mode', help='Either DEV or PROD')] +[group('python')] +sync mode='DEV': + if [[ "{{ mode }}" == "DEV" ]]; then \ + uv sync --locked --all-extras --dev; \ + else \ + uv sync --frozen; \ + fi + +# Update dependencies. +[group('python')] +update: + uv lock --upgrade + uv sync + +# Update specific dependency. +[group('python')] +update-package package: + uv lock --upgrade-package {{ package }} + uv sync + +# --------------------------------------------------------------------------- +# Formatting / linting +# --------------------------------------------------------------------------- + +# Run Python formatter. +[group('python')] +format: + uv run ruff format + +# Check that Python code is formatted correctly. +[group('python')] +format-check: + uv run ruff format --check + +# Run Python linter. +[arg('mode', help='Set to CI to output in GitHub format.')] +[group('python')] +lint mode='DEV': + @if [[ "{{ mode }}" == "CI" ]]; then \ + uv run ruff check --output-format=github; \ + else \ + uv run ruff check; \ + fi + +# Run Python type checker. +[group('python')] +typecheck: + uv run mypy --check-untyped-defs src + +# --------------------------------------------------------------------------- +# Python Tests +# --------------------------------------------------------------------------- + +# Run all tests. +[group('python')] +test args="": + uv run pytest tests {{ args }} + +# Run tests and produce code coverage. +[group('python')] +coverage: + uv run pytest \ + tests \ + --cov=src \ + --cov-report=term-missing \ + --cov-report=xml \ + --cov-fail-under={{ coverage_goal }} + +# --------------------------------------------------------------------------- +# Security +# --------------------------------------------------------------------------- + +# Audit dependencies for vulnerabilities. +[group('security')] +audit: + uv audit --no-dev + +# --------------------------------------------------------------------------- +# Common workflows +# --------------------------------------------------------------------------- + +# Run pre-commit checks. +[group('workflow')] +check: format-check lint typecheck test + +# Remove build and/or run files. +[group('workflow')] +clean: + rm -rf \ + dist \ + .mypy_cache \ + .pytest_cache \ + .ruff_cache \ + .coverage \ + coverage.xml + find . \ + -type d \ + -name __pycache__ \ + -prune \ + -exec rm -rf {} +