1import subprocess
2import sys
3from pathlib import Path
4
5import click
6
7from plain.cli import register_cli
8from plain.cli.runtime import without_runtime_setup
9
10
11@without_runtime_setup
12@register_cli("contrib")
13@click.command("contribute", hidden=True)
14@click.option("--repo", default="../plain", help="Path to the plain repo")
15@click.option(
16 "--reset", is_flag=True, help="Undo any changes to pyproject.toml and uv.lock"
17)
18@click.option(
19 "--all", "all_packages", is_flag=True, help="Link all installed plain packages"
20)
21@click.argument("packages", nargs=-1)
22def cli(packages: tuple[str, ...], repo: str, reset: bool, all_packages: bool) -> None:
23 """Contribute to plain by linking packages locally."""
24
25 if reset:
26 click.secho("Undoing any changes to pyproject.toml and uv.lock", bold=True)
27 result = subprocess.run(["git", "checkout", "pyproject.toml", "uv.lock"])
28 if result.returncode:
29 click.secho("Failed to checkout pyproject.toml and uv.lock", fg="red")
30 sys.exit(result.returncode)
31
32 click.secho("Running uv sync", bold=True)
33 result = subprocess.run(["uv", "sync", "--reinstall"])
34 if result.returncode:
35 click.secho("Failed to sync", fg="red")
36 sys.exit(result.returncode)
37
38 return
39
40 packages_list = list(packages)
41
42 repo_path = Path(repo)
43 if not repo_path.exists():
44 click.secho(f"Repo not found at {repo_path}", fg="red")
45 return
46
47 repo_branch = (
48 subprocess.check_output(
49 [
50 "git",
51 "rev-parse",
52 "--abbrev-ref",
53 "HEAD",
54 ],
55 cwd=repo_path,
56 )
57 .decode()
58 .strip()
59 )
60 click.secho(f"Using repo at {repo_path} ({repo_branch} branch)", bold=True)
61
62 plain_packages = []
63 plainx_packages = []
64 skipped_plainx_packages = []
65
66 if all_packages:
67 # get all installed plain packages
68 output = subprocess.check_output(["uv", "pip", "freeze"])
69
70 installed_packages = output.decode()
71 if not installed_packages:
72 click.secho("No installed packages found", fg="red")
73 sys.exit(1)
74
75 packages_list = []
76 for line in installed_packages.splitlines():
77 if not line.startswith("plain"):
78 continue
79 package = line.split("==")[0]
80 if package.startswith("plainx-"):
81 skipped_plainx_packages.append(package)
82 else:
83 packages_list.append(package)
84
85 if skipped_plainx_packages:
86 click.secho(
87 "Skipping plainx packages: "
88 + ", ".join(sorted(skipped_plainx_packages))
89 + " (unknown repo)",
90 fg="yellow",
91 )
92
93 for package in packages_list:
94 package = package.replace(".", "-")
95 click.secho(f"Linking {package} to {repo_path}", bold=True)
96 if package == "plain" or package.startswith("plain-"):
97 plain_packages.append(str(repo_path / package))
98 elif package.startswith("plainx-"):
99 plainx_packages.append(str(repo_path))
100 else:
101 raise click.UsageError(f"Unknown package {package}")
102
103 if plain_packages:
104 result = subprocess.run(["uv", "add", "--editable", "--dev"] + plain_packages)
105 if result.returncode:
106 click.secho("Failed to link plain packages", fg="red")
107 sys.exit(result.returncode)
108
109 if plainx_packages:
110 result = subprocess.run(["uv", "add", "--editable", "--dev"] + plainx_packages)
111 if result.returncode:
112 click.secho("Failed to link plainx packages", fg="red")
113 sys.exit(result.returncode)