From 5311bec8d7dee788df4a9949cf7e0bab8db786a1 Mon Sep 17 00:00:00 2001 From: Taylor Madore Date: Mon, 14 Oct 2024 14:20:04 -0400 Subject: [PATCH] add handling for more path-based version specifiers These are all valid version specifiers for path-style packages in package.json/yarn.lock: ./some/relative/path ../some/relative/path file:some/relative/path file:../some/relative/path link:some/relative/path link:../some/relative/path\ /some/absolute/path Ensure that the Package class provides a path in each of these cases. Signed-off-by: Taylor Madore --- pyarn/lockfile.py | 21 ++++++++++++++++++--- tests/test_lockfile.py | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/pyarn/lockfile.py b/pyarn/lockfile.py index 078ee9b..9f86abe 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 @@ -63,8 +64,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, @@ -76,6 +77,20 @@ 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: + 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 825f48c..7f79d7c 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,7 +199,7 @@ 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].path == expected_path def test_package_with_comma():