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