Skip to content

Commit

Permalink
assets: implement js package linking for pnpm
Browse files Browse the repository at this point in the history
* as discussed, we implement the `pnpm link` to basically do the same
  thing as the current `npm link` procedure, to not require any changes
  in JS packages
  • Loading branch information
max-moser committed Mar 6, 2025
1 parent ce68d45 commit 62a7f3a
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 45 deletions.
56 changes: 11 additions & 45 deletions invenio_cli/commands/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,6 @@ def _watch_js_module(self, pkg):
status_code=status_code,
)

@staticmethod
def _run_script(module_pkg):
"""Run script and return a ProcessResponse."""
status_code = module_pkg.run_script("link-dist")
if status_code == 0:
return ProcessResponse(
output="Module linked correctly to global", status_code=0
)
else:
return ProcessResponse(
error=f"Unable to link-dist. Got error code {status_code}",
status_code=status_code,
)

def _npm_install_command(self, path, module_pkg):
"""Run command and return a ProcessResponse."""
install_args = self.cli_config.javascript_package_manager.install_local_package(
Expand Down Expand Up @@ -88,28 +74,6 @@ def _build_script(module_pkg):
status_code=status_code,
)

@staticmethod
def _assets_link(assets_pkg, module_pkg):
try:
module_name = module_pkg.package_json["name"]
except FileNotFoundError as e:
return ProcessResponse(
error="No module found on the specified path. "
f"File not found {e.filename}",
status_code=1,
)

status_code = assets_pkg.link(module_name)
if status_code == 0:
return ProcessResponse(
output="Global module linked correctly to local folder", status_code=0
)
else:
return ProcessResponse(
error=f"Unable to link module. Got error code {status_code}",
status_code=status_code,
)

def watch_assets(self):
"""High-level command to watch assets for changes."""
watch_cmd = self.cli_config.python_package_manager.run_command(
Expand Down Expand Up @@ -142,17 +106,19 @@ def link_js_module(self, path):
args={"module_pkg": module_pkg},
message="Building...",
),
FunctionStep( # Create link to global folder
func=self._run_script,
args={"module_pkg": module_pkg},
message="Linking module to global dist...",
),
FunctionStep( # Link the global folder to the assets folder.
func=self._assets_link,
]

# The commands necessary for linking local JS packages vary by package manager
js_package_manager = self.cli_config.javascript_package_manager
link_steps = [
FunctionStep(
func=step.function,
args={"assets_pkg": assets_pkg, "module_pkg": module_pkg},
message="Linking module to assets...",
),
message=step.message,
)
for step in js_package_manager.package_linking_steps()
]
steps.extend(link_steps)

return steps

Expand Down
133 changes: 133 additions & 0 deletions invenio_cli/helpers/package_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@

import os
from abc import ABC
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Union

from pynpm import NPMPackage, PNPMPackage

from ..helpers.process import ProcessResponse


class PythonPackageManager(ABC):
"""Interface for creating tool-specific Python package management commands."""
Expand Down Expand Up @@ -176,6 +180,14 @@ def start_activated_subshell(self) -> List[str]:
return [shell, "-c", f"source .venv/bin/activate; exec {shell} -i"]


@dataclass
class AssetsFunction:
"""Function to run an assets command with `assets_pkg` and `module_pkg`."""

function: Callable[[NPMPackage, NPMPackage], ProcessResponse]
message: str


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

Expand All @@ -193,6 +205,10 @@ def env_overrides(self) -> Dict[str, str]:
"""Provide environment overrides for building Invenio assets."""
return {}

def package_linking_steps(self) -> List[AssetsFunction]:
"""Generate steps to link the target package to the project."""
raise NotImplementedError()


class NPM(JavascriptPackageManager):
"""Generate ``npm`` commands for managing JS packages."""
Expand All @@ -211,6 +227,52 @@ def env_overrides(self):
"""Provide environment overrides for building Invenio assets."""
return {"INVENIO_WEBPACKEXT_NPM_PKG_CLS": "pynpm:NPMPackage"}

def package_linking_steps(self):
"""Generate steps to link the target package to the project."""

def _link_package_to_global(
assets_pkg: NPMPackage, module_pkg: NPMPackage
) -> ProcessResponse:
status_code = module_pkg.run_script("link-dist")
if status_code == 0:
return ProcessResponse(
output="Module linked correctly to global", status_code=0
)
else:
return ProcessResponse(
error=f"Unable to link-dist. Got error code {status_code}",
status_code=status_code,
)

def _link_global_package(
assets_pkg: NPMPackage, module_pkg: NPMPackage
) -> ProcessResponse:
try:
module_name = module_pkg.package_json["name"]
except FileNotFoundError as e:
return ProcessResponse(
error="No module found on the specified path. "
f"File not found {e.filename}",
status_code=1,
)

status_code = assets_pkg.link(module_name)
if status_code == 0:
return ProcessResponse(
output="Global module linked correctly to local folder",
status_code=0,
)
else:
return ProcessResponse(
error=f"Unable to link module. Got error code {status_code}",
status_code=status_code,
)

return [
AssetsFunction(_link_package_to_global, "Linking module to global dist..."),
AssetsFunction(_link_global_package, "Linking module to assets..."),
]


class PNPM(JavascriptPackageManager):
"""Generate ``pnpm`` commands for managing JS packages."""
Expand All @@ -228,3 +290,74 @@ def install_local_package(self, path):
def env_overrides(self):
"""Provide environment overrides for building Invenio assets."""
return {"INVENIO_WEBPACKEXT_NPM_PKG_CLS": "pynpm:PNPMPackage"}

def package_linking_steps(self):
"""Generate steps to link the target package to the project."""

def _prelink_dist(
assets_pkg: NPMPackage, module_pkg: NPMPackage
) -> ProcessResponse:
"""Execute the "prelink-dist" script."""
status_code = module_pkg.run_script("prelink-dist")
if status_code == 0:
return ProcessResponse(
output="Successfully ran prelink-dist script.",
status_code=0,
)
else:
return ProcessResponse(
error=f"Unable to prelink-dist. Got error code {status_code}",
status_code=status_code,
)

def _link_package_single_step(
assets_pkg: NPMPackage, module_pkg: NPMPackage
) -> ProcessResponse:
"""Execute the PNPM single-step package linking."""
try:
# Accessing `package_json` fails if the file can't be found
module_pkg.package_json["name"]

# This is geared towards Invenio JS packages...
# But so are all the other steps
status_code = assets_pkg.link(
str(module_pkg.package_json_path.parent / "dist")
)
except FileNotFoundError as e:
return ProcessResponse(
error="No module found on the specified path. "
f"File not found {e.filename}",
status_code=1,
)
if status_code == 0:
return ProcessResponse(
output="Module linked successfully to assets",
status_code=0,
)
else:
return ProcessResponse(
error=f"Unable to link module. Got error code {status_code}",
status_code=status_code,
)

def _postlink_dist(
assets_pkg: NPMPackage, module_pkg: NPMPackage
) -> ProcessResponse:
"""Execute the "postlink-dist" script."""
status_code = module_pkg.run_script("postlink-dist")
if status_code == 0:
return ProcessResponse(
output="Successfully ran postlink-dist script.",
status_code=0,
)
else:
return ProcessResponse(
error=f"Unable to run postlink-dist. Got error code {status_code}",
status_code=status_code,
)

return [
AssetsFunction(_prelink_dist, "Executing prelink-dist script..."),
AssetsFunction(_link_package_single_step, "Linking module to assets..."),
AssetsFunction(_postlink_dist, "Executing postlink-dist script..."),
]

0 comments on commit 62a7f3a

Please sign in to comment.