Skip to content
Merged
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
5 changes: 5 additions & 0 deletions codeflash/cli_cmds/github_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class DependencyManager(Enum):
def install_github_actions(override_formatter_check: bool = False, *, skip_confirm: bool = False) -> None:
try:
config, _config_file_path = parse_config_file(override_formatter_check=override_formatter_check)
interactive_stdin = sys.stdin.isatty()

ph("cli-github-actions-install-started")
try:
Expand Down Expand Up @@ -102,6 +103,8 @@ def install_github_actions(override_formatter_check: bool = False, *, skip_confi

if skip_confirm:
benchmark_mode = True
elif not interactive_stdin:
benchmark_mode = False
else:
benchmark_questions = [
inquirer.Confirm("benchmark_mode", message="Run GitHub Actions in benchmark mode?", default=True)
Expand All @@ -126,6 +129,8 @@ def install_github_actions(override_formatter_check: bool = False, *, skip_confi

if skip_confirm:
confirm_creation = True
elif not interactive_stdin:
confirm_creation = False
else:
creation_questions = [
inquirer.Confirm(
Expand Down
20 changes: 14 additions & 6 deletions codeflash/cli_cmds/init_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ def get_toml_key(self) -> str:
}


def confirm_with_default_on_eof(
prompt: str, *, default: bool, show_default: bool = True, **kwargs: Any
) -> bool:
"""Return the prompt default instead of crashing when stdin is unavailable."""
from rich.prompt import Confirm

try:
return Confirm.ask(prompt, default=default, show_default=show_default, **kwargs)
except EOFError:
return default


@lru_cache(maxsize=1)
def get_valid_subdirs(current_dir: Optional[Path] = None) -> list[str]:

Expand Down Expand Up @@ -148,8 +160,6 @@ def should_modify_pyproject_toml(*, skip_confirm: bool = False) -> tuple[bool, d

If it does, ask the user if they want to re-configure it.
"""
from rich.prompt import Confirm

pyproject_toml_path = Path.cwd() / "pyproject.toml"

found, _ = config_found(pyproject_toml_path)
Expand All @@ -164,7 +174,7 @@ def should_modify_pyproject_toml(*, skip_confirm: bool = False) -> tuple[bool, d
if skip_confirm:
return False, config

return Confirm.ask(
return confirm_with_default_on_eof(
"✅ A valid Codeflash config already exists in this project. Do you want to re-configure it?",
default=False,
show_default=True,
Expand Down Expand Up @@ -285,9 +295,7 @@ def create_empty_pyproject_toml(pyproject_toml_path: Path) -> None:

def ask_for_telemetry() -> bool:
"""Prompt the user to enable or disable telemetry."""
from rich.prompt import Confirm

return Confirm.ask(
return confirm_with_default_on_eof(
"⚡️ Help us improve Codeflash by sharing anonymous usage data (e.g. errors encountered)?",
default=True,
show_default=True,
Expand Down
9 changes: 3 additions & 6 deletions codeflash/cli_cmds/init_java.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from rich.text import Text

from codeflash.cli_cmds.console import apologize_and_exit, console
from codeflash.cli_cmds.init_config import confirm_with_default_on_eof
from codeflash.code_utils.code_utils import validate_relative_directory_path
from codeflash.code_utils.compat import LF
from codeflash.code_utils.git_utils import get_git_remotes
Expand Down Expand Up @@ -215,8 +216,6 @@ def init_java_project(*, skip_confirm: bool = False, skip_api_key: bool = False)

def should_modify_java_config(*, skip_confirm: bool = False) -> tuple[bool, dict[str, Any] | None]:
"""Check if the project already has Codeflash config."""
from rich.prompt import Confirm

project_root = Path.cwd()

# Check for existing codeflash config in pom.xml properties or gradle.properties
Expand All @@ -228,7 +227,7 @@ def should_modify_java_config(*, skip_confirm: bool = False) -> tuple[bool, dict
if existing:
if skip_confirm:
return False, None
return Confirm.ask(
return confirm_with_default_on_eof(
"A Codeflash config already exists. Do you want to re-configure it?", default=False, show_default=True
), None
except ValueError:
Expand All @@ -239,8 +238,6 @@ def should_modify_java_config(*, skip_confirm: bool = False) -> tuple[bool, dict

def collect_java_setup_info(*, skip_confirm: bool = False) -> JavaSetupInfo:
"""Collect setup information for Java projects."""
from rich.prompt import Confirm

from codeflash.cli_cmds.init_config import ask_for_telemetry

curdir = Path.cwd()
Expand Down Expand Up @@ -282,7 +279,7 @@ def collect_java_setup_info(*, skip_confirm: bool = False) -> JavaSetupInfo:
test_root_override = None
formatter_override = None

if Confirm.ask("Would you like to change any of these settings?", default=False):
if confirm_with_default_on_eof("Would you like to change any of these settings?", default=False):
# Source root override
module_root_override = _prompt_directory_override("source", detected_source_root, curdir)

Expand Down
11 changes: 5 additions & 6 deletions codeflash/cli_cmds/init_javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from git import InvalidGitRepositoryError, Repo
from rich.console import Group
from rich.panel import Panel
from rich.prompt import Confirm
from rich.table import Table
from rich.text import Text

Expand Down Expand Up @@ -297,6 +296,8 @@ def init_js_project(language: ProjectLanguage, *, skip_confirm: bool = False, sk

def should_modify_package_json_config(*, skip_confirm: bool = False) -> tuple[bool, dict[str, Any] | None]:
"""Check if package.json has valid codeflash config for JS/TS projects."""
from codeflash.cli_cmds.init_config import confirm_with_default_on_eof

package_json_path = Path("package.json")

if not package_json_path.exists():
Expand Down Expand Up @@ -326,7 +327,7 @@ def should_modify_package_json_config(*, skip_confirm: bool = False) -> tuple[bo
return False, config

# Config is valid - ask if user wants to reconfigure
return Confirm.ask(
return confirm_with_default_on_eof(
"✅ A valid Codeflash config already exists in package.json. Do you want to re-configure it?",
default=False,
show_default=True,
Expand All @@ -341,7 +342,7 @@ def collect_js_setup_info(language: ProjectLanguage, *, skip_confirm: bool = Fal
Uses auto-detection for most settings and only asks for overrides if needed.
When skip_confirm is True, uses all auto-detected defaults without prompting.
"""
from codeflash.cli_cmds.init_config import ask_for_telemetry, get_valid_subdirs
from codeflash.cli_cmds.init_config import ask_for_telemetry, confirm_with_default_on_eof, get_valid_subdirs
from codeflash.code_utils.config_js import (
detect_formatter,
detect_module_root,
Expand Down Expand Up @@ -378,8 +379,6 @@ def collect_js_setup_info(language: ProjectLanguage, *, skip_confirm: bool = Fal
pass
return JSSetupInfo(git_remote=git_remote)

from rich.prompt import Confirm

# Build detection summary
formatter_display = detected_formatter[0] if detected_formatter else "none detected"
detection_table = Table(show_header=False, box=None, padding=(0, 2))
Expand All @@ -401,7 +400,7 @@ def collect_js_setup_info(language: ProjectLanguage, *, skip_confirm: bool = Fal
module_root_override = None
formatter_override = None

if Confirm.ask("Would you like to change any of these settings?", default=False):
if confirm_with_default_on_eof("Would you like to change any of these settings?", default=False):
# Module root override
valid_subdirs = get_valid_subdirs()
curdir_option = f"current directory ({curdir})"
Expand Down
24 changes: 24 additions & 0 deletions tests/test_github_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,27 @@ def test_install_github_actions_skip_confirm_supports_go_projects(tmp_path: Path
assert "Optimize new Go code" in workflow_text
assert "actions/setup-go@v5" in workflow_text
assert "go mod download" in workflow_text


def test_install_github_actions_non_tty_skips_optional_setup(tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)
Repo.init(tmp_path)
(tmp_path / "tests").mkdir()
(tmp_path / "pyproject.toml").write_text(
"""
[tool.codeflash]
module-root = "."
tests-root = "tests"
formatter-cmds = ["disabled"]
""".strip(),
encoding="utf-8",
)

stdin = type("Stdin", (), {"isatty": lambda self: False})()
monkeypatch.setattr("codeflash.cli_cmds.github_workflow.sys.stdin", stdin)

with patch("codeflash.cli_cmds.github_workflow.inquirer.prompt") as mock_prompt:
install_github_actions()

mock_prompt.assert_not_called()
assert not (tmp_path / ".github" / "workflows" / "codeflash.yaml").exists()
51 changes: 51 additions & 0 deletions tests/test_init_java_go.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ def test_collect_go_setup_info_skip_confirm_uses_defaults(tmp_path: Path, monkey
assert setup_info.disable_telemetry is False


def test_collect_go_setup_info_uses_default_telemetry_on_eof(tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "go.mod").write_text("module example.com/demo\n\ngo 1.21\n", encoding="utf-8")

get_git_remote = Mock(return_value="origin")
monkeypatch.setattr("codeflash.cli_cmds.init_go._get_git_remote_for_setup", get_git_remote)

with patch("rich.prompt.Confirm.ask", side_effect=EOFError):
setup_info = collect_go_setup_info()

get_git_remote.assert_called_once_with()
assert setup_info.git_remote == "origin"
assert setup_info.disable_telemetry is False


def test_collect_java_setup_info_skip_confirm_uses_defaults(tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "build.gradle").write_text("plugins { id 'java' }\n", encoding="utf-8")
Expand All @@ -52,6 +67,30 @@ def test_collect_java_setup_info_skip_confirm_uses_defaults(tmp_path: Path, monk
assert setup_info.disable_telemetry is False


def test_collect_java_setup_info_uses_defaults_on_eof(tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "build.gradle").write_text("plugins { id 'java' }\n", encoding="utf-8")
(tmp_path / "src" / "main" / "java").mkdir(parents=True)
(tmp_path / "src" / "test" / "java").mkdir(parents=True)

get_git_remote = Mock(return_value="origin")
monkeypatch.setattr("codeflash.cli_cmds.init_java._get_git_remote_for_setup", get_git_remote)
monkeypatch.setattr("codeflash.cli_cmds.init_config.ask_for_telemetry", Mock(return_value=True))

with patch("codeflash.cli_cmds.init_java.inquirer") as mock_inquirer, patch(
"rich.prompt.Confirm.ask", side_effect=EOFError
):
setup_info = collect_java_setup_info()

mock_inquirer.prompt.assert_not_called()
get_git_remote.assert_called_once_with()
assert setup_info.module_root_override is None
assert setup_info.test_root_override is None
assert setup_info.formatter_override is None
assert setup_info.git_remote == "origin"
assert setup_info.disable_telemetry is False


def test_should_modify_java_config_skip_confirm_skips_reconfigure_prompt(tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "build.gradle").write_text("plugins { id 'java' }\n", encoding="utf-8")
Expand All @@ -62,3 +101,15 @@ def test_should_modify_java_config_skip_confirm_skips_reconfigure_prompt(tmp_pat

assert should_modify is False
assert config is None


def test_should_modify_java_config_uses_default_on_eof(tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "build.gradle").write_text("plugins { id 'java' }\n", encoding="utf-8")
(tmp_path / "gradle.properties").write_text("codeflash.moduleRoot=src/main/java\n", encoding="utf-8")

with patch("rich.prompt.Confirm.ask", side_effect=EOFError):
should_modify, config = should_modify_java_config()

assert should_modify is False
assert config is None
41 changes: 40 additions & 1 deletion tests/test_init_javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import json
import tempfile
from pathlib import Path
from unittest.mock import patch
from unittest.mock import Mock, patch

import pytest

from codeflash.cli_cmds.init_javascript import (
JsPackageManager,
ProjectLanguage,
collect_js_setup_info,
detect_project_language,
determine_js_package_manager,
get_package_install_command,
Expand Down Expand Up @@ -334,6 +335,20 @@ def test_should_modify_skip_confirm_with_invalid_config(
assert should_modify is True
assert config is None

def test_should_modify_valid_config_uses_default_on_eof(
self, tmp_project: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""EOF on the reconfigure prompt should keep the existing config."""
monkeypatch.chdir(tmp_project)
codeflash_config = {"moduleRoot": "."}
(tmp_project / "package.json").write_text(json.dumps({"name": "test", "codeflash": codeflash_config}))

with patch("rich.prompt.Confirm.ask", side_effect=EOFError):
should_modify, config = should_modify_package_json_config()

assert should_modify is False
assert config == codeflash_config


class TestCollectJsSetupInfoSkipConfirm:
"""Tests for collect_js_setup_info with skip_confirm."""
Expand All @@ -354,6 +369,30 @@ def test_collect_js_setup_info_skip_confirm(self, tmp_project: Path, monkeypatch
assert setup_info.formatter_override is None
assert setup_info.git_remote == "origin"

def test_collect_js_setup_info_uses_defaults_on_eof(
self, tmp_project: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""EOF on the settings confirm should keep auto-detected defaults."""
monkeypatch.chdir(tmp_project)
(tmp_project / "package.json").write_text(json.dumps({"name": "test"}))

get_git_remote = Mock(return_value="origin")
monkeypatch.setattr("codeflash.cli_cmds.init_javascript._get_git_remote_for_setup", get_git_remote)
monkeypatch.setattr("codeflash.cli_cmds.init_config.ask_for_telemetry", Mock(return_value=True))

with (
patch("rich.prompt.Confirm.ask", side_effect=EOFError),
patch("codeflash.cli_cmds.init_javascript.inquirer") as mock_inquirer,
):
setup_info = collect_js_setup_info(ProjectLanguage.JAVASCRIPT)

mock_inquirer.prompt.assert_not_called()
get_git_remote.assert_called_once_with()
assert setup_info.module_root_override is None
assert setup_info.formatter_override is None
assert setup_info.git_remote == "origin"
assert setup_info.disable_telemetry is False


class TestDetectProjectLanguage:
"""Tests for detect_project_language function."""
Expand Down
17 changes: 17 additions & 0 deletions tests/test_init_yes.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ def test_should_modify_pyproject_toml_skip_confirm_skips_reconfigure_prompt(
assert config["git_remote"] == "upstream"


def test_should_modify_pyproject_toml_uses_default_on_eof(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "src").mkdir()
(tmp_path / "tests").mkdir()
(tmp_path / "pyproject.toml").write_text(
'[tool.codeflash]\nmodule-root = "src"\ntests-root = "tests"\ngit-remote = "upstream"\n',
encoding="utf-8",
)

with patch("rich.prompt.Confirm.ask", side_effect=EOFError):
should_modify, config = should_modify_pyproject_toml()

assert should_modify is False
assert config is not None
assert config["git_remote"] == "upstream"


def test_init_codeflash_skip_confirm_reuses_existing_python_config(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
Expand Down
Loading