From 23b579f5610529b9fc913c6a0db123f354c4ef31 Mon Sep 17 00:00:00 2001 From: Jaixii Date: Mon, 29 Jun 2026 04:44:40 -0400 Subject: [PATCH 1/4] feat: add --require-license strict mode flag Closes #32 --- src/configdrift/cli.py | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/configdrift/cli.py b/src/configdrift/cli.py index 1a3639c..9d6b44b 100644 --- a/src/configdrift/cli.py +++ b/src/configdrift/cli.py @@ -1,5 +1,7 @@ """ConfigDrift CLI entry point.""" +import os + import typer try: @@ -34,6 +36,8 @@ def require_license(product: str) -> None: # type: ignore[misc] ) console = Console() +_require_license_strict: bool = False + def _version_callback(value: bool) -> None: if value: @@ -51,8 +55,34 @@ def main( callback=_version_callback, is_eager=True, ), + require_license_flag: bool = typer.Option( + False, + "--require-license", + help=( + "Exit with an error if revenueholdings-license is not installed " + "or if the license check fails. " + "Also enabled via REVENUEHOLDINGS_REQUIRE_LICENSE=1." + ), + ), ) -> None: """ConfigDrift CLI — detect and fix configuration drift.""" + global _require_license_strict + _require_license_strict = require_license_flag or bool( + os.environ.get("REVENUEHOLDINGS_REQUIRE_LICENSE") + ) + if _require_license_strict: + try: + from revenueholdings_license import require_license as _rl + _rl("configdrift") + except ImportError: + console.print( + "[bold red]Error:[/bold red] revenueholdings-license is not installed. " + "Install it with: pip install revenueholdings-license", + err=True, + ) + raise typer.Exit(code=1) from None + except Exception: + raise class OutputFormat(str, Enum): @@ -148,7 +178,7 @@ def _output_table(results: dict[str, Any], baseline_env: str) -> None: if not diff_result.changes: continue - table = Table(title=f"Config Drift: {baseline_env} → {env_name}") + table = Table(title=f"Config Drift: {baseline_env} \u2192 {env_name}") table.add_column("Key", style="cyan") table.add_column("Change", style="bold") table.add_column("Old Value", style="yellow") @@ -179,7 +209,7 @@ def _output_table(results: dict[str, Any], baseline_env: str) -> None: console.print(table) console.print(f"Total changes: {diff_result.count}") if diff_result.has_breaking: - console.print("[red]⚠ BREAKING CHANGES DETECTED[/red]") + console.print("[red]\u26a0 BREAKING CHANGES DETECTED[/red]") console.print() @@ -226,7 +256,7 @@ def scan( ): """Scan directories of config files and compare environments.""" if config: - # Load config file for directory → env mapping (raw, not flattened) + # Load config file for directory -> env mapping (raw, not flattened) import yaml as _yaml with open(config, encoding="utf-8") as _f: From 3893aa2b0ae4dcc5d7d7d2c2ab959b5288506161 Mon Sep 17 00:00:00 2001 From: Jaixii Date: Mon, 29 Jun 2026 04:57:01 -0400 Subject: [PATCH 2/4] fix: reorder imports to comply with isort (stdlib before third-party/local) --- src/configdrift/cli.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/configdrift/cli.py b/src/configdrift/cli.py index 9d6b44b..5263003 100644 --- a/src/configdrift/cli.py +++ b/src/configdrift/cli.py @@ -1,8 +1,15 @@ """ConfigDrift CLI entry point.""" +from __future__ import annotations + import os +from enum import Enum +from pathlib import Path +from typing import Any import typer +from rich.console import Console +from rich.table import Table try: from revenueholdings_license import require_license @@ -23,11 +30,6 @@ def require_license(product: str) -> None: # type: ignore[misc] diff_environments, ) from configdrift.loader import load_file -from enum import Enum -from pathlib import Path -from rich.console import Console -from rich.table import Table -from typing import Any app = typer.Typer( name="configdrift", @@ -178,7 +180,7 @@ def _output_table(results: dict[str, Any], baseline_env: str) -> None: if not diff_result.changes: continue - table = Table(title=f"Config Drift: {baseline_env} \u2192 {env_name}") + table = Table(title=f"Config Drift: {baseline_env} → {env_name}") table.add_column("Key", style="cyan") table.add_column("Change", style="bold") table.add_column("Old Value", style="yellow") @@ -209,7 +211,7 @@ def _output_table(results: dict[str, Any], baseline_env: str) -> None: console.print(table) console.print(f"Total changes: {diff_result.count}") if diff_result.has_breaking: - console.print("[red]\u26a0 BREAKING CHANGES DETECTED[/red]") + console.print("[red]⚠ BREAKING CHANGES DETECTED[/red]") console.print() From 268fbb07f28fe4518a15d5f3ea35ce9b80e1f24a Mon Sep 17 00:00:00 2001 From: Jaixii Date: Mon, 29 Jun 2026 05:01:17 -0400 Subject: [PATCH 3/4] fix: move noqa comments to opening typer.Option lines (ruff B008) --- src/configdrift/cli.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/configdrift/cli.py b/src/configdrift/cli.py index 5263003..c0ec0e3 100644 --- a/src/configdrift/cli.py +++ b/src/configdrift/cli.py @@ -49,7 +49,7 @@ def _version_callback(value: bool) -> None: @app.callback() def main( - version: bool = typer.Option( + version: bool = typer.Option( # noqa: B008 False, "--version", "-V", @@ -57,7 +57,7 @@ def main( callback=_version_callback, is_eager=True, ), - require_license_flag: bool = typer.Option( + require_license_flag: bool = typer.Option( # noqa: B008 False, "--require-license", help=( @@ -243,18 +243,18 @@ def scan( None, help="Directories containing config files. Each dir is treated as an environment.", ), - baseline: str = typer.Option( + baseline: str = typer.Option( # noqa: B008 "dev", "--baseline", "-b", help="Baseline directory name for comparison." - ), # noqa: B008 - config: str | None = typer.Option( + ), + config: str | None = typer.Option( # noqa: B008 None, "--config", "-c", help="Path to .configdrift.yaml config file." - ), # noqa: B008 - output: OutputFormat = typer.Option( + ), + output: OutputFormat = typer.Option( # noqa: B008 OutputFormat.TABLE, "--output", "-o", help="Output format." - ), # noqa: B008 - strict: bool = typer.Option( + ), + strict: bool = typer.Option( # noqa: B008 False, "--strict", help="Exit 1 on ANY drift, not just breaking changes." - ), # noqa: B008 + ), ): """Scan directories of config files and compare environments.""" if config: @@ -322,7 +322,7 @@ def scan( @app.command() def init( - path: str = typer.Argument(".", help="Directory to create .configdrift.yaml in."), + path: str = typer.Argument(".", help="Directory to create .configdrift.yaml in."), # noqa: B008 ): """Generate a .configdrift.yaml configuration file.""" template = """# ConfigDrift configuration From 1c8035669f6e51fe641ec4bc7b2076e960105824 Mon Sep 17 00:00:00 2001 From: Jaixii Date: Mon, 29 Jun 2026 05:03:07 -0400 Subject: [PATCH 4/4] fix: apply ruff isort (known-first-party=* means one import block) --- src/configdrift/cli.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/configdrift/cli.py b/src/configdrift/cli.py index c0ec0e3..a7f499f 100644 --- a/src/configdrift/cli.py +++ b/src/configdrift/cli.py @@ -3,13 +3,12 @@ from __future__ import annotations import os +import typer from enum import Enum from pathlib import Path -from typing import Any - -import typer from rich.console import Console from rich.table import Table +from typing import Any try: from revenueholdings_license import require_license