Skip to content

Commit

Permalink
packages: make javascript package manager configurable
Browse files Browse the repository at this point in the history
* bump pynpm dependency for the PNPMPackage
* tell invenio about which JS package manager is actually selected
  currently (npm or pnpm), via the new `ASSETS_NPM_PKG_CLS` config
* also, use the module_pkg to execute the install command in the target
  path (rather than the instance's path, which made a difference in the
  case of pnpm)
  • Loading branch information
utnapischtim authored and max-moser committed Mar 6, 2025
1 parent 79de4ce commit 0a4920e
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 25 deletions.
21 changes: 10 additions & 11 deletions invenio_cli/commands/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@

"""Invenio module to ease the creation and management of applications."""

import subprocess
from pathlib import Path

import click
from pynpm import NPMPackage

from ..helpers import env
from ..helpers.process import ProcessResponse, run_interactive
Expand All @@ -26,17 +24,16 @@ def __init__(self, cli_config):
"""Constructor."""
super().__init__(cli_config)

@staticmethod
def _module_pkg(path):
def _module_pkg(self, path):
"""NPM package for the given path."""
return NPMPackage(Path(path) / "package.json")
path = Path(path) / "package.json"
return self.cli_config.javascript_package_manager.create_pynpm_package(path)

def _assets_pkg(self):
"""NPM package for the instance's webpack project."""
return self._module_pkg(self.cli_config.get_instance_path() / "assets")

@staticmethod
def _watch_js_module(pkg):
def _watch_js_module(self, pkg):
"""Watch the JS module for changes."""
click.secho("Starting watching module...", fg="green")
status_code = pkg.run_script("watch")
Expand All @@ -62,10 +59,12 @@ def _run_script(module_pkg):
status_code=status_code,
)

@staticmethod
def _npm_install_command(path):
def _npm_install_command(self, path, module_pkg):
"""Run command and return a ProcessResponse."""
status_code = subprocess.call(["npm", "install", "--prefix", path])
install_args = self.cli_config.javascript_package_manager.install_local_package(
path
)
status_code = module_pkg.install(" ".join(install_args))
if status_code == 0:
return ProcessResponse(
output="Dependent packages installed correctly", status_code=0
Expand Down Expand Up @@ -135,7 +134,7 @@ def link_js_module(self, path):
steps = [
FunctionStep( # Install dependent packages
func=self._npm_install_command,
args={"path": path},
args={"path": path, "module_pkg": module_pkg},
message="Installing dependent packages...",
),
FunctionStep( # Run build script
Expand Down
17 changes: 10 additions & 7 deletions invenio_cli/commands/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,17 @@ def update_statics_and_assets(self, force, debug=False, log_file=None):
Needed here (parent) because is used by Assets and Install commands.
"""
# Commands
pkg_man = self.cli_config.python_package_manager
ops = [pkg_man.run_command("invenio", "collect", "--verbose")]
py_pkg_man = self.cli_config.python_package_manager
js_pkg_man = self.cli_config.javascript_package_manager
ops = [py_pkg_man.run_command("invenio", "collect", "--verbose")]

if force:
ops.append(pkg_man.run_command("invenio", "webpack", "clean", "create"))
ops.append(pkg_man.run_command("invenio", "webpack", "install"))
ops.append(py_pkg_man.run_command("invenio", "webpack", "clean", "create"))
ops.append(py_pkg_man.run_command("invenio", "webpack", "install"))
else:
ops.append(pkg_man.run_command("invenio", "webpack", "create"))
ops.append(py_pkg_man.run_command("invenio", "webpack", "create"))
ops.append(self._statics)
ops.append(pkg_man.run_command("invenio", "webpack", "build"))
ops.append(py_pkg_man.run_command("invenio", "webpack", "build"))
# Keep the same messages for some of the operations for backward compatibility
messages = {
"build": "Building assets...",
Expand All @@ -108,7 +109,9 @@ def update_statics_and_assets(self, force, debug=False, log_file=None):
if op[-1] in messages:
click.secho(messages[op[-1]], fg="green")
response = run_interactive(
op, env={"PIPENV_VERBOSITY": "-1"}, log_file=log_file
op,
env={"PIPENV_VERBOSITY": "-1", **js_pkg_man.env_overrides()},
log_file=log_file,
)
if response.status_code != 0:
break
Expand Down
29 changes: 24 additions & 5 deletions invenio_cli/helpers/cli_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@
"""Invenio-cli configuration file."""

from configparser import ConfigParser
from functools import cached_property
from pathlib import Path

from ..errors import InvenioCLIConfigError
from .filesystem import get_created_files
from .package_managers import UV, Pipenv, PythonPackageManager
from .package_managers import (
NPM,
PNPM,
UV,
JavascriptPackageManager,
Pipenv,
PythonPackageManager,
)
from .process import ProcessResponse


Expand Down Expand Up @@ -62,12 +70,10 @@ def __init__(self, project_dir="./"):
with open(self.private_config_path) as cfg_file:
self.private_config.read_file(cfg_file)

@property
@cached_property
def python_package_manager(self) -> PythonPackageManager:
"""Get python packages manager."""
manager_name = self.config[CLIConfig.CLI_SECTION].get(
"python_package_manager", None
)
manager_name = self.config[CLIConfig.CLI_SECTION].get("python_package_manager")
if manager_name == Pipenv.name:
return Pipenv()
elif manager_name == UV.name:
Expand All @@ -82,6 +88,19 @@ def python_package_manager(self) -> PythonPackageManager:
"Could not determine the Python package manager, please configure it."
)

@cached_property
def javascript_package_manager(self) -> JavascriptPackageManager:
"""Get javascript packages manager."""
manager_name = self.config[CLIConfig.CLI_SECTION].get(
"javascript_package_manager"
)
if manager_name == NPM.name:
return NPM()
elif manager_name == PNPM.name:
return PNPM()

return NPM()

def get_project_dir(self):
"""Returns path to project directory."""
return self.config_path.parent.resolve()
Expand Down
59 changes: 58 additions & 1 deletion invenio_cli/helpers/package_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

import os
from abc import ABC
from typing import List
from pathlib import Path
from typing import Dict, List, Union

from pynpm import NPMPackage, PNPMPackage


class PythonPackageManager(ABC):
Expand Down Expand Up @@ -171,3 +174,57 @@ def start_activated_subshell(self) -> List[str]:
# Also, it has a good chance of not properly setting a PS1...
shell = os.getenv("SHELL")
return [shell, "-c", f"source .venv/bin/activate; exec {shell} -i"]


class JavascriptPackageManager(ABC):
"""Interface for creating tool-specific JS package management commands."""

name = None

def create_pynpm_package(self, package_json_path: Union[Path, str]) -> NPMPackage:
"""Create a variant of ``NPMPackage`` with the path to ``package.json``."""
raise NotImplementedError()

def install_local_package(self, path: Union[Path, str]) -> List[str]:
"""Install the local JS package."""
raise NotImplementedError()

def env_overrides(self) -> Dict[str, str]:
"""Provide environment overrides for building Invenio assets."""
return {}


class NPM(JavascriptPackageManager):
"""Generate ``npm`` commands for managing JS packages."""

name = "npm"

def create_pynpm_package(self, package_json_path):
"""Create an ``NPMPackage`` with the path to ``package.json``."""
return NPMPackage(package_json_path)

def install_local_package(self, path):
"""Install the local JS package."""
return ["--prefix", str(path)]

def env_overrides(self):
"""Provide environment overrides for building Invenio assets."""
return {"INVENIO_WEBPACKEXT_NPM_PKG_CLS": "pynpm:NPMPackage"}


class PNPM(JavascriptPackageManager):
"""Generate ``pnpm`` commands for managing JS packages."""

name = "pnpm"

def create_pynpm_package(self, package_json_path):
"""Create a ``PNPMPackage`` with the path to ``package.json``."""
return PNPMPackage(package_json_path)

def install_local_package(self, path):
"""Install the local JS package."""
return ["-C", str(path)]

def env_overrides(self):
"""Provide environment overrides for building Invenio assets."""
return {"INVENIO_WEBPACKEXT_NPM_PKG_CLS": "pynpm:PNPMPackage"}
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ install_requires =
pipfile>=0.0.2
pipenv>=2020.6.2
PyYAML>=5.1.2
pynpm>=0.1.2
pynpm>=0.3.0
virtualenv>=20.0.35

[options.extras_require]
Expand Down

0 comments on commit 0a4920e

Please sign in to comment.