Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions src/apm_cli/commands/deps/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,41 @@ def _logical_local_display(physical_name: str) -> str:
return physical_name


def _local_basename(path: str) -> str:
"""Cross-platform basename of a local dependency path.

A Windows-form path parsed on POSIX (``C:\\packages\\pkg``) is opaque to
``PurePosixPath`` -- the backslashes are ordinary characters, so ``.name``
returns the whole string and the drive leaks. Route drive/UNC/backslash
paths through ``PureWindowsPath`` and everything else through
``PurePosixPath`` so the leaf name is recovered on either host.
"""
if "\\" in path or PureWindowsPath(path).drive:
return PureWindowsPath(path).name
return PurePosixPath(path).name


def _dep_display_name(dep: LockedDependency) -> str:
"""Get display name for a locked dependency (key@version).

Local deps render a logical, portable identity instead of the lockfile
unique key. For anchored transitive local deps the unique key is an
absolute ``local:/...`` slot (see build_dependency_unique_key), which would
leak the host filesystem path into user-facing tree output. Prefer the
declared relative ``local_path`` (``../pkg``); fall back to the logical
``repo_url`` (``_local/pkg``) when the path is absent or itself absolute
(``/Users/...``, ``~/...``, ``C:\\...``). Remote deps keep their canonical
unique key.
declared relative ``local_path`` (``../pkg``). For an absolute declared path
(``/Users/...``, ``~/...``, ``C:\\...``) derive a hash-free logical
``_local/<basename>`` from ``local_path`` -- never the ``repo_url``, which a
Windows-form path can pollute with the drive and backslashes
(``_local/C:\\packages\\pkg``). Fall back to ``repo_url`` only when
``local_path`` is absent. Remote deps keep their canonical unique key.
"""
if dep.source == "local":
if dep.local_path and not _is_absolute_local_path(dep.local_path):
key = dep.local_path
else:
if not dep.local_path:
key = dep.repo_url
elif _is_absolute_local_path(dep.local_path):
key = f"_local/{_local_basename(dep.local_path)}"
else:
key = dep.local_path
else:
key = dep.get_unique_key()
version = (
Expand Down Expand Up @@ -344,18 +362,22 @@ def _resolve_scope_deps(apm_dir, logger, insecure_only=False):
# (``_local/<hash>/pkg``); report and orphan-check against the
# logical lockfile key (``_local/pkg``) instead of leaking the slot.
# A genuinely orphaned slot has no lockfile entry, so it stays keyed by
# its raw physical identity for correct detection -- but its displayed
# name is always the hash-free logical form.
# its raw physical identity for correct detection -- and only then is
# its displayed name reduced to the hash-free logical form.
logical_name = physical_to_logical.get(org_repo_name, org_repo_name)
display_name = _logical_local_display(logical_name)
is_orphaned = logical_name not in declared_with_ancestors
# Hash-stripping is reserved for a CONFIRMED orphan: a slot with no
# lockfile mapping to a logical key. A declared/mapped slot keeps its
# logical name verbatim so a legitimately hashed logical identity
# (``_local/<12hex>/pkg``) is not corrupted into ``_local/pkg``.
display_name = _logical_local_display(logical_name) if is_orphaned else logical_name
try:
version = "unknown"
if has_apm_yml:
package = APMPackage.from_apm_yml(candidate / APM_YML_FILENAME)
version = package.version or "unknown"
primitives = _count_primitives(candidate)

is_orphaned = logical_name not in declared_with_ancestors
if is_orphaned:
orphaned_packages.append(display_name)

Expand Down
64 changes: 64 additions & 0 deletions tests/unit/commands/test_deps_cli_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,31 @@ def test_local_dep_with_home_prefixed_path_renders_logical_repo_url(self):
assert result == "_local/pkg@latest"
assert "~" not in result

def test_local_dep_windows_path_repo_url_pollution_renders_clean_basename(self):
"""A Windows-form path parsed on POSIX leaves the drive and backslashes
embedded in ``repo_url`` (``_local/C:\\packages\\pkg``). Falling back to
that polluted ``repo_url`` would leak host structure, so the display must
derive a clean cross-platform basename from ``local_path`` instead.

Exercises the real parse -> LockedDependency -> display chain.
"""
from apm_cli.deps.lockfile import LockedDependency
from apm_cli.models.dependency.reference import DependencyReference

ref = DependencyReference.parse(r"C:\packages\pkg")
# Guard the precondition: repo_url is genuinely polluted by the parse.
assert "C:" in ref.repo_url
dep = LockedDependency(
repo_url=ref.repo_url,
source="local",
local_path=ref.local_path,
version="1.0.0",
)
result = _dep_display_name(dep)
assert result == "_local/pkg@1.0.0"
assert "C:" not in result
assert "\\" not in result


# ---------------------------------------------------------------------------
# _resolve_scope_deps - filesystem paths
Expand Down Expand Up @@ -261,6 +286,45 @@ def test_orphan_hashed_slot_is_orphaned_and_shows_no_hash(self, tmp_path):
assert _HASH_SLOT not in name


class TestResolveScopeDepsDeclaredHashSlot:
"""Hash-stripping is a *last resort* reserved for confirmed unmapped
orphans. A declared/locked local slot whose logical key legitimately looks
like ``_local/<12hex>/pkg`` must be preserved verbatim -- stripping it would
corrupt a real identity and mask a mapped dependency as if it were orphaned.
"""

def test_declared_hashed_local_slot_is_preserved_not_stripped(self, tmp_path):
from apm_cli.deps.lockfile import LockedDependency, LockFile, get_lockfile_path

modules_dir = tmp_path / APM_MODULES_DIR
slot = modules_dir / "_local" / _HASH_SLOT / "pkg"
slot.mkdir(parents=True)
(slot / APM_YML_FILENAME).write_text("name: pkg\nversion: 1.0.0\n")

# Declare the slot in the lockfile with a 3-part hashed logical
# ``repo_url``. Its install slot maps elsewhere (``_local/pkg``), so the
# scanned slot stays UNMAPPED yet DECLARED -> not an orphan.
lockfile = LockFile()
lockfile.add_dependency(
LockedDependency(
repo_url=f"_local/{_HASH_SLOT}/pkg",
source="local",
local_path="../pkg",
version="1.0.0",
)
)
lockfile.write(get_lockfile_path(tmp_path))

installed, orphaned = _resolve_scope_deps(tmp_path, _make_logger())

assert installed is not None
names = [package["name"] for package in installed]
# Declared, non-orphan slot: logical name is preserved verbatim.
assert names == [f"_local/{_HASH_SLOT}/pkg"]
assert orphaned == []
assert installed[0]["is_orphaned"] is False


class TestResolveScopeDepsReadFailure:
"""A malformed package apm.yml makes ``APMPackage.from_apm_yml`` raise a
``ValueError`` embedding the absolute apm.yml path. The read-failure
Expand Down
Loading