Plain is headed towards 1.0! Subscribe for development updates →

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