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
33 changes: 27 additions & 6 deletions src/apm_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ def _validate_and_add_packages_to_apm_yml(packages, dry_run=False):

current_deps = data['dependencies']['apm'] or []
validated_packages = []
conflicts = []

# First, validate all packages
_rich_info(f"Validating {len(packages)} package(s)...")
Expand All @@ -346,9 +347,10 @@ def _validate_and_add_packages_to_apm_yml(packages, dry_run=False):
_rich_error(f"Invalid package format: {package}. Use 'owner/repo' format.")
continue

# Check if package is already in dependencies
# Check if package is already in dependencies -> treat as conflict
if package in current_deps:
_rich_warning(f"Package {package} already exists in apm.yml")
conflicts.append(package)
_rich_error(f"Package conflict: {package} already exists in apm.yml")
continue

# Validate package exists and is accessible
Expand All @@ -359,6 +361,10 @@ def _validate_and_add_packages_to_apm_yml(packages, dry_run=False):
_rich_error(f"βœ— {package} - not accessible or doesn't exist")

if not validated_packages:
if conflicts and not dry_run:
_rich_error(f"Aborting due to {len(conflicts)} package conflict(s)")
sys.exit(1)

if dry_run:
_rich_warning("No new valid packages to add")
return []
Expand Down Expand Up @@ -445,10 +451,25 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run):
apm install --dry-run # Show what would be installed
"""
try:
# Check if apm.yml exists
if not Path('apm.yml').exists():
_rich_error("No apm.yml found. Run 'apm init' first.")
sys.exit(1)
# Check if apm.yml exists. If packages were provided, create a minimal
# project (apm.yml + .apm/ structure) so the install command can add
# package entries automatically. Otherwise, require the user to run
# 'apm init' first.
apm_yml_path = Path('apm.yml')
if not apm_yml_path.exists():
if packages:
_rich_info("No apm.yml found - creating minimal project so packages can be added")
# Create a minimal default config and project files
try:
default_config = _get_default_config(Path.cwd().name)
_create_project_files(default_config)
_rich_success("Created minimal apm.yml and .apm/ structure")
except Exception as e:
_rich_error(f"Failed to create minimal project files: {e}")
sys.exit(1)
else:
_rich_error("No apm.yml found. Run 'apm init' first.")
sys.exit(1)

# If packages are specified, validate and add them to apm.yml first
if packages:
Expand Down
65 changes: 65 additions & 0 deletions tests/test_install_auto_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import os
from pathlib import Path

import yaml
import pytest
from click.testing import CliRunner

import apm_cli.cli as cli_mod
from apm_cli.cli import cli


def test_install_creates_minimal_project(tmp_path, monkeypatch):
"""When apm.yml is missing and a package is passed to `apm install`,
the CLI should create a minimal apm.yml and .apm/ structure so the
installation can proceed (dry-run used to avoid network operations).
"""
runner = CliRunner()
# Avoid network/git checks during validation
monkeypatch.setattr(cli_mod, "_validate_package_exists", lambda pkg: True)

# CliRunner.invoke in this environment does not accept cwd reliably, so
# change directory manually for the duration of the invocation.
old_cwd = Path.cwd()
try:
os.chdir(tmp_path)
result = runner.invoke(cli, ["install", "owner/repo", "--dry-run"])
finally:
os.chdir(old_cwd)
assert result.exit_code == 0, result.output

apm_yml = tmp_path / "apm.yml"
assert apm_yml.exists(), "apm.yml should be created when running install with packages"

# Load config to ensure it is valid YAML and contains expected keys
cfg = yaml.safe_load(apm_yml.read_text())
assert isinstance(cfg, dict)
assert "dependencies" in cfg


def test_install_conflict_aborts(tmp_path, monkeypatch):
"""If the package already exists in apm.yml, `apm install <pkg>` should
report a conflict and abort (non-zero exit)."""
runner = CliRunner()
# Create an apm.yml that already contains the package
content = {
"name": "proj",
"version": "1.0.0",
"dependencies": {"apm": ["owner/repo"], "mcp": []},
}
apm_yml = tmp_path / "apm.yml"
apm_yml.write_text(yaml.safe_dump(content))

# Avoid network/git checks during validation
monkeypatch.setattr(cli_mod, "_validate_package_exists", lambda pkg: True)

old_cwd = Path.cwd()
try:
os.chdir(tmp_path)
result = runner.invoke(cli, ["install", "owner/repo"])
finally:
os.chdir(old_cwd)

# The CLI should exit with non-zero status due to conflict
assert result.exit_code != 0
assert "Package conflict" in result.output or "Aborting due to" in result.output