diff --git a/src/apm_cli/bundle/local_bundle.py b/src/apm_cli/bundle/local_bundle.py index 264cf61c87..9c442e57b5 100644 --- a/src/apm_cli/bundle/local_bundle.py +++ b/src/apm_cli/bundle/local_bundle.py @@ -179,6 +179,13 @@ def _read_bundle_lockfile(bundle_dir: Path) -> dict[str, Any] | None: return data +def _has_pack_manifest(bundle_dir: Path) -> bool: + """Whether ``apm.lock.yaml`` carries the pack manifest written by ``apm pack``.""" + lockfile = _read_bundle_lockfile(bundle_dir) + pack = (lockfile or {}).get("pack") + return isinstance(pack, dict) and bool(pack) + + def _extract_pack_targets(lockfile: dict[str, Any] | None) -> list[str]: """Return list of pack targets from a parsed bundle lockfile.""" if not lockfile: @@ -345,6 +352,11 @@ def detect_local_bundle(path: Path) -> LocalBundleInfo | None: if path.is_dir(): if not (path / "plugin.json").is_file(): return None + if (path / "apm.yml").is_file() and not _has_pack_manifest(path): + # An APM source package that happens to ship a plugin.json is a normal + # local dependency, not a bundle: only packed bundles carry the pack + # manifest in apm.lock.yaml. + return None return _build_info( path, is_archive=False, diff --git a/tests/unit/bundle/test_local_bundle.py b/tests/unit/bundle/test_local_bundle.py index f00c5c189f..c6639439ac 100644 --- a/tests/unit/bundle/test_local_bundle.py +++ b/tests/unit/bundle/test_local_bundle.py @@ -117,6 +117,27 @@ def _make_plugin_bundle( return bundle +def test_detect_local_bundle_skips_source_package_with_apm_yml(tmp_path: Path) -> None: + """A source package with apm.yml and plugin.json is a dependency, not a bundle.""" + package = tmp_path / "demo-pkg" + package.mkdir() + (package / "apm.yml").write_text("name: demo-pkg\n", encoding="utf-8") + (package / "plugin.json").write_text(json.dumps({"id": "demo-pkg"}), encoding="utf-8") + + assert detect_local_bundle(package) is None + + +def test_detect_local_bundle_keeps_packed_bundle_with_apm_yml(tmp_path: Path) -> None: + """A packed bundle stays a bundle even when it also ships an apm.yml.""" + bundle = _make_plugin_bundle(tmp_path) + (bundle / "apm.yml").write_text("name: demo-pkg\n", encoding="utf-8") + + info = detect_local_bundle(bundle) + + assert info is not None + assert info.source_dir == bundle + + def _make_plugin_tarball(tmp_path: Path, bundle_dir: Path) -> Path: """Archive a bundle directory into .tar.gz.""" tmp_path.mkdir(parents=True, exist_ok=True)