Skip to content

Commit de42535

Browse files
jawwad-aliclaude
andcommitted
fix(init): honor piped y/n for 'init --here', error only on no-input
Per maintainer review: restore the second-revision shape. Calling typer.confirm normally keeps 'echo y | specify init --here' reaching the non-destructive preserve-merge path (and piped 'n' cancels with exit 0). Only when no confirmation input is available at all (closed/empty stdin -> typer.Abort/EOFError) is it converted into the actionable error that points at --force. This drops the _stdin_is_interactive fail-fast that broke the common piped-confirm idiom and made preserve-merge interactive-only. The preserve test no longer needs to monkeypatch _stdin_is_interactive - it passes on the real contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f5f46ea commit de42535

2 files changed

Lines changed: 16 additions & 25 deletions

File tree

src/specify_cli/commands/init.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -228,28 +228,25 @@ def init(
228228
console.print(
229229
"[cyan]--force supplied: skipping confirmation and proceeding with merge[/cyan]"
230230
)
231-
elif not _stdin_is_interactive():
232-
# No interactive terminal: do NOT prompt. A non-TTY stdin that
233-
# is open but idle (a common CI/agent scenario) would block on
234-
# typer.confirm, so fail fast and require an explicit --force
235-
# for a non-interactive merge. No merge happens here, so no
236-
# merge/overwrite warning is printed.
237-
console.print(
238-
"[red]Error:[/red] Current directory is not empty and no "
239-
"interactive terminal is available to confirm. Re-run with "
240-
"[bold]--force[/bold] to merge into it."
241-
)
242-
raise typer.Exit(1)
243231
else:
244232
console.print(
245233
"[yellow]Template files will be merged with existing content and may overwrite existing files[/yellow]"
246234
)
235+
# Call typer.confirm normally so piped y/n is honored — e.g.
236+
# `echo y | specify init --here` keeps reaching the
237+
# non-destructive preserve-merge path. Only when no
238+
# confirmation input is available at all (closed/empty stdin
239+
# → EOF/Abort) do we convert it into an actionable error that
240+
# points at --force, rather than blocking or failing opaquely.
247241
try:
248242
proceed = typer.confirm("Do you want to continue?")
249-
except typer.Abort:
250-
# Interactive cancel (e.g. Ctrl+C): a normal cancellation.
251-
console.print("[yellow]Operation cancelled[/yellow]")
252-
raise typer.Exit(0) from None
243+
except (typer.Abort, EOFError):
244+
console.print(
245+
"[red]Error:[/red] Current directory is not empty and no "
246+
"confirmation input is available. Re-run with "
247+
"[bold]--force[/bold] to merge into it."
248+
)
249+
raise typer.Exit(1) from None
253250
if not proceed:
254251
console.print("[yellow]Operation cancelled[/yellow]")
255252
raise typer.Exit(0)

tests/integrations/test_cli.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -860,17 +860,11 @@ def test_init_here_force_overwrites_shared_infra(self, tmp_path):
860860
# --force should overwrite the custom file
861861
assert (scripts_dir / "common.sh").read_text(encoding="utf-8") != custom_content
862862

863-
def test_init_here_without_force_preserves_shared_infra(self, tmp_path, monkeypatch):
864-
"""E2E: confirming the merge interactively (no --force) preserves existing
865-
shared infra files (unlike --force, which overwrites them)."""
863+
def test_init_here_without_force_preserves_shared_infra(self, tmp_path):
864+
"""E2E: confirming the merge with piped "y" (no --force) preserves
865+
existing shared infra files (unlike --force, which overwrites them)."""
866866
from typer.testing import CliRunner
867867
from specify_cli import app
868-
from specify_cli.commands import init as init_mod
869-
870-
# Simulate an interactive terminal so the confirmation prompt is reached;
871-
# the piped "y" then exercises the preserve-merge path. (Non-interactive
872-
# sessions now fail fast and require --force.)
873-
monkeypatch.setattr(init_mod, "_stdin_is_interactive", lambda: True)
874868

875869
project = tmp_path / "e2e-no-force"
876870
project.mkdir()

0 commit comments

Comments
 (0)