Skip to content
Draft
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
34 changes: 32 additions & 2 deletions src/apm_cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,23 @@ def _validate_and_add_packages_to_apm_yml(
return validated_packages, outcome


def _prepare_dry_run_manifest_path(
manifest_path: Path,
*,
dry_run: bool,
user_scope: bool,
has_packages: bool,
):
"""Redirect an absent user manifest to temporary storage during previews."""
if not (dry_run and user_scope and has_packages and not manifest_path.exists()):
return manifest_path, None

import tempfile

temp_dir = tempfile.TemporaryDirectory(prefix="apm-dry-run-")
return Path(temp_dir.name) / manifest_path.name, temp_dir


# ---------------------------------------------------------------------------
# MCP CLI helpers (W3 --mcp flag)
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1176,6 +1193,7 @@ def install( # noqa: PLR0913
logger = None
command_result: InstallResult | None = None
transaction: InstallTransaction | None = None
dry_run_manifest_tmp = None
from ..install.service import InstallService

try:
Expand Down Expand Up @@ -1423,7 +1441,8 @@ def install( # noqa: PLR0913
scope = InstallScope.USER if global_ else InstallScope.PROJECT

if scope is InstallScope.USER:
ensure_user_dirs()
if not dry_run:
ensure_user_dirs()
logger.progress("Installing to user scope (~/.apm/)")
_scope_warn = warn_unsupported_user_scope()
if _scope_warn:
Expand All @@ -1434,6 +1453,12 @@ def install( # noqa: PLR0913
apm_dir = get_apm_dir(scope)
# Display name for messages (short for project scope, full for user scope)
manifest_display = str(manifest_path) if scope is InstallScope.USER else APM_YML_FILENAME
manifest_path, dry_run_manifest_tmp = _prepare_dry_run_manifest_path(
manifest_path,
dry_run=dry_run,
user_scope=scope is InstallScope.USER,
has_packages=bool(packages),
)

# Project root for integration (used by both dep and local integration)
from ..core.scope import get_deploy_root
Expand Down Expand Up @@ -1471,7 +1496,10 @@ def install( # noqa: PLR0913
if manifest_targets := manifest_targets_from_target_option(target):
config["targets"] = manifest_targets
_create_minimal_apm_yml(config, target_path=manifest_path)
logger.success(f"Created {manifest_display}")
if dry_run:
logger.progress(f"Dry run: Would create {manifest_display}")
else:
logger.success(f"Created {manifest_display}")
if manifest_targets:
logger.progress(
f"Targets set: {', '.join(manifest_targets)} (persisted to {manifest_display})"
Expand Down Expand Up @@ -1645,6 +1673,8 @@ def install( # noqa: PLR0913
_root_redirect.__exit__(None, None, None)
if transaction is not None:
transaction.__exit__(*sys.exc_info())
if dry_run_manifest_tmp is not None:
dry_run_manifest_tmp.cleanup()
# F5 (#1116): render minimal elapsed-time line on exit paths that
# did not already render the full install summary. Best-effort:
# never let a render failure mask the original exception/exit.
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/regressions/test_issue_2549_global_dry_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from apm_cli.commands.install import _prepare_dry_run_manifest_path


def test_global_dry_run_redirects_absent_manifest_without_creating_user_state(tmp_path):
user_manifest = tmp_path / "home" / ".apm" / "apm.yml"

preview_manifest, temp_dir = _prepare_dry_run_manifest_path(
user_manifest,
dry_run=True,
user_scope=True,
has_packages=True,
)
try:
assert preview_manifest != user_manifest
assert preview_manifest.name == "apm.yml"
assert not user_manifest.parent.exists()
finally:
temp_dir.cleanup()


def test_existing_user_manifest_is_read_in_place(tmp_path):
user_manifest = tmp_path / ".apm" / "apm.yml"
user_manifest.parent.mkdir(parents=True)
user_manifest.write_text("name: test\n", encoding="utf-8")

preview_manifest, temp_dir = _prepare_dry_run_manifest_path(
user_manifest,
dry_run=True,
user_scope=True,
has_packages=True,
)

assert preview_manifest == user_manifest
assert temp_dir is None