Skip to content

Commit

Permalink
add handling for more path-based version specifiers
Browse files Browse the repository at this point in the history
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 <tmadore@redhat.com>
  • Loading branch information
taylormadore committed Oct 30, 2024
1 parent b7feac8 commit e141ed3
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 7 deletions.
23 changes: 20 additions & 3 deletions pyarn/lockfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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) :]
Expand Down
37 changes: 33 additions & 4 deletions tests/test_lockfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,46 @@ 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
assert packages[0].name == "breakfast"
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():
Expand Down

0 comments on commit e141ed3

Please sign in to comment.