diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3c56f47 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 5 + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 3 \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b03be38..7803545 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,6 +32,9 @@ jobs: run: | pip install build twine + - name: Lint with ruff + run: pip install ruff && ruff check src/ --target-version py310 + - name: Build package run: | python -m build diff --git a/.gitignore b/.gitignore index 5f52f3a..6b1ebb3 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ Thumbs.db .pytest_cache/ .coverage htmlcov/ +.ruff_cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0df2342 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2025-05-17 + +### Added +- Initial beta release +- Core functionality +- CLI interface +- Test suite +- CI/CD workflows with ruff lint and pytest +- CONTRIBUTING.md \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index bec13af..510a85e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ ] dependencies = [ - "typer>=0.9.0", + "typer>=0.25.1", "rich>=13.0.0", "python-dotenv>=1.0.0", "pyyaml>=6.0", diff --git a/src/envault/cli.py b/src/envault/cli.py index 51dc073..87f2cbe 100644 --- a/src/envault/cli.py +++ b/src/envault/cli.py @@ -39,7 +39,7 @@ def load_config(config_path: str = "") -> EnvaultConfig: return EnvaultConfig.load(path) -# ── Init ──────────────────────────────────────────────────────────────────── +# ── ── Init ──────────────────────────────────â @app.command() def init( @@ -53,7 +53,7 @@ def init( console.print("Then run: envault diff, envault sync, envault rotate") -# ── Diff ──────────────────────────────────────────────────────────────────── +# ── ── Diff ──────────────────────────────────â @app.command() def diff( @@ -95,7 +95,7 @@ def diff_files( raise typer.Exit(0) -# ── Sync ──────────────────────────────────────────────────────────────────── +# ── ── Sync ──────────────────────────────────â @app.command() def sync( @@ -172,7 +172,7 @@ def sync( (f" +{len(result.skipped) - 10}" if len(result.skipped) > 10 else "")) -# ── Rotate ────────────────────────────────────────────────────────────────── +# ── ── Rotate ─────────────────────────────────┠@app.command() def rotate( @@ -260,7 +260,7 @@ def rotate_all( console.print(f"[green]✓[/green] Rotated {rotated} variables in {env}") -# ── Store Commands ────────────────────────────────────────────────────────── +# ── ── Store Commands ─────────────────────────────── store_app = typer.Typer(name="store", help="Manage secret store integrations.") app.add_typer(store_app) @@ -335,7 +335,7 @@ def store_set( console.print(f"[green]✓[/green] Set {key}") -# ── Audit ─────────────────────────────────────────────────────────────────── +# ── ── Audit ────────────────────────────────── @app.command() def audit( @@ -371,7 +371,7 @@ def audit( console.print(table) -# ── Version ───────────────────────────────────────────────────────────────── +# ── ── Version ─────────────────────────────────â @app.command() def version(): diff --git a/src/envault/stores/__init__.py b/src/envault/stores/__init__.py index 092e7d8..7277322 100644 --- a/src/envault/stores/__init__.py +++ b/src/envault/stores/__init__.py @@ -340,7 +340,10 @@ def get(self, key: str) -> str | None: for item in item_list: fields = item.get("fields", []) for field in fields: - if field.get("purpose") == "PASSWORD" or field.get("label", "").lower() in ("password", "value", "credential"): + if ( + field.get("purpose") == "PASSWORD" + or field.get("label", "").lower() in ("password", "value", "credential") + ): return field.get("value", "") return None diff --git a/tests/test_envault.py b/tests/test_envault.py index 376a21e..6b88b36 100644 --- a/tests/test_envault.py +++ b/tests/test_envault.py @@ -1,19 +1,13 @@ """Tests for Envault CLI.""" -import json -import os -import tempfile -from pathlib import Path - import pytest - from envault import __version__ from envault.audit import AuditLogger from envault.config import EnvaultConfig, init_config -from envault.diff import diff_envs, diff_env_files, format_diff, load_env_file -from envault.rotate import generate_secret, rotate_value, rotate_env_var -from envault.sync import sync_envs, sync_env_files, SyncConflict, write_env_file - +from envault.diff import diff_env_files, diff_envs, format_diff, load_env_file +from envault.rotate import generate_secret, rotate_env_var, rotate_value +from envault.sync import SyncConflict, sync_env_files, sync_envs, write_env_file +from pathlib import Path # ── Version ───────────────────────────────────────────────────────────────── @@ -230,7 +224,10 @@ def test_generate_secret_no_symbols(): def test_generate_secret_exclude(): - secret = generate_secret(100, use_upper=True, use_lower=True, use_digits=True, use_symbols=False, exclude_chars="abc") + secret = generate_secret( + 100, use_upper=True, use_lower=True, + use_digits=True, use_symbols=False, exclude_chars="abc", + ) assert "a" not in secret assert "b" not in secret assert "c" not in secret @@ -432,14 +429,14 @@ def test_set_many(self, tmp_path): def test_store_factory_default(): - from envault.stores import get_store, LocalEnvStore + from envault.stores import LocalEnvStore, get_store store = get_store("some_path") assert isinstance(store, LocalEnvStore) def test_store_factory_local_config(tmp_path): from envault.config import SecretStoreConfig - from envault.stores import get_store, LocalEnvStore + from envault.stores import LocalEnvStore, get_store config = SecretStoreConfig(type="local", path_prefix=str(tmp_path / ".env")) store = get_store(config) assert isinstance(store, LocalEnvStore) @@ -447,7 +444,7 @@ def test_store_factory_local_config(tmp_path): def test_store_factory_unknown(): from envault.config import SecretStoreConfig - from envault.stores import get_store, SecretStoreError + from envault.stores import SecretStoreError, get_store config = SecretStoreConfig(type="nonexistent") import pytest with pytest.raises(SecretStoreError):