Plain is headed towards 1.0! Subscribe for development updates โ†’

  1import os
  2import subprocess
  3import sys
  4import tomllib
  5from importlib.util import find_spec
  6from pathlib import Path
  7
  8import click
  9
 10from plain.cli import register_cli
 11from plain.cli.print import print_event
 12
 13
 14def install_git_hook():
 15    hook_path = os.path.join(".git", "hooks", "pre-commit")
 16    if os.path.exists(hook_path):
 17        print("pre-commit hook already exists")
 18    else:
 19        with open(hook_path, "w") as f:
 20            f.write(
 21                """#!/bin/sh
 22plain pre-commit"""
 23            )
 24        os.chmod(hook_path, 0o755)
 25        print("pre-commit hook installed")
 26
 27
 28@register_cli("pre-commit")
 29@click.command()
 30@click.option("--install", is_flag=True)
 31def cli(install):
 32    """Git pre-commit checks"""
 33    if install:
 34        install_git_hook()
 35        return
 36
 37    pyproject = Path("pyproject.toml")
 38
 39    if pyproject.exists():
 40        with open(pyproject, "rb") as f:
 41            pyproject = tomllib.load(f)
 42        for name, data in (
 43            pyproject.get("tool", {})
 44            .get("plain", {})
 45            .get("pre-commit", {})
 46            .get("run", {})
 47        ).items():
 48            cmd = data["cmd"]
 49            print_event(f"Custom: {name} -> {cmd}")
 50            result = subprocess.run(cmd, shell=True)
 51            if result.returncode != 0:
 52                sys.exit(result.returncode)
 53
 54    # Run this first since it's probably the most likely to fail
 55    if find_spec("plain.code"):
 56        check_short("Running plain code checks", "plain", "code", "check")
 57
 58    if Path("uv.lock").exists():
 59        check_short("Checking uv.lock", "uv", "lock", "--check")
 60
 61    if plain_db_connected():
 62        check_short(
 63            "Running preflight checks",
 64            "plain",
 65            "preflight",
 66            "--database",
 67        )
 68        check_short("Checking Plain migrations", "plain", "migrate", "--check")
 69        check_short(
 70            "Checking for Plain models missing migrations",
 71            "plain",
 72            "makemigrations",
 73            "--dry-run",
 74            "--check",
 75        )
 76    else:
 77        check_short("Running Plain checks (without database)", "plain", "preflight")
 78        click.secho("--> Skipping migration checks", bold=True, fg="yellow")
 79
 80    print_event("Running plain build")
 81    result = subprocess.run(["plain", "build"])
 82    if result.returncode != 0:
 83        sys.exit(result.returncode)
 84
 85    if find_spec("plain.pytest"):
 86        print_event("Running tests")
 87        result = subprocess.run(["plain", "test"])
 88        if result.returncode != 0:
 89            sys.exit(result.returncode)
 90
 91
 92def plain_db_connected():
 93    result = subprocess.run(
 94        [
 95            "plain",
 96            "models",
 97            "show-migrations",
 98        ],
 99        stdout=subprocess.DEVNULL,
100        stderr=subprocess.DEVNULL,
101    )
102    return result.returncode == 0
103
104
105def check_short(message, *args):
106    print_event(message, newline=False)
107    result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
108    if result.returncode != 0:
109        click.secho("โœ˜", fg="red")
110        click.secho(result.stdout.decode("utf-8"))
111        sys.exit(1)
112    else:
113        click.secho("โœ”", fg="green")