From 4be6f72575fc57fca9284ec4a0dab2d23e6852e4 Mon Sep 17 00:00:00 2001 From: Anne Schuth Date: Tue, 7 Apr 2026 15:50:34 +0200 Subject: [PATCH 1/3] feat: replace version subcommand with --version flag Standard CLI convention - users expect `zad --version`, not `zad version`. --- src/zad_cli/cli.py | 14 ++++++++------ tests/test_backwards_compat.py | 1 - tests/test_cli.py | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/zad_cli/cli.py b/src/zad_cli/cli.py index 715cdf8..d6c6f18 100644 --- a/src/zad_cli/cli.py +++ b/src/zad_cli/cli.py @@ -27,7 +27,7 @@ class _GlobalOptionsGroup(TyperGroup): """Hoist global options to before the subcommand so they work in any position.""" _OPTS_WITH_VALUE = frozenset({"--output", "-o", "--api-key", "--api-url", "--project", "-p"}) - _FLAGS = frozenset({"--no-wait", "--verbose", "-v"}) + _FLAGS = frozenset({"--no-wait", "--verbose", "-v", "--version", "-V"}) def parse_args(self, ctx, args): # noqa: ANN001 global_args: list[str] = [] @@ -79,6 +79,12 @@ def parse_args(self, ctx, args): # noqa: ANN001 app.add_typer(open_app, name="open") +def _version_callback(value: bool) -> None: + if value: + print(f"zad-cli {__version__}") + raise typer.Exit() + + @app.callback() def main_callback( ctx: typer.Context, @@ -88,6 +94,7 @@ def main_callback( project_id: str = typer.Option(None, "--project", "-p", envvar="ZAD_PROJECT_ID", help="Project ID"), no_wait: bool = typer.Option(False, "--no-wait", help="Don't wait for async operations, return task ID"), verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose request logging"), + version: bool = typer.Option(False, "--version", "-V", help="Show version and exit", callback=_version_callback, is_eager=True), ) -> None: """Global options applied to all commands.""" from zad_cli.output.formatter import OutputFormatter @@ -102,11 +109,6 @@ def main_callback( ctx.obj["no_wait"] = no_wait -@app.command() -def version() -> None: - """Show version information.""" - print(f"zad-cli {__version__}") - def main() -> None: """CLI entrypoint.""" diff --git a/tests/test_backwards_compat.py b/tests/test_backwards_compat.py index ac5e654..cfe2503 100644 --- a/tests/test_backwards_compat.py +++ b/tests/test_backwards_compat.py @@ -84,7 +84,6 @@ def run_help(*args: str) -> subprocess.CompletedProcess: "logs", "metrics", "open", - "version", ], "project": ["list", "status", "refresh", "delete", "subdomains", "check-subdomain"], "deployment": ["list", "describe", "create", "update-image", "refresh", "delete"], diff --git a/tests/test_cli.py b/tests/test_cli.py index 466a4bb..4f7c331 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -38,7 +38,7 @@ def test_help_exits_zero(): def test_version(): result = subprocess.run( - [sys.executable, "-m", "zad_cli", "version"], + [sys.executable, "-m", "zad_cli", "--version"], capture_output=True, text=True, env=_PLAIN_ENV, From 9a1c884e2fd3cc0bfa209109912a57f005d351f1 Mon Sep 17 00:00:00 2001 From: Anne Schuth Date: Tue, 7 Apr 2026 15:55:41 +0200 Subject: [PATCH 2/3] fix: keep deprecated version subcommand for backwards compatibility Restores `zad version` as a deprecated command with a warning, per the backwards compatibility policy (deprecation before removal). --- src/zad_cli/cli.py | 7 +++++++ tests/test_backwards_compat.py | 1 + tests/test_cli.py | 14 +++++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/zad_cli/cli.py b/src/zad_cli/cli.py index d6c6f18..25d38a0 100644 --- a/src/zad_cli/cli.py +++ b/src/zad_cli/cli.py @@ -109,6 +109,13 @@ def main_callback( ctx.obj["no_wait"] = no_wait +@app.command(deprecated=True) +def version() -> None: + """[Deprecated] Use `zad --version` instead.""" + typer.echo("Warning: `zad version` is deprecated, use `zad --version` instead.", err=True) + print(f"zad-cli {__version__}") + + def main() -> None: """CLI entrypoint.""" diff --git a/tests/test_backwards_compat.py b/tests/test_backwards_compat.py index cfe2503..ac5e654 100644 --- a/tests/test_backwards_compat.py +++ b/tests/test_backwards_compat.py @@ -84,6 +84,7 @@ def run_help(*args: str) -> subprocess.CompletedProcess: "logs", "metrics", "open", + "version", ], "project": ["list", "status", "refresh", "delete", "subdomains", "check-subdomain"], "deployment": ["list", "describe", "create", "update-image", "refresh", "delete"], diff --git a/tests/test_cli.py b/tests/test_cli.py index 4f7c331..a818491 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -36,7 +36,7 @@ def test_help_exits_zero(): assert "--verbose" in out -def test_version(): +def test_version_flag(): result = subprocess.run( [sys.executable, "-m", "zad_cli", "--version"], capture_output=True, @@ -47,6 +47,18 @@ def test_version(): assert "zad-cli" in result.stdout +def test_version_subcommand_deprecated(): + result = subprocess.run( + [sys.executable, "-m", "zad_cli", "version"], + capture_output=True, + text=True, + env=_PLAIN_ENV, + ) + assert result.returncode == 0 + assert "zad-cli" in result.stdout + assert "deprecated" in result.stderr.lower() + + def test_project_help_without_api_key(): result = _run_help("project") out = _strip_ansi(result.stdout) From 6e6b90c53f9cb5ba06726f633aefe283b02720d9 Mon Sep 17 00:00:00 2001 From: Anne Schuth Date: Tue, 7 Apr 2026 15:58:42 +0200 Subject: [PATCH 3/3] style: format long line in version option --- src/zad_cli/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/zad_cli/cli.py b/src/zad_cli/cli.py index 25d38a0..84d4439 100644 --- a/src/zad_cli/cli.py +++ b/src/zad_cli/cli.py @@ -94,7 +94,9 @@ def main_callback( project_id: str = typer.Option(None, "--project", "-p", envvar="ZAD_PROJECT_ID", help="Project ID"), no_wait: bool = typer.Option(False, "--no-wait", help="Don't wait for async operations, return task ID"), verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose request logging"), - version: bool = typer.Option(False, "--version", "-V", help="Show version and exit", callback=_version_callback, is_eager=True), + version: bool = typer.Option( + False, "--version", "-V", help="Show version and exit", callback=_version_callback, is_eager=True + ), ) -> None: """Global options applied to all commands.""" from zad_cli.output.formatter import OutputFormatter @@ -116,7 +118,6 @@ def version() -> None: print(f"zad-cli {__version__}") - def main() -> None: """CLI entrypoint.""" from dotenv import load_dotenv