diff --git a/cachi2/core/package_managers/yarn_classic/resolver.py b/cachi2/core/package_managers/yarn_classic/resolver.py index bb747c68a..a217f6f76 100644 --- a/cachi2/core/package_managers/yarn_classic/resolver.py +++ b/cachi2/core/package_managers/yarn_classic/resolver.py @@ -10,6 +10,7 @@ from cachi2.core.errors import PackageRejected, UnexpectedFormat from cachi2.core.package_managers.npm import NPM_REGISTRY_CNAMES from cachi2.core.package_managers.yarn_classic.project import PackageJson, Project, YarnLock +from cachi2.core.package_managers.yarn_classic.utils import find_non_dev_deps from cachi2.core.package_managers.yarn_classic.workspaces import ( Workspace, extract_workspace_metadata, @@ -79,8 +80,9 @@ class LinkPackage(_BasePackage, _RelpathMixin): class _YarnClassicPackageFactory: - def __init__(self, source_dir: RootedPath): + def __init__(self, source_dir: RootedPath, non_dev_deps: set[str]) -> None: self._source_dir = source_dir + self._non_dev_deps = non_dev_deps def create_package_from_pyarn_package(self, package: PYarnPackage) -> YarnClassicPackage: def assert_package_has_relative_path(package: PYarnPackage) -> None: @@ -93,12 +95,15 @@ def assert_package_has_relative_path(package: PYarnPackage) -> None: solution="Ensure that file/link packages in yarn.lock do not have absolute paths.", ) + dev = f"{package.name}@{package.version}" not in self._non_dev_deps + if _is_from_npm_registry(package.url): return RegistryPackage( name=package.name, version=package.version, integrity=package.checksum, url=package.url, + dev=dev, ) elif package.path is not None: # Ensure path is not absolute @@ -112,17 +117,20 @@ def assert_package_has_relative_path(package: PYarnPackage) -> None: version=package.version, relpath=path.subpath_from_root, integrity=package.checksum, + dev=dev, ) return LinkPackage( name=package.name, version=package.version, relpath=path.subpath_from_root, + dev=dev, ) elif _is_git_url(package.url): return GitPackage( name=package.name, version=package.version, url=package.url, + dev=dev, ) elif _is_tarball_url(package.url): return UrlPackage( @@ -130,6 +138,7 @@ def assert_package_has_relative_path(package: PYarnPackage) -> None: version=package.version, url=package.url, integrity=package.checksum, + dev=dev, ) else: raise UnexpectedFormat( @@ -187,11 +196,11 @@ def _is_from_npm_registry(url: str) -> bool: def _get_packages_from_lockfile( - source_dir: RootedPath, yarn_lock: YarnLock + source_dir: RootedPath, yarn_lock: YarnLock, non_dev_deps: set[str] ) -> list[YarnClassicPackage]: """Return a list of Packages for all dependencies in yarn.lock.""" pyarn_packages: list[PYarnPackage] = yarn_lock.yarn_lockfile.packages() - package_factory = _YarnClassicPackageFactory(source_dir) + package_factory = _YarnClassicPackageFactory(source_dir, non_dev_deps) return [ package_factory.create_package_from_pyarn_package(package) for package in pyarn_packages @@ -230,8 +239,10 @@ def resolve_packages(project: Project) -> Iterable[YarnClassicPackage]: """Return a list of Packages corresponding to all project dependencies.""" workspaces = extract_workspace_metadata(project.source_dir) yarn_lock = YarnLock.from_file(project.source_dir.join_within_root("yarn.lock")) + non_dev_deps = find_non_dev_deps(project.package_json, yarn_lock, workspaces) + return chain( [_get_main_package(project.package_json)], _get_workspace_packages(project.source_dir, workspaces), - _get_packages_from_lockfile(project.source_dir, yarn_lock), + _get_packages_from_lockfile(project.source_dir, yarn_lock, non_dev_deps), ) diff --git a/tests/unit/package_managers/yarn_classic/test_resolver.py b/tests/unit/package_managers/yarn_classic/test_resolver.py index 1b5ec44b2..3cff796c1 100644 --- a/tests/unit/package_managers/yarn_classic/test_resolver.py +++ b/tests/unit/package_managers/yarn_classic/test_resolver.py @@ -179,7 +179,10 @@ def test__is_from_npm_registry_can_parse_incorrect_registry_urls() -> None: def test_create_package_from_pyarn_package( pyarn_package: PYarnPackage, expected_package: YarnClassicPackage, rooted_tmp_path: RootedPath ) -> None: - package_factory = _YarnClassicPackageFactory(rooted_tmp_path) + package_factory = _YarnClassicPackageFactory( + rooted_tmp_path, + set({f"{pyarn_package.name}@{pyarn_package.version}"}), + ) assert package_factory.create_package_from_pyarn_package(pyarn_package) == expected_package @@ -194,7 +197,7 @@ def test_create_package_from_pyarn_package_fail_absolute_path(rooted_tmp_path: R f"({pyarn_package.path}), which is not permitted." ) - package_factory = _YarnClassicPackageFactory(rooted_tmp_path) + package_factory = _YarnClassicPackageFactory(rooted_tmp_path, set()) with pytest.raises(PackageRejected, match=re.escape(error_msg)): package_factory.create_package_from_pyarn_package(pyarn_package) @@ -208,7 +211,7 @@ def test_create_package_from_pyarn_package_fail_path_outside_root( path="../path/outside/root", ) - package_factory = _YarnClassicPackageFactory(rooted_tmp_path) + package_factory = _YarnClassicPackageFactory(rooted_tmp_path, set()) with pytest.raises(PathOutsideRoot): package_factory.create_package_from_pyarn_package(pyarn_package) @@ -222,7 +225,7 @@ def test_create_package_from_pyarn_package_fail_unexpected_format( url="ftp://some-tarball.tgz", ) - package_factory = _YarnClassicPackageFactory(rooted_tmp_path) + package_factory = _YarnClassicPackageFactory(rooted_tmp_path, set()) with pytest.raises(UnexpectedFormat): package_factory.create_package_from_pyarn_package(pyarn_package) @@ -233,7 +236,6 @@ def test_create_package_from_pyarn_package_fail_unexpected_format( def test__get_packages_from_lockfile( mock_create_package: mock.Mock, rooted_tmp_path: RootedPath ) -> None: - # Setup lockfile instance mock_pyarn_lockfile = mock.Mock() mock_yarn_lock = mock.Mock(yarn_lockfile=mock_pyarn_lockfile) @@ -250,7 +252,7 @@ def test__get_packages_from_lockfile( mock.call(mock_pyarn_package_2), ] - output = _get_packages_from_lockfile(rooted_tmp_path, mock_yarn_lock) + output = _get_packages_from_lockfile(rooted_tmp_path, mock_yarn_lock, set()) mock_pyarn_lockfile.packages.assert_called_once() mock_create_package.assert_has_calls(create_package_expected_calls) @@ -262,7 +264,9 @@ def test__get_packages_from_lockfile( @mock.patch("cachi2.core.package_managers.yarn_classic.resolver.extract_workspace_metadata") @mock.patch("cachi2.core.package_managers.yarn_classic.resolver._get_packages_from_lockfile") @mock.patch("cachi2.core.package_managers.yarn_classic.resolver._get_main_package") +@mock.patch("cachi2.core.package_managers.yarn_classic.resolver.find_non_dev_deps") def test_resolve_packages( + mock_find_non_dev_deps: mock.Mock, mock_get_main_package: mock.Mock, mock_get_lockfile_packages: mock.Mock, mock_extract_workspaces: mock.Mock, @@ -278,6 +282,7 @@ def test_resolve_packages( lockfile_packages = [mock.Mock(), mock.Mock()] expected_output = [main_package, *workspace_packages, *lockfile_packages] + mock_find_non_dev_deps.return_value = set() mock_get_main_package.return_value = main_package mock_get_lockfile_packages.return_value = lockfile_packages mock_get_workspace_packages.return_value = workspace_packages @@ -290,7 +295,9 @@ def test_resolve_packages( rooted_tmp_path, mock_extract_workspaces.return_value ) mock_get_lockfile_packages.assert_called_once_with( - rooted_tmp_path, mock_get_yarn_lock.return_value + rooted_tmp_path, + mock_get_yarn_lock.return_value, + mock_find_non_dev_deps.return_value, ) assert list(output) == expected_output