Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Fix `apm init` showing overwrite confirmation prompt three times on Windows CP950 terminals (#602)
- Fix `apm marketplace add` silently failing for private repos by using credentials when probing `marketplace.json` (#701)
- Pin codex setup to `rust-v0.118.0` for security and reproducibility; update config to `wire_api = "responses"` (#663)
- Propagate headers and environment variables through OpenCode MCP adapter with defensive copies to prevent mutation (#622)
Expand Down
10 changes: 1 addition & 9 deletions src/apm_cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
_create_plugin_json,
_get_console,
_get_default_config,
_lazy_confirm,
_rich_blank_line,
_validate_plugin_name,
)
Expand Down Expand Up @@ -75,14 +74,7 @@ def init(ctx, project_name, yes, plugin, verbose):
logger.warning("apm.yml already exists")

if not yes:
Confirm = _lazy_confirm()
if Confirm:
try:
confirm = Confirm.ask("Continue and overwrite?")
except Exception:
confirm = click.confirm("Continue and overwrite?")
else:
confirm = click.confirm("Continue and overwrite?")
confirm = click.confirm("Continue and overwrite?")

if not confirm:
logger.progress("Initialization cancelled.")
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/test_init_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,53 @@ def test_init_existing_project_interactive_cancel(self):
finally:
os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup

def test_init_existing_project_confirm_prompt_shown_once(self):
"""Test that overwrite confirmation prompt appears exactly once (#602).

On Windows CP950 terminals, Rich Confirm.ask() could fail on encoding,
retry internally, then fall back to click.confirm(), showing the prompt
three times. After the fix, only click.confirm() is used.
"""
with tempfile.TemporaryDirectory() as tmp_dir:
os.chdir(tmp_dir)
try:

# Create existing apm.yml
Path("apm.yml").write_text("name: existing-project\nversion: 0.1.0\n")

# Say yes to overwrite, then provide interactive setup input
user_input = "y\nmy-project\n1.0.0\nA description\nAuthor\ny\n"
result = self.runner.invoke(cli, ["init"], input=user_input)

assert result.exit_code == 0
# The overwrite prompt must appear exactly once
assert result.output.count("Continue and overwrite?") == 1
finally:
os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup

def test_init_existing_project_confirm_uses_click(self):
"""Test that overwrite confirmation uses click.confirm, not Rich (#602)."""
with tempfile.TemporaryDirectory() as tmp_dir:
os.chdir(tmp_dir)
try:

# Create existing apm.yml
Path("apm.yml").write_text("name: existing-project\nversion: 0.1.0\n")

with patch("apm_cli.commands.init.click.confirm", return_value=True) as mock_confirm:
result = self.runner.invoke(cli, ["init", "--yes"])
assert result.exit_code == 0
# --yes skips the prompt entirely, so confirm should NOT be called
mock_confirm.assert_not_called()

with patch("apm_cli.commands.init.click.confirm", return_value=False) as mock_confirm:
result = self.runner.invoke(cli, ["init"])
Comment thread
sergio-sisternes-epam marked this conversation as resolved.
assert result.exit_code == 0
mock_confirm.assert_called_once_with("Continue and overwrite?")
Comment thread
sergio-sisternes-epam marked this conversation as resolved.
assert "Initialization cancelled" in result.output
finally:
os.chdir(self.original_dir) # restore CWD before TemporaryDirectory cleanup

def test_init_validates_project_structure(self):
"""Test that init creates minimal project structure."""
with tempfile.TemporaryDirectory() as tmp_dir:
Expand Down
Loading