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")