diff --git a/pyarn/lockfile.py b/pyarn/lockfile.py index 120e3a3..7db40bc 100644 --- a/pyarn/lockfile.py +++ b/pyarn/lockfile.py @@ -18,7 +18,8 @@ import json import logging import re -from typing import Pattern +from pathlib import Path +from typing import Optional, Pattern from ply import lex, yacc @@ -91,8 +92,8 @@ def from_dict(cls, raw_name, data): alias = name name, _version = _must_match(name_at_version, _remove_prefix(_version, "npm:")).groups() - if _version and _version.startswith("file:"): - path = _remove_prefix(_version, "file:") + if _version: + path = cls.get_path_from_version_specifier(_version) return cls( name=name, @@ -104,6 +105,22 @@ def from_dict(cls, raw_name, data): alias=alias, ) + @staticmethod + def get_path_from_version_specifier(version: str) -> Optional[str]: + """Return the path from a package.json file dependency version specifier.""" + version_path = Path(version) + + if version.startswith("file:"): + return _remove_prefix(version, "file:") + elif version.startswith("link:"): + return _remove_prefix(version, "link:") + elif version_path.is_absolute() or version.startswith(("./", "../")): + return str(version_path) + else: + # Some non-path version specifier, (e.g. "1.0.0" or a web link) + # See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#dependencies + return None + def _remove_prefix(s: str, prefix: str) -> str: return s[len(prefix) :] diff --git a/tests/test_lockfile.py b/tests/test_lockfile.py index 00c2579..9efc9f5 100644 --- a/tests/test_lockfile.py +++ b/tests/test_lockfile.py @@ -161,8 +161,37 @@ def test_packages_checksum(): assert packages[0].path is None -def test_path(): - data = '"breakfast@file:some/relative/path":\n version "0.0.0"' +@pytest.mark.parametrize( + "data, expected_path", + [ + pytest.param( + '"breakfast@file:some/relative/path":\n version "0.0.0"', + "some/relative/path", + id="relpath_with_file_prefix", + ), + pytest.param( + '"breakfast@link:some/relative/path":\n version "0.0.0"', + "some/relative/path", + id="relpath_with_link_prefix", + ), + pytest.param( + '"breakfast@./some/relative/path":\n version "0.0.0"', + "some/relative/path", + id="relpath_with_dot_prefix", + ), + pytest.param( + '"breakfast@../some/relative/path":\n version "0.0.0"', + "../some/relative/path", + id="relpath_to_parent_dir", + ), + pytest.param( + '"breakfast@/some/absolute/path":\n version "0.0.0"', + "/some/absolute/path", + id="absolute_path", + ), + ], +) +def test_package_with_path(data: str, expected_path: str) -> None: lock = lockfile.Lockfile.from_str(data) packages = lock.packages() assert len(packages) == 1 @@ -170,8 +199,8 @@ def test_path(): assert packages[0].version == "0.0.0" assert packages[0].checksum is None assert packages[0].url is None - assert packages[0].path == "some/relative/path" - assert packages[0].relpath == "some/relative/path" # test backwards compatibility + assert packages[0].path == expected_path + assert packages[0].relpath == expected_path # test backwards compatibility def test_package_with_comma():