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