104 lines
3.2 KiB
Python
104 lines
3.2 KiB
Python
"""Declarative global npm command packages for Decman profiles."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
|
|
import decman
|
|
import decman.core.error as errors
|
|
import decman.core.module as module
|
|
import decman.core.output as output
|
|
import decman.core.store as store_module
|
|
from decman import Plugin
|
|
from decman.plugins import run_methods_with_attribute
|
|
|
|
|
|
def packages(fn):
|
|
"""Annotate a method that returns globally installed npm package names."""
|
|
fn.__npm_packages__ = True
|
|
return fn
|
|
|
|
|
|
class Npm(Plugin):
|
|
"""Install declared global npm packages without pruning unrelated packages."""
|
|
|
|
NAME = "npm"
|
|
|
|
def __init__(self) -> None:
|
|
self.packages: set[str] = set()
|
|
|
|
def process_modules(
|
|
self, store: store_module.Store, modules: list[module.Module]
|
|
) -> None:
|
|
store.ensure("npm_packages_for_module", {})
|
|
|
|
for mod in modules:
|
|
store["npm_packages_for_module"].setdefault(mod.name, set())
|
|
declared = set().union(
|
|
*run_methods_with_attribute(mod, "__npm_packages__")
|
|
)
|
|
|
|
if store["npm_packages_for_module"][mod.name] != declared:
|
|
mod._changed = True
|
|
output.print_debug(
|
|
f"Module '{mod.name}' changed due to modified global npm packages."
|
|
)
|
|
|
|
self.packages |= declared
|
|
store["npm_packages_for_module"][mod.name] = declared
|
|
|
|
def apply(
|
|
self,
|
|
store: store_module.Store,
|
|
dry_run: bool = False,
|
|
params: list[str] | None = None,
|
|
) -> bool:
|
|
if not self.packages:
|
|
return True
|
|
|
|
if shutil.which("npm") is None:
|
|
if dry_run:
|
|
output.print_list(
|
|
"Installing global npm packages after npm becomes available:",
|
|
sorted(self.packages),
|
|
)
|
|
return True
|
|
output.print_error("npm is unavailable after the pacman step.")
|
|
return False
|
|
|
|
try:
|
|
installed = self._installed_packages()
|
|
to_install = self.packages - installed
|
|
output.print_list("Installing global npm packages:", sorted(to_install))
|
|
if to_install and not dry_run:
|
|
decman.prg(
|
|
[
|
|
"npm",
|
|
"install",
|
|
"--global",
|
|
"--no-audit",
|
|
"--no-fund",
|
|
*sorted(to_install),
|
|
]
|
|
)
|
|
except (errors.CommandFailedError, ValueError) as error:
|
|
output.print_error(f"Managing global npm packages failed: {error}")
|
|
return False
|
|
|
|
return True
|
|
|
|
@staticmethod
|
|
def _installed_packages() -> set[str]:
|
|
raw = decman.prg(
|
|
["npm", "list", "--global", "--depth=0", "--json"], pty=False
|
|
)
|
|
try:
|
|
result = json.loads(raw)
|
|
except json.JSONDecodeError as error:
|
|
raise ValueError("npm returned invalid JSON") from error
|
|
dependencies = result.get("dependencies", {})
|
|
if not isinstance(dependencies, dict):
|
|
raise ValueError("npm dependencies must be an object")
|
|
return set(dependencies)
|