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