diff --git a/cachi2/core/checksum.py b/cachi2/core/checksum.py index 8bef08826..04abc65a2 100644 --- a/cachi2/core/checksum.py +++ b/cachi2/core/checksum.py @@ -31,6 +31,9 @@ def to_sri(self) -> str: base64_sha = base64.b64encode(bytes_sha).decode("utf-8") return f"{self.algorithm}-{base64_sha}" + def __str__(self) -> str: + return f"{self.algorithm}:{self.hexdigest}" + @classmethod def from_sri(cls, sri: str) -> "ChecksumInfo": """Convert the input Subresource Integrity value to ChecksumInfo.""" diff --git a/cachi2/core/package_managers/npm.py b/cachi2/core/package_managers/npm.py index 2926c0207..48e97c7cc 100644 --- a/cachi2/core/package_managers/npm.py +++ b/cachi2/core/package_managers/npm.py @@ -358,8 +358,7 @@ def get_purl( else: # dep_type == "https" qualifiers = {"download_url": resolved_url} if integrity: - algorithm, digest = ChecksumInfo.from_sri(integrity) - qualifiers["checksum"] = f"{algorithm}:{digest}" + qualifiers["checksum"] = str(ChecksumInfo.from_sri(integrity)) return PackageURL( type="npm", diff --git a/cachi2/core/package_managers/yarn_classic/main.py b/cachi2/core/package_managers/yarn_classic/main.py index d73d48c22..4b6c160f4 100644 --- a/cachi2/core/package_managers/yarn_classic/main.py +++ b/cachi2/core/package_managers/yarn_classic/main.py @@ -1,13 +1,12 @@ import logging -import re from collections import Counter from pathlib import Path from typing import Any, Iterable -from urllib.parse import urlparse from cachi2.core.errors import PackageManagerError, PackageRejected from cachi2.core.models.input import Request from cachi2.core.models.output import Component, EnvironmentVariable, RequestOutput +from cachi2.core.models.property_semantics import PropertySet from cachi2.core.package_managers.yarn.utils import ( VersionsRange, extract_yarn_version_from_env, @@ -21,6 +20,10 @@ YarnClassicPackage, resolve_packages, ) +from cachi2.core.package_managers.yarn_classic.utils import ( + get_git_tarball_mirror_name, + get_tarball_mirror_name, +) from cachi2.core.rooted_path import RootedPath log = logging.getLogger(__name__) @@ -59,14 +62,16 @@ def _ensure_mirror_dir_exists(output_dir: RootedPath) -> None: for package in request.yarn_packages: package_path = request.source_dir.join_within_root(package.path) _ensure_mirror_dir_exists(request.output_dir) - _resolve_yarn_project(Project.from_source_dir(package_path), request.output_dir) + components.extend( + _resolve_yarn_project(Project.from_source_dir(package_path), request.output_dir) + ) return RequestOutput.from_obj_list( components, _generate_build_environment_variables(), project_files=[] ) -def _resolve_yarn_project(project: Project, output_dir: RootedPath) -> None: +def _resolve_yarn_project(project: Project, output_dir: RootedPath) -> list[Component]: """Process a request for a single yarn source directory.""" log.info(f"Fetching the yarn dependencies at the subpath {project.source_dir}") @@ -74,9 +79,28 @@ def _resolve_yarn_project(project: Project, output_dir: RootedPath) -> None: prefetch_env = _get_prefetch_environment_variables(output_dir) _verify_corepack_yarn_version(project.source_dir, prefetch_env) _fetch_dependencies(project.source_dir, prefetch_env) - packages = resolve_packages(project) + packages = resolve_packages(project, output_dir.join_within_root(MIRROR_DIR)) _verify_no_offline_mirror_collisions(packages) + return _create_sbom_components(packages) + + +def _create_sbom_components(packages: Iterable[YarnClassicPackage]) -> list[Component]: + """Create SBOM components from the given yarn packages.""" + result = [] + for package in packages: + properties = PropertySet(npm_development=package.dev).to_properties() + result.append( + Component( + name=package.name, + purl=package.purl, + version=package.version, + properties=properties, + ) + ) + + return result + def _fetch_dependencies(source_dir: RootedPath, env: dict[str, str]) -> None: """Fetch dependencies using 'yarn install'. @@ -182,10 +206,10 @@ def _verify_no_offline_mirror_collisions(packages: Iterable[YarnClassicPackage]) tarballs = [] for p in packages: if isinstance(p, (RegistryPackage, UrlPackage)): - tarball_name = _get_tarball_mirror_name(p.url) + tarball_name = get_tarball_mirror_name(p.url) tarballs.append(tarball_name) elif isinstance(p, GitPackage): - tarball_name = _get_git_tarball_mirror_name(p.url) + tarball_name = get_git_tarball_mirror_name(p.url) tarballs.append(tarball_name) else: # file, link, and workspace packages are not copied to the offline mirror @@ -197,42 +221,5 @@ def _verify_no_offline_mirror_collisions(packages: Iterable[YarnClassicPackage]) raise PackageManagerError(f"Duplicate tarballs detected: {', '.join(duplicate_tarballs)}") -# https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/fetchers/tarball-fetcher.js#L21 -RE_URL_NAME_MATCH = r"/(?:(@[^/]+)(?:\/|%2f))?[^/]+/(?:-|_attachments)/(?:@[^/]+\/)?([^/]+)$" - - -# https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/fetchers/tarball-fetcher.js#L65 -def _get_tarball_mirror_name(url: str) -> str: - parsed_url = urlparse(url) - path = Path(parsed_url.path) - - match = re.search(RE_URL_NAME_MATCH, str(path)) - - if match is not None: - scope, tarball_basename = match.groups() - package_filename = f"{scope}-{tarball_basename}" if scope else tarball_basename - else: - package_filename = path.name - - return package_filename - - -# https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/fetchers/git-fetcher.js#L40 -def _get_git_tarball_mirror_name(url: str) -> str: - parsed_url = urlparse(url) - path = Path(parsed_url.path) - - package_filename = path.name - hash = parsed_url.fragment - - if hash: - package_filename = f"{package_filename}-{hash}" - - if package_filename.startswith(":"): - package_filename = package_filename[1:] - - return package_filename - - # References # [yarn_classic_trait]: https://github.com/yarnpkg/berry/blob/13d5b3041794c33171808fdce635461ff4ab5c4e/packages/yarnpkg-core/sources/Project.ts#L434 diff --git a/cachi2/core/package_managers/yarn_classic/resolver.py b/cachi2/core/package_managers/yarn_classic/resolver.py index 8ea1972e8..c1af5acc3 100644 --- a/cachi2/core/package_managers/yarn_classic/resolver.py +++ b/cachi2/core/package_managers/yarn_classic/resolver.py @@ -1,21 +1,30 @@ +import json import re -from itertools import chain +import tarfile +from abc import ABC, abstractmethod +from dataclasses import dataclass from pathlib import Path -from typing import Iterable, Optional, Union +from typing import Any, Iterable, Optional, Union from urllib.parse import urlparse +from packageurl import PackageURL from pyarn.lockfile import Package as PYarnPackage -from pydantic import BaseModel +from cachi2.core.checksum import ChecksumInfo 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_runtime_deps +from cachi2.core.package_managers.yarn_classic.utils import ( + find_runtime_deps, + get_git_tarball_mirror_name, + get_tarball_mirror_name, +) from cachi2.core.package_managers.yarn_classic.workspaces import ( Workspace, extract_workspace_metadata, ) from cachi2.core.rooted_path import RootedPath +from cachi2.core.scm import get_repo_id # https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/resolvers/exotics/git-resolver.js#L15-L17 GIT_HOSTS = frozenset(("github.com", "gitlab.com", "bitbucket.com", "bitbucket.org")) @@ -27,8 +36,11 @@ re.compile(r"^https?:.+\.git#.+"), ) +YARN_REGISTRY_URL = "https://registry.yarnpkg.com" + -class _BasePackage(BaseModel): +@dataclass +class _BasePackage(ABC): """A base Yarn 1.x package.""" name: str @@ -36,38 +48,133 @@ class _BasePackage(BaseModel): integrity: Optional[str] = None dev: bool = False + @property + @abstractmethod + def purl(self) -> str: + """Return the package URL.""" -class _UrlMixin(BaseModel): + +@dataclass +class _UrlMixin: url: str -class _RelpathMixin(BaseModel): - relpath: Path +@dataclass +class _PathMixin: + path: RootedPath +@dataclass class RegistryPackage(_BasePackage, _UrlMixin): """A Yarn 1.x package from the registry.""" + @property + def purl(self) -> str: + """Return package URL.""" + qualifiers = {} + + if YARN_REGISTRY_URL in self.url: + qualifiers = {"repository_url": YARN_REGISTRY_URL} + if self.integrity: + qualifiers["checksum"] = str(ChecksumInfo.from_sri(self.integrity)) + + return PackageURL( + type="npm", + name=self.name, + version=self.version, + qualifiers=qualifiers, + ).to_string() + + +@dataclass class GitPackage(_BasePackage, _UrlMixin): """A Yarn 1.x package from a git repo.""" - + @property + def purl(self) -> str: + """Return package URL.""" + parsed_url = urlparse(self.url) + ref = parsed_url.fragment + clean_url = parsed_url._replace(fragment="").geturl() + qualifiers = {"vcs_url": f"git+{clean_url}@{ref}"} + return PackageURL( + type="npm", + name=self.name, + version=self.version, + qualifiers=qualifiers, + ).to_string() + + +@dataclass class UrlPackage(_BasePackage, _UrlMixin): """A Yarn 1.x package from a http/https URL.""" + @property + def purl(self) -> str: + """Return package URL.""" + qualifiers = {"download_url": self.url} + return PackageURL( + type="npm", + name=self.name, + version=self.version, + qualifiers=qualifiers, + ).to_string() -class FilePackage(_BasePackage, _RelpathMixin): - """A Yarn 1.x package from a local file path.""" +@dataclass +class FilePackage(_BasePackage, _PathMixin): + """A Yarn 1.x package from a local file path.""" -class WorkspacePackage(_BasePackage, _RelpathMixin): + @property + def purl(self) -> str: + """Return package URL.""" + repo_id = get_repo_id(self.path.root) + qualifiers = {"vcs_url": repo_id.as_vcs_url_qualifier()} + return PackageURL( + type="npm", + name=self.name, + version=self.version, + qualifiers=qualifiers, + subpath=str(self.path.subpath_from_root), + ).to_string() + + +@dataclass +class WorkspacePackage(_BasePackage, _PathMixin): """A Yarn 1.x local workspace package.""" - -class LinkPackage(_BasePackage, _RelpathMixin): + @property + def purl(self) -> str: + """Return package URL.""" + repo_id = get_repo_id(self.path.root) + qualifiers = {"vcs_url": repo_id.as_vcs_url_qualifier()} + return PackageURL( + type="npm", + name=self.name, + version=self.version, + qualifiers=qualifiers, + subpath=str(self.path.subpath_from_root), + ).to_string() + + +@dataclass +class LinkPackage(_BasePackage, _PathMixin): """A Yarn 1.x local link package.""" + @property + def purl(self) -> str: + """Return package URL.""" + repo_id = get_repo_id(self.path.root) + qualifiers = {"vcs_url": repo_id.as_vcs_url_qualifier()} + return PackageURL( + type="npm", + name=self.name, + version=self.version, + qualifiers=qualifiers, + subpath=str(self.path.subpath_from_root), + ).to_string() + YarnClassicPackage = Union[ FilePackage, @@ -80,8 +187,11 @@ class LinkPackage(_BasePackage, _RelpathMixin): class _YarnClassicPackageFactory: - def __init__(self, source_dir: RootedPath, runtime_deps: set[str]) -> None: + def __init__( + self, source_dir: RootedPath, mirror_dir: RootedPath, runtime_deps: set[str] + ) -> None: self._source_dir = source_dir + self._mirror_dir = mirror_dir self._runtime_deps = runtime_deps def create_package_from_pyarn_package(self, package: PYarnPackage) -> YarnClassicPackage: @@ -99,6 +209,7 @@ def assert_package_has_relative_path(package: PYarnPackage) -> None: dev = package_id not in self._runtime_deps if _is_from_npm_registry(package.url): + # registry packages should already have the correct name return RegistryPackage( name=package.name, version=package.version, @@ -113,29 +224,47 @@ def assert_package_has_relative_path(package: PYarnPackage) -> None: path = self._source_dir.join_within_root(package.path) # File packages have a url, whereas link packages do not if package.url: + real_name = _read_name_from_tarball(path) + return FilePackage( - name=package.name, + name=real_name, version=package.version, - relpath=path.subpath_from_root, + path=path, integrity=package.checksum, dev=dev, ) + + package_json_path = path.join_within_root("package.json") + # package.json is not required for link packages + if package_json_path.path.exists(): + real_name = _read_name_from_package_json(package_json_path) + else: + real_name = package.name + return LinkPackage( - name=package.name, + name=real_name, version=package.version, - relpath=path.subpath_from_root, + path=path, dev=dev, ) elif _is_git_url(package.url): + tarball_name = get_git_tarball_mirror_name(package.url) + tarball_path = self._mirror_dir.join_within_root(tarball_name) + real_name = _read_name_from_tarball(tarball_path) + return GitPackage( - name=package.name, + name=real_name, version=package.version, url=package.url, dev=dev, ) elif _is_tarball_url(package.url): + tarball_name = get_tarball_mirror_name(package.url) + tarball_path = self._mirror_dir.join_within_root(tarball_name) + real_name = _read_name_from_tarball(tarball_path) + return UrlPackage( - name=package.name, + name=real_name, version=package.version, url=package.url, integrity=package.checksum, @@ -197,18 +326,18 @@ def _is_from_npm_registry(url: str) -> bool: def _get_packages_from_lockfile( - source_dir: RootedPath, yarn_lock: YarnLock, runtime_deps: set[str] + source_dir: RootedPath, mirror_dir: RootedPath, yarn_lock: YarnLock, runtime_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, runtime_deps) + package_factory = _YarnClassicPackageFactory(source_dir, mirror_dir, runtime_deps) return [ package_factory.create_package_from_pyarn_package(package) for package in pyarn_packages ] -def _get_main_package(package_json: PackageJson) -> WorkspacePackage: +def _get_main_package(source_dir: RootedPath, package_json: PackageJson) -> WorkspacePackage: """Return a WorkspacePackage for the main package in package.json.""" if "name" not in package_json._data: raise PackageRejected( @@ -216,9 +345,9 @@ def _get_main_package(package_json: PackageJson) -> WorkspacePackage: solution="Ensure the package.json file has a valid name.", ) return WorkspacePackage( - name=package_json.data["name"], + name=package_json.data.get("name"), # type: ignore version=package_json.data.get("version"), - relpath=package_json.path.subpath_from_root.parent, + path=source_dir, ) @@ -228,22 +357,55 @@ def _get_workspace_packages( """Return a WorkspacePackage for each Workspace.""" return [ WorkspacePackage( - name=ws.package_json.data.get("name"), + name=ws.package_json.data.get("name"), # type: ignore version=ws.package_json.data.get("version"), - relpath=ws.path.relative_to(source_dir.path), + path=source_dir.join_within_root(ws.path), ) for ws in workspaces ] -def resolve_packages(project: Project) -> Iterable[YarnClassicPackage]: +def resolve_packages(project: Project, mirror_dir: RootedPath) -> 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")) runtime_deps = find_runtime_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, runtime_deps), + result: list[YarnClassicPackage] = [] + + result.append(_get_main_package(project.source_dir, project.package_json)) + result.extend(_get_workspace_packages(project.source_dir, workspaces)) + result.extend( + _get_packages_from_lockfile(project.source_dir, mirror_dir, yarn_lock, runtime_deps) ) + return result + + +def _read_name_from_tarball(tarball_path: RootedPath) -> str: + """Read the package name from the package.json file in the cached tarball.""" + with tarfile.open(tarball_path) as tar: + names = tar.getnames() + package_json_subpath = next((n for n in names if n.endswith("package.json")), None) + if package_json_subpath is None: + raise ValueError(f"No package.json found in the tarball {tarball_path}") + + package_json = tar.extractfile(package_json_subpath) + if package_json is None: + raise ValueError(f"Failed to extract package.json from {tarball_path}") + + package_json_content: dict[str, Any] = json.loads(package_json.read()) + + if (name := package_json_content.get("name")) is None: + raise ValueError(f"No 'name' field found in package.json in {tarball_path}") + + return name + + +def _read_name_from_package_json(path: RootedPath) -> str: + """Read the package name from a package.json file.""" + package_json = PackageJson.from_file(path) + + if (name := package_json.data.get("name")) is None: + raise ValueError(f"No 'name' field found in package.json in {path}") + + return name diff --git a/cachi2/core/package_managers/yarn_classic/utils.py b/cachi2/core/package_managers/yarn_classic/utils.py index f782aa11d..2bce2d631 100644 --- a/cachi2/core/package_managers/yarn_classic/utils.py +++ b/cachi2/core/package_managers/yarn_classic/utils.py @@ -1,5 +1,8 @@ +import re from collections import deque +from pathlib import Path from typing import Any +from urllib.parse import urlparse from pyarn.lockfile import Package as PYarnPackage @@ -110,3 +113,42 @@ def _find_transitive_deps( bfs_queue.append(new_dep) return visited + + +# https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/fetchers/tarball-fetcher.js#L21 +RE_URL_NAME_MATCH = r"/(?:(@[^/]+)(?:\/|%2f))?[^/]+/(?:-|_attachments)/(?:@[^/]+\/)?([^/]+)$" + + +# https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/fetchers/tarball-fetcher.js#L65 +def get_tarball_mirror_name(url: str) -> str: + """Get the name of the tarball file that will be stored in the offline mirror.""" + parsed_url = urlparse(url) + path = Path(parsed_url.path) + + match = re.search(RE_URL_NAME_MATCH, str(path)) + + if match is not None: + scope, tarball_basename = match.groups() + package_filename = f"{scope}-{tarball_basename}" if scope else tarball_basename + else: + package_filename = path.name + + return package_filename + + +# https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/fetchers/git-fetcher.js#L40 +def get_git_tarball_mirror_name(url: str) -> str: + """Get the name of the tarball file that will be stored in the offline mirror for git packages.""" + parsed_url = urlparse(url) + path = Path(parsed_url.path) + + package_filename = path.name + hash = parsed_url.fragment + + if hash: + package_filename = f"{package_filename}-{hash}" + + if package_filename.startswith(":"): + package_filename = package_filename[1:] + + return package_filename diff --git a/tests/integration/test_data/yarn_classic_e2e_test/bom.json b/tests/integration/test_data/yarn_classic_e2e_test/bom.json index 958e49b30..43a214bfd 100644 --- a/tests/integration/test_data/yarn_classic_e2e_test/bom.json +++ b/tests/integration/test_data/yarn_classic_e2e_test/bom.json @@ -1,6 +1,755 @@ { "bomFormat": "CycloneDX", - "components": [], + "components": [ + { + "name": "@colors/colors", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/%40colors/colors@1.6.0?checksum=sha512:22bf803a26eaceb22c2fa6a3b77473dcbb2407b3a23151ea96d666b296d6fd326e4d5bb238c8ab56a0248df63a2484a22c783236a89c002f00c871c6ccd77f74&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.6.0" + }, + { + "name": "ansi-align", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/ansi-align@3.0.1?checksum=sha512:20e7f0c0117989ccce8e9fd6798e18c728ea005310a19b9f750583775f52104c5b54b357aafa73489fcced96b8fec08f990d3e191aaea00edb19c20d7317b0eb&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "3.0.1" + }, + { + "name": "ansi-regex", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/ansi-regex@3.0.1?checksum=sha512:f8ef4972df307fefa55f1c4573885cf0bb23692ab41c5cf32fb715b30f2944320f763283da64f8cad0d994bb92059e1c2ca6455c8acc18eec36d00ad30924c2b&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "3.0.1" + }, + { + "name": "ansi-regex", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/ansi-regex@5.0.1?checksum=sha512:aae2505e54d25062f62c7f52517a3c570b18e2ca1a9e1828e8b3529bce04d4b05c13cb373b4c29762473c91f73fd9649325316bf7eea38e6fda5d26531410a15&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "5.0.1" + }, + { + "name": "ansi-styles", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/ansi-styles@4.3.0?checksum=sha512:cdb07dac22404f5adb8e25436f686a2851cd60bc60b64f0d511c59dc86700f717a36dc5b5d94029e74a2d4b931f880e885d3e5169db6db05402c885e64941212&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.3.0" + }, + { + "name": "assertion-error", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + }, + { + "name": "cdx:npm:package:development", + "value": "true" + } + ], + "purl": "pkg:npm/assertion-error@2.0.1?checksum=sha512:2338bc45071f7ea09e3558058a02a58b5b2c92521ba479c261ce809275c662807a82b26ac9e6f2ee3bf5d895108264c09c80e76dc935bb192c4f87733773d604&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.1" + }, + { + "name": "boxen", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/boxen@5.1.2?vcs_url=git%2Bhttps://github.com/cachito-testing/cachi2-yarn.git%404fe0919c4b2fb9a0f6fb98999ceaeb732f4b1d9d#external-packages/boxen-5.1.2.tgz", + "type": "library", + "version": "5.1.2" + }, + { + "name": "camelcase", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/camelcase@5.3.1?checksum=sha512:2f6f124c1d7bd27c164badd48ed944384ddd95d400a5a257664388d6e3057f37f7ad1b8f7a01da1deb3279ef98c50f96e92bd10d057a52b74e751891d79df026&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "5.3.1" + }, + { + "name": "camelcase", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/camelcase@6.3.0?checksum=sha512:1a6cba161625098eee3849595126f1a365020c7f28c0493df7a8246eba6c806b6b24b33727b8c6c65f4873b430c23e22bce13901665644c79c0dd17b86a1a314&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "6.3.0" + }, + { + "name": "camelcase", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/camelcase@8.0.0?download_url=https://github.com/sindresorhus/camelcase/archive/2f1e273fc50281098d99627fef0853aa431139b5.tar.gz%232843ebd77073532e04e3382af6f2b92ae4e696f6", + "type": "library", + "version": "8.0.0" + }, + { + "name": "chai", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + }, + { + "name": "cdx:npm:package:development", + "value": "true" + } + ], + "purl": "pkg:npm/chai@5.1.1?checksum=sha512:a53d5980ff2b3cda9482279568463eaf242be90e075cd83c122f549cb52b8cde0803b76f402e4907efe4c5570f3431f205c73fdba0973e46cdceadeac2b38428&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "5.1.1" + }, + { + "name": "chalk", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/chalk@4.1.2?checksum=sha512:a0a9db845c91217a54b9ecfc881326c846b89db8f820e432ba173fc32f6463bfd654f73020ef5503aebc3eef1190eefed06efa48b44e7b2c3d0a9434eb58b898&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.1.2" + }, + { + "name": "chalk", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/chalk@5.3.0?checksum=sha512:74b8ad1bbf5df8657535bfd561c083162bc978ad618ae92df508d13553ac52d4f2d6b475609b26a46193677a89a2cfaec3b5a6585e3053005df63c63a1c142db&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "5.3.0" + }, + { + "name": "check-error", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + }, + { + "name": "cdx:npm:package:development", + "value": "true" + } + ], + "purl": "pkg:npm/check-error@2.1.1?checksum=sha512:38095bf93ed5e0ea7d3b07648e682e611aa771d971e498a87f038052499317e8cd747c334da4ece2c4401a9ccb177a0ecf9c40c831ab3e07880c54260ce4e227&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.1.1" + }, + { + "name": "cli-boxes", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/cli-boxes@2.2.1?checksum=sha512:cb872831cca581209d5629e388306e47c3c20b66cb8f2193c0498f6fd34747f41a354e8450e5dc4d8fece958c6303e8435211a790607098d063464b98df3303f&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.2.1" + }, + { + "name": "cliui", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/cliui@6.0.0?checksum=sha512:b7ac1b82da025ef033b2ded0817c4962a3edd2eb047db81075fb443db2cbfdcbefe873c4e5582fa82b80203474360539d9db3aac5c2aae06a434bac712309bad&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "6.0.0" + }, + { + "name": "color-convert", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/color-convert@2.0.1?checksum=sha512:4511023ec8fb8aeff16f9a0a61cb051d2a6914d9ec8ffe763954d129be333f9a275f0545df3566993a0d70e7c60be0910e97cafd4e7ce1f320dfc64709a12529&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.1" + }, + { + "name": "color-name", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/color-name@1.1.4?checksum=sha512:74ecbedc0b96ddadb035b64722e319a537208c6b8b53fb812ffb9b71917d3976c3a3c7dfe0ef32569e417f479f4bcb84a18a39ab8171edd63d3a04065e002c40&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.1.4" + }, + { + "name": "cowsay", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/cowsay@1.6.0?checksum=sha512:f02e07d6376b80dbac4d0af762ee120a6f992ac0250c56e96b4292d19de29bcb9e6a0fbda4639fdc2ae2a2bbaf99e696fc0e68aa0f4bd1aafaa9e7ed021d378f&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.6.0" + }, + { + "name": "decamelize", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/decamelize@1.2.0?checksum=sha512:cf64be5bd5fbde10145248be37ef596b694196e9fcf738a03b21abb1ac7e29443ac0a5b86685a91180641a1423c008e30c2916c6163454a12193cc3363b17970&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.2.0" + }, + { + "name": "deep-eql", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + }, + { + "name": "cdx:npm:package:development", + "value": "true" + } + ], + "purl": "pkg:npm/deep-eql@5.0.2?checksum=sha512:87993fe54e74209245a737cbea73bd8da6ae99f8cefdfd8d8cafe8601d838f39b8a7d2fedd3f6a5a966a6768406cb3eeb98abdc2b534f164320532f919b3e4e5&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "5.0.2" + }, + { + "name": "emoji-regex", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/emoji-regex@8.0.0?checksum=sha512:3128d8cdc58d380d1ec001e9cf4331a5816fc20eb28f2d4d1b7c6d7a8ab3eb8e150a8fd13e09ebd7f186b7e89cde2253cd0f04bb74dd335e126b09d5526184e8&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "8.0.0" + }, + { + "name": "fecha", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/fecha@4.2.3?vcs_url=git%2Bhttps://github.com/taylorhakes/fecha.git%402aa169dea2746dca03187abbfcceecf9002f9dd4", + "type": "library", + "version": "4.2.3" + }, + { + "name": "find-up", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/find-up@4.1.0?checksum=sha512:3e93b001d43f6255d0daf8fc6b787c222a43b98462df071e550406616c4d20d71cab8d009f0ec196c11708c6edd59b7e38b03a16af6cb88a48583d0eb2721297&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.1.0" + }, + { + "name": "foo", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/foo@1.0.0?vcs_url=git%2Bhttps://github.com/cachito-testing/cachi2-yarn.git%404fe0919c4b2fb9a0f6fb98999ceaeb732f4b1d9d#packages/foo", + "type": "library", + "version": "1.0.0" + }, + { + "name": "get-caller-file", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/get-caller-file@2.0.5?checksum=sha512:0f214fdc133fdd81d340e0942ffc343991d1d25a4a786af1a2d70759ca8d11d9e5b6a1705d57e110143de1e228df801f429a34ac6922e1cc8889fb58d3a87616&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.5" + }, + { + "name": "get-func-name", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + }, + { + "name": "cdx:npm:package:development", + "value": "true" + } + ], + "purl": "pkg:npm/get-func-name@2.0.2?checksum=sha512:f2f5cebee135ebb0ad21cdcec88b5ca3b37f76946d05b60eb0fb170b3ed7fcf3279468d88d21ae64980cd58ee699ec3b04a7fd06abcb5f6b67395cb504152cc5&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.2" + }, + { + "name": "get-stdin", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/get-stdin@8.0.0?checksum=sha512:b18db6680eb1721033a6b8f2aa648442fe146c003344dd0bd9d401d0d94de5a7134caf43a27ea78687377806d49e966208034031f77ffb8d2455d29f17282886&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "8.0.0" + }, + { + "name": "has-flag", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/has-flag@4.0.0?checksum=sha512:1329094ff4352a34d672da698080207d23b4b4a56e6548e180caf5ee4a93ba6325e807efdc421295e53ba99533a170c54c01d30c2e0d3a81bf67153712f94c3d&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.0.0" + }, + { + "name": "is-fullwidth-code-point", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/is-fullwidth-code-point@2.0.0?checksum=sha512:547b2400a60cf117d2157c1e7b9b7971b3793d97aad56ae1eaa7796e4ca25c87fa51070deb0fc0d1e5ccf6beadf1df8660e87ea3a6618849dbf3c2cdfd8f26db&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.0" + }, + { + "name": "is-fullwidth-code-point", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/is-fullwidth-code-point@3.0.0?checksum=sha512:cf29a6e7ebbeb02b125b20fda8d69e8d5dc316f84229c94a762cd868952e1c0f3744b8dbee74ae1a775d0871afd2193e298ec130096c59e2b851e83a115e9742&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "3.0.0" + }, + { + "name": "is-number", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/is-number@0.0.0?vcs_url=git%2Bhttps://github.com/cachito-testing/cachi2-yarn.git%404fe0919c4b2fb9a0f6fb98999ceaeb732f4b1d9d#external-packages/is-number", + "type": "library", + "version": "0.0.0" + }, + { + "name": "is-positive", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/is-positive@3.1.0?checksum=sha512:f0d0f58f7cbdfc73fde133af1b3afaf7f1606e45f6aee3a57612c4b135b070955fa38a118f07a6266271b7b44990a607f2dcfbbd804ff4970a418f022ee27dd1&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "3.1.0" + }, + { + "name": "leftpad", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/leftpad@0.0.1?checksum=sha512:90102ec41409949f392c373e4a71925fa816267251f5093895b8179b3fea3df08e0a278293b060a0ddd8bf3a0daf90548eac500ce4316b0249bd75dde9cfba32&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.0.1" + }, + { + "name": "locate-path", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/locate-path@5.0.0?checksum=sha512:b7b870f6923e5afbb03495f0939cd51e9ca122ace0daa4e592524e7f4995c4649b7b7169d9589e65c76e3588da2c3a32ea9f6e1a94041961bced6a4c2a536af2&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "5.0.0" + }, + { + "name": "loupe", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + }, + { + "name": "cdx:npm:package:development", + "value": "true" + } + ], + "purl": "pkg:npm/loupe@3.1.1?checksum=sha512:79d36effc0f930a55f1951518457fc680c624cce96ba67f3e57b1a6ad4b1943df0e11d5dfd610a513c9d09d3c66e5f4aed01bf09adc69c3576b08288a5745073&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "3.1.1" + }, + { + "name": "p-limit", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/p-limit@2.3.0?checksum=sha512:ffff3c985592271f25c42cf07400014c92f6332581d76f9e218ecc0cbd92a8b98091e294f6ac51bd6b92c938e6dc5526a4110cb857dc90022a11a546503c5beb&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.3.0" + }, + { + "name": "p-locate", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/p-locate@4.1.0?checksum=sha512:47bf5967fd30031286bb7a18325cfc8f2fe46e1b0dad2ed2299ecfc441c1809e7e1769ad156d9f2b670eb4187570762442c6f3155ec8f84a1129ee98b74a0aec&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.1.0" + }, + { + "name": "p-try", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/p-try@2.2.0?checksum=sha512:4789cf0154c053407d0f7e7f1a4dee25fffb5d86d0732a2148a76f03121148d821165e1eef5855a069c1350cfd716697c4ed88d742930bede331dbefa0ac3a75&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.2.0" + }, + { + "name": "path-exists", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/path-exists@4.0.0?checksum=sha512:6a4f50cb943b8d86f65b071ecb9169be0d8aa0073f64884b48b392066466ca03ec1b091556dd1f65ad2aaed333fa6ead2530077d943c167981e0c1b82d6cbbff&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.0.0" + }, + { + "name": "pathval", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + }, + { + "name": "cdx:npm:package:development", + "value": "true" + } + ], + "purl": "pkg:npm/pathval@2.0.0?checksum=sha512:bc4ec9291c844b4f4a8ae9dab97ee777643dfcbee5868938b263fd4594c3783e0c56cef60e9daa345573dfd373e5ad055445b404947a0b40d8aeaeae9e8ce264&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.0" + }, + { + "name": "require-directory", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/require-directory@2.1.1?checksum=sha512:7c6c4423bfb0b06f71aef763b2b9662f6d8e3134e21d1c0032ba2211e320abc833a0b0bf3d0afb46c4434932d483f6d9019b45f9354890773aff84482abba2f9&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.1.1" + }, + { + "name": "require-main-filename", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/require-main-filename@2.0.0?checksum=sha512:34a37990c0f294aba577160b4947eb6e8e53bb387885dfb613c34f3d7d36999b67d55b911104e861efd9765272f89dee0a97da886174e5eec1f16d225db4079a&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.0" + }, + { + "name": "set-blocking", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/set-blocking@2.0.0?checksum=sha512:2a22814bc0275861322f3a1f15f9af2b0a5d3f3aa2cb5e8bbd07cadf2bff7d51fb063d77ff097725247527eadf81113dabbc5424ae2abe04bcada48e78b51e87&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.0" + }, + { + "name": "string-width", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/string-width@2.1.1?checksum=sha512:9cea87e7d75e0aaf52447971ab5030f39267b78c3a2af2caa9656293aa00f599255cb3483a5aa0e05db2ad3d4c55a4e302abd5c1d7de67bc3b682bc90fbba093&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.1.1" + }, + { + "name": "string-width", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/string-width@4.2.3?checksum=sha512:c0ac90450a63274b08a7ad84ad265d1ac8cc256b1aa79a1136284786ee86ec954effd8c807a5327af2feb57b8eaab9e0f23fdcc4a4d6c96530bd24eb8a2673fe&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.2.3" + }, + { + "name": "strip-ansi", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/strip-ansi@4.0.0?checksum=sha512:e17689db341d0b344e6438af1152033e47109fc2cc1526bc923f06c5bfcb9f0ceff40f1572d359fa57e2bc2fec5778af5bc1252531115d9a0f051ad92a434aa3&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.0.0" + }, + { + "name": "strip-ansi", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/strip-ansi@6.0.1?checksum=sha512:637f153d21dcaa416b0a916743dbee4979aabaebf9a1738aa46793e9a1abaf7a3719cf409556ba2417d448e0a76f1186645fbfd28a08ecaacfb944b3b54754e4&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "6.0.1" + }, + { + "name": "strip-final-newline", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/strip-final-newline@2.0.0?checksum=sha512:06ba6f7cd004ddd72fabb965df156e9b38ca8d9439b48d6c11420aaf752892cd17525e394addc595ab55a9e7fda6b9388d10f3856e96660fb76e4f77cbaa4b8c&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.0" + }, + { + "name": "supports-color", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/supports-color@7.2.0?checksum=sha512:aa9080bd197db2db8e1ef78ab27ec79dc251befe74d6a21a70acd094effe2f0c5cf7ed2adb02f2bf80dfbedf34fc33e7da9a8e06c25d0e2a205c647df8ebf047&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "7.2.0" + }, + { + "name": "type-fest", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/type-fest@0.20.2?checksum=sha512:35ef9e138af4fe25a7a40c43f39db3dc0f8dd01b7944dfff36327045dd95147126af2c317f9bec66587847a962c65e81fb0cfff1dfa669348090dd452242372d&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.20.2" + }, + { + "name": "uuid", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/uuid@10.0.0?vcs_url=git%2Bhttps://github.com/cachito-testing/cachi2-yarn.git%404fe0919c4b2fb9a0f6fb98999ceaeb732f4b1d9d#external-packages/uuid-10.0.0.tgz", + "type": "library", + "version": "10.0.0" + }, + { + "name": "which-module", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/which-module@2.0.1?checksum=sha512:881759e7b443be7391f4018184c2f6bc565fee1f2f9818e1a1a66a3832411561d5b4a90398ab876a2ddcc793e054cad7e580cda76ec0a1f61b03072d492faf85&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.1" + }, + { + "name": "widest-line", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/widest-line@3.1.0?checksum=sha512:36c9a85da96c5950cc1aea71679474f246bd7e56638e22ef1d501660e2ad88a33cba3b595abf5c45f7da93eb92138f3e39bf0e6da957a70c9e522c830fa40582&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "3.1.0" + }, + { + "name": "wrap-ansi", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/wrap-ansi@6.2.0?checksum=sha512:afa94f7011b1657948732984bbb227c43321756d0a0f1a4b82814b720b9ab3109a27f48e219c0835ab4af4a63fb5ff99ae5cb038a5345038f70135d405fc495c&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "6.2.0" + }, + { + "name": "wrap-ansi", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/wrap-ansi@7.0.0?checksum=sha512:6151888f691a98b493c70e8db198e80717d2c2c9f4c9c75eb26738a7e436d5ce733ee675a65f8d7f155dc4fb5d1ef98d54e43a5d2606e0052dcadfc58bb0f5e9&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "7.0.0" + }, + { + "name": "y18n", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/y18n@4.0.3?checksum=sha512:24a86a4cec12aea340d4d639952ced2751ab06252874b326219b8b88368c449fa2b4577e001544f170633af2162fead2a8d0c2ef82c24859a56ff538519e2125&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.0.3" + }, + { + "name": "yargs-parser", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/yargs-parser@18.1.3?checksum=sha512:a39d23d09793a32ff82ba39971a4265ba9725d72a1abb72c4445dc0f0936a2614f244c1434e56d24abe60ebf442357c025953265c445ee4c460569915ee76b09&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "18.1.3" + }, + { + "name": "yargs", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/yargs@15.4.1?checksum=sha512:69e3dbc4399c616fbe3daa81b09f8761417009dbf82d5bdd9e1072efc139ecf228afcfce56f84cac00c51440e1f031c3151bff3bd8b794f86c10d8ceed05f4f8&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "15.4.1" + }, + { + "name": "yarn-test-project", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/yarn-test-project@1.0.0?vcs_url=git%2Bhttps://github.com/cachito-testing/cachi2-yarn.git%404fe0919c4b2fb9a0f6fb98999ceaeb732f4b1d9d", + "type": "library", + "version": "1.0.0" + } + ], "metadata": { "tools": [ { diff --git a/tests/integration/test_data/yarn_classic_e2e_test_multiple_packages/bom.json b/tests/integration/test_data/yarn_classic_e2e_test_multiple_packages/bom.json index 958e49b30..48343b5c0 100644 --- a/tests/integration/test_data/yarn_classic_e2e_test_multiple_packages/bom.json +++ b/tests/integration/test_data/yarn_classic_e2e_test_multiple_packages/bom.json @@ -1,6 +1,905 @@ { "bomFormat": "CycloneDX", - "components": [], + "components": [ + { + "name": "accepts", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/accepts@1.3.8?checksum=sha512:3d802d8536b69b654ac6ebd20f70cf0bf1b2f94fac380d4b02e4fc9a4991bafc3e34009269e5c443e34771517bace365eaa71ac55dd4b9e9b06b093eefe4892f&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.3.8" + }, + { + "name": "array-flatten", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/array-flatten@1.1.1?checksum=sha512:3c254042cc167a6bba51dc6c0c5157ffe815798a8a0287770f75159bdd631f0ca782e3b002f60f871f2736533ef8da9170ae82c71a5469f8e684874a88789baa&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.1.1" + }, + { + "name": "asynckit", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/asynckit@0.4.0?checksum=sha512:39e8bd387e2d461d18a94dc6c615fbf5d33f9b0560bdb64969235a464f9bb21923d12e5c7c772061a92b7818eb1f06ad5ca6f3f88a087582f1aca8a6d8c8d6d1&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.4.0" + }, + { + "name": "axios", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/axios@1.5.1?checksum=sha512:436f226025b33478c09bec84028b7941a00cc613208562c555feeb45dc21508f9cda38b1d835178c01d7562facd626ecde68cf3bf70281b03efb70630f4bcffc&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.5.1" + }, + { + "name": "axios", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/axios@1.6.4?checksum=sha512:85e26722ce8de1a6b5792b6184df4ce62a082eef168bcbe6416f621d0f4d52f7e425bd251040d4888750340b81b5f52ddc5c5179a29da500390db98c3aacc5fc&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.6.4" + }, + { + "name": "body-parser", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/body-parser@1.20.1?checksum=sha512:8d68bb69b4db6306a33b2b56090737ed5ba599689169ee51c93a5a0b20dc4b9fe531db704b3e653a90c4ebbb2bc3f1d87b7e5fd73ddf0d0c3ededc60ee036d5b&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.20.1" + }, + { + "name": "bytes", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/bytes@3.1.2?checksum=sha512:fcd7fb4f2cd3c7a4b7c9124e6ce015efde7aafc72bdbe3a3f000b976df3048fdc1400a1e5f9f0da07c8253c3fccc690d5d2b634d28ba7f33ba174a4175c61b12&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "3.1.2" + }, + { + "name": "call-bind", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/call-bind@1.0.7?checksum=sha512:1874d2352608090eec707eec67e336ac5a294682e1f2dd9b2d25ba05b82bb4bb1a84e201e62c805497fd1a358addc6130da323e17741a4cd5c03aa484b42afdb&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.7" + }, + { + "name": "combined-stream", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/combined-stream@1.0.8?checksum=sha512:1503783117ee25e1dfedc05b04c2455e12920eafb690002b06599106f72f144e410751d9297b5214048385d973f73398c3187c943767be630e7bffb971da0476&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.8" + }, + { + "name": "content-disposition", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/content-disposition@0.5.4?checksum=sha512:16f7994cdb86c34e1cc6502259bce2eb34c02ff9617a16966d3b6096e261e3f13de43a8cc139a16b7299375680580f1c148847ccc654bcb7af930e51aa4fad49&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.5.4" + }, + { + "name": "content-type", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/content-type@1.0.5?checksum=sha512:9d38ea7dc045122a4a7570afe180d05827e670b64a9bcd65745d29028a53bf2ac51956dc47a3ff54001de46ecdfb4b53afc42a894d2d15a743e852b836d27038&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.5" + }, + { + "name": "cookie-signature", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/cookie-signature@1.0.6?checksum=sha512:4000f395a1dcf22715f08eef6da257270a1df47598a7cb82a9fd716b839f36ed53ec9571408ad480e5ad1dd343b4f8b2c2615b892d76563a2d2172eb28cde8ad&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.6" + }, + { + "name": "cookie", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/cookie@0.5.0?checksum=sha512:619dc65329ffa3c81f289967957ee0ef1ab88323ba392ba118f29a686b2c181daa803512d203e0b53be8c992d3b7d01be9d0b885f73d755e5aae4bdcfce0a6af&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.5.0" + }, + { + "name": "debug", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/debug@2.6.9?checksum=sha512:6c2ec496b7496899cf6c03fed44a2d62fa99b1bdde725e708ba05f8ba0494d470da30a7a72fb298348d7ce74532838e6fc4ec076014155e00f54c35c286b0730&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.6.9" + }, + { + "name": "define-data-property", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/define-data-property@1.1.4?checksum=sha512:ac132f23396903cbfa13e489668a3ef87018aac2eb920ecc49f2229cc3c5866928af0ed7f9d39754942cf904faf731a4cccc9f0e720c3765a2775f8d6cbdd3f8&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.1.4" + }, + { + "name": "delayed-stream", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/delayed-stream@1.0.0?checksum=sha512:672483ecd7fdd5a2c1d11c4be0a1ab28705797b11db350c098475ca156b05e72c3ed20e1a4d82db88236680920edaed04b8d63c4f499d7ba7855d1a730793731&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.0" + }, + { + "name": "depd", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/depd@2.0.0?checksum=sha512:83b9c7e8fe9dc838a8268800006a6b1a90ad5489898693e4feba02cdd6f77c887ad7fb3f9cfb1f47aa27c8cc2408047f3a50b7c810b49444af52840402cb08af&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.0" + }, + { + "name": "destroy", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/destroy@1.2.0?checksum=sha512:dac246253697208691d70e22252368374867318ec6a5cfe7f03e2a482270f10a855977fb72e0209c41f1069c1e69570f7af0b69772a98d80b1dcdca941081a26&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.2.0" + }, + { + "name": "ee-first", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/ee-first@1.1.1?checksum=sha512:58cc26f4b851528f9651a44dfaf46e113a86f3d22066985548d91d16079beac4bf1383ab0c837bb78f0201ec121d773a0bc95e7c3f0a29faf9bd8eb56eb425a3&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.1.1" + }, + { + "name": "encodeurl", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/encodeurl@1.0.2?checksum=sha512:4cf257abc26a15a5589b609698fbe73f6232a3865233bfd029c4a6b8c2c339b7e91f97e2ed150699dfeb4c37feaeeb7fb1a88389011e5533600262447403b1d3&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.2" + }, + { + "name": "es-define-property", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/es-define-property@1.0.0?checksum=sha512:8f16b22ca4a1ac4aaacc9d1eba641b5614d840cdbb09f4f54f7e7e8028031682fcd892ec5ea4c9efacefe80d182ce8049cb50cbcbcec0ec188ae5f0d1694f681&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.0" + }, + { + "name": "es-errors", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/es-errors@1.3.0?checksum=sha512:65fe47d8ac6ddb18d3bdb26f3f66562c4202c40ea3fa1026333225ca9cb8c5c060d6f2959f1f3d5b2d066d2fa47f9730095145cdd0858765d20853542d2e9cb3&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.3.0" + }, + { + "name": "escape-html", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/escape-html@1.0.3?checksum=sha512:3624aea59e0e7ae1b0afaf251887b29bf92c219309a1d506392099fc54a74f172b7a46efaab81d53194938ca628da299563009ad6ac6b3fe89cbc38cbb28fda3&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.3" + }, + { + "name": "etag", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/etag@1.8.1?checksum=sha512:6882f9171ee66b055adf4d1a976067104e2236fa35a844f12eb3c8fe8d392fbcfa828edf0b0d49e844266cae05989d804bb920545fca1195ae7c17dd0a531c3e&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.8.1" + }, + { + "name": "express", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/express@4.18.2?checksum=sha512:e7f3ec2fa8863dd7d0fe528cd54ba27a5620bf7054a097f3d5a53053dbc767e27b832bf07505c510120421ac5e19fd0621cade013372044c6d6a58ac0dbb8ca9&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.18.2" + }, + { + "name": "finalhandler", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/finalhandler@1.2.0?checksum=sha512:e6e5dc5157ed9503059d60bdaaefecbe45afdc64ddd8f7d484aff73cb9183407bb15ba8932ddf9d791dac44e9e44bef819db2b8a2c2e8e26b075a0750691084a&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.2.0" + }, + { + "name": "first-pkg", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/first-pkg?vcs_url=git%2Bhttps://github.com/cachito-testing/cachi2-yarn.git%40db27afd28d5b5fdc349f3ffb12b0f58140f6be32#first-pkg", + "type": "library" + }, + { + "name": "follow-redirects", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/follow-redirects@1.15.9?checksum=sha512:81ec381ac5e2ccd81da11caa9b27cc1f20265ec64393a987912c59142cd6f60c256ed396e38083b586af33ecbeef6a83fd5ab6979e7490c179d834fc7ce2c9a9&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.15.9" + }, + { + "name": "form-data", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/form-data@4.0.0?checksum=sha512:1131249521a2e6dd10319ba25e803f43abdc9f170b40fe6f76e812a6e0328ba4951a2d9c94f3e9fb180486e31a1c2fb31a09f7d4a776df95b7e5fec7ca491ac3&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.0.0" + }, + { + "name": "forwarded", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/forwarded@0.2.0?checksum=sha512:6ee446d1fa41b511d24c238049eea10f6e7cb44b9b16844b6f864d03a3713151cdc3680e7301e8f70c9a6e5ccccce039cfdc40f4bd4a36393f36de8c4fd698a3&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.2.0" + }, + { + "name": "fresh", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/fresh@0.5.2?checksum=sha512:cc9da6418335f2b1053ae75e57819285318843b45bcc0ee8cdb53d23f5c1a66ee4aa0332c209b294cc171f16499a45686249daf5dda95575573dd6133fd7a3f1&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.5.2" + }, + { + "name": "function-bind", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/function-bind@1.1.2?checksum=sha512:ed71cdc47eea5fdc46e66230c6486e993a31fcc21135c3a00ebc56b0cb76a40af6dd61e9e8cad194dec50521690a9afea153b417be38894811f369c931f1b648&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.1.2" + }, + { + "name": "get-intrinsic", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/get-intrinsic@1.2.4?checksum=sha512:e6e621b091fc549053bfba2c960e01ce7258843a1123ac1a602c4c9827674eb702ac703f7c214aa13173d8928a1341dd0c5505effa10ba1cee99724aee968145&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.2.4" + }, + { + "name": "gopd", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/gopd@1.0.1?checksum=sha512:77ae5b36521a771be96ff03669b55d96a2aa579eb78ee4676755ad93ab35b0847cb8db1747bd31a88cd5ab155fd5e4ea0ee9f04f632473311e69ecc2293661c0&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.1" + }, + { + "name": "has-property-descriptors", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/has-property-descriptors@1.0.2?checksum=sha512:e7924d2ae216fafab829ed418ce4e333661cb5022f093ec61731f099f64f1a8e709eb82489dd1842d9c095e152aae9999b86b3de7d814be7ab6f2e62a49760ae&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.2" + }, + { + "name": "has-proto", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/has-proto@1.0.3?checksum=sha512:489d5a999009522652f8f86c54b7f9b46c9d95a541f04745a5a48ee209a250a50ec64f2ace7e40232e19789526876db39c8764fee300513da9977171cd5507f9&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.3" + }, + { + "name": "has-symbols", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/has-symbols@1.0.3?checksum=sha512:9772c2b85e8c8033704c32a47581848a1623b79a513db120e3aaed9669d23e551b82607c2ce22b2896d86050526e73da25ec4c2ad88f3bc8667918d1cf64ddf8&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.3" + }, + { + "name": "hasown", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/hasown@2.0.2?checksum=sha512:d21254f5208fbe633320175916a34f5d66ba76a87b59d1f470823dcbe0b24bcac6de72f8f01725adaf4798a8555541f23d6347e58ef10f0001edb7e04a391431&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.2" + }, + { + "name": "http-errors", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/http-errors@2.0.0?checksum=sha512:16dc2b1bf7ae0736848d8791a8e825cbb1b4aaf8a25e82569ef107d99d6994175781bca3bf7e291d349bf73a1e1ccc83cb7dfe0d6cb95adf56a3e4d446d39849&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.0" + }, + { + "name": "iconv-lite", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/iconv-lite@0.4.24?checksum=sha512:bf73179d901cbe7cb091350466898801cb657bb4575de79d391df5c3097b565ca85cee108bd6abbd27a73505a77b54dc4708422f51f02c8db56c4a9da63f3fac&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.4.24" + }, + { + "name": "inherits", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/inherits@2.0.4?checksum=sha512:93fbc6697e3f6256b75b3c8c0af4d039761e207bea38ab67a8176ecd31e9ce9419cc0b2428c859d8af849c189233dcc64a820578ca572b16b8758799210a9ec1&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.4" + }, + { + "name": "ipaddr.js", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/ipaddr.js@1.9.1?checksum=sha512:d0a23feb4ef1a31493a07ec68cdd457d26cba14d3e6ed4e2723b1049642587f859ca437c2a998c7fbb98c0f5b747e6a467a47fc35f199574870585e26143cede&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.9.1" + }, + { + "name": "lodash", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/lodash@4.17.21?checksum=sha512:bf690311ee7b95e713ba568322e3533f2dd1cb880b189e99d4edef13592b81764daec43e2c54c61d5c558dc5cfb35ecb85b65519e74026ff17675b6f8f916f4a&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "4.17.21" + }, + { + "name": "media-typer", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/media-typer@0.3.0?checksum=sha512:76afaa7a543d6a41e970e97f8145514f15483a4009d70477400bdbe11b158d2f285681630c64dcebbf702589949a49d41791f030b3a06f93be6b72b17d66a93d&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.3.0" + }, + { + "name": "merge-descriptors", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/merge-descriptors@1.0.1?checksum=sha512:7028ba837fd9af58aa422eb249bb1e3355efa286bdf0dd30df58f3518ad73d7db1a8e6e61461c9d2d439bbbe07de6561ef02e8b93b1e672608ab7f60f1c369d7&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.1" + }, + { + "name": "methods", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/methods@1.1.2?checksum=sha512:89c9401de36a366ebccc5b676747bed4bdb250876fccda1ab8a53858103756f1ffbcf162785eea7d197051953e0c0f4ff5b3d7212f74ba5c68528087db7b15db&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.1.2" + }, + { + "name": "mime-db", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/mime-db@1.52.0?checksum=sha512:b0f538b95edd625bed589c70c311c3d0fba285536213b4f201b439496c43081f66518bce82ba103b061040e28f27c0886c4fb51135653a82b5502da7537818be&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.52.0" + }, + { + "name": "mime-types", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/mime-types@2.1.35?checksum=sha512:64363e6cf9b9cd34c5f98a42ac053d9cad148080983d3d10b53d4d65616fe2cfbe4cd91c815693d20ebee11dae238323423cf2b07075cf1b962f9d21cda7978b&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.1.35" + }, + { + "name": "mime", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/mime@1.6.0?checksum=sha512:c74567f2ca48fb0b89d4ee92ee09db69083c3f187834d1dbeca4883661162a23c4e1128ea65be28e7f8d92662699180febc99cef48f611b793151b2bb306907a&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.6.0" + }, + { + "name": "ms", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/ms@2.0.0?checksum=sha512:4e9a7ad0fe885090d3b8eabfe59f1c76c93326e8dfc2a7ce4e4af02308fb211212a679099d3e92c89e0f08f9c63281630bd75d85a979295218b40b7dee2c74e4&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.0" + }, + { + "name": "ms", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/ms@2.1.3?checksum=sha512:e85973b9b4cb646dc9d9afcd542025784863ceae68c601f268253dc985ef70bb2fa1568726afece715c8ebf5d73fab73ed1f7100eb479d23bfb57b45dd645394&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.1.3" + }, + { + "name": "negotiator", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/negotiator@0.6.3?checksum=sha512:f8452ca863cbb0cfa3ff37428598ec9d7e758385eb1c53885f07e70953c695093f9398226a470ab2ec4239b051bba0d29bda29c3f3bab2559b25d82140ce1b06&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.6.3" + }, + { + "name": "object-inspect", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/object-inspect@1.13.2?checksum=sha512:21165246ecc98b29de9805cf62d3dee41a08fd111235847b4d89b9d0c0b932a6dddc99b0e72efdd2c12b630dd5e92af21490fae1bef8a9042cf709f9060fe4de&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.13.2" + }, + { + "name": "on-finished", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/on-finished@2.4.1?checksum=sha512:a15973920dc4340842936cddbfb209c1dfd0503e33d91c51c2991c198f29b0255c09864dab8c189d55802c733e6ebb6e26378f5a2605fc2966b83afc0a1e7e92&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.4.1" + }, + { + "name": "parseurl", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/parseurl@1.3.3?checksum=sha512:0a2c9e3b1153fc96723799b4cfd3df5f0e1208127a4b2833d43a65d30aa39610c418604fd469ec51510bd29eb78681b57dc8f77c7ca75e2f4d60ee2758e2fea9&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.3.3" + }, + { + "name": "path-to-regexp", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/path-to-regexp@0.1.7?checksum=sha512:e43164ba8aa5bf5b9840ac72f2898505e24f41c768134ecabf6b1f7ab0c2ac0ab5a21394f8c483b300c86e7c7760033ad2a20e9d86b9df00615d6d046cca27ad&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.1.7" + }, + { + "name": "proxy-addr", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/proxy-addr@2.0.7?checksum=sha512:96542c30b4940d43d3e388ddad4fcedfbaa59e27e2b433fe670ae699972848ac8b2afb59c69c95d27dbf6c3fcde2d040019fe024475953b28cadaa0ad7e5d802&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.7" + }, + { + "name": "proxy-from-env", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/proxy-from-env@1.1.0?checksum=sha512:0fece439109b03d7f5b5d5912b445a091dc63efe7470cc5caf3e17f24e4b4d2503d43930e3b98a24465036e9c8b514e45b082d6944a8d515454481bd65788562&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.1.0" + }, + { + "name": "qs", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/qs@6.11.0?checksum=sha512:32f8e830227011aad26d4624e4efa79a84b34aeb52b13c05f39cdc1cf43d3ab945a193982236aa040248a885e3a6dc83e6f4e1c46ab9d97bbf31a273464224e1&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "6.11.0" + }, + { + "name": "range-parser", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/range-parser@1.2.1?checksum=sha512:1eb82cc7ea2baa8ca09e68456ca68713a736f7a27e1d30105e8c4417a80dba944e9a6189468cb37c6ddc700bdea8206bc2bff6cb143905577f1939796a03b04a&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.2.1" + }, + { + "name": "raw-body", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/raw-body@2.5.1?checksum=sha512:aaa241b44c95812d1998f19d0853d627716b7a8aaf1b83154259ff902805ece96af7921b3a9d3f056c8cc1b76d9f8553be433c63b921090d97824fed72b0978a&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.5.1" + }, + { + "name": "safe-buffer", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/safe-buffer@5.2.1?checksum=sha512:ae9dd2a34eca71d9a629b1af81a37141226bedb1954959394bd12ad45fa9a5b468ef4f9879a0f1930e4377c34f37e183e9b8e7626d95b8fb825e6a6e62f9825d&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "5.2.1" + }, + { + "name": "safer-buffer", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/safer-buffer@2.1.2?checksum=sha512:619a372bcd920fb462ca2d04d4440fa232f3ee4a5ea6749023d2323db1c78355d75debdbe5d248eeda72376003c467106c71bbbdcc911e4d1c6f0a9c42b894b6&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.1.2" + }, + { + "name": "second-pkg", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/second-pkg?vcs_url=git%2Bhttps://github.com/cachito-testing/cachi2-yarn.git%40db27afd28d5b5fdc349f3ffb12b0f58140f6be32#second-pkg", + "type": "library" + }, + { + "name": "send", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/send@0.18.0?checksum=sha512:aaa5b3b8e8d214ebaa3e315ee0d3ac30b69f4e8410c0148e1294be17012ddc0d95def2ae6d3aae4f7be62d3429160317a7c02515616e3f5a8a68964eb4fa555e&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "0.18.0" + }, + { + "name": "serve-static", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/serve-static@1.15.0?checksum=sha512:5c6b910cd8d75228ec50bd2f97a9d20fb730511bb31208256ce685b9933d8379300d7396553724d232f38cfcc60fe4dacd66dba1962ee76ffdfd73dd5209def6&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.15.0" + }, + { + "name": "set-function-length", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/set-function-length@1.2.2?checksum=sha512:a6045ce21278fec363582492f409a74b8d31ddb34c0d39271e02f951a3014ccc899d4f741205a1d51cfe302f5e16ee01b8dfd4c198ca42e63fd6fdeb33b1cc7e&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.2.2" + }, + { + "name": "setprototypeof", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/setprototypeof@1.2.0?checksum=sha512:1392c35fb5aba7ce4a8a5e5b859bf8ea3f2339e6e82aae4932660cde05467461fcc45a4f59750cb0dae53830ab928c4c11e362fd7648c2e46f6385cdc18309a7&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.2.0" + }, + { + "name": "side-channel", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/side-channel@1.0.6?checksum=sha512:7c35bf119e90f5188ef1e146f078feeeefe85be5eb3d320287008e336fad87603a39b943b58608a6f7bd9be2af23d6780bda9211795a191e9b4c460745eba094&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.6" + }, + { + "name": "statuses", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/statuses@2.0.1?checksum=sha512:470340f59ffb3eb2b4eab60b23314c95a17e97bde2c29ceca9120581b30b6d370b0fa70e6a8f364da59e7cf5d0bc1d9f382e008ee612127752ecdfe64c26e475&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "2.0.1" + }, + { + "name": "toidentifier", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/toidentifier@1.0.1?checksum=sha512:a39b123ca12483f0c840d987e37574fee7ab2eba7355e764521f2d18dbda797a5fa6ec2329e9e54a8c7fd8efc14e5654b447be246eece58844cfad3c3e500744&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.1" + }, + { + "name": "type-is", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/type-is@1.6.18?checksum=sha512:4e444aafdb144f1107f0c75fb8248fed58b3272cd134c8e3d89d9da3626bdcaca6e7df0955d124b2eccf4029e514f5b8932f50fa203e99af411a6d3a5d0072f2&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.6.18" + }, + { + "name": "unpipe", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/unpipe@1.0.0?checksum=sha512:a63cb66d8852b2e7f05a52b03dcfa5ddc37bfb0b8994aeaecf461d2443a54036e5ea3a3f6253e2e266fc6a0524542f0117b57c36ecdec8f36a464b00de1ced29&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.0" + }, + { + "name": "utils-merge", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/utils-merge@1.0.1?checksum=sha512:a4c653bc8913d5df93146bc33aaa1d39c971d105a49208ba4dda1af200bc7df18002acfda733d36560326dbb071e8103ff3b4cb64bff5686136324a1527f3584&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.0.1" + }, + { + "name": "vary", + "properties": [ + { + "name": "cachi2:found_by", + "value": "cachi2" + } + ], + "purl": "pkg:npm/vary@1.1.2?checksum=sha512:04d19b58b7ddd1e50f69b8645d4566d23f2ebaf444c93879a2f45afddca8c3f06a01b649c82fb97d4f88cd03b39802b362a6110084a8461750af778867f3d7aa&repository_url=https://registry.yarnpkg.com", + "type": "library", + "version": "1.1.2" + } + ], "metadata": { "tools": [ { diff --git a/tests/unit/package_managers/yarn_classic/test_main.py b/tests/unit/package_managers/yarn_classic/test_main.py index edf54fdbb..cefdc0799 100644 --- a/tests/unit/package_managers/yarn_classic/test_main.py +++ b/tests/unit/package_managers/yarn_classic/test_main.py @@ -1,3 +1,4 @@ +import itertools import json from pathlib import Path from typing import Any, Iterable @@ -10,6 +11,7 @@ from cachi2.core.models.output import BuildConfig, EnvironmentVariable, RequestOutput from cachi2.core.models.sbom import Component from cachi2.core.package_managers.yarn_classic.main import ( + MIRROR_DIR, _fetch_dependencies, _generate_build_environment_variables, _get_prefetch_environment_variables, @@ -53,19 +55,48 @@ def test_generate_build_environment_variables( @pytest.mark.parametrize( - "input_request, components", - [ + "input_request, package_components", + ( pytest.param( [{"type": "yarn", "path": "."}], - [], + [ + [ + Component( + name="foo", + purl="pkg:npm/foo@1.0.0", + version="1.0.0", + ), + Component(name="bar", purl="pkg:npm/bar@2.0.0", version="2.0.0"), + ], + ], id="single_input_package", ), pytest.param( [{"type": "yarn", "path": "."}, {"type": "yarn", "path": "./path"}], - [], + [ + [ + Component( + name="foo", + purl="pkg:npm/foo@1.0.0", + version="1.0.0", + ), + ], + [ + Component( + name="bar", + purl="pkg:npm/bar@2.0.0", + version="2.0.0", + ), + Component( + name="baz", + purl="pkg:npm/baz@3.0.0", + version="3.0.0", + ), + ], + ], id="multiple_input_packages", ), - ], + ), indirect=["input_request"], ) @mock.patch("cachi2.core.package_managers.yarn_classic.main._resolve_yarn_project") @@ -74,26 +105,28 @@ def test_fetch_yarn_source( mock_create_project: mock.Mock, mock_resolve_yarn: mock.Mock, input_request: Request, + package_components: list[Component], yarn_classic_env_variables: list[EnvironmentVariable], - components: list[Component], ) -> None: - expected_output = RequestOutput( - components=components, - build_config=BuildConfig(environment_variables=yarn_classic_env_variables), - ) package_dirs = [ input_request.source_dir.join_within_root(p.path) for p in input_request.packages ] projects = [_prepare_project(path, {}) for path in package_dirs] + mock_create_project.side_effect = projects + mock_resolve_yarn.side_effect = package_components output = fetch_yarn_source(input_request) mock_create_project.assert_has_calls([mock.call(path) for path in package_dirs]) mock_resolve_yarn.assert_has_calls([mock.call(p, input_request.output_dir) for p in projects]) - assert input_request.output_dir.join_within_root("deps/yarn-classic").path.exists() + expected_output = RequestOutput( + components=list(itertools.chain.from_iterable(package_components)), + build_config=BuildConfig(environment_variables=yarn_classic_env_variables), + ) assert output == expected_output + assert input_request.output_dir.join_within_root(MIRROR_DIR).path.exists() @mock.patch("cachi2.core.package_managers.yarn_classic.main.resolve_packages") @@ -122,7 +155,7 @@ def test_resolve_yarn_project( mock_fetch_dependencies.assert_called_once_with( project.source_dir, mock_prefetch_env_vars.return_value ) - mock_resolve_packages.assert_called_once_with(project) + mock_resolve_packages.assert_called_once_with(project, output_dir.join_within_root(MIRROR_DIR)) @mock.patch("cachi2.core.package_managers.yarn_classic.main.run_yarn_cmd") diff --git a/tests/unit/package_managers/yarn_classic/test_resolver.py b/tests/unit/package_managers/yarn_classic/test_resolver.py index ef6686a84..44be48ad4 100644 --- a/tests/unit/package_managers/yarn_classic/test_resolver.py +++ b/tests/unit/package_managers/yarn_classic/test_resolver.py @@ -1,11 +1,17 @@ +import io +import json import re -from pathlib import Path +import tarfile from unittest import mock +from urllib.parse import quote +import git import pytest from pyarn.lockfile import Package as PYarnPackage +from cachi2.core.checksum import ChecksumInfo from cachi2.core.errors import PackageRejected, UnexpectedFormat +from cachi2.core.package_managers.yarn_classic.main import MIRROR_DIR from cachi2.core.package_managers.yarn_classic.project import PackageJson from cachi2.core.package_managers.yarn_classic.resolver import ( FilePackage, @@ -21,11 +27,13 @@ _is_from_npm_registry, _is_git_url, _is_tarball_url, + _read_name_from_tarball, _YarnClassicPackageFactory, resolve_packages, ) from cachi2.core.package_managers.yarn_classic.workspaces import Workspace from cachi2.core.rooted_path import PathOutsideRoot, RootedPath +from cachi2.core.scm import get_repo_id VALID_GIT_URLS = [ "git://git.host.com/some/path", @@ -103,9 +111,12 @@ def test__is_from_npm_registry_can_parse_incorrect_registry_urls() -> None: assert not _is_from_npm_registry("https://example.org/fecha.tar.gz") -@pytest.mark.parametrize( - "pyarn_package, expected_package", - [ +@mock.patch("cachi2.core.package_managers.yarn_classic.resolver._read_name_from_tarball") +def test_create_package_from_pyarn_package( + mock_read_name_from_tarball: mock.Mock, + rooted_tmp_path: RootedPath, +) -> None: + test_cases: list[tuple[PYarnPackage, YarnClassicPackage]] = [ ( PYarnPackage( name="foo", @@ -132,7 +143,7 @@ def test__is_from_npm_registry_can_parse_incorrect_registry_urls() -> None: name="foo", version="1.0.0", dev=False, - relpath=Path("path/foo-1.0.0.tgz"), + path=rooted_tmp_path.join_within_root("path/foo-1.0.0.tgz"), ), ), ( @@ -145,7 +156,7 @@ def test__is_from_npm_registry_can_parse_incorrect_registry_urls() -> None: name="foo", version="0.0.0", dev=False, - relpath=Path("link"), + path=rooted_tmp_path.join_within_root("link"), ), ), ( @@ -174,17 +185,18 @@ def test__is_from_npm_registry_can_parse_incorrect_registry_urls() -> None: url="https://example.com/foo-1.0.0.tgz", ), ), - ], -) -def test_create_package_from_pyarn_package( - pyarn_package: PYarnPackage, expected_package: YarnClassicPackage, rooted_tmp_path: RootedPath -) -> None: - runtime_deps = ( - set() if expected_package.dev else set({f"{pyarn_package.name}@{pyarn_package.version}"}) - ) + ] - package_factory = _YarnClassicPackageFactory(rooted_tmp_path, runtime_deps) - assert package_factory.create_package_from_pyarn_package(pyarn_package) == expected_package + for pyarn_package, expected_package in test_cases: + mock_read_name_from_tarball.return_value = expected_package.name + runtime_deps = ( + set() + if expected_package.dev + else set({f"{pyarn_package.name}@{pyarn_package.version}"}) + ) + + package_factory = _YarnClassicPackageFactory(rooted_tmp_path, rooted_tmp_path, runtime_deps) + assert package_factory.create_package_from_pyarn_package(pyarn_package) == expected_package def test_create_package_from_pyarn_package_fail_absolute_path(rooted_tmp_path: RootedPath) -> None: @@ -198,7 +210,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, set()) + package_factory = _YarnClassicPackageFactory(rooted_tmp_path, rooted_tmp_path, set()) with pytest.raises(PackageRejected, match=re.escape(error_msg)): package_factory.create_package_from_pyarn_package(pyarn_package) @@ -212,7 +224,7 @@ def test_create_package_from_pyarn_package_fail_path_outside_root( path="../path/outside/root", ) - package_factory = _YarnClassicPackageFactory(rooted_tmp_path, set()) + package_factory = _YarnClassicPackageFactory(rooted_tmp_path, rooted_tmp_path, set()) with pytest.raises(PathOutsideRoot): package_factory.create_package_from_pyarn_package(pyarn_package) @@ -226,7 +238,7 @@ def test_create_package_from_pyarn_package_fail_unexpected_format( url="ftp://some-tarball.tgz", ) - package_factory = _YarnClassicPackageFactory(rooted_tmp_path, set()) + package_factory = _YarnClassicPackageFactory(rooted_tmp_path, rooted_tmp_path, set()) with pytest.raises(UnexpectedFormat): package_factory.create_package_from_pyarn_package(pyarn_package) @@ -253,7 +265,7 @@ def test__get_packages_from_lockfile( mock.call(mock_pyarn_package_2), ] - output = _get_packages_from_lockfile(rooted_tmp_path, mock_yarn_lock, set()) + output = _get_packages_from_lockfile(rooted_tmp_path, rooted_tmp_path, mock_yarn_lock, set()) mock_pyarn_lockfile.packages.assert_called_once() mock_create_package.assert_has_calls(create_package_expected_calls) @@ -288,15 +300,16 @@ def test_resolve_packages( mock_get_lockfile_packages.return_value = lockfile_packages mock_get_workspace_packages.return_value = workspace_packages - output = resolve_packages(project) + output = resolve_packages(project, rooted_tmp_path.join_within_root(MIRROR_DIR)) mock_extract_workspaces.assert_called_once_with(rooted_tmp_path) mock_get_yarn_lock.assert_called_once_with(yarn_lock_path) - mock_get_main_package.assert_called_once_with(project.package_json) + mock_get_main_package.assert_called_once_with(project.source_dir, project.package_json) mock_get_workspace_packages.assert_called_once_with( rooted_tmp_path, mock_extract_workspaces.return_value ) mock_get_lockfile_packages.assert_called_once_with( rooted_tmp_path, + rooted_tmp_path.join_within_root(MIRROR_DIR), mock_get_yarn_lock.return_value, find_runtime_deps.return_value, ) @@ -311,10 +324,10 @@ def test__get_main_package(rooted_tmp_path: RootedPath) -> None: expected_output = WorkspacePackage( name="foo", version="1.0.0", - relpath=rooted_tmp_path.subpath_from_root, + path=rooted_tmp_path, ) - output = _get_main_package(package_json) + output = _get_main_package(rooted_tmp_path, package_json) assert output == expected_output @@ -328,7 +341,7 @@ def test__get_main_package_no_name(rooted_tmp_path: RootedPath) -> None: ) with pytest.raises(PackageRejected, match=error_msg): - _get_main_package(package_json) + _get_main_package(rooted_tmp_path, package_json) def test__get_workspace_packages(rooted_tmp_path: RootedPath) -> None: @@ -348,9 +361,120 @@ def test__get_workspace_packages(rooted_tmp_path: RootedPath) -> None: WorkspacePackage( name="foo", version="1.0.0", - relpath=workspace_path.path.relative_to(rooted_tmp_path.path), + path=workspace_path, ) ] output = _get_workspace_packages(rooted_tmp_path, [workspace]) assert output == expected + + +def test_package_purl(rooted_tmp_path_repo: RootedPath) -> None: + repo = git.Repo(rooted_tmp_path_repo) + repo.create_remote("origin", "https://github.com/org/repo.git") + + example_repo_id = get_repo_id(repo) + example_vcs_url = example_repo_id.as_vcs_url_qualifier() + purl_vcs_url = quote(example_vcs_url, safe=":/") + + example_sri_integrity = "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==" + example_checksum = ChecksumInfo.from_sri(example_sri_integrity) + + yarn_classic_packages: list[tuple[YarnClassicPackage, str]] = [ + ( + RegistryPackage( + name="npm-registry-pkg", + version="1.0.0", + integrity=example_sri_integrity, + url="https://registry.npmjs.org", + ), + f"pkg:npm/npm-registry-pkg@1.0.0?checksum={str(example_checksum)}", + ), + ( + RegistryPackage( + name="yarn-registry-pkg", + version="2.0.0", + integrity=example_sri_integrity, + url="https://registry.yarnpkg.com", + ), + f"pkg:npm/yarn-registry-pkg@2.0.0?checksum={str(example_checksum)}&repository_url=https://registry.yarnpkg.com", + ), + ( + GitPackage( + name="git-pkg", + version="3.0.0", + url=f"https://github.com/org/repo.git#{repo.head.commit.hexsha}", + ), + f"pkg:npm/git-pkg@3.0.0?vcs_url={purl_vcs_url}", + ), + ( + UrlPackage( + name="url-pkg", + version="4.0.0", + url="https://example.com/package.tar.gz", + ), + "pkg:npm/url-pkg@4.0.0?download_url=https://example.com/package.tar.gz", + ), + ( + FilePackage( + name="file-pkg", + version="5.0.0", + path=rooted_tmp_path_repo.join_within_root("path/to/package"), + ), + f"pkg:npm/file-pkg@5.0.0?vcs_url={purl_vcs_url}#path/to/package", + ), + ( + WorkspacePackage( + name="workspace-pkg", + version="6.0.0", + path=rooted_tmp_path_repo.join_within_root("workspace/package"), + ), + f"pkg:npm/workspace-pkg@6.0.0?vcs_url={purl_vcs_url}#workspace/package", + ), + ( + LinkPackage( + name="link-pkg", + version="7.0.0", + path=rooted_tmp_path_repo.join_within_root("link/to/package"), + ), + f"pkg:npm/link-pkg@7.0.0?vcs_url={purl_vcs_url}#link/to/package", + ), + ] + + for package, expected_purl in yarn_classic_packages: + assert package.purl == expected_purl + + +def mock_tarball(path: RootedPath, package_json_content: dict[str, str]) -> RootedPath: + tarball_path = path.join_within_root("package.tar.gz") + + if not package_json_content: + with tarfile.open(tarball_path, mode="w:gz") as tar: + tar.addfile(tarfile.TarInfo(name="foo"), io.BytesIO()) + + return tarball_path + + with tarfile.open(tarball_path, mode="w:gz") as tar: + package_json_bytes = json.dumps(package_json_content).encode("utf-8") + info = tarfile.TarInfo(name="package.json") + info.size = len(package_json_bytes) + tar.addfile(info, io.BytesIO(package_json_bytes)) + + return tarball_path + + +def test_successful_name_extraction(rooted_tmp_path: RootedPath) -> None: + tarball_path = mock_tarball(path=rooted_tmp_path, package_json_content={"name": "foo"}) + assert _read_name_from_tarball(tarball_path) == "foo" + + +def test_no_package_json(rooted_tmp_path: RootedPath) -> None: + tarball_path = mock_tarball(path=rooted_tmp_path, package_json_content={}) + with pytest.raises(ValueError, match="No package.json found"): + _read_name_from_tarball(tarball_path) + + +def test_missing_name_field(rooted_tmp_path: RootedPath) -> None: + tarball_path = mock_tarball(path=rooted_tmp_path, package_json_content={"key": "foo"}) + with pytest.raises(ValueError, match="No 'name' field found"): + _read_name_from_tarball(tarball_path)