1from __future__ import annotations
2
3from pathlib import Path
4
5import click
6
7from plain.assets.finders import APP_ASSETS_DIR
8from plain.cli import register_cli
9
10from .deps import Dependency, get_deps
11from .exceptions import DependencyError
12
13VENDOR_DIR = APP_ASSETS_DIR / "vendor"
14
15
16@register_cli("vendor")
17@click.group()
18def cli() -> None:
19 """Manage vendored CSS/JS dependencies"""
20
21
22@cli.command()
23def sync() -> None:
24 """Clear vendored assets and re-download"""
25 click.secho("Clearing existing vendored dependencies...", bold=True)
26 if VENDOR_DIR.exists():
27 for path in VENDOR_DIR.iterdir():
28 path.unlink()
29
30 deps = get_deps()
31 if not deps:
32 click.echo(
33 "No vendored dependencies found in pyproject.toml. Use [tool.plain.vendor.dependencies]"
34 )
35 return
36
37 errors = []
38
39 for dep in deps:
40 click.secho(f"Installing {dep.name}...", bold=True, nl=False)
41 try:
42 vendored_path = dep.install()
43 except DependencyError as e:
44 click.secho(f" {e}", fg="red")
45 errors.append(e)
46
47 vendored_path = vendored_path.relative_to(Path.cwd())
48
49 click.secho(f" {dep.installed}", fg="green", nl=False)
50 click.secho(f" -> {vendored_path}")
51
52 if errors:
53 click.secho("Failed to install some dependencies.", fg="red")
54 exit(1)
55
56
57@cli.command()
58@click.argument("name", nargs=-1, default=None)
59def update(name: tuple[str, ...]) -> None:
60 """Update vendored dependencies in pyproject.toml"""
61 deps = get_deps()
62 if not deps:
63 click.echo(
64 "No vendored dependencies found in pyproject.toml. Use [tool.plain.vendor.dependencies]"
65 )
66 return
67
68 errors = []
69
70 if name:
71 deps = [dep for dep in deps if dep.name in name]
72 if len(deps) != len(name):
73 not_found = set(name) - {dep.name for dep in deps}
74 raise click.UsageError(
75 f"Some dependencies not found: {', '.join(not_found)}"
76 )
77
78 for dep in deps:
79 click.secho(f"Updating {dep.name} {dep.installed}...", bold=True, nl=False)
80 try:
81 vendored_path = dep.update()
82 vendored_path = vendored_path.relative_to(Path.cwd())
83
84 click.secho(f" {dep.installed}", fg="green", nl=False)
85 click.secho(f" -> {vendored_path}")
86 except DependencyError as e:
87 click.secho(f" {e}", fg="red")
88 errors.append(e)
89
90 if errors:
91 click.secho("Failed to install some dependencies.", fg="red")
92 exit(1)
93
94
95@cli.command()
96@click.argument("url")
97@click.option("--name", help="Name of the dependency")
98@click.option("--sourcemap/--no-sourcemap", default=True, help="Download sourcemap")
99def add(url: str, name: str | None, sourcemap: bool) -> None:
100 """Add a new vendored dependency to pyproject.toml"""
101 if not name:
102 name = url.split("/")[-1].split("?")[0].split("#")[0]
103
104 dep = Dependency(name, url=url, sourcemap=sourcemap)
105
106 click.secho(f"Installing {dep.name}", bold=True, nl=False)
107
108 try:
109 vendored_path = dep.update()
110 except DependencyError as e:
111 click.secho(f" {e}", fg="red")
112 exit(1)
113
114 vendored_path = vendored_path.relative_to(Path.cwd())
115
116 click.secho(f" {dep.installed}", fg="green", nl=False)
117 click.secho(f" -> {vendored_path}")
118
119 if not dep.installed:
120 click.secho(
121 "No version was parsed from the url. You can configure it manually if you need to.",
122 fg="yellow",
123 )