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() -> None:
 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: bool) -> None:
 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            "check",
 67            "--quiet",
 68        )
 69        check_short("Checking Plain migrations", "plain", "migrate", "--check")
 70        check_short(
 71            "Checking for Plain models missing migrations",
 72            "plain",
 73            "makemigrations",
 74            "--dry-run",
 75            "--check",
 76        )
 77    else:
 78        check_short(
 79            "Running Plain checks (without database)",
 80            "plain",
 81            "preflight",
 82            "check",
 83            "--quiet",
 84        )
 85        click.secho("--> Skipping migration checks", bold=True, fg="yellow")
 86
 87    print_event("Running plain build")
 88    result = subprocess.run(["plain", "build"])
 89    if result.returncode != 0:
 90        sys.exit(result.returncode)
 91
 92    if find_spec("plain.pytest"):
 93        print_event("Running tests")
 94        result = subprocess.run(["plain", "test"])
 95        if result.returncode != 0:
 96            sys.exit(result.returncode)
 97
 98
 99def plain_db_connected() -> bool:
100    result = subprocess.run(
101        [
102            "plain",
103            "models",
104            "show-migrations",
105        ],
106        stdout=subprocess.DEVNULL,
107        stderr=subprocess.DEVNULL,
108    )
109    return result.returncode == 0
110
111
112def check_short(message: str, *args: str) -> None:
113    print_event(message, newline=False)
114    result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
115    if result.returncode != 0:
116        click.secho("โœ˜", fg="red")
117        click.secho(result.stdout.decode("utf-8"))
118        sys.exit(1)
119    else:
120        click.secho("โœ”", fg="green")